summaryrefslogtreecommitdiff
path: root/modules/gdscript
diff options
context:
space:
mode:
Diffstat (limited to 'modules/gdscript')
-rw-r--r--modules/gdscript/SCsub10
-rw-r--r--modules/gdscript/config.py1
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml1406
-rw-r--r--modules/gdscript/doc_classes/GDScript.xml6
-rw-r--r--modules/gdscript/doc_classes/GDScriptFunctionState.xml2
-rw-r--r--modules/gdscript/doc_classes/GDScriptNativeClass.xml2
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp49
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.h2
-rw-r--r--modules/gdscript/gdscript.cpp93
-rw-r--r--modules/gdscript/gdscript.h14
-rw-r--r--modules/gdscript/gdscript_compiler.cpp51
-rw-r--r--modules/gdscript/gdscript_editor.cpp372
-rw-r--r--modules/gdscript/gdscript_function.cpp172
-rw-r--r--modules/gdscript/gdscript_functions.cpp202
-rw-r--r--modules/gdscript/gdscript_functions.h8
-rw-r--r--modules/gdscript/gdscript_parser.cpp1059
-rw-r--r--modules/gdscript/gdscript_parser.h37
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp127
-rw-r--r--modules/gdscript/gdscript_tokenizer.h6
-rw-r--r--modules/gdscript/icons/icon_g_d_script.svg5
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp740
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.h101
-rw-r--r--modules/gdscript/language_server/gdscript_language_protocol.cpp235
-rw-r--r--modules/gdscript/language_server/gdscript_language_protocol.h93
-rw-r--r--modules/gdscript/language_server/gdscript_language_server.cpp89
-rw-r--r--modules/gdscript/language_server/gdscript_language_server.h60
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.cpp423
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.h75
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.cpp534
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.h91
-rw-r--r--modules/gdscript/language_server/lsp.hpp1662
-rw-r--r--modules/gdscript/register_types.cpp22
32 files changed, 6949 insertions, 800 deletions
diff --git a/modules/gdscript/SCsub b/modules/gdscript/SCsub
index 6904154953..74e653ce43 100644
--- a/modules/gdscript/SCsub
+++ b/modules/gdscript/SCsub
@@ -8,4 +8,12 @@ env_gdscript = env_modules.Clone()
env_gdscript.add_source_files(env.modules_sources, "*.cpp")
if env['tools']:
- env_gdscript.add_source_files(env.modules_sources, "./editor/*.cpp")
+ env_gdscript.add_source_files(env.modules_sources, "./editor/*.cpp")
+
+ # Those two modules are required for the language server protocol
+ if env['module_jsonrpc_enabled'] and env['module_websocket_enabled']:
+ env_gdscript.add_source_files(env.modules_sources, "./language_server/*.cpp")
+ else:
+ # Using a define in the disabled case, to avoid having an extra define
+ # in regular builds where all modules are enabled.
+ env_gdscript.Append(CPPDEFINES=['GDSCRIPT_NO_LSP'])
diff --git a/modules/gdscript/config.py b/modules/gdscript/config.py
index 95b40d90af..a525eedaaa 100644
--- a/modules/gdscript/config.py
+++ b/modules/gdscript/config.py
@@ -6,6 +6,7 @@ def configure(env):
def get_doc_classes():
return [
+ "@GDScript",
"GDScript",
"GDScriptFunctionState",
"GDScriptNativeClass",
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
new file mode 100644
index 0000000000..502a68cd61
--- /dev/null
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -0,0 +1,1406 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="@GDScript" category="Core" version="3.2">
+ <brief_description>
+ Built-in GDScript functions.
+ </brief_description>
+ <description>
+ List of core built-in GDScript functions. Math functions and other utilities. Everything else is provided by objects. (Keywords: builtin, built in, global functions.)
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="Color8">
+ <return type="Color">
+ </return>
+ <argument index="0" name="r8" type="int">
+ </argument>
+ <argument index="1" name="g8" type="int">
+ </argument>
+ <argument index="2" name="b8" type="int">
+ </argument>
+ <argument index="3" name="a8" type="int" default="255">
+ </argument>
+ <description>
+ Returns a color constructed from integer red, green, blue, and alpha channels. Each channel should have 8 bits of information ranging from 0 to 255.
+ [code]r8[/code] red channel
+ [code]g8[/code] green channel
+ [code]b8[/code] blue channel
+ [code]a8[/code] alpha channel
+ [codeblock]
+ red = Color8(255, 0, 0)
+ [/codeblock]
+ </description>
+ </method>
+ <method name="ColorN">
+ <return type="Color">
+ </return>
+ <argument index="0" name="name" type="String">
+ </argument>
+ <argument index="1" name="alpha" type="float" default="1.0">
+ </argument>
+ <description>
+ Returns a color according to the standardised [code]name[/code] with [code]alpha[/code] ranging from 0 to 1.
+ [codeblock]
+ red = ColorN("red", 1)
+ [/codeblock]
+ Supported color names:
+ "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige", "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflower", "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod", "darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen", "darkslateblue", "darkslategray", "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod", "gray", "webgray", "green", "webgreen", "greenyellow", "honeydew", "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lightcyan", "lightgoldenrod", "lightgray", "lightgreen", "lightpink", "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta", "maroon", "webmaroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise", "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", "navajowhite", "navyblue", "oldlace", "olive", "olivedrab", "orange", "orangered", "orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", "purple", "webpurple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", "snow", "springgreen", "steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white", "whitesmoke", "yellow", "yellowgreen".
+ </description>
+ </method>
+ <method name="abs">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Returns the absolute value of parameter [code]s[/code] (i.e. unsigned value, works for integer and float).
+ [codeblock]
+ # a is 1
+ a = abs(-1)
+ [/codeblock]
+ </description>
+ </method>
+ <method name="acos">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Returns the arc cosine of [code]s[/code] in radians. Use to get the angle of cosine [code]s[/code].
+ [codeblock]
+ # c is 0.523599 or 30 degrees if converted with rad2deg(s)
+ c = acos(0.866025)
+ [/codeblock]
+ </description>
+ </method>
+ <method name="asin">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Returns the arc sine of [code]s[/code] in radians. Use to get the angle of sine [code]s[/code].
+ [codeblock]
+ # s is 0.523599 or 30 degrees if converted with rad2deg(s)
+ s = asin(0.5)
+ [/codeblock]
+ </description>
+ </method>
+ <method name="assert">
+ <return type="void">
+ </return>
+ <argument index="0" name="condition" type="bool">
+ </argument>
+ <description>
+ Asserts that the [code]condition[/code] is [code]true[/code] . If the [code]condition[/code] is [code]false[/code], an error is generated and the program is halted until you resume it. Only executes in debug builds, or when running the game from the editor. Use it for debugging purposes, to make sure a statement is [code]true[/code] during development.
+ [codeblock]
+ # Imagine we always want speed to be between 0 and 20
+ speed = -10
+ assert(speed &lt; 20) # True, the program will continue
+ assert(speed &gt;= 0) # False, the program will stop
+ assert(speed &gt;= 0 &amp;&amp; speed &lt; 20) # You can also combine the two conditional statements in one check
+ [/codeblock]
+ </description>
+ </method>
+ <method name="atan">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Returns the arc tangent of [code]s[/code] in radians. Use it to get the angle from an angle's tangent in trigonometry: [code]atan(tan(angle)) == angle[/code].
+ The method cannot know in which quadrant the angle should fall. See [method atan2] if you always want an exact angle.
+ [codeblock]
+ a = atan(0.5) # a is 0.463648
+ [/codeblock]
+ </description>
+ </method>
+ <method name="atan2">
+ <return type="float">
+ </return>
+ <argument index="0" name="y" type="float">
+ </argument>
+ <argument index="1" name="x" type="float">
+ </argument>
+ <description>
+ Returns the arc tangent of [code]y/x[/code] in radians. Use to get the angle of tangent [code]y/x[/code]. To compute the value, the method takes into account the sign of both arguments in order to determine the quadrant.
+ [codeblock]
+ a = atan2(0, -1) # a is 3.141593
+ [/codeblock]
+ </description>
+ </method>
+ <method name="bytes2var">
+ <return type="Variant">
+ </return>
+ <argument index="0" name="bytes" type="PoolByteArray">
+ </argument>
+ <argument index="1" name="allow_objects" type="bool" default="false">
+ </argument>
+ <description>
+ Decodes a byte array back to a value. When [code]allow_objects[/code] is [code]true[/code] decoding objects is allowed.
+ [b]WARNING:[/b] Deserialized object can contain code which gets executed. Do not use this option if the serialized object comes from untrusted sources to avoid potential security threats (remote code execution).
+ </description>
+ </method>
+ <method name="cartesian2polar">
+ <return type="Vector2">
+ </return>
+ <argument index="0" name="x" type="float">
+ </argument>
+ <argument index="1" name="y" type="float">
+ </argument>
+ <description>
+ Converts a 2D point expressed in the cartesian coordinate system (X and Y axis) to the polar coordinate system (a distance from the origin and an angle).
+ </description>
+ </method>
+ <method name="ceil">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Rounds [code]s[/code] upward, returning the smallest integral value that is not less than [code]s[/code].
+ [codeblock]
+ i = ceil(1.45) # i is 2
+ i = ceil(1.001) # i is 2
+ [/codeblock]
+ </description>
+ </method>
+ <method name="char">
+ <return type="String">
+ </return>
+ <argument index="0" name="ascii" type="int">
+ </argument>
+ <description>
+ Returns a character as a String of the given ASCII code.
+ [codeblock]
+ a = char(65) # a is "A"
+ a = char(65 + 32) # a is "a"
+ [/codeblock]
+ </description>
+ </method>
+ <method name="clamp">
+ <return type="float">
+ </return>
+ <argument index="0" name="value" type="float">
+ </argument>
+ <argument index="1" name="min" type="float">
+ </argument>
+ <argument index="2" name="max" type="float">
+ </argument>
+ <description>
+ Clamps [code]value[/code] and returns a value not less than [code]min[/code] and not more than [code]max[/code].
+ [codeblock]
+ speed = 1000
+ # a is 20
+ a = clamp(speed, 1, 20)
+
+ speed = -10
+ # a is 1
+ a = clamp(speed, 1, 20)
+ [/codeblock]
+ </description>
+ </method>
+ <method name="convert">
+ <return type="Variant">
+ </return>
+ <argument index="0" name="what" type="Variant">
+ </argument>
+ <argument index="1" name="type" type="int">
+ </argument>
+ <description>
+ Converts from a type to another in the best way possible. The [code]type[/code] parameter uses the [enum Variant.Type] values.
+ [codeblock]
+ a = Vector2(1, 0)
+ # Prints 1
+ print(a.length())
+ a = convert(a, TYPE_STRING)
+ # Prints 6 as "(1, 0)" is 6 characters
+ print(a.length())
+ [/codeblock]
+ </description>
+ </method>
+ <method name="cos">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Returns the cosine of angle [code]s[/code] in radians.
+ [codeblock]
+ # Prints 1 then -1
+ print(cos(PI * 2))
+ print(cos(PI))
+ [/codeblock]
+ </description>
+ </method>
+ <method name="cosh">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Returns the hyperbolic cosine of [code]s[/code] in radians.
+ [codeblock]
+ # Prints 1.543081
+ print(cosh(1))
+ [/codeblock]
+ </description>
+ </method>
+ <method name="db2linear">
+ <return type="float">
+ </return>
+ <argument index="0" name="db" type="float">
+ </argument>
+ <description>
+ Converts from decibels to linear energy (audio).
+ </description>
+ </method>
+ <method name="decimals">
+ <return type="int">
+ </return>
+ <argument index="0" name="step" type="float">
+ </argument>
+ <description>
+ Deprecated alias for [method step_decimals].
+ </description>
+ </method>
+ <method name="dectime">
+ <return type="float">
+ </return>
+ <argument index="0" name="value" type="float">
+ </argument>
+ <argument index="1" name="amount" type="float">
+ </argument>
+ <argument index="2" name="step" type="float">
+ </argument>
+ <description>
+ Returns the result of [code]value[/code] decreased by [code]step[/code] * [code]amount[/code].
+ [codeblock]
+ # a = 59
+ a = dectime(60, 10, 0.1))
+ [/codeblock]
+ </description>
+ </method>
+ <method name="deg2rad">
+ <return type="float">
+ </return>
+ <argument index="0" name="deg" type="float">
+ </argument>
+ <description>
+ Returns degrees converted to radians.
+ [codeblock]
+ # r is 3.141593
+ r = deg2rad(180)
+ [/codeblock]
+ </description>
+ </method>
+ <method name="dict2inst">
+ <return type="Object">
+ </return>
+ <argument index="0" name="dict" type="Dictionary">
+ </argument>
+ <description>
+ Converts a previously converted instance to a dictionary, back into an instance. Useful for deserializing.
+ </description>
+ </method>
+ <method name="ease">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <argument index="1" name="curve" type="float">
+ </argument>
+ <description>
+ Easing function, based on exponent. 0 is constant, 1 is linear, 0 to 1 is ease-in, 1+ is ease out. Negative values are in-out/out in.
+ </description>
+ </method>
+ <method name="exp">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ The natural exponential function. It raises the mathematical constant [b]e[/b] to the power of [code]s[/code] and returns it.
+ [b]e[/b] has an approximate value of 2.71828.
+ For exponents to other bases use the method [method pow].
+ [codeblock]
+ a = exp(2) # Approximately 7.39
+ [/codeblock]
+ </description>
+ </method>
+ <method name="floor">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Rounds [code]s[/code] to the closest smaller integer and returns it.
+ [codeblock]
+ # a is 2.0
+ a = floor(2.99)
+ # a is -3.0
+ a = floor(-2.99)
+ [/codeblock]
+ [b]Note:[/b] This method returns a float. If you need an integer, you can use [code]int(s)[/code] directly.
+ </description>
+ </method>
+ <method name="fmod">
+ <return type="float">
+ </return>
+ <argument index="0" name="a" type="float">
+ </argument>
+ <argument index="1" name="b" type="float">
+ </argument>
+ <description>
+ Returns the floating-point remainder of [code]a/b[/code], keeping the sign of [code]a[/code].
+ [codeblock]
+ # Remainder is 1.5
+ var remainder = fmod(7, 5.5)
+ [/codeblock]
+ For the integer remainder operation, use the % operator.
+ </description>
+ </method>
+ <method name="fposmod">
+ <return type="float">
+ </return>
+ <argument index="0" name="a" type="float">
+ </argument>
+ <argument index="1" name="b" type="float">
+ </argument>
+ <description>
+ Returns the floating-point modulus of [code]a/b[/code] that wraps equally in positive and negative.
+ [codeblock]
+ var i = -6
+ while i &lt; 5:
+ prints(i, fposmod(i, 3))
+ i += 1
+ [/codeblock]
+ Produces:
+ [codeblock]
+ -6 0
+ -5 1
+ -4 2
+ -3 0
+ -2 1
+ -1 2
+ 0 0
+ 1 1
+ 2 2
+ 3 0
+ 4 1
+ [/codeblock]
+ </description>
+ </method>
+ <method name="funcref">
+ <return type="FuncRef">
+ </return>
+ <argument index="0" name="instance" type="Object">
+ </argument>
+ <argument index="1" name="funcname" type="String">
+ </argument>
+ <description>
+ Returns a reference to the specified function [code]funcname[/code] in the [code]instance[/code] node. As functions aren't first-class objects in GDscript, use [code]funcref[/code] to store a [FuncRef] in a variable and call it later.
+ [codeblock]
+ func foo():
+ return("bar")
+
+ a = funcref(self, "foo")
+ print(a.call_func()) # Prints bar
+ [/codeblock]
+ </description>
+ </method>
+ <method name="get_stack">
+ <return type="Array">
+ </return>
+ <description>
+ Returns an array of dictionaries representing the current call stack.
+ [codeblock]
+ func _ready():
+ foo()
+
+ func foo():
+ bar()
+
+ func bar():
+ print(get_stack())
+ [/codeblock]
+ would print
+ [codeblock]
+ [{function:bar, line:12, source:res://script.gd}, {function:foo, line:9, source:res://script.gd}, {function:_ready, line:6, source:res://script.gd}]
+ [/codeblock]
+ </description>
+ </method>
+ <method name="hash">
+ <return type="int">
+ </return>
+ <argument index="0" name="var" type="Variant">
+ </argument>
+ <description>
+ Returns the integer hash of the variable passed.
+ [codeblock]
+ print(hash("a")) # Prints 177670
+ [/codeblock]
+ </description>
+ </method>
+ <method name="inst2dict">
+ <return type="Dictionary">
+ </return>
+ <argument index="0" name="inst" type="Object">
+ </argument>
+ <description>
+ Returns the passed instance converted to a dictionary (useful for serializing).
+ [codeblock]
+ var foo = "bar"
+ func _ready():
+ var d = inst2dict(self)
+ print(d.keys())
+ print(d.values())
+ [/codeblock]
+ Prints out:
+ [codeblock]
+ [@subpath, @path, foo]
+ [, res://test.gd, bar]
+ [/codeblock]
+ </description>
+ </method>
+ <method name="instance_from_id">
+ <return type="Object">
+ </return>
+ <argument index="0" name="instance_id" type="int">
+ </argument>
+ <description>
+ Returns the Object that corresponds to [code]instance_id[/code]. All Objects have a unique instance ID.
+ [codeblock]
+ var foo = "bar"
+ func _ready():
+ var id = get_instance_id()
+ var inst = instance_from_id(id)
+ print(inst.foo) # Prints bar
+ [/codeblock]
+ </description>
+ </method>
+ <method name="inverse_lerp">
+ <return type="float">
+ </return>
+ <argument index="0" name="from" type="float">
+ </argument>
+ <argument index="1" name="to" type="float">
+ </argument>
+ <argument index="2" name="weight" type="float">
+ </argument>
+ <description>
+ Returns a normalized value considering the given range.
+ [codeblock]
+ inverse_lerp(3, 5, 4) # Returns 0.5
+ [/codeblock]
+ </description>
+ </method>
+ <method name="is_equal_approx">
+ <return type="bool">
+ </return>
+ <argument index="0" name="a" type="float">
+ </argument>
+ <argument index="1" name="b" type="float">
+ </argument>
+ <description>
+ Returns [code]true[/code] if [code]a[/code] and [code]b[/code] are approximately equal to each other.
+ </description>
+ </method>
+ <method name="is_inf">
+ <return type="bool">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Returns whether [code]s[/code] is an infinity value (either positive infinity or negative infinity).
+ </description>
+ </method>
+ <method name="is_instance_valid">
+ <return type="bool">
+ </return>
+ <argument index="0" name="instance" type="Object">
+ </argument>
+ <description>
+ Returns whether [code]instance[/code] is a valid object (e.g. has not been deleted from memory).
+ </description>
+ </method>
+ <method name="is_nan">
+ <return type="bool">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Returns whether [code]s[/code] is a NaN (Not-A-Number) value.
+ </description>
+ </method>
+ <method name="is_zero_approx">
+ <return type="bool">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Returns [code]true[/code] if [code]s[/code] is zero or almost zero.
+ </description>
+ </method>
+ <method name="len">
+ <return type="int">
+ </return>
+ <argument index="0" name="var" type="Variant">
+ </argument>
+ <description>
+ Returns length of Variant [code]var[/code]. Length is the character count of String, element count of Array, size of Dictionary, etc.
+ [b]Note:[/b] Generates a fatal error if Variant can not provide a length.
+ [codeblock]
+ a = [1, 2, 3, 4]
+ len(a) # Returns 4
+ [/codeblock]
+ </description>
+ </method>
+ <method name="lerp">
+ <return type="Variant">
+ </return>
+ <argument index="0" name="from" type="Variant">
+ </argument>
+ <argument index="1" name="to" type="Variant">
+ </argument>
+ <argument index="2" name="weight" type="float">
+ </argument>
+ <description>
+ Linearly interpolates between two values by a normalized value.
+ If the [code]from[/code] and [code]to[/code] arguments are of type [int] or [float], the return value is a [float].
+ If both are of the same vector type ([Vector2], [Vector3] or [Color]), the return value will be of the same type ([code]lerp[/code] then calls the vector type's [code]linear_interpolate[/code] method).
+ [codeblock]
+ lerp(0, 4, 0.75) # Returns 3.0
+ lerp(Vector2(1, 5), Vector2(3, 2), 0.5) # Returns Vector2(2, 3.5)
+ [/codeblock]
+ </description>
+ </method>
+ <method name="lerp_angle">
+ <return type="float">
+ </return>
+ <argument index="0" name="from" type="float">
+ </argument>
+ <argument index="1" name="to" type="float">
+ </argument>
+ <argument index="2" name="weight" type="float">
+ </argument>
+ <description>
+ Linearly interpolates between two angles (in radians) by a normalized value.
+ Similar to [method lerp] but interpolate correctly when the angles wrap around [constant @GDScript.TAU].
+ [codeblock]
+ extends Sprite
+ var elapsed = 0.0
+ func _process(delta):
+ var min_angle = deg2rad(0.0)
+ var max_angle = deg2rad(90.0)
+ rotation = lerp_angle(min_angle, max_angle, elapsed)
+ elapsed += delta
+ [/codeblock]
+ </description>
+ </method>
+ <method name="linear2db">
+ <return type="float">
+ </return>
+ <argument index="0" name="nrg" type="float">
+ </argument>
+ <description>
+ Converts from linear energy to decibels (audio).
+ </description>
+ </method>
+ <method name="load">
+ <return type="Resource">
+ </return>
+ <argument index="0" name="path" type="String">
+ </argument>
+ <description>
+ Loads a resource from the filesystem located at [code]path[/code].
+ [b]Note:[/b] Resource paths can be obtained by right-clicking on a resource in the FileSystem dock and choosing [b]Copy Path[/b].
+ [codeblock]
+ # Load a scene called main located in the root of the project directory.
+ var main = load("res://main.tscn")
+ [/codeblock]
+ [b]Important:[/b] The path must be absolute, a local path will just return [code]null[/code].
+ </description>
+ </method>
+ <method name="log">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Natural logarithm. The amount of time needed to reach a certain level of continuous growth.
+ [b]Note:[/b] This is not the same as the "log" function on most calculators, which uses a base 10 logarithm.
+ [codeblock]
+ log(10) # Returns 2.302585
+ [/codeblock]
+ </description>
+ </method>
+ <method name="max">
+ <return type="float">
+ </return>
+ <argument index="0" name="a" type="float">
+ </argument>
+ <argument index="1" name="b" type="float">
+ </argument>
+ <description>
+ Returns the maximum of two values.
+ [codeblock]
+ max(1, 2) # Returns 2
+ max(-3.99, -4) # Returns -3.99
+ [/codeblock]
+ </description>
+ </method>
+ <method name="min">
+ <return type="float">
+ </return>
+ <argument index="0" name="a" type="float">
+ </argument>
+ <argument index="1" name="b" type="float">
+ </argument>
+ <description>
+ Returns the minimum of two values.
+ [codeblock]
+ min(1, 2) # Returns 1
+ min(-3.99, -4) # Returns -4
+ [/codeblock]
+ </description>
+ </method>
+ <method name="move_toward">
+ <return type="float">
+ </return>
+ <argument index="0" name="from" type="float">
+ </argument>
+ <argument index="1" name="to" type="float">
+ </argument>
+ <argument index="2" name="delta" type="float">
+ </argument>
+ <description>
+ Moves [code]from[/code] toward [code]to[/code] by the [code]delta[/code] value.
+ Use a negative [code]delta[/code] value to move away.
+ [codeblock]
+ move_toward(10, 5, 4) # Returns 6
+ [/codeblock]
+ </description>
+ </method>
+ <method name="nearest_po2">
+ <return type="int">
+ </return>
+ <argument index="0" name="value" type="int">
+ </argument>
+ <description>
+ Returns the nearest larger power of 2 for integer [code]value[/code].
+ [codeblock]
+ nearest_po2(3) # Returns 4
+ nearest_po2(4) # Returns 4
+ nearest_po2(5) # Returns 8
+ [/codeblock]
+ </description>
+ </method>
+ <method name="ord">
+ <return type="int">
+ </return>
+ <argument index="0" name="char" type="String">
+ </argument>
+ <description>
+ </description>
+ </method>
+ <method name="parse_json">
+ <return type="Variant">
+ </return>
+ <argument index="0" name="json" type="String">
+ </argument>
+ <description>
+ Parse JSON text to a Variant (use [method typeof] to check if it is what you expect).
+ Be aware that the JSON specification does not define integer or float types, but only a number type. Therefore, parsing a JSON text will convert all numerical values to [float] types.
+ Note that JSON objects do not preserve key order like Godot dictionaries, thus you should not rely on keys being in a certain order if a dictionary is constructed from JSON. In contrast, JSON arrays retain the order of their elements:
+ [codeblock]
+ p = parse_json('["a", "b", "c"]')
+ if typeof(p) == TYPE_ARRAY:
+ print(p[0]) # Prints a
+ else:
+ print("unexpected results")
+ [/codeblock]
+ </description>
+ </method>
+ <method name="polar2cartesian">
+ <return type="Vector2">
+ </return>
+ <argument index="0" name="r" type="float">
+ </argument>
+ <argument index="1" name="th" type="float">
+ </argument>
+ <description>
+ Converts a 2D point expressed in the polar coordinate system (a distance from the origin [code]r[/code] and an angle [code]th[/code]) to the cartesian coordinate system (X and Y axis).
+ </description>
+ </method>
+ <method name="posmod">
+ <return type="int">
+ </return>
+ <argument index="0" name="a" type="int">
+ </argument>
+ <argument index="1" name="b" type="int">
+ </argument>
+ <description>
+ Returns the integer modulus of [code]a/b[/code] that wraps equally in positive and negative.
+ [codeblock]
+ var i = -6
+ while i &lt; 5:
+ prints(i, posmod(i, 3))
+ i += 1
+ [/codeblock]
+ Produces:
+ [codeblock]
+ -6 0
+ -5 1
+ -4 2
+ -3 0
+ -2 1
+ -1 2
+ 0 0
+ 1 1
+ 2 2
+ 3 0
+ 4 1
+ [/codeblock]
+ </description>
+ </method>
+ <method name="pow">
+ <return type="float">
+ </return>
+ <argument index="0" name="base" type="float">
+ </argument>
+ <argument index="1" name="exp" type="float">
+ </argument>
+ <description>
+ Returns the result of [code]x[/code] raised to the power of [code]y[/code].
+ [codeblock]
+ pow(2, 5) # Returns 32
+ [/codeblock]
+ </description>
+ </method>
+ <method name="preload">
+ <return type="Resource">
+ </return>
+ <argument index="0" name="path" type="String">
+ </argument>
+ <description>
+ Returns a resource from the filesystem that is loaded during script parsing.
+ [b]Note:[/b] Resource paths can be obtained by right clicking on a resource in the Assets Panel and choosing "Copy Path".
+ [codeblock]
+ # Load a scene called main located in the root of the project directory.
+ var main = preload("res://main.tscn")
+ [/codeblock]
+ </description>
+ </method>
+ <method name="print" qualifiers="vararg">
+ <return type="void">
+ </return>
+ <description>
+ Converts one or more arguments to strings in the best way possible and prints them to the console.
+ [codeblock]
+ a = [1, 2, 3]
+ print("a", "b", a) # Prints ab[1, 2, 3]
+ [/codeblock]
+ </description>
+ </method>
+ <method name="print_debug" qualifiers="vararg">
+ <return type="void">
+ </return>
+ <description>
+ Like [method print], but prints only when used in debug mode.
+ </description>
+ </method>
+ <method name="print_stack">
+ <return type="void">
+ </return>
+ <description>
+ Prints a stack track at code location, only works when running with debugger turned on.
+ Output in the console would look something like this:
+ [codeblock]
+ Frame 0 - res://test.gd:16 in function '_process'
+ [/codeblock]
+ </description>
+ </method>
+ <method name="printerr" qualifiers="vararg">
+ <return type="void">
+ </return>
+ <description>
+ Prints one or more arguments to strings in the best way possible to standard error line.
+ [codeblock]
+ printerr("prints to stderr")
+ [/codeblock]
+ </description>
+ </method>
+ <method name="printraw" qualifiers="vararg">
+ <return type="void">
+ </return>
+ <description>
+ Prints one or more arguments to strings in the best way possible to console. No newline is added at the end.
+ [codeblock]
+ printraw("A")
+ printraw("B")
+ # Prints AB
+ [/codeblock]
+ [b]Note:[/b] Due to limitations with Godot's built-in console, this only prints to the terminal. If you need to print in the editor, use another method, such as [method print].
+ </description>
+ </method>
+ <method name="prints" qualifiers="vararg">
+ <return type="void">
+ </return>
+ <description>
+ Prints one or more arguments to the console with a space between each argument.
+ [codeblock]
+ prints("A", "B", "C") # Prints A B C
+ [/codeblock]
+ </description>
+ </method>
+ <method name="printt" qualifiers="vararg">
+ <return type="void">
+ </return>
+ <description>
+ Prints one or more arguments to the console with a tab between each argument.
+ [codeblock]
+ printt("A", "B", "C") # Prints A B C
+ [/codeblock]
+ </description>
+ </method>
+ <method name="push_error">
+ <return type="void">
+ </return>
+ <argument index="0" name="message" type="String">
+ </argument>
+ <description>
+ Pushes an error message to Godot's built-in debugger and to the OS terminal.
+ [codeblock]
+ push_error("test error") # Prints "test error" to debugger and terminal as error call
+ [/codeblock]
+ </description>
+ </method>
+ <method name="push_warning">
+ <return type="void">
+ </return>
+ <argument index="0" name="message" type="String">
+ </argument>
+ <description>
+ Pushes a warning message to Godot's built-in debugger and to the OS terminal.
+ [codeblock]
+ push_warning("test warning") # Prints "test warning" to debugger and terminal as warning call
+ [/codeblock]
+ </description>
+ </method>
+ <method name="rad2deg">
+ <return type="float">
+ </return>
+ <argument index="0" name="rad" type="float">
+ </argument>
+ <description>
+ Converts from radians to degrees.
+ [codeblock]
+ rad2deg(0.523599) # Returns 30
+ [/codeblock]
+ </description>
+ </method>
+ <method name="rand_range">
+ <return type="float">
+ </return>
+ <argument index="0" name="from" type="float">
+ </argument>
+ <argument index="1" name="to" type="float">
+ </argument>
+ <description>
+ Random range, any floating point value between [code]from[/code] and [code]to[/code].
+ [codeblock]
+ prints(rand_range(0, 1), rand_range(0, 1)) # Prints e.g. 0.135591 0.405263
+ [/codeblock]
+ </description>
+ </method>
+ <method name="rand_seed">
+ <return type="Array">
+ </return>
+ <argument index="0" name="seed" type="int">
+ </argument>
+ <description>
+ Random from seed: pass a [code]seed[/code], and an array with both number and new seed is returned. "Seed" here refers to the internal state of the pseudo random number generator. The internal state of the current implementation is 64 bits.
+ </description>
+ </method>
+ <method name="randf">
+ <return type="float">
+ </return>
+ <description>
+ Returns a random floating point value on the interval [code][0, 1][/code].
+ [codeblock]
+ randf() # Returns e.g. 0.375671
+ [/codeblock]
+ </description>
+ </method>
+ <method name="randi">
+ <return type="int">
+ </return>
+ <description>
+ Returns a random unsigned 32 bit integer. Use remainder to obtain a random value in the interval [code][0, N][/code] (where N is smaller than 2^32 -1).
+ [codeblock]
+ randi() # Returns random integer between 0 and 2^32 - 1
+ randi() % 20 # Returns random integer between 0 and 19
+ randi() % 100 # Returns random integer between 0 and 99
+ randi() % 100 + 1 # Returns random integer between 1 and 100
+ [/codeblock]
+ </description>
+ </method>
+ <method name="randomize">
+ <return type="void">
+ </return>
+ <description>
+ Randomizes the seed (or the internal state) of the random number generator. Current implementation reseeds using a number based on time.
+ [codeblock]
+ func _ready():
+ randomize()
+ [/codeblock]
+ </description>
+ </method>
+ <method name="range" qualifiers="vararg">
+ <return type="Array">
+ </return>
+ <description>
+ Returns an array with the given range. Range can be 1 argument N (0 to N-1), two arguments (initial, final-1) or three arguments (initial, final-1, increment).
+ [codeblock]
+ for i in range(4):
+ print(i)
+ for i in range(2, 5):
+ print(i)
+ for i in range(0, 6, 2):
+ print(i)
+ [/codeblock]
+ Output:
+ [codeblock]
+ 0
+ 1
+ 2
+ 3
+
+ 2
+ 3
+ 4
+
+ 0
+ 2
+ 4
+ [/codeblock]
+ </description>
+ </method>
+ <method name="range_lerp">
+ <return type="float">
+ </return>
+ <argument index="0" name="value" type="float">
+ </argument>
+ <argument index="1" name="istart" type="float">
+ </argument>
+ <argument index="2" name="istop" type="float">
+ </argument>
+ <argument index="3" name="ostart" type="float">
+ </argument>
+ <argument index="4" name="ostop" type="float">
+ </argument>
+ <description>
+ Maps a [code]value[/code] from range [code][istart, istop][/code] to [code][ostart, ostop][/code].
+ [codeblock]
+ range_lerp(75, 0, 100, -1, 1) # Returns 0.5
+ [/codeblock]
+ </description>
+ </method>
+ <method name="round">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Returns the integral value that is nearest to [code]s[/code], with halfway cases rounded away from zero.
+ [codeblock]
+ round(2.6) # Returns 3
+ [/codeblock]
+ </description>
+ </method>
+ <method name="seed">
+ <return type="void">
+ </return>
+ <argument index="0" name="seed" type="int">
+ </argument>
+ <description>
+ Sets seed for the random number generator.
+ [codeblock]
+ my_seed = "Godot Rocks"
+ seed(my_seed.hash())
+ [/codeblock]
+ </description>
+ </method>
+ <method name="sign">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Returns the sign of [code]s[/code]: -1 or 1. Returns 0 if [code]s[/code] is 0.
+ [codeblock]
+ sign(-6) # Returns -1
+ sign(0) # Returns 0
+ sign(6) # Returns 1
+ [/codeblock]
+ </description>
+ </method>
+ <method name="sin">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Returns the sine of angle [code]s[/code] in radians.
+ [codeblock]
+ sin(0.523599) # Returns 0.5
+ [/codeblock]
+ </description>
+ </method>
+ <method name="sinh">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Returns the hyperbolic sine of [code]s[/code].
+ [codeblock]
+ a = log(2.0) # Returns 0.693147
+ sinh(a) # Returns 0.75
+ [/codeblock]
+ </description>
+ </method>
+ <method name="smoothstep">
+ <return type="float">
+ </return>
+ <argument index="0" name="from" type="float">
+ </argument>
+ <argument index="1" name="to" type="float">
+ </argument>
+ <argument index="2" name="weight" type="float">
+ </argument>
+ <description>
+ Returns a number smoothly interpolated between the [code]from[/code] and [code]to[/code], based on the [code]weight[/code]. Similar to [method lerp], but interpolates faster at the beginning and slower at the end.
+ [codeblock]
+ smoothstep(0, 2, 0.5) # Returns 0.15
+ smoothstep(0, 2, 1.0) # Returns 0.5
+ smoothstep(0, 2, 2.0) # Returns 1.0
+ [/codeblock]
+ </description>
+ </method>
+ <method name="sqrt">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Returns the square root of [code]s[/code].
+ [codeblock]
+ sqrt(9) # Returns 3
+ [/codeblock]
+ </description>
+ </method>
+ <method name="step_decimals">
+ <return type="int">
+ </return>
+ <argument index="0" name="step" type="float">
+ </argument>
+ <description>
+ Returns the position of the first non-zero digit, after the decimal point. Note that the maximum return value is 10, which is a design decision in the implementation.
+ [codeblock]
+ # n is 0
+ n = step_decimals(5)
+ # n is 4
+ n = step_decimals(1.0005)
+ # n is 9
+ n = step_decimals(0.000000005)
+ [/codeblock]
+ </description>
+ </method>
+ <method name="stepify">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <argument index="1" name="step" type="float">
+ </argument>
+ <description>
+ Snaps float value [code]s[/code] to a given [code]step[/code]. This can also be used to round a floating point number to an arbitrary number of decimals.
+ [codeblock]
+ stepify(100, 32) # Returns 96
+ stepify(3.14159, 0.01) # Returns 3.14
+ [/codeblock]
+ </description>
+ </method>
+ <method name="str" qualifiers="vararg">
+ <return type="String">
+ </return>
+ <description>
+ Converts one or more arguments to string in the best way possible.
+ [codeblock]
+ var a = [10, 20, 30]
+ var b = str(a);
+ len(a) # Returns 3
+ len(b) # Returns 12
+ [/codeblock]
+ </description>
+ </method>
+ <method name="str2var">
+ <return type="Variant">
+ </return>
+ <argument index="0" name="string" type="String">
+ </argument>
+ <description>
+ Converts a formatted string that was returned by [method var2str] to the original value.
+ [codeblock]
+ a = '{ "a": 1, "b": 2 }'
+ b = str2var(a)
+ print(b["a"]) # Prints 1
+ [/codeblock]
+ </description>
+ </method>
+ <method name="tan">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Returns the tangent of angle [code]s[/code] in radians.
+ [codeblock]
+ tan(deg2rad(45)) # Returns 1
+ [/codeblock]
+ </description>
+ </method>
+ <method name="tanh">
+ <return type="float">
+ </return>
+ <argument index="0" name="s" type="float">
+ </argument>
+ <description>
+ Returns the hyperbolic tangent of [code]s[/code].
+ [codeblock]
+ a = log(2.0) # Returns 0.693147
+ tanh(a) # Returns 0.6
+ [/codeblock]
+ </description>
+ </method>
+ <method name="to_json">
+ <return type="String">
+ </return>
+ <argument index="0" name="var" type="Variant">
+ </argument>
+ <description>
+ Converts a Variant [code]var[/code] to JSON text and return the result. Useful for serializing data to store or send over the network.
+ [codeblock]
+ a = { "a": 1, "b": 2 }
+ b = to_json(a)
+ print(b) # {"a":1, "b":2}
+ [/codeblock]
+ </description>
+ </method>
+ <method name="type_exists">
+ <return type="bool">
+ </return>
+ <argument index="0" name="type" type="String">
+ </argument>
+ <description>
+ Returns whether the given class exists in [ClassDB].
+ [codeblock]
+ type_exists("Sprite") # Returns true
+ type_exists("Variant") # Returns false
+ [/codeblock]
+ </description>
+ </method>
+ <method name="typeof">
+ <return type="int">
+ </return>
+ <argument index="0" name="what" type="Variant">
+ </argument>
+ <description>
+ Returns the internal type of the given Variant object, using the [enum Variant.Type] values.
+ [codeblock]
+ p = parse_json('["a", "b", "c"]')
+ if typeof(p) == TYPE_ARRAY:
+ print(p[0]) # Prints a
+ else:
+ print("unexpected results")
+ [/codeblock]
+ </description>
+ </method>
+ <method name="validate_json">
+ <return type="String">
+ </return>
+ <argument index="0" name="json" type="String">
+ </argument>
+ <description>
+ Checks that [code]json[/code] is valid JSON data. Returns an empty string if valid, or an error message otherwise.
+ [codeblock]
+ j = to_json([1, 2, 3])
+ v = validate_json(j)
+ if not v:
+ print("valid")
+ else:
+ prints("invalid", v)
+ [/codeblock]
+ </description>
+ </method>
+ <method name="var2bytes">
+ <return type="PoolByteArray">
+ </return>
+ <argument index="0" name="var" type="Variant">
+ </argument>
+ <argument index="1" name="full_objects" type="bool" default="false">
+ </argument>
+ <description>
+ Encodes a variable value to a byte array. When [code]full_objects[/code] is [code]true[/code] encoding objects is allowed (and can potentially include code).
+ </description>
+ </method>
+ <method name="var2str">
+ <return type="String">
+ </return>
+ <argument index="0" name="var" type="Variant">
+ </argument>
+ <description>
+ Converts a Variant [code]var[/code] to a formatted string that can later be parsed using [method str2var].
+ [codeblock]
+ a = { "a": 1, "b": 2 }
+ print(var2str(a))
+ [/codeblock]
+ prints
+ [codeblock]
+ {
+ "a": 1,
+ "b": 2
+ }
+ [/codeblock]
+ </description>
+ </method>
+ <method name="weakref">
+ <return type="WeakRef">
+ </return>
+ <argument index="0" name="obj" type="Object">
+ </argument>
+ <description>
+ Returns a weak reference to an object.
+ A weak reference to an object is not enough to keep the object alive: when the only remaining references to a referent are weak references, garbage collection is free to destroy the referent and reuse its memory for something else. However, until the object is actually destroyed the weak reference may return the object even if there are no strong references to it.
+ </description>
+ </method>
+ <method name="wrapf">
+ <return type="float">
+ </return>
+ <argument index="0" name="value" type="float">
+ </argument>
+ <argument index="1" name="min" type="float">
+ </argument>
+ <argument index="2" name="max" type="float">
+ </argument>
+ <description>
+ Wraps float [code]value[/code] between [code]min[/code] and [code]max[/code].
+ Usable for creating loop-alike behavior or infinite surfaces.
+ [codeblock]
+ # a is 0.5
+ a = wrapf(10.5, 0.0, 10.0)
+ [/codeblock]
+ [codeblock]
+ # a is 9.5
+ a = wrapf(-0.5, 0.0, 10.0)
+ [/codeblock]
+ [codeblock]
+ # Infinite loop between 0.0 and 0.99
+ f = wrapf(f + 0.1, 0.0, 1.0)
+ [/codeblock]
+ [codeblock]
+ # Infinite rotation (in radians)
+ angle = wrapf(angle + 0.1, 0.0, TAU)
+ [/codeblock]
+ [b]Note:[/b] If you just want to wrap between 0.0 and [code]n[/code] (where [code]n[/code] is a positive floating-point value), it is better for performance to use the [method fmod] method like [code]fmod(number, n)[/code].
+ [code]wrapf[/code] is more flexible than using the [method fmod] approach by giving the user a simple control over the minimum value. It also fully supports negative numbers, e.g.
+ [codeblock]
+ # Infinite rotation (in radians)
+ angle = wrapf(angle + 0.1, -PI, PI)
+ [/codeblock]
+ </description>
+ </method>
+ <method name="wrapi">
+ <return type="int">
+ </return>
+ <argument index="0" name="value" type="int">
+ </argument>
+ <argument index="1" name="min" type="int">
+ </argument>
+ <argument index="2" name="max" type="int">
+ </argument>
+ <description>
+ Wraps integer [code]value[/code] between [code]min[/code] and [code]max[/code].
+ Usable for creating loop-alike behavior or infinite surfaces.
+ [codeblock]
+ # a is 0
+ a = wrapi(10, 0, 10)
+ [/codeblock]
+ [codeblock]
+ # a is 9
+ a = wrapi(-1, 0, 10)
+ [/codeblock]
+ [codeblock]
+ # Infinite loop between 0 and 9
+ frame = wrapi(frame + 1, 0, 10)
+ [/codeblock]
+ [b]Note:[/b] If you just want to wrap between 0 and [code]n[/code] (where [code]n[/code] is a positive integer value), it is better for performance to use the modulo operator like [code]number % n[/code].
+ [code]wrapi[/code] is more flexible than using the modulo approach by giving the user a simple control over the minimum value. It also fully supports negative numbers, e.g.
+ [codeblock]
+ # result is -2
+ var result = wrapi(-6, -5, -1)
+ [/codeblock]
+ </description>
+ </method>
+ <method name="yield">
+ <return type="GDScriptFunctionState">
+ </return>
+ <argument index="0" name="object" type="Object" default="null">
+ </argument>
+ <argument index="1" name="signal" type="String" default="&quot;&quot;">
+ </argument>
+ <description>
+ Stops the function execution and returns the current suspended state to the calling function.
+ From the caller, call [method GDScriptFunctionState.resume] on the state to resume execution. This invalidates the state. Within the resumed function, [code]yield()[/code] returns whatever was passed to the [code]resume()[/code] function call.
+ If passed an object and a signal, the execution is resumed when the object emits the given signal. In this case, [code]yield()[/code] returns the argument passed to [code]emit_signal()[/code] if the signal takes only one argument, or an array containing all the arguments passed to [code]emit_signal()[/code] if the signal takes multiple arguments.
+ You can also use [code]yield[/code] to wait for a function to finish:
+ [codeblock]
+ func _ready():
+ yield(do_something(), "completed")
+ yield(do_something_else(), "completed")
+ print("All functions are done!")
+
+ func do_something():
+ print("Something is done!")
+
+ func do_something_else():
+ print("Something else is done!")
+
+ # prints:
+ # Something is done!
+ # Something else is done!
+ # All functions are done!
+ [/codeblock]
+ When yielding on a function, the [code]completed[/code] signal will be emitted automatically when the function returns. It can, therefore, be used as the [code]signal[/code] parameter of the [code]yield[/code] method to resume.
+ If you are planning on calling the same function within a loop, you should consider using [code]yield(get_tree(), "idle_frame")[/code] also.
+ </description>
+ </method>
+ </methods>
+ <constants>
+ <constant name="PI" value="3.141593">
+ Constant that represents how many times the diameter of a circle fits around its perimeter.
+ </constant>
+ <constant name="TAU" value="6.283185">
+ The circle constant, the circumference of the unit circle.
+ </constant>
+ <constant name="INF" value="inf">
+ A positive infinity. (For negative infinity, use -INF).
+ </constant>
+ <constant name="NAN" value="nan">
+ Macro constant that expands to an expression of type float that represents a NaN.
+ The NaN values are used to identify undefined or non-representable values for floating-point elements, such as the square root of negative numbers or the result of 0/0.
+ </constant>
+ </constants>
+</class>
diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml
index 46796c68eb..8e175a7ab8 100644
--- a/modules/gdscript/doc_classes/GDScript.xml
+++ b/modules/gdscript/doc_classes/GDScript.xml
@@ -4,14 +4,12 @@
A script implemented in the GDScript programming language.
</brief_description>
<description>
- A script implemented in the GDScript programming language. The script exends the functionality of all objects that instance it.
+ A script implemented in the GDScript programming language. The script extends the functionality of all objects that instance it.
[method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes.
</description>
<tutorials>
<link>https://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/index.html</link>
</tutorials>
- <demos>
- </demos>
<methods>
<method name="get_as_byte_code" qualifiers="const">
<return type="PoolByteArray">
@@ -21,7 +19,7 @@
</description>
</method>
<method name="new" qualifiers="vararg">
- <return type="Object">
+ <return type="Variant">
</return>
<description>
Returns a new instance of the script.
diff --git a/modules/gdscript/doc_classes/GDScriptFunctionState.xml b/modules/gdscript/doc_classes/GDScriptFunctionState.xml
index f38f39b612..690953108f 100644
--- a/modules/gdscript/doc_classes/GDScriptFunctionState.xml
+++ b/modules/gdscript/doc_classes/GDScriptFunctionState.xml
@@ -8,8 +8,6 @@
</description>
<tutorials>
</tutorials>
- <demos>
- </demos>
<methods>
<method name="is_valid" qualifiers="const">
<return type="bool">
diff --git a/modules/gdscript/doc_classes/GDScriptNativeClass.xml b/modules/gdscript/doc_classes/GDScriptNativeClass.xml
index e86b69c31c..70583d47a7 100644
--- a/modules/gdscript/doc_classes/GDScriptNativeClass.xml
+++ b/modules/gdscript/doc_classes/GDScriptNativeClass.xml
@@ -6,8 +6,6 @@
</description>
<tutorials>
</tutorials>
- <demos>
- </demos>
<methods>
<method name="new">
<return type="Variant">
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 4f59b06ae6..4d6279074c 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -56,6 +56,10 @@ static bool _is_hex_symbol(CharType c) {
return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
}
+static bool _is_bin_symbol(CharType c) {
+ return (c == '0' || c == '1');
+}
+
Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) {
Map<int, TextEdit::HighlighterInfo> color_map;
@@ -76,6 +80,7 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_
bool in_member_variable = false;
bool in_node_path = false;
bool is_hex_notation = false;
+ bool is_bin_notation = false;
bool expect_type = false;
Color keyword_color;
Color color;
@@ -118,14 +123,26 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_
is_hex_notation = false;
}
+ // disallow anything not a 0 or 1
+ if (is_bin_notation && (_is_bin_symbol(str[j]))) {
+ is_number = true;
+ } else if (is_bin_notation) {
+ is_bin_notation = false;
+ is_number = false;
+ } else {
+ is_bin_notation = false;
+ }
+
// check for dot or underscore or 'x' for hex notation in floating point number or 'e' for scientific notation
- if ((str[j] == '.' || str[j] == 'x' || str[j] == '_' || str[j] == 'e') && !in_word && prev_is_number && !is_number) {
+ if ((str[j] == '.' || str[j] == 'x' || str[j] == 'b' || str[j] == '_' || str[j] == 'e') && !in_word && prev_is_number && !is_number) {
is_number = true;
is_symbol = false;
is_char = false;
if (str[j] == 'x' && str[j - 1] == '0') {
is_hex_notation = true;
+ } else if (str[j] == 'b' && str[j - 1] == '0') {
+ is_bin_notation = true;
}
}
@@ -230,7 +247,7 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_
in_function_args = false;
}
- if (expect_type && prev_is_char) {
+ if (expect_type && (prev_is_char || str[j] == '=')) {
expect_type = false;
}
@@ -330,7 +347,7 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_
return color_map;
}
-String GDScriptSyntaxHighlighter::get_name() {
+String GDScriptSyntaxHighlighter::get_name() const {
return "GDScript";
}
@@ -347,20 +364,28 @@ void GDScriptSyntaxHighlighter::_update_cache() {
number_color = text_editor->get_color("number_color");
member_color = text_editor->get_color("member_variable_color");
- EditorSettings *settings = EditorSettings::get_singleton();
- String text_editor_color_theme = settings->get("text_editor/theme/color_theme");
+ const String text_editor_color_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme");
+ const bool default_theme = text_editor_color_theme == "Default";
- bool default_theme = text_editor_color_theme == "Default";
- bool dark_theme = settings->is_dark_theme();
-
- function_definition_color = Color::html(default_theme ? "#01e1ff" : dark_theme ? "#01e1ff" : "#00a5ba");
- node_path_color = Color::html(default_theme ? "#64c15a" : dark_theme ? "64c15a" : "#518b4b");
+ if (default_theme || EditorSettings::get_singleton()->is_dark_theme()) {
+ function_definition_color = Color(0.4, 0.9, 1.0);
+ node_path_color = Color(0.39, 0.76, 0.35);
+ } else {
+ function_definition_color = Color(0.0, 0.65, 0.73);
+ node_path_color = Color(0.32, 0.55, 0.29);
+ }
EDITOR_DEF("text_editor/highlighting/gdscript/function_definition_color", function_definition_color);
EDITOR_DEF("text_editor/highlighting/gdscript/node_path_color", node_path_color);
if (text_editor_color_theme == "Adaptive" || default_theme) {
- settings->set_initial_value("text_editor/highlighting/gdscript/function_definition_color", function_definition_color, true);
- settings->set_initial_value("text_editor/highlighting/gdscript/node_path_color", node_path_color, true);
+ EditorSettings::get_singleton()->set_initial_value(
+ "text_editor/highlighting/gdscript/function_definition_color",
+ function_definition_color,
+ true);
+ EditorSettings::get_singleton()->set_initial_value(
+ "text_editor/highlighting/gdscript/node_path_color",
+ node_path_color,
+ true);
}
function_definition_color = EDITOR_GET("text_editor/highlighting/gdscript/function_definition_color");
diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h
index 9dc10a5d1b..9ba2c80552 100644
--- a/modules/gdscript/editor/gdscript_highlighter.h
+++ b/modules/gdscript/editor/gdscript_highlighter.h
@@ -65,7 +65,7 @@ public:
virtual void _update_cache();
virtual Map<int, TextEdit::HighlighterInfo> _get_line_syntax_highlighting(int p_line);
- virtual String get_name();
+ virtual String get_name() const;
virtual List<String> get_supported_languages();
};
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 4385cf12ad..2f620df8fb 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -30,6 +30,7 @@
#include "gdscript.h"
+#include "core/core_string_names.h"
#include "core/engine.h"
#include "core/global_constants.h"
#include "core/io/file_access_encrypted.h"
@@ -66,10 +67,7 @@ void GDScriptNativeClass::_bind_methods() {
Variant GDScriptNativeClass::_new() {
Object *o = instance();
- if (!o) {
- ERR_EXPLAIN("Class type: '" + String(name) + "' is not instantiable.");
- ERR_FAIL_COND_V(!o, Variant());
- }
+ ERR_FAIL_COND_V_MSG(!o, Variant(), "Class type: '" + String(name) + "' is not instantiable.");
Reference *ref = Object::cast_to<Reference>(o);
if (ref) {
@@ -101,7 +99,7 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco
#endif
instance->owner->set_script_instance(instance);
- /* STEP 2, INITIALIZE AND CONSRTUCT */
+ /* STEP 2, INITIALIZE AND CONSTRUCT */
#ifndef NO_THREADS
GDScriptLanguage::singleton->lock->lock();
@@ -157,8 +155,7 @@ Variant GDScript::_new(const Variant **p_args, int p_argcount, Variant::CallErro
} else {
owner = memnew(Reference); //by default, no base means use reference
}
- ERR_EXPLAIN("Can't inherit from a virtual class");
- ERR_FAIL_COND_V(!owner, Variant());
+ ERR_FAIL_COND_V_MSG(!owner, Variant(), "Can't inherit from a virtual class.");
Reference *r = Object::cast_to<Reference>(owner);
if (r) {
@@ -202,7 +199,7 @@ StringName GDScript::get_instance_base_type() const {
if (native.is_valid())
return native->get_name();
- if (base.is_valid())
+ if (base.is_valid() && base->is_valid())
return base->get_instance_base_type();
return StringName();
}
@@ -226,7 +223,7 @@ void GDScript::get_script_method_list(List<MethodInfo> *p_list) const {
const GDScript *current = this;
while (current) {
- for (const Map<StringName, GDScriptFunction *>::Element *E = member_functions.front(); E; E = E->next()) {
+ for (const Map<StringName, GDScriptFunction *>::Element *E = current->member_functions.front(); E; E = E->next()) {
GDScriptFunction *func = E->get();
MethodInfo mi;
mi.name = E->key();
@@ -325,13 +322,12 @@ ScriptInstance *GDScript::instance_create(Object *p_this) {
if (ScriptDebugger::get_singleton()) {
GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), 0, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'");
}
- ERR_EXPLAIN("Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'");
- ERR_FAIL_V(NULL);
+ ERR_FAIL_V_MSG(NULL, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instanced in object of type '" + p_this->get_class() + "'" + ".");
}
}
Variant::CallError unchecked_error;
- return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this), unchecked_error);
+ return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this) != NULL, unchecked_error);
}
PlaceHolderScriptInstance *GDScript::placeholder_instance_create(Object *p_this) {
@@ -490,7 +486,7 @@ bool GDScript::_update_exports() {
placeholder_fallback_enabled = false;
- if (base_cache.is_valid()) {
+ if (base_cache.is_valid() && base_cache->is_valid()) {
if (base_cache->_update_exports()) {
changed = true;
}
@@ -597,7 +593,7 @@ Error GDScript::reload(bool p_keep_state) {
return err;
}
}
-#if DEBUG_ENABLED
+#ifdef DEBUG_ENABLED
for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) {
const GDScriptWarning &warning = E->get();
if (ScriptDebugger::get_singleton()) {
@@ -647,10 +643,7 @@ Variant GDScript::call(const StringName &p_method, const Variant **p_args, int p
Map<StringName, GDScriptFunction *>::Element *E = top->member_functions.find(p_method);
if (E) {
- if (!E->get()->is_static()) {
- ERR_EXPLAIN("Can't call non-static function: '" + String(p_method) + "' in script.");
- ERR_FAIL_V(Variant());
- }
+ ERR_FAIL_COND_V_MSG(!E->get()->is_static(), Variant(), "Can't call non-static function '" + String(p_method) + "' in script.");
return E->get()->call(NULL, p_args, p_argcount, r_error);
}
@@ -717,7 +710,7 @@ void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const {
void GDScript::_bind_methods() {
- ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &GDScript::_new, MethodInfo(Variant::OBJECT, "new"));
+ ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &GDScript::_new, MethodInfo("new"));
ClassDB::bind_method(D_METHOD("get_as_byte_code"), &GDScript::get_as_byte_code);
}
@@ -825,8 +818,7 @@ Error GDScript::load_source_code(const String &p_path) {
String s;
if (s.parse_utf8((const char *)w.ptr())) {
- ERR_EXPLAIN("Script '" + p_path + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode.");
- ERR_FAIL_V(ERR_INVALID_DATA);
+ ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded. Please ensure that scripts are saved in valid UTF-8 unicode.");
}
source = s;
@@ -1064,7 +1056,7 @@ Variant::Type GDScriptInstance::get_property_type(const StringName &p_name, bool
}
void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const {
- // exported members, not doen yet!
+ // exported members, not done yet!
const GDScript *sptr = script.ptr();
List<PropertyInfo> props;
@@ -1078,11 +1070,8 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const
Variant ret = const_cast<GDScriptFunction *>(E->get())->call(const_cast<GDScriptInstance *>(this), NULL, 0, err);
if (err.error == Variant::CallError::CALL_OK) {
- if (ret.get_type() != Variant::ARRAY) {
+ ERR_FAIL_COND_MSG(ret.get_type() != Variant::ARRAY, "Wrong type for _get_property_list, must be an array of dictionaries.");
- ERR_EXPLAIN("Wrong type for _get_property list, must be an array of dictionaries.");
- ERR_FAIL();
- }
Array arr = ret;
for (int i = 0; i < arr.size(); i++) {
@@ -1166,8 +1155,6 @@ bool GDScriptInstance::has_method(const StringName &p_method) const {
}
Variant GDScriptInstance::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
- //printf("calling %ls:%i method %ls\n", script->get_path().c_str(), -1, String(p_method).c_str());
-
GDScript *sptr = script.ptr();
while (sptr) {
Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(p_method);
@@ -1234,6 +1221,26 @@ void GDScriptInstance::notification(int p_notification) {
}
}
+String GDScriptInstance::to_string(bool *r_valid) {
+ if (has_method(CoreStringNames::get_singleton()->_to_string)) {
+ Variant::CallError ce;
+ Variant ret = call(CoreStringNames::get_singleton()->_to_string, NULL, 0, ce);
+ if (ce.error == Variant::CallError::CALL_OK) {
+ if (ret.get_type() != Variant::STRING) {
+ if (r_valid)
+ *r_valid = false;
+ ERR_FAIL_V_MSG(String(), "Wrong type for " + CoreStringNames::get_singleton()->_to_string + ", must be a String.");
+ }
+ if (r_valid)
+ *r_valid = true;
+ return ret.operator String();
+ }
+ }
+ if (r_valid)
+ *r_valid = false;
+ return String();
+}
+
Ref<Script> GDScriptInstance::get_script() const {
return script;
@@ -1943,7 +1950,11 @@ String GDScriptWarning::get_message() const {
} break;
case UNUSED_VARIABLE: {
CHECK_SYMBOLS(1);
- return "The local variable '" + symbols[0] + "' is declared but never used in the block.";
+ return "The local variable '" + symbols[0] + "' is declared but never used in the block. If this is intended, prefix it with an underscore: '_" + symbols[0] + "'";
+ } break;
+ case SHADOWED_VARIABLE: {
+ CHECK_SYMBOLS(2);
+ return "The local variable '" + symbols[0] + "' is shadowing an already-defined variable at line " + symbols[1] + ".";
} break;
case UNUSED_CLASS_VARIABLE: {
CHECK_SYMBOLS(1);
@@ -1951,7 +1962,7 @@ String GDScriptWarning::get_message() const {
} break;
case UNUSED_ARGUMENT: {
CHECK_SYMBOLS(2);
- return "The argument '" + symbols[1] + "' is never used in the function '" + symbols[0] + "'.";
+ return "The argument '" + symbols[1] + "' is never used in the function '" + symbols[0] + "'. If this is intended, prefix it with an underscore: '_" + symbols[1] + "'";
} break;
case UNREACHABLE_CODE: {
CHECK_SYMBOLS(1);
@@ -2031,8 +2042,7 @@ String GDScriptWarning::get_message() const {
} break;
case WARNING_MAX: break; // Can't happen, but silences warning
}
- ERR_EXPLAIN("Invalid GDScript warning code: " + get_name_from_code(code));
- ERR_FAIL_V(String());
+ ERR_FAIL_V_MSG(String(), "Invalid GDScript warning code: " + get_name_from_code(code) + ".");
#undef CHECK_SYMBOLS
}
@@ -2048,6 +2058,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"UNASSIGNED_VARIABLE",
"UNASSIGNED_VARIABLE_OP_ASSIGN",
"UNUSED_VARIABLE",
+ "SHADOWED_VARIABLE",
"UNUSED_CLASS_VARIABLE",
"UNUSED_ARGUMENT",
"UNREACHABLE_CODE",
@@ -2083,8 +2094,7 @@ GDScriptWarning::Code GDScriptWarning::get_code_from_name(const String &p_name)
}
}
- ERR_EXPLAIN("Invalid GDScript warning name: " + p_name);
- ERR_FAIL_V(WARNING_MAX);
+ ERR_FAIL_V_MSG(WARNING_MAX, "Invalid GDScript warning name: " + p_name);
}
#endif // DEBUG_ENABLED
@@ -2129,10 +2139,12 @@ GDScriptLanguage::GDScriptLanguage() {
#ifdef DEBUG_ENABLED
GLOBAL_DEF("debug/gdscript/warnings/enable", true);
GLOBAL_DEF("debug/gdscript/warnings/treat_warnings_as_errors", false);
+ GLOBAL_DEF("debug/gdscript/warnings/exclude_addons", true);
GLOBAL_DEF("debug/gdscript/completion/autocomplete_setters_and_getters", false);
for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower();
- GLOBAL_DEF("debug/gdscript/warnings/" + warning, !warning.begins_with("unsafe_"));
+ bool default_enabled = !warning.begins_with("unsafe_") && i != GDScriptWarning::UNUSED_CLASS_VARIABLE;
+ GLOBAL_DEF("debug/gdscript/warnings/" + warning, default_enabled);
}
#endif // DEBUG_ENABLED
}
@@ -2165,11 +2177,11 @@ RES ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_ori
script->set_script_path(p_original_path); // script needs this.
script->set_path(p_original_path);
Error err = script->load_byte_code(p_path);
- ERR_FAIL_COND_V(err != OK, RES());
+ ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load byte code from file '" + p_path + "'.");
} else {
Error err = script->load_source_code(p_path);
- ERR_FAIL_COND_V(err != OK, RES());
+ ERR_FAIL_COND_V_MSG(err != OK, RES(), "Cannot load source code from file '" + p_path + "'.");
script->set_script_path(p_original_path); // script needs this.
script->set_path(p_original_path);
@@ -2205,7 +2217,7 @@ String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) con
void ResourceFormatLoaderGDScript::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
FileAccessRef file = FileAccess::open(p_path, FileAccess::READ);
- ERR_FAIL_COND(!file);
+ ERR_FAIL_COND_MSG(!file, "Cannot open file '" + p_path + "'.");
String source = file->get_as_utf8_string();
if (source.empty()) {
@@ -2232,10 +2244,7 @@ Error ResourceFormatSaverGDScript::save(const String &p_path, const RES &p_resou
Error err;
FileAccess *file = FileAccess::open(p_path, FileAccess::WRITE, &err);
- if (err) {
-
- ERR_FAIL_COND_V(err, err);
- }
+ ERR_FAIL_COND_V_MSG(err, err, "Cannot save GDScript file '" + p_path + "'.");
file->store_string(source);
if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) {
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index ded873c7d3..a5ad23c75d 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -251,6 +251,7 @@ public:
Variant debug_get_member_by_index(int p_idx) const { return members[p_idx]; }
virtual void notification(int p_notification);
+ String to_string(bool *r_valid);
virtual Ref<Script> get_script() const;
@@ -273,6 +274,7 @@ struct GDScriptWarning {
UNASSIGNED_VARIABLE, // Variable used but never assigned
UNASSIGNED_VARIABLE_OP_ASSIGN, // Variable never assigned but used in an assignment operation (+=, *=, etc)
UNUSED_VARIABLE, // Local variable is declared but never used
+ SHADOWED_VARIABLE, // Variable name shadowed by other variable
UNUSED_CLASS_VARIABLE, // Class variable is declared but never used in the file
UNUSED_ARGUMENT, // Function argument is never used
UNREACHABLE_CODE, // Code after a return statement
@@ -406,9 +408,10 @@ public:
csi.resize(_debug_call_stack_pos);
for (int i = 0; i < _debug_call_stack_pos; i++) {
csi.write[_debug_call_stack_pos - i - 1].line = _call_stack[i].line ? *_call_stack[i].line : 0;
- if (_call_stack[i].function)
+ if (_call_stack[i].function) {
csi.write[_debug_call_stack_pos - i - 1].func = _call_stack[i].function->get_name();
- csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_path();
+ csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_path();
+ }
}
return csi;
}
@@ -444,6 +447,7 @@ public:
virtual void get_reserved_words(List<String> *p_words) const;
virtual void get_comment_delimiters(List<String> *p_delimiters) const;
virtual void get_string_delimiters(List<String> *p_delimiters) const;
+ virtual String _get_processed_template(const String &p_template, const String &p_base_class_name) const;
virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const;
virtual bool is_using_templates();
virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script);
@@ -454,9 +458,9 @@ public:
virtual bool can_inherit_from_file() { return true; }
virtual int find_function(const String &p_function, const String &p_code) const;
virtual String make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const;
- virtual Error complete_code(const String &p_code, const String &p_base_path, Object *p_owner, List<String> *r_options, bool &r_forced, String &r_call_hint);
+ virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint);
#ifdef TOOLS_ENABLED
- virtual Error lookup_code(const String &p_code, const String &p_symbol, const String &p_base_path, Object *p_owner, LookupResult &r_result);
+ virtual Error lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result);
#endif
virtual String _get_indentation() const;
virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const;
@@ -505,7 +509,6 @@ public:
};
class ResourceFormatLoaderGDScript : public ResourceFormatLoader {
- GDCLASS(ResourceFormatLoaderGDScript, ResourceFormatLoader)
public:
virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL);
virtual void get_recognized_extensions(List<String> *p_extensions) const;
@@ -515,7 +518,6 @@ public:
};
class ResourceFormatSaverGDScript : public ResourceFormatSaver {
- GDCLASS(ResourceFormatSaverGDScript, ResourceFormatSaver)
public:
virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0);
virtual void get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index ae67521749..dea2225e91 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -480,16 +480,16 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
switch (cast_type.kind) {
case GDScriptDataType::BUILTIN: {
codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_BUILTIN);
- codegen.opcodes.push_back(cn->cast_type.builtin_type);
+ codegen.opcodes.push_back(cast_type.builtin_type);
} break;
case GDScriptDataType::NATIVE: {
int class_idx;
- if (GDScriptLanguage::get_singleton()->get_global_map().has(cn->cast_type.native_type)) {
+ if (GDScriptLanguage::get_singleton()->get_global_map().has(cast_type.native_type)) {
- class_idx = GDScriptLanguage::get_singleton()->get_global_map()[cn->cast_type.native_type];
+ class_idx = GDScriptLanguage::get_singleton()->get_global_map()[cast_type.native_type];
class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root)
} else {
- _set_error("Invalid native class type '" + String(cn->cast_type.native_type) + "'.", cn);
+ _set_error("Invalid native class type '" + String(cast_type.native_type) + "'.", cn);
return -1;
}
codegen.opcodes.push_back(GDScriptFunction::OPCODE_CAST_TO_NATIVE); // perform operator
@@ -498,7 +498,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
case GDScriptDataType::SCRIPT:
case GDScriptDataType::GDSCRIPT: {
- Variant script = cn->cast_type.script_type;
+ Variant script = cast_type.script_type;
int idx = codegen.get_constant_pos(script);
idx |= GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS; //make it a local constant (faster access)
@@ -1073,7 +1073,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
int set_index;
bool named = false;
- if (static_cast<const GDScriptParser::OperatorNode *>(op)->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED) {
+ if (op->op == GDScriptParser::OperatorNode::OP_INDEX_NAMED) {
set_index = codegen.get_name_map_pos(static_cast<const GDScriptParser::IdentifierNode *>(op->arguments[1])->name);
named = true;
@@ -1241,8 +1241,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
} break;
default: {
- ERR_EXPLAIN("Bug in bytecode compiler, unexpected operator #" + itos(on->op) + " in parse tree while parsing expression.");
- ERR_FAIL_V(0); //unreachable code
+ ERR_FAIL_V_MSG(0, "Bug in bytecode compiler, unexpected operator #" + itos(on->op) + " in parse tree while parsing expression."); //unreachable code
} break;
}
@@ -1255,12 +1254,9 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
//TYPE_TYPE,
default: {
- ERR_EXPLAIN("Bug in bytecode compiler, unexpected node in parse tree while parsing expression.");
- ERR_FAIL_V(-1); //unreachable code
+ ERR_FAIL_V_MSG(-1, "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); //unreachable code
} break;
}
-
- ERR_FAIL_V(-1); //unreachable code
}
Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::BlockNode *p_block, int p_stack_level, int p_break_addr, int p_continue_addr) {
@@ -1524,8 +1520,16 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Blo
if (ret2 < 0)
return ERR_PARSE_ERROR;
+ int message_ret = 0;
+ if (as->message) {
+ message_ret = _parse_expression(codegen, as->message, p_stack_level + 1, false);
+ if (message_ret < 0)
+ return ERR_PARSE_ERROR;
+ }
+
codegen.opcodes.push_back(GDScriptFunction::OPCODE_ASSERT);
codegen.opcodes.push_back(ret2);
+ codegen.opcodes.push_back(message_ret);
#endif
} break;
case GDScriptParser::Node::TYPE_BREAKPOINT: {
@@ -1863,6 +1867,19 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
p_script->base = base;
p_script->_base = base.ptr();
p_script->member_indices = base->member_indices;
+
+ if (p_class->base_type.kind == GDScriptParser::DataType::CLASS) {
+ if (!parsed_classes.has(p_script->_base)) {
+ if (parsing_classes.has(p_script->_base)) {
+ _set_error("Cyclic class reference for '" + String(p_class->name) + "'.", p_class);
+ return ERR_PARSE_ERROR;
+ }
+ Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state);
+ if (err) {
+ return err;
+ }
+ }
+ }
} break;
default: {
_set_error("Parser bug: invalid inheritance.", p_class);
@@ -1964,12 +1981,12 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
for (int i = 0; i < p_class->subclasses.size(); i++) {
StringName name = p_class->subclasses[i]->name;
-
- GDScript *subclass = p_script->subclasses[name].ptr();
+ Ref<GDScript> &subclass = p_script->subclasses[name];
+ GDScript *subclass_ptr = subclass.ptr();
// Subclass might still be parsing, just skip it
- if (!parsed_classes.has(subclass) && !parsing_classes.has(subclass)) {
- Error err = _parse_class_level(subclass, p_class->subclasses[i], p_keep_state);
+ if (!parsed_classes.has(subclass_ptr) && !parsing_classes.has(subclass_ptr)) {
+ Error err = _parse_class_level(subclass_ptr, p_class->subclasses[i], p_keep_state);
if (err)
return err;
}
@@ -2055,7 +2072,7 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa
}
instance->owner->set_script_instance(instance);
- /* STEP 2, INITIALIZE AND CONSRTUCT */
+ /* STEP 2, INITIALIZE AND CONSTRUCT */
Variant::CallError ce;
p_script->initializer->call(instance, NULL, 0, ce);
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index fafc73b7e6..280bc37dc0 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -44,48 +44,62 @@ void GDScriptLanguage::get_comment_delimiters(List<String> *p_delimiters) const
p_delimiters->push_back("#");
}
+
void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const {
p_delimiters->push_back("\" \"");
p_delimiters->push_back("' '");
p_delimiters->push_back("\"\"\" \"\"\"");
}
+
+String GDScriptLanguage::_get_processed_template(const String &p_template, const String &p_base_class_name) const {
+
+ String processed_template = p_template;
+
+#ifdef TOOLS_ENABLED
+ if (EDITOR_DEF("text_editor/completion/add_type_hints", false)) {
+ processed_template = processed_template.replace("%INT_TYPE%", ": int");
+ processed_template = processed_template.replace("%STRING_TYPE%", ": String");
+ processed_template = processed_template.replace("%FLOAT_TYPE%", ": float");
+ processed_template = processed_template.replace("%VOID_RETURN%", " -> void");
+ } else {
+ processed_template = processed_template.replace("%INT_TYPE%", "");
+ processed_template = processed_template.replace("%STRING_TYPE%", "");
+ processed_template = processed_template.replace("%FLOAT_TYPE%", "");
+ processed_template = processed_template.replace("%VOID_RETURN%", "");
+ }
+#else
+ processed_template = processed_template.replace("%INT_TYPE%", "");
+ processed_template = processed_template.replace("%STRING_TYPE%", "");
+ processed_template = processed_template.replace("%FLOAT_TYPE%", "");
+ processed_template = processed_template.replace("%VOID_RETURN%", "");
+#endif
+
+ processed_template = processed_template.replace("%BASE%", p_base_class_name);
+ processed_template = processed_template.replace("%TS%", _get_indentation());
+
+ return processed_template;
+}
+
Ref<Script> GDScriptLanguage::get_template(const String &p_class_name, const String &p_base_class_name) const {
String _template = "extends %BASE%\n"
"\n"
+ "\n"
"# Declare member variables here. Examples:\n"
"# var a%INT_TYPE% = 2\n"
"# var b%STRING_TYPE% = \"text\"\n"
"\n"
+ "\n"
"# Called when the node enters the scene tree for the first time.\n"
"func _ready()%VOID_RETURN%:\n"
"%TS%pass # Replace with function body.\n"
"\n"
+ "\n"
"# Called every frame. 'delta' is the elapsed time since the previous frame.\n"
"#func _process(delta%FLOAT_TYPE%)%VOID_RETURN%:\n"
"#%TS%pass\n";
-#ifdef TOOLS_ENABLED
- if (EDITOR_DEF("text_editor/completion/add_type_hints", false)) {
- _template = _template.replace("%INT_TYPE%", ": int");
- _template = _template.replace("%STRING_TYPE%", ": String");
- _template = _template.replace("%FLOAT_TYPE%", ": float");
- _template = _template.replace("%VOID_RETURN%", " -> void");
- } else {
- _template = _template.replace("%INT_TYPE%", "");
- _template = _template.replace("%STRING_TYPE%", "");
- _template = _template.replace("%FLOAT_TYPE%", "");
- _template = _template.replace("%VOID_RETURN%", "");
- }
-#else
- _template = _template.replace("%INT_TYPE%", "");
- _template = _template.replace("%STRING_TYPE%", "");
- _template = _template.replace("%FLOAT_TYPE%", "");
- _template = _template.replace("%VOID_RETURN%", "");
-#endif
-
- _template = _template.replace("%BASE%", p_base_class_name);
- _template = _template.replace("%TS%", _get_indentation());
+ _template = _get_processed_template(_template, p_base_class_name);
Ref<GDScript> script;
script.instance();
@@ -101,10 +115,8 @@ bool GDScriptLanguage::is_using_templates() {
void GDScriptLanguage::make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script) {
- String src = p_script->get_source_code();
- src = src.replace("%BASE%", p_base_class_name);
- src = src.replace("%TS%", _get_indentation());
- p_script->set_source_code(src);
+ String _template = _get_processed_template(p_script->get_source_code(), p_base_class_name);
+ p_script->set_source_code(_template);
}
bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const {
@@ -150,7 +162,11 @@ bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &
for (int i = 0; i < cl->subclasses.size(); i++) {
for (int j = 0; j < cl->subclasses[i]->functions.size(); j++) {
- funcs[cl->subclasses[i]->functions[j]->line] = String(cl->subclasses[i]->name) + "." + String(cl->subclasses[i]->functions[j]->name);
+ funcs[cl->subclasses[i]->functions[j]->line] = String(cl->subclasses[i]->name) + "." + cl->subclasses[i]->functions[j]->name;
+ }
+ for (int j = 0; j < cl->subclasses[i]->static_functions.size(); j++) {
+
+ funcs[cl->subclasses[i]->static_functions[j]->line] = String(cl->subclasses[i]->name) + "." + cl->subclasses[i]->static_functions[j]->name;
}
}
@@ -210,7 +226,7 @@ bool GDScriptLanguage::debug_break_parse(const String &p_file, int p_line, const
_debug_parse_err_line = p_line;
_debug_parse_err_file = p_file;
_debug_error = p_error;
- ScriptDebugger::get_singleton()->debug(this, false);
+ ScriptDebugger::get_singleton()->debug(this, false, true);
return true;
} else {
return false;
@@ -224,7 +240,8 @@ bool GDScriptLanguage::debug_break(const String &p_error, bool p_allow_continue)
_debug_parse_err_line = -1;
_debug_parse_err_file = "";
_debug_error = p_error;
- ScriptDebugger::get_singleton()->debug(this, p_allow_continue);
+ bool is_error_breakpoint = p_error != "Breakpoint";
+ ScriptDebugger::get_singleton()->debug(this, p_allow_continue, is_error_breakpoint);
return true;
} else {
return false;
@@ -373,8 +390,6 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant>
String GDScriptLanguage::debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) {
- if (_debug_parse_err_line >= 0)
- return "";
return "";
}
@@ -500,10 +515,14 @@ struct GDScriptCompletionIdentifier {
assigned_expression(NULL) {}
};
-static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Set<String> &r_list) {
+static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map<String, ScriptCodeCompletionOption> &r_list) {
+
+ const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\"";
for (int i = 0; i < p_dir->get_file_count(); i++) {
- r_list.insert("\"" + p_dir->get_file_path(i) + "\"");
+ ScriptCodeCompletionOption option(p_dir->get_file_path(i), ScriptCodeCompletionOption::KIND_FILE_PATH);
+ option.insert_text = quote_style + option.display + quote_style;
+ r_list.insert(option.display, option);
}
for (int i = 0; i < p_dir->get_subdir_count(); i++) {
@@ -619,7 +638,7 @@ static GDScriptCompletionIdentifier _type_from_gdtype(const GDScriptDataType &p_
switch (p_gdtype.kind) {
case GDScriptDataType::UNINITIALIZED: {
- ERR_EXPLAIN("Uninitialized completion. Please report a bug.");
+ ERR_PRINT("Uninitialized completion. Please report a bug.");
} break;
case GDScriptDataType::BUILTIN: {
ci.type.kind = GDScriptParser::DataType::BUILTIN;
@@ -1061,7 +1080,8 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G
case GDScriptParser::OperatorNode::OP_BIT_AND: vop = Variant::OP_BIT_AND; break;
case GDScriptParser::OperatorNode::OP_BIT_OR: vop = Variant::OP_BIT_OR; break;
case GDScriptParser::OperatorNode::OP_BIT_XOR: vop = Variant::OP_BIT_XOR; break;
- default: {}
+ default: {
+ }
}
if (vop == Variant::OP_MAX) {
@@ -1116,7 +1136,8 @@ static bool _guess_expression_type(GDScriptCompletionContext &p_context, const G
} break;
}
} break;
- default: {}
+ default: {
+ }
}
// It may have found a null, but that's never useful
@@ -1794,14 +1815,15 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio
return arghint;
}
-static void _find_enumeration_candidates(const String p_enum_hint, Set<String> &r_result) {
+static void _find_enumeration_candidates(const String p_enum_hint, Map<String, ScriptCodeCompletionOption> &r_result) {
if (p_enum_hint.find(".") == -1) {
// Global constant
StringName current_enum = p_enum_hint;
for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) {
if (GlobalConstants::get_global_constant_enum(i) == current_enum) {
- r_result.insert(GlobalConstants::get_global_constant_name(i));
+ ScriptCodeCompletionOption option(GlobalConstants::get_global_constant_name(i), ScriptCodeCompletionOption::KIND_ENUM);
+ r_result.insert(option.display, option);
}
}
} else {
@@ -1816,15 +1838,17 @@ static void _find_enumeration_candidates(const String p_enum_hint, Set<String> &
ClassDB::get_enum_constants(class_name, enum_name, &enum_constants);
for (List<StringName>::Element *E = enum_constants.front(); E; E = E->next()) {
String candidate = class_name + "." + E->get();
- r_result.insert(candidate);
+ ScriptCodeCompletionOption option(candidate, ScriptCodeCompletionOption::KIND_ENUM);
+ r_result.insert(option.display, option);
}
}
}
-static void _find_identifiers_in_block(const GDScriptCompletionContext &p_context, Set<String> &r_result) {
+static void _find_identifiers_in_block(const GDScriptCompletionContext &p_context, Map<String, ScriptCodeCompletionOption> &r_result) {
for (Map<StringName, GDScriptParser::LocalVarNode *>::Element *E = p_context.block->variables.front(); E; E = E->next()) {
if (E->get()->line < p_context.line) {
- r_result.insert(E->key().operator String());
+ ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_VARIABLE);
+ r_result.insert(option.display, option);
}
}
if (p_context.block->parent_block) {
@@ -1834,40 +1858,47 @@ static void _find_identifiers_in_block(const GDScriptCompletionContext &p_contex
}
}
-static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Set<String> &r_result);
+static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result);
-static void _find_identifiers_in_class(const GDScriptCompletionContext &p_context, bool p_static, bool p_only_functions, bool p_parent_only, Set<String> &r_result) {
+static void _find_identifiers_in_class(const GDScriptCompletionContext &p_context, bool p_static, bool p_only_functions, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result) {
if (!p_parent_only) {
if (!p_static && !p_only_functions) {
for (int i = 0; i < p_context._class->variables.size(); i++) {
- r_result.insert(p_context._class->variables[i].identifier);
+ ScriptCodeCompletionOption option(p_context._class->variables[i].identifier, ScriptCodeCompletionOption::KIND_MEMBER);
+ r_result.insert(option.display, option);
}
}
if (!p_only_functions) {
for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_context._class->constant_expressions.front(); E; E = E->next()) {
- r_result.insert(E->key());
+ ScriptCodeCompletionOption option(E->key(), ScriptCodeCompletionOption::KIND_CONSTANT);
+ r_result.insert(option.display, option);
}
for (int i = 0; i < p_context._class->subclasses.size(); i++) {
- r_result.insert(p_context._class->subclasses[i]->name);
+ ScriptCodeCompletionOption option(p_context._class->subclasses[i]->name, ScriptCodeCompletionOption::KIND_CLASS);
+ r_result.insert(option.display, option);
}
}
for (int i = 0; i < p_context._class->static_functions.size(); i++) {
+ ScriptCodeCompletionOption option(p_context._class->static_functions[i]->name.operator String(), ScriptCodeCompletionOption::KIND_FUNCTION);
if (p_context._class->static_functions[i]->arguments.size()) {
- r_result.insert(p_context._class->static_functions[i]->name.operator String() + "(");
+ option.insert_text += "(";
} else {
- r_result.insert(p_context._class->static_functions[i]->name.operator String() + "()");
+ option.insert_text += "()";
}
+ r_result.insert(option.display, option);
}
if (!p_static) {
for (int i = 0; i < p_context._class->functions.size(); i++) {
+ ScriptCodeCompletionOption option(p_context._class->functions[i]->name.operator String(), ScriptCodeCompletionOption::KIND_FUNCTION);
if (p_context._class->functions[i]->arguments.size()) {
- r_result.insert(p_context._class->functions[i]->name.operator String() + "(");
+ option.insert_text += "(";
} else {
- r_result.insert(p_context._class->functions[i]->name.operator String() + "()");
+ option.insert_text += "()";
}
+ r_result.insert(option.display, option);
}
}
}
@@ -1885,12 +1916,14 @@ static void _find_identifiers_in_class(const GDScriptCompletionContext &p_contex
_find_identifiers_in_base(c, base_type, p_only_functions, r_result);
}
-static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Set<String> &r_result) {
+static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) {
GDScriptParser::DataType base_type = p_base.type;
bool _static = base_type.is_meta_type;
if (_static && base_type.kind != GDScriptParser::DataType::BUILTIN) {
- r_result.insert("new(");
+ ScriptCodeCompletionOption option("new", ScriptCodeCompletionOption::KIND_FUNCTION);
+ option.insert_text += "(";
+ r_result.insert(option.display, option);
}
while (base_type.has_type) {
@@ -1907,27 +1940,40 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context
Ref<GDScript> script = base_type.script_type;
if (script.is_valid()) {
if (!_static && !p_only_functions) {
+ if (p_context.base && p_context.base->get_script_instance()) {
+ List<PropertyInfo> members;
+ p_context.base->get_script_instance()->get_property_list(&members);
+ for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) {
+ ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER);
+ r_result.insert(option.display, option);
+ }
+ }
for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) {
- r_result.insert(E->get().operator String());
+ ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_MEMBER);
+ r_result.insert(option.display, option);
}
}
if (!p_only_functions) {
for (const Map<StringName, Variant>::Element *E = script->get_constants().front(); E; E = E->next()) {
- r_result.insert(E->key().operator String());
+ ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT);
+ r_result.insert(option.display, option);
}
}
for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) {
if (!_static || E->get()->is_static()) {
+ ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_FUNCTION);
if (E->get()->get_argument_count()) {
- r_result.insert(E->key().operator String() + "(");
+ option.insert_text += "(";
} else {
- r_result.insert(E->key().operator String() + "()");
+ option.insert_text += "()";
}
+ r_result.insert(option.display, option);
}
}
if (!p_only_functions) {
for (const Map<StringName, Ref<GDScript> >::Element *E = script->get_subclasses().front(); E; E = E->next()) {
- r_result.insert(E->key().operator String());
+ ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
+ r_result.insert(option.display, option);
}
}
base_type = GDScriptParser::DataType();
@@ -1938,7 +1984,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context
} else {
base_type.has_type = script->get_instance_base_type() != StringName();
base_type.kind = GDScriptParser::DataType::NATIVE;
- base_type.script_type = script->get_instance_base_type();
+ base_type.native_type = script->get_instance_base_type();
}
} else {
return;
@@ -1951,25 +1997,29 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context
List<PropertyInfo> members;
scr->get_script_property_list(&members);
for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) {
- r_result.insert(E->get().name);
+ ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER);
+ r_result.insert(option.display, option);
}
}
if (!p_only_functions) {
Map<StringName, Variant> constants;
scr->get_constants(&constants);
for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) {
- r_result.insert(E->key().operator String());
+ ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT);
+ r_result.insert(option.display, option);
}
}
List<MethodInfo> methods;
scr->get_script_method_list(&methods);
for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
+ ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION);
if (E->get().arguments.size()) {
- r_result.insert(E->get().name + "(");
+ option.insert_text += "(";
} else {
- r_result.insert(E->get().name + "()");
+ option.insert_text += "()";
}
+ r_result.insert(option.display, option);
}
Ref<Script> base_script = scr->get_base_script();
@@ -1996,7 +2046,8 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context
List<String> constants;
ClassDB::get_integer_constant_list(type, &constants);
for (List<String>::Element *E = constants.front(); E; E = E->next()) {
- r_result.insert(E->get());
+ ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CONSTANT);
+ r_result.insert(option.display, option);
}
if (!_static) {
@@ -2009,7 +2060,8 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context
if (E->get().name.find("/") != -1) {
continue;
}
- r_result.insert(E->get().name);
+ ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER);
+ r_result.insert(option.display, option);
}
}
}
@@ -2022,11 +2074,13 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context
if (E->get().name.begins_with("_")) {
continue;
}
+ ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION);
if (E->get().arguments.size()) {
- r_result.insert(E->get().name + "(");
+ option.insert_text += "(";
} else {
- r_result.insert(E->get().name + "()");
+ option.insert_text += "()";
}
+ r_result.insert(option.display, option);
}
}
@@ -2041,11 +2095,12 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context
if (!p_only_functions) {
List<PropertyInfo> members;
- tmp.get_property_list(&members);
+ p_base.value.get_property_list(&members);
for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) {
if (String(E->get().name).find("/") == -1) {
- r_result.insert(E->get().name);
+ ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER);
+ r_result.insert(option.display, option);
}
}
}
@@ -2053,11 +2108,13 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context
List<MethodInfo> methods;
tmp.get_method_list(&methods);
for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
+ ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION);
if (E->get().arguments.size()) {
- r_result.insert(E->get().name + "(");
+ option.insert_text += "(";
} else {
- r_result.insert(E->get().name + "()");
+ option.insert_text += "()";
}
+ r_result.insert(option.display, option);
}
return;
@@ -2069,7 +2126,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context
}
}
-static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p_only_functions, Set<String> &r_result) {
+static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) {
const GDScriptParser::BlockNode *block = p_context.block;
@@ -2078,7 +2135,8 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p
const GDScriptParser::FunctionNode *f = p_context.function;
for (int i = 0; i < f->arguments.size(); i++) {
- r_result.insert(f->arguments[i].operator String());
+ ScriptCodeCompletionOption option(f->arguments[i].operator String(), ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
+ r_result.insert(option.display, option);
}
}
@@ -2103,11 +2161,13 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p
for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) {
MethodInfo mi = GDScriptFunctions::get_info(GDScriptFunctions::Function(i));
+ ScriptCodeCompletionOption option(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))), ScriptCodeCompletionOption::KIND_FUNCTION);
if (mi.arguments.size() || (mi.flags & METHOD_FLAG_VARARG)) {
- r_result.insert(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) + "(");
+ option.insert_text += "(";
} else {
- r_result.insert(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) + "()");
+ option.insert_text += "()";
}
+ r_result.insert(option.display, option);
}
static const char *_type_names[Variant::VARIANT_MAX] = {
@@ -2117,7 +2177,8 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p
};
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
- r_result.insert(_type_names[i]);
+ ScriptCodeCompletionOption option(_type_names[i], ScriptCodeCompletionOption::KIND_CLASS);
+ r_result.insert(option.display, option);
}
static const char *_keywords[] = {
@@ -2131,7 +2192,8 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p
const char **kw = _keywords;
while (*kw) {
- r_result.insert(*kw);
+ ScriptCodeCompletionOption option(*kw, ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
+ r_result.insert(option.display, option);
kw++;
}
@@ -2145,7 +2207,8 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p
}
String path = ProjectSettings::get_singleton()->get(s);
if (path.begins_with("*")) {
- r_result.insert(s.get_slice("/", 1));
+ ScriptCodeCompletionOption option(s.get_slice("/", 1), ScriptCodeCompletionOption::KIND_CONSTANT);
+ r_result.insert(option.display, option);
}
}
@@ -2153,19 +2216,22 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p
List<StringName> named_scripts;
ScriptServer::get_global_class_list(&named_scripts);
for (List<StringName>::Element *E = named_scripts.front(); E; E = E->next()) {
- r_result.insert(E->get().operator String());
+ ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
+ r_result.insert(option.display, option);
}
// Native classes
for (const Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) {
- r_result.insert(E->key().operator String());
+ ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
+ r_result.insert(option.display, option);
}
}
-static void _find_call_arguments(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, int p_argidx, bool p_static, Set<String> &r_result, String &r_arghint) {
+static void _find_call_arguments(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, int p_argidx, bool p_static, Map<String, ScriptCodeCompletionOption> &r_result, String &r_arghint) {
Variant base = p_base.value;
GDScriptParser::DataType base_type = p_base.type;
- bool _static = false;
+
+ const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\"";
while (base_type.has_type) {
switch (base_type.kind) {
@@ -2176,18 +2242,18 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con
return;
}
}
- if (!_static) {
- for (int i = 0; i < base_type.class_type->functions.size(); i++) {
- if (base_type.class_type->functions[i]->name == p_method) {
- r_arghint = _make_arguments_hint(base_type.class_type->functions[i], p_argidx);
- return;
- }
+ for (int i = 0; i < base_type.class_type->functions.size(); i++) {
+ if (base_type.class_type->functions[i]->name == p_method) {
+ r_arghint = _make_arguments_hint(base_type.class_type->functions[i], p_argidx);
+ return;
}
}
if ((p_method == "connect" || p_method == "emit_signal") && p_argidx == 0) {
for (int i = 0; i < base_type.class_type->_signals.size(); i++) {
- r_result.insert("\"" + base_type.class_type->_signals[i].name.operator String() + "\"");
+ ScriptCodeCompletionOption option(base_type.class_type->_signals[i].name.operator String(), ScriptCodeCompletionOption::KIND_SIGNAL);
+ option.insert_text = quote_style + option.display + quote_style;
+ r_result.insert(option.display, option);
}
}
@@ -2200,7 +2266,9 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con
List<MethodInfo> signals;
gds->get_script_signal_list(&signals);
for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
- r_result.insert("\"" + E->get().name + "\"");
+ ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_SIGNAL);
+ option.insert_text = quote_style + option.display + quote_style;
+ r_result.insert(option.display, option);
}
}
Ref<GDScript> base_script = gds->get_base_script();
@@ -2238,7 +2306,8 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con
List<String> options;
obj->get_argument_options(p_method, p_argidx, &options);
for (List<String>::Element *F = options.front(); F; F = F->next()) {
- r_result.insert(F->get());
+ ScriptCodeCompletionOption option(F->get(), ScriptCodeCompletionOption::KIND_FUNCTION);
+ r_result.insert(option.display, option);
}
}
}
@@ -2259,7 +2328,9 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con
List<MethodInfo> signals;
ClassDB::get_signal_list(class_name, &signals);
for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
- r_result.insert("\"" + E->get().name + "\"");
+ ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_SIGNAL);
+ option.insert_text = quote_style + option.display + quote_style;
+ r_result.insert(option.display, option);
}
}
@@ -2274,7 +2345,9 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con
continue;
}
String name = s.get_slice("/", 1);
- r_result.insert("\"/root/" + name + "\"");
+ ScriptCodeCompletionOption option("/root/" + name, ScriptCodeCompletionOption::KIND_NODE_PATH);
+ option.insert_text = quote_style + option.display + quote_style;
+ r_result.insert(option.display, option);
}
}
@@ -2288,7 +2361,9 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con
continue;
}
String name = s.get_slice("/", 1);
- r_result.insert("\"" + name + "\"");
+ ScriptCodeCompletionOption option(name, ScriptCodeCompletionOption::KIND_CONSTANT);
+ option.insert_text = quote_style + option.display + quote_style;
+ r_result.insert(option.display, option);
}
}
@@ -2321,7 +2396,9 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con
}
}
-static void _find_call_arguments(GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_node, int p_argidx, Set<String> &r_result, bool &r_forced, String &r_arghint) {
+static void _find_call_arguments(GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_node, int p_argidx, Map<String, ScriptCodeCompletionOption> &r_result, bool &r_forced, String &r_arghint) {
+
+ const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\"";
if (!p_node || p_node->type != GDScriptParser::Node::TYPE_OPERATOR) {
return;
@@ -2437,23 +2514,27 @@ static void _find_call_arguments(GDScriptCompletionContext &p_context, const GDS
_find_call_arguments(p_context, ci, function, p_argidx, _static, r_result, r_arghint);
if (function == "connect" && p_argidx == 2) {
- Set<String> methods;
+ Map<String, ScriptCodeCompletionOption> methods;
_find_identifiers_in_base(p_context, connect_base, true, methods);
- for (Set<String>::Element *E = methods.front(); E; E = E->next()) {
- r_result.insert("\"" + E->get().replace("(", "").replace(")", "") + "\"");
+ for (Map<String, ScriptCodeCompletionOption>::Element *E = methods.front(); E; E = E->next()) {
+ ScriptCodeCompletionOption &option = E->value();
+ option.insert_text = quote_style + option.display + quote_style;
+ r_result.insert(option.display, option);
}
}
r_forced = r_result.size() > 0;
}
-Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base_path, Object *p_owner, List<String> *r_options, bool &r_forced, String &r_call_hint) {
+Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) {
+
+ const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\"";
GDScriptParser parser;
- parser.parse(p_code, p_base_path, false, "", true);
+ parser.parse(p_code, p_path.get_base_dir(), false, p_path, true);
r_forced = false;
- Set<String> options;
+ Map<String, ScriptCodeCompletionOption> options;
GDScriptCompletionContext context;
context._class = parser.get_completion_class();
context.block = parser.get_completion_block();
@@ -2462,7 +2543,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
if (!context._class || context._class->owner == NULL) {
context.base = p_owner;
- context.base_path = p_base_path;
+ context.base_path = p_path.get_base_dir();
}
bool is_function = false;
@@ -2474,7 +2555,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
List<StringName> constants;
Variant::get_constants_for_type(parser.get_completion_built_in_constant(), &constants);
for (List<StringName>::Element *E = constants.front(); E; E = E->next()) {
- options.insert(E->get().operator String());
+ ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT);
+ options.insert(option.display, option);
}
} break;
case GDScriptParser::COMPLETION_PARENT_FUNCTION: {
@@ -2482,7 +2564,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
} break;
case GDScriptParser::COMPLETION_FUNCTION: {
is_function = true;
- } // fallthrough
+ FALLTHROUGH;
+ }
case GDScriptParser::COMPLETION_IDENTIFIER: {
_find_identifiers(context, is_function, options);
} break;
@@ -2498,9 +2581,11 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
r_forced = true;
String idopt = opt.unquote();
if (idopt.replace("/", "_").is_valid_identifier()) {
- options.insert(idopt);
+ ScriptCodeCompletionOption option(idopt, ScriptCodeCompletionOption::KIND_NODE_PATH);
+ options.insert(option.display, option);
} else {
- options.insert(opt);
+ ScriptCodeCompletionOption option(opt, ScriptCodeCompletionOption::KIND_NODE_PATH);
+ options.insert(option.display, option);
}
}
}
@@ -2515,13 +2600,15 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
continue;
}
String name = s.get_slice("/", 1);
- options.insert("\"/root/" + name + "\"");
+ ScriptCodeCompletionOption option(quote_style + "/root/" + name + quote_style, ScriptCodeCompletionOption::KIND_NODE_PATH);
+ options.insert(option.display, option);
}
}
} break;
case GDScriptParser::COMPLETION_METHOD: {
is_function = true;
- } // fallthrough
+ FALLTHROUGH;
+ }
case GDScriptParser::COMPLETION_INDEX: {
const GDScriptParser::Node *node = parser.get_completion_node();
if (node->type != GDScriptParser::Node::TYPE_OPERATOR) {
@@ -2637,7 +2724,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
}
method_hint += ":";
- options.insert(method_hint);
+ ScriptCodeCompletionOption option(method_hint, ScriptCodeCompletionOption::KIND_FUNCTION);
+ options.insert(option.display, option);
}
} break;
case GDScriptParser::COMPLETION_YIELD: {
@@ -2655,7 +2743,9 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
switch (base_type.kind) {
case GDScriptParser::DataType::CLASS: {
for (int i = 0; i < base_type.class_type->_signals.size(); i++) {
- options.insert("\"" + base_type.class_type->_signals[i].name.operator String() + "\"");
+ ScriptCodeCompletionOption option(base_type.class_type->_signals[i].name.operator String(), ScriptCodeCompletionOption::KIND_SIGNAL);
+ option.insert_text = quote_style + option.display + quote_style;
+ options.insert(option.display, option);
}
base_type = base_type.class_type->base_type;
} break;
@@ -2666,7 +2756,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
List<MethodInfo> signals;
scr->get_script_signal_list(&signals);
for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
- options.insert("\"" + E->get().name + "\"");
+ ScriptCodeCompletionOption option(quote_style + E->get().name + quote_style, ScriptCodeCompletionOption::KIND_SIGNAL);
+ options.insert(option.display, option);
}
Ref<Script> base_script = scr->get_base_script();
if (base_script.is_valid()) {
@@ -2693,7 +2784,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
List<MethodInfo> signals;
ClassDB::get_signal_list(class_name, &signals);
for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
- options.insert("\"" + E->get().name + "\"");
+ ScriptCodeCompletionOption option(quote_style + E->get().name + quote_style, ScriptCodeCompletionOption::KIND_SIGNAL);
+ options.insert(option.display, option);
}
} break;
default: {
@@ -2730,18 +2822,31 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
c.line = E->value().expression->line;
if (_guess_expression_type(c, E->value().expression, constant)) {
if (constant.type.has_type && constant.type.is_meta_type) {
- options.insert(E->key().operator String());
+ ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
+ options.insert(option.display, option);
}
}
}
for (int i = 0; i < clss->subclasses.size(); i++) {
if (clss->subclasses[i]->name != StringName()) {
- options.insert(clss->subclasses[i]->name.operator String());
+ ScriptCodeCompletionOption option(clss->subclasses[i]->name.operator String(), ScriptCodeCompletionOption::KIND_CLASS);
+ options.insert(option.display, option);
}
}
clss = clss->owner;
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
- options.insert(Variant::get_type_name((Variant::Type)i));
+ ScriptCodeCompletionOption option(Variant::get_type_name((Variant::Type)i), ScriptCodeCompletionOption::KIND_CLASS);
+ options.insert(option.display, option);
+ }
+ List<PropertyInfo> props;
+ ProjectSettings::get_singleton()->get_property_list(&props);
+ for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ String s = E->get().name;
+ if (!s.begins_with("autoload/")) {
+ continue;
+ }
+ ScriptCodeCompletionOption option(s.get_slice("/", 1), ScriptCodeCompletionOption::KIND_CLASS);
+ options.insert(option.display, option);
}
}
@@ -2755,18 +2860,21 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
if (Engine::get_singleton()->has_singleton(class_name)) {
continue;
}
- options.insert(class_name);
+ ScriptCodeCompletionOption option(class_name, ScriptCodeCompletionOption::KIND_CLASS);
+ options.insert(option.display, option);
}
// Named scripts
List<StringName> named_scripts;
ScriptServer::get_global_class_list(&named_scripts);
for (List<StringName>::Element *E = named_scripts.front(); E; E = E->next()) {
- options.insert(E->get().operator String());
+ ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
+ options.insert(option.display, option);
}
if (parser.get_completion_identifier_is_function()) {
- options.insert("void");
+ ScriptCodeCompletionOption option("void", ScriptCodeCompletionOption::KIND_PLAIN_TEXT);
+ options.insert(option.display, option);
}
r_forced = true;
} break;
@@ -2813,13 +2921,15 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
c2.line = E->value().expression->line;
if (_guess_expression_type(c2, E->value().expression, constant)) {
if (constant.type.has_type && constant.type.is_meta_type) {
- options.insert(E->key().operator String());
+ ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
+ options.insert(option.display, option);
}
}
}
for (int i = 0; i < base_type.class_type->subclasses.size(); i++) {
if (base_type.class_type->subclasses[i]->name != StringName()) {
- options.insert(base_type.class_type->subclasses[i]->name.operator String());
+ ScriptCodeCompletionOption option(base_type.class_type->subclasses[i]->name.operator String(), ScriptCodeCompletionOption::KIND_CLASS);
+ options.insert(option.display, option);
}
}
@@ -2837,7 +2947,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) {
Ref<Script> const_scr = E->value();
if (const_scr.is_valid()) {
- options.insert(E->key().operator String());
+ ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS);
+ options.insert(option.display, option);
}
}
Ref<Script> base_script = scr->get_base_script();
@@ -2859,7 +2970,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
} break;
}
- for (Set<String>::Element *E = options.front(); E; E = E->next()) {
+ for (Map<String, ScriptCodeCompletionOption>::Element *E = options.front(); E; E = E->next()) {
r_options->push_back(E->get());
}
@@ -2868,7 +2979,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
#else
-Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base_path, Object *p_owner, List<String> *r_options, bool &r_forced, String &r_call_hint) {
+Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) {
return OK;
}
@@ -2879,7 +2990,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_base
String GDScriptLanguage::_get_indentation() const {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
- bool use_space_indentation = EDITOR_DEF("text_editor/indent/type", 0);
+ bool use_space_indentation = EDITOR_DEF("text_editor/indent/type", false);
if (use_space_indentation) {
int indent_size = EDITOR_DEF("text_editor/indent/size", 4);
@@ -2997,8 +3108,8 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
}
}
}
+ base_type = base_type.class_type->base_type;
}
- base_type = base_type.class_type->base_type;
} break;
case GDScriptParser::DataType::SCRIPT:
case GDScriptParser::DataType::GDSCRIPT: {
@@ -3139,7 +3250,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
return ERR_CANT_RESOLVE;
}
-Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_base_path, Object *p_owner, LookupResult &r_result) {
+Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) {
//before parsing, try the usual stuff
if (ClassDB::class_exists(p_symbol)) {
@@ -3181,7 +3292,7 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
}
GDScriptParser parser;
- parser.parse(p_code, p_base_path, false, "", true);
+ parser.parse(p_code, p_path.get_base_dir(), false, p_path, true);
if (parser.get_completion_type() == GDScriptParser::COMPLETION_NONE) {
return ERR_CANT_RESOLVE;
@@ -3193,7 +3304,7 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
context.block = parser.get_completion_block();
context.line = parser.get_completion_line();
context.base = p_owner;
- context.base_path = p_base_path;
+ context.base_path = p_path.get_base_dir();
if (context._class && context._class->extends_class.size() > 0) {
bool success = false;
@@ -3218,7 +3329,8 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
case GDScriptParser::COMPLETION_PARENT_FUNCTION:
case GDScriptParser::COMPLETION_FUNCTION: {
is_function = true;
- } // fallthrough
+ FALLTHROUGH;
+ }
case GDScriptParser::COMPLETION_IDENTIFIER: {
if (!is_function) {
@@ -3349,7 +3461,8 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
} break;
case GDScriptParser::COMPLETION_METHOD: {
is_function = true;
- } // fallthrough
+ FALLTHROUGH;
+ }
case GDScriptParser::COMPLETION_INDEX: {
const GDScriptParser::Node *node = parser.get_completion_node();
if (node->type != GDScriptParser::Node::TYPE_OPERATOR) {
@@ -3371,7 +3484,8 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol
return OK;
}
} break;
- default: {}
+ default: {
+ }
}
return ERR_CANT_RESOLVE;
diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp
index cff9ba55b8..0a01321851 100644
--- a/modules/gdscript/gdscript_function.cpp
+++ b/modules/gdscript/gdscript_function.cpp
@@ -86,8 +86,7 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta
o = o->_owner;
}
- ERR_EXPLAIN("GDScriptCompiler bug...");
- ERR_FAIL_V(NULL);
+ ERR_FAIL_V_MSG(NULL, "GDScriptCompiler bug.");
} break;
case ADDR_TYPE_LOCAL_CONSTANT: {
#ifdef DEBUG_ENABLED
@@ -128,40 +127,17 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta
} break;
}
- ERR_EXPLAIN("Bad Code! (Addressing Mode)");
- ERR_FAIL_V(NULL);
+ ERR_FAIL_V_MSG(NULL, "Bad code! (unknown addressing mode).");
return NULL;
}
-String GDScriptFunction::_get_call_error(const Variant::CallError &p_err, const String &p_where, const Variant **argptrs) const {
-
- String err_text;
-
- if (p_err.error == Variant::CallError::CALL_ERROR_INVALID_ARGUMENT) {
- int errorarg = p_err.argument;
- err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(p_err.expected) + ".";
- } else if (p_err.error == Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) {
- err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments.";
- } else if (p_err.error == Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) {
- err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments.";
- } else if (p_err.error == Variant::CallError::CALL_ERROR_INVALID_METHOD) {
- err_text = "Invalid call. Nonexistent " + p_where + ".";
- } else if (p_err.error == Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL) {
- err_text = "Attempt to call " + p_where + " on a null instance.";
- } else {
- err_text = "Bug, call error: #" + itos(p_err.error);
- }
-
- return err_text;
-}
-
#ifdef DEBUG_ENABLED
-static String _get_var_type(const Variant *p_type) {
+static String _get_var_type(const Variant *p_var) {
String basestr;
- if (p_type->get_type() == Variant::OBJECT) {
- Object *bobj = *p_type;
+ if (p_var->get_type() == Variant::OBJECT) {
+ Object *bobj = *p_var;
if (!bobj) {
basestr = "null instance";
} else {
@@ -176,12 +152,42 @@ static String _get_var_type(const Variant *p_type) {
}
} else {
- basestr = Variant::get_type_name(p_type->get_type());
+ basestr = Variant::get_type_name(p_var->get_type());
}
return basestr;
}
-#endif
+#endif // DEBUG_ENABLED
+
+String GDScriptFunction::_get_call_error(const Variant::CallError &p_err, const String &p_where, const Variant **argptrs) const {
+
+ String err_text;
+
+ if (p_err.error == Variant::CallError::CALL_ERROR_INVALID_ARGUMENT) {
+ int errorarg = p_err.argument;
+ // Handle the Object to Object case separately as we don't have further class details.
+#ifdef DEBUG_ENABLED
+ if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) {
+ err_text = "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") is not a subclass of the expected argument class.";
+ } else
+#endif // DEBUG_ENABLED
+ {
+ err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(p_err.expected) + ".";
+ }
+ } else if (p_err.error == Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) {
+ err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments.";
+ } else if (p_err.error == Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) {
+ err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments.";
+ } else if (p_err.error == Variant::CallError::CALL_ERROR_INVALID_METHOD) {
+ err_text = "Invalid call. Nonexistent " + p_where + ".";
+ } else if (p_err.error == Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL) {
+ err_text = "Attempt to call " + p_where + " on a null instance.";
+ } else {
+ err_text = "Bug, call error: #" + itos(p_err.error);
+ }
+
+ return err_text;
+}
#if defined(__GNUC__)
#define OPCODES_TABLE \
@@ -391,8 +397,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (unlikely(m_cond)) { \
_err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition ' " _STR(m_cond) " ' is true. Breaking..:"); \
OPCODE_BREAK; \
- } else \
- _err_error_exists = false; \
+ } \
}
#define CHECK_SPACE(m_space) \
@@ -425,6 +430,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
profile.frame_call_count++;
}
bool exit_ok = false;
+ bool yielded = false;
#endif
#ifdef DEBUG_ENABLED
@@ -1317,6 +1323,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
#ifdef DEBUG_ENABLED
exit_ok = true;
+ yielded = true;
#endif
OPCODE_BREAK;
}
@@ -1411,7 +1418,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (!container->iter_init(*counter, valid)) {
#ifdef DEBUG_ENABLED
if (!valid) {
- err_text = "Unable to iterate on object of type " + Variant::get_type_name(container->get_type()) + "'.";
+ err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "'.";
OPCODE_BREAK;
}
#endif
@@ -1424,7 +1431,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
*iterator = container->iter_get(*counter, valid);
#ifdef DEBUG_ENABLED
if (!valid) {
- err_text = "Unable to obtain iterator object of type " + Variant::get_type_name(container->get_type()) + "'.";
+ err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "'.";
OPCODE_BREAK;
}
#endif
@@ -1444,7 +1451,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
if (!container->iter_next(*counter, valid)) {
#ifdef DEBUG_ENABLED
if (!valid) {
- err_text = "Unable to iterate on object of type " + Variant::get_type_name(container->get_type()) + "' (type changed since first iteration?).";
+ err_text = "Unable to iterate on object of type '" + Variant::get_type_name(container->get_type()) + "' (type changed since first iteration?).";
OPCODE_BREAK;
}
#endif
@@ -1457,7 +1464,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
*iterator = container->iter_get(*counter, valid);
#ifdef DEBUG_ENABLED
if (!valid) {
- err_text = "Unable to obtain iterator object of type " + Variant::get_type_name(container->get_type()) + "' (but was obtained on first iteration?).";
+ err_text = "Unable to obtain iterator object of type '" + Variant::get_type_name(container->get_type()) + "' (but was obtained on first iteration?).";
OPCODE_BREAK;
}
#endif
@@ -1467,20 +1474,25 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
DISPATCH_OPCODE;
OPCODE(OPCODE_ASSERT) {
- CHECK_SPACE(2);
+ CHECK_SPACE(3);
#ifdef DEBUG_ENABLED
GET_VARIANT_PTR(test, 1);
+ GET_VARIANT_PTR(message, 2);
bool result = test->booleanize();
if (!result) {
-
- err_text = "Assertion failed.";
+ const String &message_str = *message;
+ if (message_str.empty()) {
+ err_text = "Assertion failed.";
+ } else {
+ err_text = "Assertion failed: " + message_str;
+ }
OPCODE_BREAK;
}
#endif
- ip += 2;
+ ip += 3;
}
DISPATCH_OPCODE;
@@ -1548,14 +1560,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
//error
// function, file, line, error, explanation
String err_file;
- if (p_instance)
+ if (p_instance && ObjectDB::instance_validate(p_instance->owner) && p_instance->script->is_valid() && p_instance->script->path != "")
err_file = p_instance->script->path;
else if (script)
err_file = script->path;
if (err_file == "")
err_file = "<built-in>";
String err_func = name;
- if (p_instance && p_instance->script->name != "")
+ if (p_instance && ObjectDB::instance_validate(p_instance->owner) && p_instance->script->is_valid() && p_instance->script->name != "")
err_func = p_instance->script->name + "." + err_func;
int err_line = line;
if (err_text == "") {
@@ -1583,15 +1595,24 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GDScriptLanguage::get_singleton()->script_frame_time += time_taken - function_call_time;
}
- if (ScriptDebugger::get_singleton())
- GDScriptLanguage::get_singleton()->exit_function();
+ // Check if this is the last time the function is resuming from yield
+ // Will be true if never yielded as well
+ // When it's the last resume it will postpone the exit from stack,
+ // so the debugger knows which function triggered the resume of the next function (if any)
+ if (!p_state || yielded) {
+ if (ScriptDebugger::get_singleton())
+ GDScriptLanguage::get_singleton()->exit_function();
#endif
- if (_stack_size) {
- //free stack
- for (int i = 0; i < _stack_size; i++)
- stack[i].~Variant();
+ if (_stack_size) {
+ //free stack
+ for (int i = 0; i < _stack_size; i++)
+ stack[i].~Variant();
+ }
+
+#ifdef DEBUG_ENABLED
}
+#endif
return retvalue;
}
@@ -1765,20 +1786,9 @@ GDScriptFunction::~GDScriptFunction() {
Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
- if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) {
-#ifdef DEBUG_ENABLED
- ERR_EXPLAIN("Resumed after yield, but class instance is gone");
- ERR_FAIL_V(Variant());
-#else
- return Variant();
-#endif
- }
-
Variant arg;
r_error.error = Variant::CallError::CALL_OK;
- ERR_FAIL_COND_V(!function, Variant());
-
if (p_argcount == 0) {
r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
r_error.argument = 1;
@@ -1804,33 +1814,7 @@ Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_ar
return Variant();
}
- state.result = arg;
- Variant ret = function->call(NULL, NULL, 0, r_error, &state);
-
- bool completed = true;
-
- // If the return value is a GDScriptFunctionState reference,
- // then the function did yield again after resuming.
- if (ret.is_ref()) {
- GDScriptFunctionState *gdfs = Object::cast_to<GDScriptFunctionState>(ret);
- if (gdfs && gdfs->function == function) {
- completed = false;
- gdfs->first_state = first_state.is_valid() ? first_state : Ref<GDScriptFunctionState>(this);
- }
- }
-
- function = NULL; //cleaned up;
- state.result = Variant();
-
- if (completed) {
- if (first_state.is_valid()) {
- first_state->emit_signal("completed", ret);
- } else {
- emit_signal("completed", ret);
- }
- }
-
- return ret;
+ return resume(arg);
}
bool GDScriptFunctionState::is_valid(bool p_extended_check) const {
@@ -1852,8 +1836,7 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) {
ERR_FAIL_COND_V(!function, Variant());
if (state.instance_id && !ObjectDB::get_instance(state.instance_id)) {
#ifdef DEBUG_ENABLED
- ERR_EXPLAIN("Resumed after yield, but class instance is gone");
- ERR_FAIL_V(Variant());
+ ERR_FAIL_V_MSG(Variant(), "Resumed function '" + String(function->get_name()) + "()' after yield, but class instance is gone. At script: " + state.script->get_path() + ":" + itos(state.line));
#else
return Variant();
#endif
@@ -1884,6 +1867,17 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) {
} else {
emit_signal("completed", ret);
}
+
+#ifdef DEBUG_ENABLED
+ if (ScriptDebugger::get_singleton())
+ GDScriptLanguage::get_singleton()->exit_function();
+ if (state.stack_size) {
+ //free stack
+ Variant *stack = (Variant *)state.stack.ptr();
+ for (int i = 0; i < state.stack_size; i++)
+ stack[i].~Variant();
+ }
+#endif
}
return ret;
diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp
index 46c9efd54f..9e05c7b574 100644
--- a/modules/gdscript/gdscript_functions.cpp
+++ b/modules/gdscript/gdscript_functions.cpp
@@ -58,6 +58,7 @@ const char *GDScriptFunctions::get_func_name(Function p_func) {
"sqrt",
"fmod",
"fposmod",
+ "posmod",
"floor",
"ceil",
"round",
@@ -68,12 +69,18 @@ const char *GDScriptFunctions::get_func_name(Function p_func) {
"exp",
"is_nan",
"is_inf",
+ "is_equal_approx",
+ "is_zero_approx",
"ease",
"decimals",
+ "step_decimals",
"stepify",
"lerp",
+ "lerp_angle",
"inverse_lerp",
"range_lerp",
+ "smoothstep",
+ "move_toward",
"dectime",
"randomize",
"randi",
@@ -99,6 +106,7 @@ const char *GDScriptFunctions::get_func_name(Function p_func) {
"typeof",
"type_exists",
"char",
+ "ord",
"str",
"print",
"printt",
@@ -238,6 +246,12 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
VALIDATE_ARG_NUM(1);
r_ret = Math::fposmod((double)*p_args[0], (double)*p_args[1]);
} break;
+ case MATH_POSMOD: {
+ VALIDATE_ARG_COUNT(2);
+ VALIDATE_ARG_NUM(0);
+ VALIDATE_ARG_NUM(1);
+ r_ret = Math::posmod((int)*p_args[0], (int)*p_args[1]);
+ } break;
case MATH_FLOOR: {
VALIDATE_ARG_COUNT(1);
VALIDATE_ARG_NUM(0);
@@ -315,6 +329,17 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
VALIDATE_ARG_NUM(0);
r_ret = Math::is_inf((double)*p_args[0]);
} break;
+ case MATH_ISEQUALAPPROX: {
+ VALIDATE_ARG_COUNT(2);
+ VALIDATE_ARG_NUM(0);
+ VALIDATE_ARG_NUM(1);
+ r_ret = Math::is_equal_approx((real_t)*p_args[0], (real_t)*p_args[1]);
+ } break;
+ case MATH_ISZEROAPPROX: {
+ VALIDATE_ARG_COUNT(1);
+ VALIDATE_ARG_NUM(0);
+ r_ret = Math::is_zero_approx((real_t)*p_args[0]);
+ } break;
case MATH_EASE: {
VALIDATE_ARG_COUNT(2);
VALIDATE_ARG_NUM(0);
@@ -325,6 +350,12 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
VALIDATE_ARG_COUNT(1);
VALIDATE_ARG_NUM(0);
r_ret = Math::step_decimals((double)*p_args[0]);
+ WARN_DEPRECATED_MSG("GDScript method 'decimals' is deprecated and has been renamed to 'step_decimals', please update your code accordingly.");
+ } break;
+ case MATH_STEP_DECIMALS: {
+ VALIDATE_ARG_COUNT(1);
+ VALIDATE_ARG_NUM(0);
+ r_ret = Math::step_decimals((double)*p_args[0]);
} break;
case MATH_STEPIFY: {
VALIDATE_ARG_COUNT(2);
@@ -353,6 +384,13 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
} break;
}
} break;
+ case MATH_LERP_ANGLE: {
+ VALIDATE_ARG_COUNT(3);
+ VALIDATE_ARG_NUM(0);
+ VALIDATE_ARG_NUM(1);
+ VALIDATE_ARG_NUM(2);
+ r_ret = Math::lerp_angle((double)*p_args[0], (double)*p_args[1], (double)*p_args[2]);
+ } break;
case MATH_INVERSE_LERP: {
VALIDATE_ARG_COUNT(3);
VALIDATE_ARG_NUM(0);
@@ -369,6 +407,20 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
VALIDATE_ARG_NUM(4);
r_ret = Math::range_lerp((double)*p_args[0], (double)*p_args[1], (double)*p_args[2], (double)*p_args[3], (double)*p_args[4]);
} break;
+ case MATH_SMOOTHSTEP: {
+ VALIDATE_ARG_COUNT(3);
+ VALIDATE_ARG_NUM(0);
+ VALIDATE_ARG_NUM(1);
+ VALIDATE_ARG_NUM(2);
+ r_ret = Math::smoothstep((double)*p_args[0], (double)*p_args[1], (double)*p_args[2]);
+ } break;
+ case MATH_MOVE_TOWARD: {
+ VALIDATE_ARG_COUNT(3);
+ VALIDATE_ARG_NUM(0);
+ VALIDATE_ARG_NUM(1);
+ VALIDATE_ARG_NUM(2);
+ r_ret = Math::move_toward((double)*p_args[0], (double)*p_args[1], (double)*p_args[2]);
+ } break;
case MATH_DECTIME: {
VALIDATE_ARG_COUNT(3);
VALIDATE_ARG_NUM(0);
@@ -520,37 +572,32 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
} break;
case OBJ_WEAKREF: {
VALIDATE_ARG_COUNT(1);
- if (p_args[0]->get_type() != Variant::OBJECT) {
-
+ if (p_args[0]->get_type() == Variant::OBJECT) {
+ if (p_args[0]->is_ref()) {
+ Ref<WeakRef> wref = memnew(WeakRef);
+ REF r = *p_args[0];
+ if (r.is_valid()) {
+ wref->set_ref(r);
+ }
+ r_ret = wref;
+ } else {
+ Ref<WeakRef> wref = memnew(WeakRef);
+ Object *obj = *p_args[0];
+ if (obj) {
+ wref->set_obj(obj);
+ }
+ r_ret = wref;
+ }
+ } else if (p_args[0]->get_type() == Variant::NIL) {
+ Ref<WeakRef> wref = memnew(WeakRef);
+ r_ret = wref;
+ } else {
r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::OBJECT;
r_ret = Variant();
return;
}
-
- if (p_args[0]->is_ref()) {
-
- REF r = *p_args[0];
- if (!r.is_valid()) {
- r_ret = Variant();
- return;
- }
-
- Ref<WeakRef> wref = memnew(WeakRef);
- wref->set_ref(r);
- r_ret = wref;
- } else {
- Object *obj = *p_args[0];
- if (!obj) {
- r_ret = Variant();
- return;
- }
- Ref<WeakRef> wref = memnew(WeakRef);
- wref->set_obj(obj);
- r_ret = wref;
- }
-
} break;
case FUNC_FUNCREF: {
VALIDATE_ARG_COUNT(2);
@@ -614,6 +661,33 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
CharType result[2] = { *p_args[0], 0 };
r_ret = String(result);
} break;
+ case TEXT_ORD: {
+
+ VALIDATE_ARG_COUNT(1);
+
+ if (p_args[0]->get_type() != Variant::STRING) {
+
+ r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 0;
+ r_error.expected = Variant::STRING;
+ r_ret = Variant();
+ return;
+ }
+
+ String str = p_args[0]->operator String();
+
+ if (str.length() != 1) {
+
+ r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 0;
+ r_error.expected = Variant::STRING;
+ r_ret = RTR("Expected a string of length 1 (a character).");
+ return;
+ }
+
+ r_ret = str.get(0);
+
+ } break;
case TEXT_STR: {
if (p_arg_count < 1) {
r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
@@ -1052,27 +1126,13 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
Dictionary d;
d["@subpath"] = cp;
- d["@path"] = p->path;
-
- p = base.ptr();
-
- while (p) {
-
- for (Set<StringName>::Element *E = p->members.front(); E; E = E->next()) {
+ d["@path"] = p->get_path();
- Variant value;
- if (ins->get(E->get(), value)) {
-
- String k = E->get();
- if (!d.has(k)) {
- d[k] = value;
- }
- }
+ for (Map<StringName, GDScript::MemberInfo>::Element *E = base->member_indices.front(); E; E = E->next()) {
+ if (!d.has(E->key())) {
+ d[E->key()] = ins->members[E->get().index];
}
-
- p = p->_base;
}
-
r_ret = d;
}
}
@@ -1200,6 +1260,7 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
if (err != OK) {
r_ret = Variant();
+ ERR_PRINTS(vformat("Error parsing JSON at line %s: %s", errl, errs));
}
} break;
@@ -1419,6 +1480,7 @@ bool GDScriptFunctions::is_deterministic(Function p_func) {
case MATH_SQRT:
case MATH_FMOD:
case MATH_FPOSMOD:
+ case MATH_POSMOD:
case MATH_FLOOR:
case MATH_CEIL:
case MATH_ROUND:
@@ -1431,10 +1493,13 @@ bool GDScriptFunctions::is_deterministic(Function p_func) {
case MATH_ISINF:
case MATH_EASE:
case MATH_DECIMALS:
+ case MATH_STEP_DECIMALS:
case MATH_STEPIFY:
case MATH_LERP:
case MATH_INVERSE_LERP:
case MATH_RANGE_LERP:
+ case MATH_SMOOTHSTEP:
+ case MATH_MOVE_TOWARD:
case MATH_DECTIME:
case MATH_DEG2RAD:
case MATH_RAD2DEG:
@@ -1452,6 +1517,7 @@ bool GDScriptFunctions::is_deterministic(Function p_func) {
case TYPE_OF:
case TYPE_EXISTS:
case TEXT_CHAR:
+ case TEXT_ORD:
case TEXT_STR:
case COLOR8:
case LEN:
@@ -1528,15 +1594,20 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
return mi;
} break;
case MATH_FMOD: {
- MethodInfo mi("fmod", PropertyInfo(Variant::REAL, "x"), PropertyInfo(Variant::REAL, "y"));
+ MethodInfo mi("fmod", PropertyInfo(Variant::REAL, "a"), PropertyInfo(Variant::REAL, "b"));
mi.return_val.type = Variant::REAL;
return mi;
} break;
case MATH_FPOSMOD: {
- MethodInfo mi("fposmod", PropertyInfo(Variant::REAL, "x"), PropertyInfo(Variant::REAL, "y"));
+ MethodInfo mi("fposmod", PropertyInfo(Variant::REAL, "a"), PropertyInfo(Variant::REAL, "b"));
mi.return_val.type = Variant::REAL;
return mi;
} break;
+ case MATH_POSMOD: {
+ MethodInfo mi("posmod", PropertyInfo(Variant::INT, "a"), PropertyInfo(Variant::INT, "b"));
+ mi.return_val.type = Variant::INT;
+ return mi;
+ } break;
case MATH_FLOOR: {
MethodInfo mi("floor", PropertyInfo(Variant::REAL, "s"));
mi.return_val.type = Variant::REAL;
@@ -1563,7 +1634,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
return mi;
} break;
case MATH_POW: {
- MethodInfo mi("pow", PropertyInfo(Variant::REAL, "x"), PropertyInfo(Variant::REAL, "y"));
+ MethodInfo mi("pow", PropertyInfo(Variant::REAL, "base"), PropertyInfo(Variant::REAL, "exp"));
mi.return_val.type = Variant::REAL;
return mi;
} break;
@@ -1587,6 +1658,16 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
mi.return_val.type = Variant::BOOL;
return mi;
} break;
+ case MATH_ISEQUALAPPROX: {
+ MethodInfo mi("is_equal_approx", PropertyInfo(Variant::REAL, "a"), PropertyInfo(Variant::REAL, "b"));
+ mi.return_val.type = Variant::BOOL;
+ return mi;
+ } break;
+ case MATH_ISZEROAPPROX: {
+ MethodInfo mi("is_zero_approx", PropertyInfo(Variant::REAL, "s"));
+ mi.return_val.type = Variant::BOOL;
+ return mi;
+ } break;
case MATH_EASE: {
MethodInfo mi("ease", PropertyInfo(Variant::REAL, "s"), PropertyInfo(Variant::REAL, "curve"));
mi.return_val.type = Variant::REAL;
@@ -1594,7 +1675,12 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
} break;
case MATH_DECIMALS: {
MethodInfo mi("decimals", PropertyInfo(Variant::REAL, "step"));
- mi.return_val.type = Variant::REAL;
+ mi.return_val.type = Variant::INT;
+ return mi;
+ } break;
+ case MATH_STEP_DECIMALS: {
+ MethodInfo mi("step_decimals", PropertyInfo(Variant::REAL, "step"));
+ mi.return_val.type = Variant::INT;
return mi;
} break;
case MATH_STEPIFY: {
@@ -1608,6 +1694,11 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
return mi;
} break;
+ case MATH_LERP_ANGLE: {
+ MethodInfo mi("lerp_angle", PropertyInfo(Variant::REAL, "from"), PropertyInfo(Variant::REAL, "to"), PropertyInfo(Variant::REAL, "weight"));
+ mi.return_val.type = Variant::REAL;
+ return mi;
+ } break;
case MATH_INVERSE_LERP: {
MethodInfo mi("inverse_lerp", PropertyInfo(Variant::REAL, "from"), PropertyInfo(Variant::REAL, "to"), PropertyInfo(Variant::REAL, "weight"));
mi.return_val.type = Variant::REAL;
@@ -1618,6 +1709,16 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
mi.return_val.type = Variant::REAL;
return mi;
} break;
+ case MATH_SMOOTHSTEP: {
+ MethodInfo mi("smoothstep", PropertyInfo(Variant::REAL, "from"), PropertyInfo(Variant::REAL, "to"), PropertyInfo(Variant::REAL, "weight"));
+ mi.return_val.type = Variant::REAL;
+ return mi;
+ } break;
+ case MATH_MOVE_TOWARD: {
+ MethodInfo mi("move_toward", PropertyInfo(Variant::REAL, "from"), PropertyInfo(Variant::REAL, "to"), PropertyInfo(Variant::REAL, "delta"));
+ mi.return_val.type = Variant::REAL;
+ return mi;
+ } break;
case MATH_DECTIME: {
MethodInfo mi("dectime", PropertyInfo(Variant::REAL, "value"), PropertyInfo(Variant::REAL, "amount"), PropertyInfo(Variant::REAL, "step"));
mi.return_val.type = Variant::REAL;
@@ -1759,6 +1860,13 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
return mi;
} break;
+ case TEXT_ORD: {
+
+ MethodInfo mi("ord", PropertyInfo(Variant::STRING, "char"));
+ mi.return_val.type = Variant::INT;
+ return mi;
+
+ } break;
case TEXT_STR: {
MethodInfo mi("str");
diff --git a/modules/gdscript/gdscript_functions.h b/modules/gdscript/gdscript_functions.h
index fcb8f32e54..9ea5dd46cf 100644
--- a/modules/gdscript/gdscript_functions.h
+++ b/modules/gdscript/gdscript_functions.h
@@ -49,6 +49,7 @@ public:
MATH_SQRT,
MATH_FMOD,
MATH_FPOSMOD,
+ MATH_POSMOD,
MATH_FLOOR,
MATH_CEIL,
MATH_ROUND,
@@ -59,12 +60,18 @@ public:
MATH_EXP,
MATH_ISNAN,
MATH_ISINF,
+ MATH_ISEQUALAPPROX,
+ MATH_ISZEROAPPROX,
MATH_EASE,
MATH_DECIMALS,
+ MATH_STEP_DECIMALS,
MATH_STEPIFY,
MATH_LERP,
+ MATH_LERP_ANGLE,
MATH_INVERSE_LERP,
MATH_RANGE_LERP,
+ MATH_SMOOTHSTEP,
+ MATH_MOVE_TOWARD,
MATH_DECTIME,
MATH_RANDOMIZE,
MATH_RAND,
@@ -90,6 +97,7 @@ public:
TYPE_OF,
TYPE_EXISTS,
TEXT_CHAR,
+ TEXT_ORD,
TEXT_STR,
TEXT_PRINT,
TEXT_PRINT_TABBED,
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index e75b8a14a3..ef1a282e51 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -89,8 +89,8 @@ bool GDScriptParser::_enter_indent_block(BlockNode *p_block) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_NEWLINE) {
// be more python-like
- int current = tab_level.back()->get();
- tab_level.push_back(current);
+ IndentLevel current_level = indent_level.back()->get();
+ indent_level.push_back(current_level);
return true;
//_set_error("newline expected after ':'.");
//return false;
@@ -105,12 +105,19 @@ bool GDScriptParser::_enter_indent_block(BlockNode *p_block) {
} else if (tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) {
int indent = tokenizer->get_token_line_indent();
- int current = tab_level.back()->get();
- if (indent <= current) {
+ int tabs = tokenizer->get_token_line_tab_indent();
+ IndentLevel current_level = indent_level.back()->get();
+ IndentLevel new_indent(indent, tabs);
+ if (new_indent.is_mixed(current_level)) {
+ _set_error("Mixed tabs and spaces in indentation.");
return false;
}
- tab_level.push_back(indent);
+ if (indent <= current_level.indent) {
+ return false;
+ }
+
+ indent_level.push_back(new_indent);
tokenizer->advance();
return true;
@@ -125,7 +132,7 @@ bool GDScriptParser::_enter_indent_block(BlockNode *p_block) {
}
}
-bool GDScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete) {
+bool GDScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete, bool p_parsing_constant) {
if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
tokenizer->advance();
@@ -149,7 +156,7 @@ bool GDScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bo
return false;
}
- Node *arg = _parse_expression(p_parent, p_static);
+ Node *arg = _parse_expression(p_parent, p_static, false, p_parsing_constant);
if (!arg) {
return false;
}
@@ -252,6 +259,16 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
}
}
+ // Check that the next token is not TK_CURSOR and if it is, the offset should be incremented.
+ int next_valid_offset = 1;
+ if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_CURSOR) {
+ next_valid_offset++;
+ // There is a chunk of the identifier that also needs to be ignored (not always there!)
+ if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_IDENTIFIER) {
+ next_valid_offset++;
+ }
+ }
+
if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
//subexpression ()
tokenizer->advance();
@@ -282,7 +299,6 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
switch (tokenizer->get_token()) {
case GDScriptTokenizer::TK_CURSOR: {
- completion_cursor = StringName();
completion_type = COMPLETION_GET_NODE;
completion_class = current_class;
completion_function = current_function;
@@ -464,7 +480,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
}
if (!path.is_abs_path() && base_path != "")
- path = base_path + "/" + path;
+ path = base_path.plus_file(path);
path = path.replace("///", "//").simplify_path();
if (path == self_path) {
@@ -505,7 +521,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
Ref<GDScript> gds = res;
if (gds.is_valid() && !gds->is_valid()) {
- _set_error("Could not fully preload the script, possible cyclic reference or compilation error. Use 'load()' instead if a cyclic reference is intended.");
+ _set_error("Couldn't fully preload the script, possible cyclic reference or compilation error. Use \"load()\" instead if a cyclic reference is intended.");
return NULL;
}
@@ -519,7 +535,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_YIELD) {
if (!current_function) {
- _set_error("yield() can only be used inside function blocks.");
+ _set_error("\"yield()\" can only be used inside function blocks.");
return NULL;
}
@@ -527,7 +543,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
tokenizer->advance();
if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
- _set_error("Expected '(' after 'yield'");
+ _set_error("Expected \"(\" after \"yield\".");
return NULL;
}
@@ -553,7 +569,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
yield->arguments.push_back(object);
if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
- _set_error("Expected ',' after first argument of 'yield'");
+ _set_error("Expected \",\" after the first argument of \"yield\".");
return NULL;
}
@@ -579,7 +595,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
yield->arguments.push_back(signal);
if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected ')' after second argument of 'yield'");
+ _set_error("Expected \")\" after the second argument of \"yield\".");
return NULL;
}
@@ -593,7 +609,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_SELF) {
if (p_static) {
- _set_error("'self'' not allowed in static function or constant expression");
+ _set_error("\"self\" isn't allowed in a static function or constant expression.");
return NULL;
}
//constant defined by tokenizer
@@ -614,7 +630,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
if (identifier == StringName()) {
- _set_error("Built-in type constant or static function expected after '.'");
+ _set_error("Built-in type constant or static function expected after \".\".");
return NULL;
}
if (!Variant::has_constant(bi_type, identifier)) {
@@ -640,7 +656,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
id->name = identifier;
op->arguments.push_back(id);
- if (!_parse_arguments(op, op->arguments, p_static, true))
+ if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant))
return NULL;
expr = op;
@@ -669,7 +685,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
expr = cn;
}
- } else if (tokenizer->get_token(1) == GDScriptTokenizer::TK_PARENTHESIS_OPEN && tokenizer->is_token_literal()) {
+ } else if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_PARENTHESIS_OPEN && tokenizer->is_token_literal()) {
// We check with is_token_literal, as this allows us to use match/sync/etc. as a name
//function or constructor
@@ -732,7 +748,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
completion_node = op;
}
if (!replaced) {
- if (!_parse_arguments(op, op->arguments, p_static, true))
+ if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant))
return NULL;
expr = op;
}
@@ -827,11 +843,12 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
}
// Check parents for the constant
- if (!bfn && cln->extends_file != StringName()) {
- Ref<GDScript> parent = ResourceLoader::load(cln->extends_file);
- if (parent.is_valid() && parent->is_valid()) {
+ if (!bfn) {
+ // Using current_class instead of cln here, since cln is const*
+ _determine_inheritance(current_class, false);
+ if (cln->base_type.has_type && cln->base_type.kind == DataType::GDSCRIPT && cln->base_type.script_type->is_valid()) {
Map<StringName, Variant> parent_constants;
- parent->get_constants(&parent_constants);
+ current_class->base_type.script_type->get_constants(&parent_constants);
if (parent_constants.has(identifier)) {
ConstantNode *constant = alloc_node<ConstantNode>();
constant->value = parent_constants[identifier];
@@ -861,7 +878,6 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
case GDScriptTokenizer::TK_OP_ASSIGN_SUB:
case GDScriptTokenizer::TK_OP_ASSIGN: {
// Assignment is not really usage
- current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] - 1;
} break;
default: {
current_function->arguments_usage.write[arg_idx] = current_function->arguments_usage[arg_idx] + 1;
@@ -888,7 +904,8 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
case GDScriptTokenizer::TK_OP_SUB: e.op = OperatorNode::OP_NEG; break;
case GDScriptTokenizer::TK_OP_NOT: e.op = OperatorNode::OP_NOT; break;
case GDScriptTokenizer::TK_OP_BIT_INVERT: e.op = OperatorNode::OP_BIT_INVERT; break;
- default: {}
+ default: {
+ }
}
tokenizer->advance();
@@ -910,6 +927,10 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_IS && tokenizer->get_token(1) == GDScriptTokenizer::TK_BUILT_IN_TYPE) {
// 'is' operator with built-in type
+ if (!expr) {
+ _set_error("Expected identifier before 'is' operator");
+ return NULL;
+ }
OperatorNode *op = alloc_node<OperatorNode>();
op->op = OperatorNode::OP_IS_BUILTIN;
op->arguments.push_back(expr);
@@ -1125,7 +1146,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
}
} else {
tokenizer->advance();
- if (!_parse_arguments(op, op->arguments, p_static)) {
+ if (!_parse_arguments(op, op->arguments, p_static, false, p_parsing_constant)) {
return NULL;
}
}
@@ -1148,10 +1169,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
return NULL; //nothing
}
- if (!expr) {
- ERR_EXPLAIN("GDScriptParser bug, couldn't figure out what expression is...");
- ERR_FAIL_COND_V(!expr, NULL);
- }
+ ERR_FAIL_COND_V_MSG(!expr, NULL, "GDScriptParser bug, couldn't figure out what expression is.");
/******************/
/* Parse Indexing */
@@ -1177,22 +1195,14 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
tokenizer->advance();
IdentifierNode *id = alloc_node<IdentifierNode>();
- if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_FUNC) {
- //small hack so built in funcs don't obfuscate methods
-
- id->name = GDScriptFunctions::get_func_name(tokenizer->get_token_built_in_func());
- tokenizer->advance();
-
- } else {
- StringName identifier;
- if (_get_completable_identifier(COMPLETION_METHOD, identifier)) {
- completion_node = op;
- //indexing stuff
- }
-
- id->name = identifier;
+ StringName identifier;
+ if (_get_completable_identifier(COMPLETION_METHOD, identifier)) {
+ completion_node = op;
+ //indexing stuff
}
+ id->name = identifier;
+
op->arguments.push_back(expr); // call what
op->arguments.push_back(id); // call func
//get arguments
@@ -1201,7 +1211,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
_make_completable_call(0);
completion_node = op;
}
- if (!_parse_arguments(op, op->arguments, p_static, true))
+ if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant))
return NULL;
expr = op;
@@ -1216,7 +1226,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
if (_get_completable_identifier(COMPLETION_INDEX, identifier)) {
if (identifier == StringName()) {
- identifier = "@temp"; //so it parses allright
+ identifier = "@temp"; //so it parses alright
}
completion_node = op;
@@ -1408,9 +1418,6 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
unary = true;
break;
case OperatorNode::OP_NEG:
- priority = 1;
- unary = true;
- break;
case OperatorNode::OP_POS:
priority = 1;
unary = true;
@@ -1493,7 +1500,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
if (next_op == -1) {
_set_error("Yet another parser bug....");
- ERR_FAIL_COND_V(next_op == -1, NULL);
+ ERR_FAIL_V(NULL);
}
// OK! create operator..
@@ -1786,8 +1793,6 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
cn->value = v;
cn->datatype = _type_from_variant(v);
return cn;
-
- } else if (op->arguments[0]->type == Node::TYPE_BUILT_IN_FUNCTION && last_not_constant == 0) {
}
return op; //don't reduce yet
@@ -1875,7 +1880,9 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
}
} break;
- default: { break; }
+ default: {
+ break;
+ }
}
//now se if all are constants
if (!all_constants)
@@ -1988,10 +1995,11 @@ GDScriptParser::Node *GDScriptParser::_reduce_expression(Node *p_node, bool p_to
return op->arguments[2];
}
} break;
- default: { ERR_FAIL_V(op); }
+ default: {
+ ERR_FAIL_V(op);
+ }
}
- ERR_FAIL_V(op);
} break;
default: {
return p_node;
@@ -2224,10 +2232,12 @@ GDScriptParser::PatternNode *GDScriptParser::_parse_pattern(bool p_static) {
}
void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBranchNode *> &p_branches, bool p_static) {
- int indent_level = tab_level.back()->get();
+ IndentLevel current_level = indent_level.back()->get();
p_block->has_return = true;
+ bool catch_all_appeared = false;
+
while (true) {
while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline())
@@ -2237,13 +2247,11 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran
if (error_set)
return;
- if (indent_level > tab_level.back()->get()) {
- return; // go back a level
+ if (current_level.indent > indent_level.back()->get().indent) {
+ break; // go back a level
}
- if (pending_newline != -1) {
- pending_newline = -1;
- }
+ pending_newline = -1;
PatternBranchNode *branch = alloc_node<PatternBranchNode>();
branch->body = alloc_node<BlockNode>();
@@ -2253,12 +2261,20 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran
branch->patterns.push_back(_parse_pattern(p_static));
if (!branch->patterns[0]) {
- return;
+ break;
}
bool has_binding = branch->patterns[0]->pt_type == PatternNode::PT_BIND;
bool catch_all = has_binding || branch->patterns[0]->pt_type == PatternNode::PT_WILDCARD;
+#ifdef DEBUG_ENABLED
+ // Branches after a wildcard or binding are unreachable
+ if (catch_all_appeared && !current_function->has_unreachable_code) {
+ _add_warning(GDScriptWarning::UNREACHABLE_CODE, -1, current_function->name.operator String());
+ current_function->has_unreachable_code = true;
+ }
+#endif
+
while (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
tokenizer->advance();
branch->patterns.push_back(_parse_pattern(p_static));
@@ -2276,6 +2292,8 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran
catch_all = catch_all || pt == PatternNode::PT_WILDCARD;
}
+ catch_all_appeared = catch_all_appeared || catch_all;
+
if (!_enter_indent_block()) {
_set_error("Expected block in pattern branch");
return;
@@ -2291,6 +2309,11 @@ void GDScriptParser::_parse_pattern_block(BlockNode *p_block, Vector<PatternBran
p_branches.push_back(branch);
}
+
+ // Even if all branches return, there is possibility of default fallthrough
+ if (!catch_all_appeared) {
+ p_block->has_return = false;
+ }
}
void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_match, Node *&p_resulting_node, Map<StringName, Node *> &p_bindings) {
@@ -2310,7 +2333,7 @@ void GDScriptParser::_generate_pattern(PatternNode *p_pattern, Node *p_node_to_m
// static type check if possible
if (pattern_type.has_type && to_match_type.has_type) {
if (!_is_type_compatible(to_match_type, pattern_type) && !_is_type_compatible(pattern_type, to_match_type)) {
- _set_error("Pattern type (" + pattern_type.to_string() + ") is not compatible with the type of the value to match (" + to_match_type.to_string() + ").",
+ _set_error("The pattern type (" + pattern_type.to_string() + ") isn't compatible with the type of the value to match (" + to_match_type.to_string() + ").",
p_pattern->line);
return;
}
@@ -2679,7 +2702,7 @@ void GDScriptParser::_transform_match_statment(MatchNode *p_match_statement) {
void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
- int indent_level = tab_level.back()->get();
+ IndentLevel current_level = indent_level.back()->get();
#ifdef DEBUG_ENABLED
@@ -2692,9 +2715,13 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
bool is_first_line = true;
while (true) {
- if (!is_first_line && tab_level.back()->prev() && tab_level.back()->prev()->get() == indent_level) {
+ if (!is_first_line && indent_level.back()->prev() && indent_level.back()->prev()->get().indent == current_level.indent) {
+ if (indent_level.back()->prev()->get().is_mixed(current_level)) {
+ _set_error("Mixed tabs and spaces in indentation.");
+ return;
+ }
// pythonic single-line expression, don't parse future lines
- tab_level.pop_back();
+ indent_level.pop_back();
p_block->end_line = tokenizer->get_token_line();
return;
}
@@ -2704,7 +2731,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
if (error_set)
return;
- if (indent_level > tab_level.back()->get()) {
+ if (current_level.indent > indent_level.back()->get().indent) {
p_block->end_line = tokenizer->get_token_line();
return; //go back a level
}
@@ -2762,24 +2789,24 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
case GDScriptTokenizer::TK_CF_PASS: {
if (tokenizer->get_token(1) != GDScriptTokenizer::TK_SEMICOLON && tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE && tokenizer->get_token(1) != GDScriptTokenizer::TK_EOF) {
- _set_error("Expected ';' or <NewLine>.");
+ _set_error("Expected \";\" or a line break.");
return;
}
_mark_line_as_safe(tokenizer->get_token_line());
tokenizer->advance();
if (tokenizer->get_token() == GDScriptTokenizer::TK_SEMICOLON) {
- // Ignore semicolon after 'pass'
+ // Ignore semicolon after 'pass'.
tokenizer->advance();
}
} break;
case GDScriptTokenizer::TK_PR_VAR: {
- //variale declaration and (eventual) initialization
+ // Variable declaration and (eventual) initialization.
tokenizer->advance();
int var_line = tokenizer->get_token_line();
if (!tokenizer->is_token_literal(0, true)) {
- _set_error("Expected identifier for local variable name.");
+ _set_error("Expected an identifier for the local variable name.");
return;
}
StringName n = tokenizer->get_token_literal();
@@ -2787,7 +2814,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
if (current_function) {
for (int i = 0; i < current_function->arguments.size(); i++) {
if (n == current_function->arguments[i]) {
- _set_error("Variable '" + String(n) + "' already defined in the scope (at line: " + itos(current_function->line) + ").");
+ _set_error("Variable \"" + String(n) + "\" already defined in the scope (at line " + itos(current_function->line) + ").");
return;
}
}
@@ -2795,7 +2822,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
BlockNode *check_block = p_block;
while (check_block) {
if (check_block->variables.has(n)) {
- _set_error("Variable '" + String(n) + "' already defined in the scope (at line: " + itos(check_block->variables[n]->line) + ").");
+ _set_error("Variable \"" + String(n) + "\" already defined in the scope (at line " + itos(check_block->variables[n]->line) + ").");
return;
}
check_block = check_block->parent_block;
@@ -2817,7 +2844,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
#endif
tokenizer->advance();
} else if (!_parse_type(lv->datatype)) {
- _set_error("Expected type for variable.");
+ _set_error("Expected a type for the variable.");
return;
}
}
@@ -2837,15 +2864,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
assigned = subexpr;
} else {
- ConstantNode *c = alloc_node<ConstantNode>();
- if (lv->datatype.has_type && lv->datatype.kind == DataType::BUILTIN) {
- Variant::CallError err;
- c->value = Variant::construct(lv->datatype.builtin_type, NULL, 0, err);
- } else {
- c->value = Variant();
- }
- c->line = var_line;
- assigned = c;
+ assigned = _get_default_value_for_type(lv->datatype, var_line);
}
lv->assign = assigned;
//must be added later, to avoid self-referencing.
@@ -2865,10 +2884,8 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
lv->assign_op = op;
lv->assign = assigned;
- lv->assign_op = op;
-
if (!_end_statement()) {
- _set_error("Expected end of statement (var)");
+ _set_error("Expected end of statement (\"var\").");
return;
}
@@ -2897,7 +2914,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
p_block->sub_blocks.push_back(cf_if->body);
if (!_enter_indent_block(cf_if->body)) {
- _set_error("Expected indented block after 'if'");
+ _set_error("Expected an indented block after \"if\".");
p_block->end_line = tokenizer->get_token_line();
return;
}
@@ -2918,16 +2935,16 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
while (tokenizer->get_token() == GDScriptTokenizer::TK_NEWLINE && _parse_newline())
;
- if (tab_level.back()->get() < indent_level) { //not at current indent level
+ if (indent_level.back()->get().indent < current_level.indent) { //not at current indent level
p_block->end_line = tokenizer->get_token_line();
return;
}
if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELIF) {
- if (tab_level.back()->get() > indent_level) {
+ if (indent_level.back()->get().indent > current_level.indent) {
- _set_error("Invalid indent");
+ _set_error("Invalid indentation.");
return;
}
@@ -2958,7 +2975,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
p_block->sub_blocks.push_back(cf_if->body);
if (!_enter_indent_block(cf_if->body)) {
- _set_error("Expected indented block after 'elif'");
+ _set_error("Expected an indented block after \"elif\".");
p_block->end_line = tokenizer->get_token_line();
return;
}
@@ -2973,8 +2990,8 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CF_ELSE) {
- if (tab_level.back()->get() > indent_level) {
- _set_error("Invalid indent");
+ if (indent_level.back()->get().indent > current_level.indent) {
+ _set_error("Invalid indentation.");
return;
}
@@ -2984,7 +3001,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
p_block->sub_blocks.push_back(cf_if->body_else);
if (!_enter_indent_block(cf_if->body_else)) {
- _set_error("Expected indented block after 'else'");
+ _set_error("Expected an indented block after \"else\".");
p_block->end_line = tokenizer->get_token_line();
return;
}
@@ -3029,7 +3046,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
p_block->sub_blocks.push_back(cf_while->body);
if (!_enter_indent_block(cf_while->body)) {
- _set_error("Expected indented block after 'while'");
+ _set_error("Expected an indented block after \"while\".");
p_block->end_line = tokenizer->get_token_line();
return;
}
@@ -3048,7 +3065,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
if (!tokenizer->is_token_literal(0, true)) {
- _set_error("identifier expected after 'for'");
+ _set_error("Identifier expected after \"for\".");
}
IdentifierNode *id = alloc_node<IdentifierNode>();
@@ -3057,7 +3074,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
tokenizer->advance();
if (tokenizer->get_token() != GDScriptTokenizer::TK_OP_IN) {
- _set_error("'in' expected after identifier");
+ _set_error("\"in\" expected after identifier.");
return;
}
@@ -3147,7 +3164,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
p_block->sub_blocks.push_back(cf_for->body);
if (!_enter_indent_block(cf_for->body)) {
- _set_error("Expected indented block after 'for'");
+ _set_error("Expected indented block after \"for\".");
p_block->end_line = tokenizer->get_token_line();
return;
}
@@ -3174,23 +3191,25 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
} break;
case GDScriptTokenizer::TK_CF_CONTINUE: {
+ _mark_line_as_safe(tokenizer->get_token_line());
tokenizer->advance();
ControlFlowNode *cf_continue = alloc_node<ControlFlowNode>();
cf_continue->cf_type = ControlFlowNode::CF_CONTINUE;
p_block->statements.push_back(cf_continue);
if (!_end_statement()) {
- _set_error("Expected end of statement (continue)");
+ _set_error("Expected end of statement (\"continue\").");
return;
}
} break;
case GDScriptTokenizer::TK_CF_BREAK: {
+ _mark_line_as_safe(tokenizer->get_token_line());
tokenizer->advance();
ControlFlowNode *cf_break = alloc_node<ControlFlowNode>();
cf_break->cf_type = ControlFlowNode::CF_BREAK;
p_block->statements.push_back(cf_break);
if (!_end_statement()) {
- _set_error("Expected end of statement (break)");
+ _set_error("Expected end of statement (\"break\").");
return;
}
} break;
@@ -3244,7 +3263,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
match_node->val_to_match = val_to_match;
if (!_enter_indent_block()) {
- _set_error("Expected indented pattern matching block after 'match'");
+ _set_error("Expected indented pattern matching block after \"match\".");
return;
}
@@ -3271,19 +3290,40 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
case GDScriptTokenizer::TK_PR_ASSERT: {
tokenizer->advance();
- Node *condition = _parse_and_reduce_expression(p_block, p_static);
- if (!condition) {
- if (_recover_from_completion()) {
- break;
- }
+
+ if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
+ _set_error("Expected '(' after assert");
+ return;
+ }
+
+ tokenizer->advance();
+
+ Vector<Node *> args;
+ const bool result = _parse_arguments(p_block, args, p_static);
+ if (!result) {
+ return;
+ }
+
+ if (args.empty() || args.size() > 2) {
+ _set_error("Wrong number of arguments, expected 1 or 2");
return;
}
+
AssertNode *an = alloc_node<AssertNode>();
- an->condition = condition;
+ an->condition = _reduce_expression(args[0], p_static);
+
+ if (args.size() == 2) {
+ an->message = _reduce_expression(args[1], p_static);
+ } else {
+ ConstantNode *message_node = alloc_node<ConstantNode>();
+ message_node->value = String();
+ an->message = message_node;
+ }
+
p_block->statements.push_back(an);
if (!_end_statement()) {
- _set_error("Expected end of statement after assert.");
+ _set_error("Expected end of statement after \"assert\".");
return;
}
} break;
@@ -3294,7 +3334,7 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
p_block->statements.push_back(bn);
if (!_end_statement()) {
- _set_error("Expected end of statement after breakpoint.");
+ _set_error("Expected end of statement after \"breakpoint\".");
return;
}
} break;
@@ -3309,7 +3349,12 @@ void GDScriptParser::_parse_block(BlockNode *p_block, bool p_static) {
}
p_block->statements.push_back(expression);
if (!_end_statement()) {
- _set_error("Expected end of statement after expression.");
+ // Attempt to guess a better error message if the user "retypes" a variable
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON && tokenizer->get_token(1) == GDScriptTokenizer::TK_OP_ASSIGN) {
+ _set_error("Unexpected ':=', use '=' instead. Expected end of statement after expression.");
+ } else {
+ _set_error(String() + "Expected end of statement after expression, got " + tokenizer->get_token_name(tokenizer->get_token()) + " instead");
+ }
return;
}
@@ -3322,32 +3367,45 @@ bool GDScriptParser::_parse_newline() {
if (tokenizer->get_token(1) != GDScriptTokenizer::TK_EOF && tokenizer->get_token(1) != GDScriptTokenizer::TK_NEWLINE) {
+ IndentLevel current_level = indent_level.back()->get();
int indent = tokenizer->get_token_line_indent();
- int current_indent = tab_level.back()->get();
+ int tabs = tokenizer->get_token_line_tab_indent();
+ IndentLevel new_level(indent, tabs);
+
+ if (new_level.is_mixed(current_level)) {
+ _set_error("Mixed tabs and spaces in indentation.");
+ return false;
+ }
- if (indent > current_indent) {
- _set_error("Unexpected indent.");
+ if (indent > current_level.indent) {
+ _set_error("Unexpected indentation.");
return false;
}
- if (indent < current_indent) {
+ if (indent < current_level.indent) {
- while (indent < current_indent) {
+ while (indent < current_level.indent) {
//exit block
- if (tab_level.size() == 1) {
- _set_error("Invalid indent. BUG?");
+ if (indent_level.size() == 1) {
+ _set_error("Invalid indentation. Bug?");
return false;
}
- tab_level.pop_back();
+ indent_level.pop_back();
- if (tab_level.back()->get() < indent) {
+ if (indent_level.back()->get().indent < indent) {
_set_error("Unindent does not match any outer indentation level.");
return false;
}
- current_indent = tab_level.back()->get();
+
+ if (indent_level.back()->get().is_mixed(current_level)) {
+ _set_error("Mixed tabs and spaces in indentation.");
+ return false;
+ }
+
+ current_level = indent_level.back()->get();
}
tokenizer->advance();
@@ -3363,13 +3421,13 @@ void GDScriptParser::_parse_extends(ClassNode *p_class) {
if (p_class->extends_used) {
- _set_error("'extends' already used for this class.");
+ _set_error("\"extends\" can only be present once per script.");
return;
}
if (!p_class->constant_expressions.empty() || !p_class->subclasses.empty() || !p_class->functions.empty() || !p_class->variables.empty()) {
- _set_error("'extends' must be used before anything else.");
+ _set_error("\"extends\" must be used before anything else.");
return;
}
@@ -3389,7 +3447,7 @@ void GDScriptParser::_parse_extends(ClassNode *p_class) {
Variant constant = tokenizer->get_token_constant();
if (constant.get_type() != Variant::STRING) {
- _set_error("'extends' constant must be a string.");
+ _set_error("\"extends\" constant must be a string.");
return;
}
@@ -3424,7 +3482,7 @@ void GDScriptParser::_parse_extends(ClassNode *p_class) {
default: {
- _set_error("Invalid 'extends' syntax, expected string constant (path) and/or identifier (parent class).");
+ _set_error("Invalid \"extends\" syntax, expected string constant (path) and/or identifier (parent class).");
return;
}
}
@@ -3445,7 +3503,7 @@ void GDScriptParser::_parse_extends(ClassNode *p_class) {
void GDScriptParser::_parse_class(ClassNode *p_class) {
- int indent_level = tab_level.back()->get();
+ IndentLevel current_level = indent_level.back()->get();
while (true) {
@@ -3453,7 +3511,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (error_set)
return;
- if (indent_level > tab_level.back()->get()) {
+ if (current_level.indent > indent_level.back()->get().indent) {
p_class->end_line = tokenizer->get_token_line();
return; //go back a level
}
@@ -3484,32 +3542,52 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (error_set)
return;
if (!_end_statement()) {
- _set_error("Expected end of statement after extends");
+ _set_error("Expected end of statement after \"extends\".");
return;
}
} break;
case GDScriptTokenizer::TK_PR_CLASS_NAME: {
+ _mark_line_as_safe(tokenizer->get_token_line());
if (p_class->owner) {
- _set_error("'class_name' is only valid for the main class namespace.");
+ _set_error("\"class_name\" is only valid for the main class namespace.");
+ return;
+ }
+ if (self_path.begins_with("res://") && self_path.find("::") != -1) {
+ _set_error("\"class_name\" isn't allowed in built-in scripts.");
return;
}
if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) {
- _set_error("'class_name' syntax: 'class_name <UniqueName>'");
+ _set_error("\"class_name\" syntax: \"class_name <UniqueName>\"");
+ return;
+ }
+ if (p_class->classname_used) {
+ _set_error("\"class_name\" can only be present once per script.");
return;
}
+ p_class->classname_used = true;
+
p_class->name = tokenizer->get_token_identifier(1);
if (self_path != String() && ScriptServer::is_global_class(p_class->name) && ScriptServer::get_global_class_path(p_class->name) != self_path) {
- _set_error("Unique global class '" + p_class->name + "' already exists at path: " + ScriptServer::get_global_class_path(p_class->name));
+ _set_error("Unique global class \"" + p_class->name + "\" already exists at path: " + ScriptServer::get_global_class_path(p_class->name));
return;
}
if (ClassDB::class_exists(p_class->name)) {
- _set_error("Class '" + p_class->name + "' shadows a native class.");
+ _set_error("The class \"" + p_class->name + "\" shadows a native class.");
+ return;
+ }
+
+ if (p_class->classname_used && ProjectSettings::get_singleton()->has_setting("autoload/" + p_class->name)) {
+ const String autoload_path = ProjectSettings::get_singleton()->get_setting("autoload/" + p_class->name);
+ if (autoload_path.begins_with("*")) {
+ // It's a singleton, and not just a regular AutoLoad script.
+ _set_error("The class \"" + p_class->name + "\" conflicts with the AutoLoad singleton of the same name, and is therefore redundant. Remove the class_name declaration to fix this error.");
+ }
return;
}
@@ -3536,12 +3614,12 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
} else {
- _set_error("Optional parameter after 'class_name' must be a string constant file path to an icon.");
+ _set_error("The optional parameter after \"class_name\" must be a string constant file path to an icon.");
return;
}
} else if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT) {
- _set_error("Class icon must be separated by a comma.");
+ _set_error("The class icon must be separated by a comma.");
return;
}
@@ -3550,7 +3628,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (p_class->tool) {
- _set_error("tool used more than once");
+ _set_error("The \"tool\" keyword can only be present once per script.");
return;
}
@@ -3565,7 +3643,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) {
- _set_error("'class' syntax: 'class <Name>:' or 'class <Name> extends <BaseClass>:'");
+ _set_error("\"class\" syntax: \"class <Name>:\" or \"class <Name> extends <BaseClass>:\"");
return;
}
name = tokenizer->get_token_identifier(1);
@@ -3573,23 +3651,23 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
// Check if name is shadowing something else
if (ClassDB::class_exists(name) || ClassDB::class_exists("_" + name.operator String())) {
- _set_error("Class '" + String(name) + "' shadows a native class.");
+ _set_error("The class \"" + String(name) + "\" shadows a native class.");
return;
}
if (ScriptServer::is_global_class(name)) {
- _set_error("Can't override name of unique global class '" + name + "' already exists at path: " + ScriptServer::get_global_class_path(p_class->name));
+ _set_error("Can't override name of the unique global class \"" + name + "\". It already exists at: " + ScriptServer::get_global_class_path(p_class->name));
return;
}
ClassNode *outer_class = p_class;
while (outer_class) {
for (int i = 0; i < outer_class->subclasses.size(); i++) {
if (outer_class->subclasses[i]->name == name) {
- _set_error("Another class named '" + String(name) + "' already exists in this scope (at line " + itos(outer_class->subclasses[i]->line) + ").");
+ _set_error("Another class named \"" + String(name) + "\" already exists in this scope (at line " + itos(outer_class->subclasses[i]->line) + ").");
return;
}
}
if (outer_class->constant_expressions.has(name)) {
- _set_error("A constant named '" + String(name) + "' already exists in the outer class scope (at line" + itos(outer_class->constant_expressions[name].expression->line) + ").");
+ _set_error("A constant named \"" + String(name) + "\" already exists in the outer class scope (at line" + itos(outer_class->constant_expressions[name].expression->line) + ").");
return;
}
@@ -3633,7 +3711,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
- _set_error("Expected 'func'.");
+ _set_error("Expected \"func\".");
return;
}
@@ -3657,18 +3735,18 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (name == StringName()) {
- _set_error("Expected identifier after 'func' (syntax: 'func <identifier>([arguments]):' ).");
+ _set_error("Expected an identifier after \"func\" (syntax: \"func <identifier>([arguments]):\").");
return;
}
for (int i = 0; i < p_class->functions.size(); i++) {
if (p_class->functions[i]->name == name) {
- _set_error("Function '" + String(name) + "' already exists in this class (at line: " + itos(p_class->functions[i]->line) + ").");
+ _set_error("The function \"" + String(name) + "\" already exists in this class (at line " + itos(p_class->functions[i]->line) + ").");
}
}
for (int i = 0; i < p_class->static_functions.size(); i++) {
if (p_class->static_functions[i]->name == name) {
- _set_error("Function '" + String(name) + "' already exists in this class (at line: " + itos(p_class->static_functions[i]->line) + ").");
+ _set_error("The function \"" + String(name) + "\" already exists in this class (at line " + itos(p_class->static_functions[i]->line) + ").");
}
}
@@ -3681,11 +3759,16 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
_add_warning(GDScriptWarning::FUNCTION_CONFLICTS_VARIABLE, -1, name);
}
}
+ for (int i = 0; i < p_class->subclasses.size(); i++) {
+ if (p_class->subclasses[i]->name == name) {
+ _add_warning(GDScriptWarning::FUNCTION_CONFLICTS_CONSTANT, -1, name);
+ }
+ }
#endif // DEBUG_ENABLED
if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
- _set_error("Expected '(' after identifier (syntax: 'func <identifier>([arguments]):' ).");
+ _set_error("Expected \"(\" after the identifier (syntax: \"func <identifier>([arguments]):\" ).");
return;
}
@@ -3717,7 +3800,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (!tokenizer->is_token_literal(0, true)) {
- _set_error("Expected identifier for argument.");
+ _set_error("Expected an identifier for an argument.");
return;
}
@@ -3735,7 +3818,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
argtype.infer_type = true;
tokenizer->advance();
} else if (!_parse_type(argtype)) {
- _set_error("Expected type for argument.");
+ _set_error("Expected a type for an argument.");
return;
}
}
@@ -3784,7 +3867,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
continue;
} else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected ',' or ')'.");
+ _set_error("Expected \",\" or \")\".");
return;
}
@@ -3813,7 +3896,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (name == "_init") {
if (_static) {
- _set_error("Constructor cannot be static.");
+ _set_error("The constructor cannot be static.");
return;
}
@@ -3830,7 +3913,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() == GDScriptTokenizer::TK_PERIOD) {
tokenizer->advance();
if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
- _set_error("expected '(' for parent constructor arguments.");
+ _set_error("Expected \"(\" for parent constructor arguments.");
return;
}
tokenizer->advance();
@@ -3850,7 +3933,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
continue;
} else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected ',' or ')'.");
+ _set_error("Expected \",\" or \")\".");
return;
}
@@ -3875,7 +3958,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() == GDScriptTokenizer::TK_FORWARD_ARROW) {
if (!_parse_type(return_type, true)) {
- _set_error("Expected return type for function.");
+ _set_error("Expected a return type for the function.");
return;
}
}
@@ -3905,7 +3988,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
if (!tokenizer->is_token_literal()) {
- _set_error("Expected identifier after 'signal'.");
+ _set_error("Expected an identifier after \"signal\".");
return;
}
@@ -3929,7 +4012,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
if (!tokenizer->is_token_literal(0, true)) {
- _set_error("Expected identifier in signal argument.");
+ _set_error("Expected an identifier in a \"signal\" argument.");
return;
}
@@ -3943,7 +4026,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
tokenizer->advance();
} else if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected ',' or ')' after signal parameter identifier.");
+ _set_error("Expected \",\" or \")\" after a \"signal\" parameter identifier.");
return;
}
}
@@ -3952,7 +4035,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
p_class->_signals.push_back(sig);
if (!_end_statement()) {
- _set_error("Expected end of statement (signal)");
+ _set_error("Expected end of statement (\"signal\").");
return;
}
} break;
@@ -4007,12 +4090,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- ERR_EXPLAIN("Exporting bit flags hint requires string constants.");
- WARN_DEPRECATED
+ WARN_DEPRECATED_MSG("Exporting bit flags hint requires string constants.");
break;
}
if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
- _set_error("Expected ',' in bit flags hint.");
+ _set_error("Expected \",\" in the bit flags hint.");
return;
}
@@ -4024,7 +4106,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
current_export = PropertyInfo();
- _set_error("Expected a string constant in named bit flags hint.");
+ _set_error("Expected a string constant in the named bit flags hint.");
return;
}
@@ -4042,7 +4124,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
current_export = PropertyInfo();
- _set_error("Expected ')' or ',' in named bit flags hint.");
+ _set_error("Expected \")\" or \",\" in the named bit flags hint.");
return;
}
tokenizer->advance();
@@ -4051,6 +4133,50 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
break;
}
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_2D_RENDER") {
+
+ tokenizer->advance();
+ if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
+ _set_error("Expected \")\" in the layers 2D render hint.");
+ return;
+ }
+ current_export.hint = PROPERTY_HINT_LAYERS_2D_RENDER;
+ break;
+ }
+
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_2D_PHYSICS") {
+
+ tokenizer->advance();
+ if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
+ _set_error("Expected \")\" in the layers 2D physics hint.");
+ return;
+ }
+ current_export.hint = PROPERTY_HINT_LAYERS_2D_PHYSICS;
+ break;
+ }
+
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_3D_RENDER") {
+
+ tokenizer->advance();
+ if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
+ _set_error("Expected \")\" in the layers 3D render hint.");
+ return;
+ }
+ current_export.hint = PROPERTY_HINT_LAYERS_3D_RENDER;
+ break;
+ }
+
+ if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "LAYERS_3D_PHYSICS") {
+
+ tokenizer->advance();
+ if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
+ _set_error("Expected \")\" in the layers 3D physics hint.");
+ return;
+ }
+ current_export.hint = PROPERTY_HINT_LAYERS_3D_PHYSICS;
+ break;
+ }
+
if (tokenizer->get_token() == GDScriptTokenizer::TK_CONSTANT && tokenizer->get_token_constant().get_type() == Variant::STRING) {
//enumeration
current_export.hint = PROPERTY_HINT_ENUM;
@@ -4060,7 +4186,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
current_export = PropertyInfo();
- _set_error("Expected a string constant in enumeration hint.");
+ _set_error("Expected a string constant in the enumeration hint.");
return;
}
@@ -4078,7 +4204,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
current_export = PropertyInfo();
- _set_error("Expected ')' or ',' in enumeration hint.");
+ _set_error("Expected \")\" or \",\" in the enumeration hint.");
return;
}
@@ -4096,7 +4222,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
current_export.hint = PROPERTY_HINT_EXP_EASING;
tokenizer->advance();
if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected ')' in hint.");
+ _set_error("Expected \")\" in the hint.");
return;
}
break;
@@ -4111,7 +4237,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE)
break;
else if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
- _set_error("Expected ')' or ',' in exponential range hint.");
+ _set_error("Expected \")\" or \",\" in the exponential range hint.");
return;
}
tokenizer->advance();
@@ -4127,7 +4253,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) {
current_export = PropertyInfo();
- _set_error("Expected a range in numeric hint.");
+ _set_error("Expected a range in the numeric hint.");
return;
}
@@ -4142,7 +4268,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
current_export = PropertyInfo();
- _set_error("Expected ',' or ')' in numeric range hint.");
+ _set_error("Expected \",\" or \")\" in the numeric range hint.");
return;
}
@@ -4157,7 +4283,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) {
current_export = PropertyInfo();
- _set_error("Expected a number as upper bound in numeric range hint.");
+ _set_error("Expected a number as upper bound in the numeric range hint.");
return;
}
@@ -4170,7 +4296,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
current_export = PropertyInfo();
- _set_error("Expected ',' or ')' in numeric range hint.");
+ _set_error("Expected \",\" or \")\" in the numeric range hint.");
return;
}
@@ -4184,7 +4310,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || !tokenizer->get_token_constant().is_num()) {
current_export = PropertyInfo();
- _set_error("Expected a number as step in numeric range hint.");
+ _set_error("Expected a number as step in the numeric range hint.");
return;
}
@@ -4203,7 +4329,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
current_export = PropertyInfo();
- _set_error("Expected a string constant in enumeration hint.");
+ _set_error("Expected a string constant in the enumeration hint.");
return;
}
@@ -4220,7 +4346,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
current_export = PropertyInfo();
- _set_error("Expected ')' or ',' in enumeration hint.");
+ _set_error("Expected \")\" or \",\" in the enumeration hint.");
return;
}
tokenizer->advance();
@@ -4240,7 +4366,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
if (tokenizer->get_token() != GDScriptTokenizer::TK_IDENTIFIER || !(tokenizer->get_token_identifier() == "GLOBAL")) {
- _set_error("Expected 'GLOBAL' after comma in directory hint.");
+ _set_error("Expected \"GLOBAL\" after comma in the directory hint.");
return;
}
if (!p_class->tool) {
@@ -4251,11 +4377,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected ')' in hint.");
+ _set_error("Expected \")\" in the hint.");
return;
}
} else {
- _set_error("Expected ')' or ',' in hint.");
+ _set_error("Expected \")\" or \",\" in the hint.");
return;
}
break;
@@ -4284,7 +4410,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
else if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA)
tokenizer->advance();
else {
- _set_error("Expected ')' or ',' in hint.");
+ _set_error("Expected \")\" or \",\" in the hint.");
return;
}
}
@@ -4292,9 +4418,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_CONSTANT || tokenizer->get_token_constant().get_type() != Variant::STRING) {
if (current_export.hint == PROPERTY_HINT_GLOBAL_FILE)
- _set_error("Expected string constant with filter");
+ _set_error("Expected string constant with filter.");
else
- _set_error("Expected 'GLOBAL' or string constant with filter");
+ _set_error("Expected \"GLOBAL\" or string constant with filter.");
return;
}
current_export.hint_string = tokenizer->get_token_constant();
@@ -4302,7 +4428,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected ')' in hint.");
+ _set_error("Expected \")\" in the hint.");
return;
}
break;
@@ -4313,7 +4439,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
current_export.hint = PROPERTY_HINT_MULTILINE_TEXT;
tokenizer->advance();
if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
- _set_error("Expected ')' in hint.");
+ _set_error("Expected \")\" in the hint.");
return;
}
break;
@@ -4324,7 +4450,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_IDENTIFIER) {
current_export = PropertyInfo();
- _set_error("Color type hint expects RGB or RGBA as hints");
+ _set_error("Color type hint expects RGB or RGBA as hints.");
return;
}
@@ -4335,7 +4461,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
//none
} else {
current_export = PropertyInfo();
- _set_error("Color type hint expects RGB or RGBA as hints");
+ _set_error("Color type hint expects RGB or RGBA as hints.");
return;
}
tokenizer->advance();
@@ -4344,7 +4470,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
default: {
current_export = PropertyInfo();
- _set_error("Type '" + Variant::get_type_name(type) + "' can't take hints.");
+ _set_error("Type \"" + Variant::get_type_name(type) + "\" can't take hints.");
return;
} break;
}
@@ -4382,7 +4508,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
} else {
current_export = PropertyInfo();
- _set_error("Export hint not a resource type.");
+ _set_error("The export hint isn't a resource type.");
}
} else if (constant.get_type() == Variant::DICTIONARY) {
// Enumeration
@@ -4396,7 +4522,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
} else {
current_export = PropertyInfo();
- _set_error("Expected 'FLAGS' after comma.");
+ _set_error("Expected \"FLAGS\" after comma.");
}
}
@@ -4433,7 +4559,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_PARENTHESIS_CLOSE) {
current_export = PropertyInfo();
- _set_error("Expected ')' or ',' after export hint.");
+ _set_error("Expected \")\" or \",\" after the export hint.");
return;
}
@@ -4453,7 +4579,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_ONREADY && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTE && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTER && tokenizer->get_token() != GDScriptTokenizer::TK_PR_PUPPET && tokenizer->get_token() != GDScriptTokenizer::TK_PR_SYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_REMOTESYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_MASTERSYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_PUPPETSYNC && tokenizer->get_token() != GDScriptTokenizer::TK_PR_SLAVE) {
current_export = PropertyInfo();
- _set_error("Expected 'var', 'onready', 'remote', 'master', 'puppet', 'sync', 'remotesync', 'mastersync', 'puppetsync'.");
+ _set_error("Expected \"var\", \"onready\", \"remote\", \"master\", \"puppet\", \"sync\", \"remotesync\", \"mastersync\", \"puppetsync\".");
return;
}
@@ -4464,7 +4590,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
//may be fallthrough from export, ignore if so
tokenizer->advance();
if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) {
- _set_error("Expected 'var'.");
+ _set_error("Expected \"var\".");
return;
}
@@ -4476,13 +4602,13 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
if (current_export.type) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) {
- _set_error("Expected 'var'.");
+ _set_error("Expected \"var\".");
return;
}
} else {
if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
- _set_error("Expected 'var' or 'func'.");
+ _set_error("Expected \"var\" or \"func\".");
return;
}
}
@@ -4496,13 +4622,13 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
if (current_export.type) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) {
- _set_error("Expected 'var'.");
+ _set_error("Expected \"var\".");
return;
}
} else {
if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
- _set_error("Expected 'var' or 'func'.");
+ _set_error("Expected \"var\" or \"func\".");
return;
}
}
@@ -4521,13 +4647,13 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
if (current_export.type) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR) {
- _set_error("Expected 'var'.");
+ _set_error("Expected \"var\".");
return;
}
} else {
if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
- _set_error("Expected 'var' or 'func'.");
+ _set_error("Expected \"var\" or \"func\".");
return;
}
}
@@ -4542,9 +4668,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
if (current_export.type)
- _set_error("Expected 'var'.");
+ _set_error("Expected \"var\".");
else
- _set_error("Expected 'var' or 'func'.");
+ _set_error("Expected \"var\" or \"func\".");
return;
}
@@ -4557,9 +4683,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
if (current_export.type)
- _set_error("Expected 'var'.");
+ _set_error("Expected \"var\".");
else
- _set_error("Expected 'var' or 'func'.");
+ _set_error("Expected \"var\" or \"func\".");
return;
}
@@ -4572,9 +4698,9 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
if (tokenizer->get_token() != GDScriptTokenizer::TK_PR_VAR && tokenizer->get_token() != GDScriptTokenizer::TK_PR_FUNCTION) {
if (current_export.type)
- _set_error("Expected 'var'.");
+ _set_error("Expected \"var\".");
else
- _set_error("Expected 'var' or 'func'.");
+ _set_error("Expected \"var\" or \"func\".");
return;
}
@@ -4597,7 +4723,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
if (!tokenizer->is_token_literal(0, true)) {
- _set_error("Expected identifier for member variable name.");
+ _set_error("Expected an identifier for the member variable name.");
return;
}
@@ -4613,18 +4739,25 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
#endif
if (current_class->constant_expressions.has(member.identifier)) {
- _set_error("A constant named '" + String(member.identifier) + "' already exists in this class (at line: " +
+ _set_error("A constant named \"" + String(member.identifier) + "\" already exists in this class (at line: " +
itos(current_class->constant_expressions[member.identifier].expression->line) + ").");
return;
}
for (int i = 0; i < current_class->variables.size(); i++) {
if (current_class->variables[i].identifier == member.identifier) {
- _set_error("Variable '" + String(member.identifier) + "' already exists in this class (at line: " +
+ _set_error("Variable \"" + String(member.identifier) + "\" already exists in this class (at line: " +
itos(current_class->variables[i].line) + ").");
return;
}
}
+
+ for (int i = 0; i < current_class->subclasses.size(); i++) {
+ if (current_class->subclasses[i]->name == member.identifier) {
+ _set_error("A class named \"" + String(member.identifier) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ").");
+ return;
+ }
+ }
#ifdef DEBUG_ENABLED
for (int i = 0; i < current_class->functions.size(); i++) {
if (current_class->functions[i]->name == member.identifier) {
@@ -4651,7 +4784,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
#endif
tokenizer->advance();
} else if (!_parse_type(member.data_type)) {
- _set_error("Expected type for class variable.");
+ _set_error("Expected a type for the class variable.");
return;
}
}
@@ -4679,7 +4812,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
IdentifierNode *id = static_cast<IdentifierNode *>(op->arguments[1]);
if (id->name == "get_node") {
- _set_error("Use 'onready var " + String(member.identifier) + " = get_node(..)' instead");
+ _set_error("Use \"onready var " + String(member.identifier) + " = get_node(...)\" instead.");
return;
}
}
@@ -4707,7 +4840,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
Object *obj = cn->value;
Resource *res = Object::cast_to<Resource>(obj);
if (res == NULL) {
- _set_error("Exported constant not a type or resource.");
+ _set_error("The exported constant isn't a type or resource.");
return;
}
member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
@@ -4725,7 +4858,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
const Variant *args = &cn->value;
cn->value = Variant::construct(member._export.type, &args, 1, err);
} else {
- _set_error("Cannot convert the provided value to the export type.");
+ _set_error("Can't convert the provided value to the export type.");
return;
}
}
@@ -4764,24 +4897,29 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
return;
}
- if (member._export.type != Variant::NIL) {
- IdentifierNode *id = alloc_node<IdentifierNode>();
- id->name = member.identifier;
+ Node *expr;
- ConstantNode *cn = alloc_node<ConstantNode>();
+ if (member.data_type.has_type) {
+ expr = _get_default_value_for_type(member.data_type);
+ } else {
+ DataType exported_type;
+ exported_type.has_type = true;
+ exported_type.kind = DataType::BUILTIN;
+ exported_type.builtin_type = member._export.type;
+ expr = _get_default_value_for_type(exported_type);
+ }
- Variant::CallError ce2;
- cn->value = Variant::construct(member._export.type, NULL, 0, ce2);
+ IdentifierNode *id = alloc_node<IdentifierNode>();
+ id->name = member.identifier;
- OperatorNode *op = alloc_node<OperatorNode>();
- op->op = OperatorNode::OP_INIT_ASSIGN;
- op->arguments.push_back(id);
- op->arguments.push_back(cn);
+ OperatorNode *op = alloc_node<OperatorNode>();
+ op->op = OperatorNode::OP_INIT_ASSIGN;
+ op->arguments.push_back(id);
+ op->arguments.push_back(expr);
- p_class->initializer->statements.push_back(op);
+ p_class->initializer->statements.push_back(op);
- member.initial_assignment = op;
- }
+ member.initial_assignment = op;
}
if (autoexport && member.data_type.has_type) {
@@ -4812,7 +4950,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() != GDScriptTokenizer::TK_COMMA) {
//just comma means using only getter
if (!tokenizer->is_token_literal()) {
- _set_error("Expected identifier for setter function after 'setget'.");
+ _set_error("Expected an identifier for the setter function after \"setget\".");
}
member.setter = tokenizer->get_token_literal();
@@ -4825,7 +4963,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
if (!tokenizer->is_token_literal()) {
- _set_error("Expected identifier for getter function after ','.");
+ _set_error("Expected an identifier for the getter function after \",\".");
}
member.getter = tokenizer->get_token_literal();
@@ -4836,7 +4974,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
p_class->variables.push_back(member);
if (!_end_statement()) {
- _set_error("Expected end of statement (continue)");
+ _set_error("Expected end of statement (\"continue\").");
return;
}
} break;
@@ -4848,7 +4986,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
tokenizer->advance();
if (!tokenizer->is_token_literal(0, true)) {
- _set_error("Expected name (identifier) for constant.");
+ _set_error("Expected an identifier for the constant.");
return;
}
@@ -4856,19 +4994,26 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
int line = tokenizer->get_token_line();
if (current_class->constant_expressions.has(const_id)) {
- _set_error("Constant '" + String(const_id) + "' already exists in this class (at line: " +
+ _set_error("Constant \"" + String(const_id) + "\" already exists in this class (at line " +
itos(current_class->constant_expressions[const_id].expression->line) + ").");
return;
}
for (int i = 0; i < current_class->variables.size(); i++) {
if (current_class->variables[i].identifier == const_id) {
- _set_error("A variable named '" + String(const_id) + "' already exists in this class (at line: " +
+ _set_error("A variable named \"" + String(const_id) + "\" already exists in this class (at line " +
itos(current_class->variables[i].line) + ").");
return;
}
}
+ for (int i = 0; i < current_class->subclasses.size(); i++) {
+ if (current_class->subclasses[i]->name == const_id) {
+ _set_error("A class named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ").");
+ return;
+ }
+ }
+
tokenizer->advance();
if (tokenizer->get_token() == GDScriptTokenizer::TK_COLON) {
@@ -4879,13 +5024,13 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
#endif
tokenizer->advance();
} else if (!_parse_type(constant.type)) {
- _set_error("Expected type for class constant.");
+ _set_error("Expected a type for the class constant.");
return;
}
}
if (tokenizer->get_token() != GDScriptTokenizer::TK_OP_ASSIGN) {
- _set_error("Constant expects assignment.");
+ _set_error("Constants must be assigned immediately.");
return;
}
@@ -4900,7 +5045,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
if (subexpr->type != Node::TYPE_CONSTANT) {
- _set_error("Expected constant expression", line);
+ _set_error("Expected a constant expression.", line);
return;
}
subexpr->line = line;
@@ -4909,7 +5054,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
p_class->constant_expressions.insert(const_id, constant);
if (!_end_statement()) {
- _set_error("Expected end of statement (constant)", line);
+ _set_error("Expected end of statement (constant).", line);
return;
}
@@ -4926,23 +5071,30 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
enum_name = tokenizer->get_token_literal();
if (current_class->constant_expressions.has(enum_name)) {
- _set_error("A constant named '" + String(enum_name) + "' already exists in this class (at line: " +
+ _set_error("A constant named \"" + String(enum_name) + "\" already exists in this class (at line " +
itos(current_class->constant_expressions[enum_name].expression->line) + ").");
return;
}
for (int i = 0; i < current_class->variables.size(); i++) {
if (current_class->variables[i].identifier == enum_name) {
- _set_error("A variable named '" + String(enum_name) + "' already exists in this class (at line: " +
+ _set_error("A variable named \"" + String(enum_name) + "\" already exists in this class (at line " +
itos(current_class->variables[i].line) + ").");
return;
}
}
+ for (int i = 0; i < current_class->subclasses.size(); i++) {
+ if (current_class->subclasses[i]->name == enum_name) {
+ _set_error("A class named \"" + String(enum_name) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ").");
+ return;
+ }
+ }
+
tokenizer->advance();
}
if (tokenizer->get_token() != GDScriptTokenizer::TK_CURLY_BRACKET_OPEN) {
- _set_error("Expected '{' in enum declaration");
+ _set_error("Expected \"{\" in the enum declaration.");
return;
}
tokenizer->advance();
@@ -4960,7 +5112,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() == GDScriptTokenizer::TK_EOF) {
_set_error("Unexpected end of file.");
} else {
- _set_error(String("Unexpected ") + GDScriptTokenizer::get_token_name(tokenizer->get_token()) + ", expected identifier");
+ _set_error(String("Unexpected ") + GDScriptTokenizer::get_token_name(tokenizer->get_token()) + ", expected an identifier.");
}
return;
@@ -4983,14 +5135,14 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
if (subexpr->type != Node::TYPE_CONSTANT) {
- _set_error("Expected constant expression");
+ _set_error("Expected a constant expression.");
return;
}
enum_value_expr = static_cast<ConstantNode *>(subexpr);
if (enum_value_expr->value.get_type() != Variant::INT) {
- _set_error("Expected an int value for enum");
+ _set_error("Expected an integer value for \"enum\".");
return;
}
@@ -5005,25 +5157,35 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
tokenizer->advance();
+ } else if (tokenizer->is_token_literal(0, true)) {
+ _set_error("Unexpected identifier.");
+ return;
}
if (enum_name != "") {
enum_dict[const_id] = enum_value_expr->value;
} else {
if (current_class->constant_expressions.has(const_id)) {
- _set_error("A constant named '" + String(const_id) + "' already exists in this class (at line: " +
+ _set_error("A constant named \"" + String(const_id) + "\" already exists in this class (at line " +
itos(current_class->constant_expressions[const_id].expression->line) + ").");
return;
}
for (int i = 0; i < current_class->variables.size(); i++) {
if (current_class->variables[i].identifier == const_id) {
- _set_error("A variable named '" + String(const_id) + "' already exists in this class (at line: " +
+ _set_error("A variable named \"" + String(const_id) + "\" already exists in this class (at line " +
itos(current_class->variables[i].line) + ").");
return;
}
}
+ for (int i = 0; i < current_class->subclasses.size(); i++) {
+ if (current_class->subclasses[i]->name == const_id) {
+ _set_error("A class named \"" + String(const_id) + "\" already exists in this class (at line " + itos(current_class->subclasses[i]->line) + ").");
+ return;
+ }
+ }
+
ClassNode::Constant constant;
constant.type.has_type = true;
constant.type.kind = DataType::BUILTIN;
@@ -5046,7 +5208,7 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
if (!_end_statement()) {
- _set_error("Expected end of statement (enum)");
+ _set_error("Expected end of statement (\"enum\").");
return;
}
@@ -5072,9 +5234,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
}
-void GDScriptParser::_determine_inheritance(ClassNode *p_class) {
+void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive) {
- if (p_class->extends_used) {
+ if (p_class->base_type.has_type) {
+ // Already determined
+ } else if (p_class->extends_used) {
//do inheritance
String path = p_class->extends_file;
@@ -5090,19 +5254,19 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class) {
String base = base_path;
if (base == "" || base.is_rel_path()) {
- _set_error("Could not resolve relative path for parent class: " + path, p_class->line);
+ _set_error("Couldn't resolve relative path for the parent class: " + path, p_class->line);
return;
}
path = base.plus_file(path).simplify_path();
}
script = ResourceLoader::load(path);
if (script.is_null()) {
- _set_error("Could not load base class: " + path, p_class->line);
+ _set_error("Couldn't load the base class: " + path, p_class->line);
return;
}
if (!script->is_valid()) {
- _set_error("Script not fully loaded (cyclic preload?): " + path, p_class->line);
+ _set_error("Script isn't fully loaded (cyclic preload?): " + path, p_class->line);
return;
}
@@ -5117,7 +5281,7 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class) {
script = subclass;
} else {
- _set_error("Could not find subclass: " + sub, p_class->line);
+ _set_error("Couldn't find the subclass: " + sub, p_class->line);
return;
}
}
@@ -5139,10 +5303,35 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class) {
if (ScriptServer::is_global_class(base)) {
base_script = ResourceLoader::load(ScriptServer::get_global_class_path(base));
if (!base_script.is_valid()) {
- _set_error("Class '" + base + "' could not be fully loaded (script error or cyclic dependency).", p_class->line);
+ _set_error("The class \"" + base + "\" couldn't be fully loaded (script error or cyclic dependency).", p_class->line);
return;
}
p = NULL;
+ } else {
+ List<PropertyInfo> props;
+ ProjectSettings::get_singleton()->get_property_list(&props);
+ for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ String s = E->get().name;
+ if (!s.begins_with("autoload/")) {
+ continue;
+ }
+ String name = s.get_slice("/", 1);
+ if (name == base) {
+ String singleton_path = ProjectSettings::get_singleton()->get(s);
+ if (singleton_path.begins_with("*")) {
+ singleton_path = singleton_path.right(1);
+ }
+ if (!singleton_path.begins_with("res://")) {
+ singleton_path = "res://" + singleton_path;
+ }
+ base_script = ResourceLoader::load(singleton_path);
+ if (!base_script.is_valid()) {
+ _set_error("Class '" + base + "' could not be fully loaded (script error or cyclic inheritance).", p_class->line);
+ return;
+ }
+ p = NULL;
+ }
+ }
}
while (p) {
@@ -5180,13 +5369,13 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class) {
if (p->constant_expressions.has(base)) {
if (p->constant_expressions[base].expression->type != Node::TYPE_CONSTANT) {
- _set_error("Could not resolve constant '" + base + "'.", p_class->line);
+ _set_error("Couldn't resolve the constant \"" + base + "\".", p_class->line);
return;
}
const ConstantNode *cn = static_cast<const ConstantNode *>(p->constant_expressions[base].expression);
base_script = cn->value;
if (base_script.is_null()) {
- _set_error("Constant is not a class: " + base, p_class->line);
+ _set_error("Constant isn't a class: " + base, p_class->line);
return;
}
break;
@@ -5198,6 +5387,7 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class) {
if (base_script.is_valid()) {
String ident = base;
+ Ref<GDScript> find_subclass = base_script;
for (int i = extend_iter; i < p_class->extends_class.size(); i++) {
@@ -5207,35 +5397,35 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class) {
if (base_script->get_subclasses().has(subclass)) {
- base_script = base_script->get_subclasses()[subclass];
+ find_subclass = base_script->get_subclasses()[subclass];
} else if (base_script->get_constants().has(subclass)) {
Ref<GDScript> new_base_class = base_script->get_constants()[subclass];
if (new_base_class.is_null()) {
- _set_error("Constant is not a class: " + ident, p_class->line);
+ _set_error("Constant isn't a class: " + ident, p_class->line);
return;
}
- base_script = new_base_class;
+ find_subclass = new_base_class;
} else {
- _set_error("Could not find subclass: " + ident, p_class->line);
+ _set_error("Couldn't find the subclass: " + ident, p_class->line);
return;
}
}
- script = base_script;
+ script = find_subclass;
} else if (!base_class) {
if (p_class->extends_class.size() > 1) {
- _set_error("Invalid inheritance (unknown class + subclasses)", p_class->line);
+ _set_error("Invalid inheritance (unknown class + subclasses).", p_class->line);
return;
}
//if not found, try engine classes
if (!GDScriptLanguage::get_singleton()->get_global_map().has(base)) {
- _set_error("Unknown class: '" + base + "'", p_class->line);
+ _set_error("Unknown class: \"" + base + "\"", p_class->line);
return;
}
@@ -5257,7 +5447,7 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class) {
p_class->base_type.kind = DataType::NATIVE;
p_class->base_type.native_type = native;
} else {
- _set_error("Could not determine inheritance", p_class->line);
+ _set_error("Couldn't determine inheritance.", p_class->line);
return;
}
@@ -5268,9 +5458,11 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class) {
p_class->base_type.native_type = "Reference";
}
- // Recursively determine subclasses
- for (int i = 0; i < p_class->subclasses.size(); i++) {
- _determine_inheritance(p_class->subclasses[i]);
+ if (p_recursive) {
+ // Recursively determine subclasses
+ for (int i = 0; i < p_class->subclasses.size(); i++) {
+ _determine_inheritance(p_class->subclasses[i], p_recursive);
+ }
}
}
@@ -5400,7 +5592,7 @@ bool GDScriptParser::_parse_type(DataType &r_type, bool p_can_be_void) {
switch (tokenizer->get_token()) {
case GDScriptTokenizer::TK_PERIOD: {
if (!can_index) {
- _set_error("Unexpected '.'.");
+ _set_error("Unexpected \".\".");
return false;
}
can_index = false;
@@ -5431,7 +5623,7 @@ bool GDScriptParser::_parse_type(DataType &r_type, bool p_can_be_void) {
}
if (tokenizer->get_token(-1) == GDScriptTokenizer::TK_PERIOD) {
- _set_error("Expected subclass identifier.");
+ _set_error("Expected a subclass identifier.");
return false;
}
@@ -5469,7 +5661,7 @@ GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source,
Ref<GDScript> gds = script;
if (gds.is_valid()) {
if (!gds->is_valid()) {
- _set_error("Class '" + id + "' could not be fully loaded (script error or cyclic dependency).", p_line);
+ _set_error("The class \"" + id + "\" couldn't be fully loaded (script error or cyclic dependency).", p_line);
return DataType();
}
result.kind = DataType::GDSCRIPT;
@@ -5478,15 +5670,55 @@ GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source,
result.kind = DataType::SCRIPT;
result.script_type = script;
} else {
- _set_error("Class '" + id + "' was found in global scope but its script could not be loaded.", p_line);
+ _set_error("The class \"" + id + "\" was found in global scope, but its script couldn't be loaded.", p_line);
return DataType();
}
}
name_part++;
continue;
- } else {
- p = current_class;
}
+ List<PropertyInfo> props;
+ ProjectSettings::get_singleton()->get_property_list(&props);
+ String singleton_path;
+ for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ String s = E->get().name;
+ if (!s.begins_with("autoload/")) {
+ continue;
+ }
+ String name = s.get_slice("/", 1);
+ if (name == id) {
+ singleton_path = ProjectSettings::get_singleton()->get(s);
+ if (singleton_path.begins_with("*")) {
+ singleton_path = singleton_path.right(1);
+ }
+ if (!singleton_path.begins_with("res://")) {
+ singleton_path = "res://" + singleton_path;
+ }
+ break;
+ }
+ }
+ if (!singleton_path.empty()) {
+ Ref<Script> script = ResourceLoader::load(singleton_path);
+ Ref<GDScript> gds = script;
+ if (gds.is_valid()) {
+ if (!gds->is_valid()) {
+ _set_error("Class '" + id + "' could not be fully loaded (script error or cyclic inheritance).", p_line);
+ return DataType();
+ }
+ result.kind = DataType::GDSCRIPT;
+ result.script_type = gds;
+ } else if (script.is_valid()) {
+ result.kind = DataType::SCRIPT;
+ result.script_type = script;
+ } else {
+ _set_error("Couldn't fully load singleton script '" + id + "' (possible cyclic reference or parse error).", p_line);
+ return DataType();
+ }
+ name_part++;
+ continue;
+ }
+
+ p = current_class;
} else if (base_type.kind == DataType::CLASS) {
p = base_type.class_type;
}
@@ -5547,28 +5779,35 @@ GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source,
}
}
- // Still look for class constants in parent script
+ // Still look for class constants in parent scripts
if (!found && (base_type.kind == DataType::GDSCRIPT || base_type.kind == DataType::SCRIPT)) {
Ref<Script> scr = base_type.script_type;
ERR_FAIL_COND_V(scr.is_null(), result);
- Map<StringName, Variant> constants;
- scr->get_constants(&constants);
+ while (scr.is_valid()) {
+ Map<StringName, Variant> constants;
+ scr->get_constants(&constants);
- if (constants.has(id)) {
- Ref<GDScript> gds = constants[id];
+ if (constants.has(id)) {
+ Ref<GDScript> gds = constants[id];
- if (gds.is_valid()) {
- result.kind = DataType::GDSCRIPT;
- result.script_type = gds;
- found = true;
- } else {
- Ref<Script> scr2 = constants[id];
- if (scr2.is_valid()) {
- result.kind = DataType::SCRIPT;
- result.script_type = scr2;
+ if (gds.is_valid()) {
+ result.kind = DataType::GDSCRIPT;
+ result.script_type = gds;
found = true;
+ } else {
+ Ref<Script> scr2 = constants[id];
+ if (scr2.is_valid()) {
+ result.kind = DataType::SCRIPT;
+ result.script_type = scr2;
+ found = true;
+ }
}
}
+ if (found) {
+ break;
+ } else {
+ scr = scr->get_base_script();
+ }
}
}
@@ -5579,8 +5818,8 @@ GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source,
} else {
base = result.to_string();
}
- _set_error("Identifier '" + String(id) + "' is not a valid type (not a script or class), or could not be found on base '" +
- base + "'.",
+ _set_error("The identifier \"" + String(id) + "\" isn't a valid type (not a script or class), or couldn't be found on base \"" +
+ base + "\".",
p_line);
return DataType();
}
@@ -5658,7 +5897,7 @@ GDScriptParser::DataType GDScriptParser::_type_from_gdtype(const GDScriptDataTyp
switch (p_gdtype.kind) {
case GDScriptDataType::UNINITIALIZED: {
- ERR_EXPLAIN("Uninitialized datatype. Please report a bug.");
+ ERR_PRINT("Uninitialized datatype. Please report a bug.");
} break;
case GDScriptDataType::BUILTIN: {
result.kind = DataType::BUILTIN;
@@ -5844,11 +6083,8 @@ bool GDScriptParser::_is_type_compatible(const DataType &p_container, const Data
if (p_container.kind == DataType::BUILTIN && p_container.builtin_type == Variant::OBJECT) {
// Object built-in is a special case, it's compatible with any object and with null
- if (p_expression.kind == DataType::BUILTIN && p_expression.builtin_type == Variant::NIL) {
- return true;
- }
if (p_expression.kind == DataType::BUILTIN) {
- return false;
+ return p_expression.builtin_type == Variant::NIL;
}
// If it's not a built-in, must be an object
return true;
@@ -5914,12 +6150,18 @@ bool GDScriptParser::_is_type_compatible(const DataType &p_container, const Data
break;
}
+ // Some classes are prefixed with `_` internally
+ if (!ClassDB::class_exists(expr_native)) {
+ expr_native = "_" + expr_native;
+ }
+
switch (p_container.kind) {
case DataType::NATIVE: {
if (p_container.is_meta_type) {
return ClassDB::is_parent_class(expr_native, GDScriptNativeClass::get_class_static());
} else {
- return ClassDB::is_parent_class(expr_native, p_container.native_type);
+ StringName container_native = ClassDB::class_exists(p_container.native_type) ? p_container.native_type : StringName("_" + p_container.native_type);
+ return ClassDB::is_parent_class(expr_native, container_native);
}
} break;
case DataType::SCRIPT:
@@ -5963,8 +6205,37 @@ bool GDScriptParser::_is_type_compatible(const DataType &p_container, const Data
return false;
}
+GDScriptParser::Node *GDScriptParser::_get_default_value_for_type(const DataType &p_type, int p_line) {
+ Node *result;
+
+ if (p_type.has_type && p_type.kind == DataType::BUILTIN && p_type.builtin_type != Variant::NIL && p_type.builtin_type != Variant::OBJECT) {
+ if (p_type.builtin_type == Variant::ARRAY) {
+ result = alloc_node<ArrayNode>();
+ } else if (p_type.builtin_type == Variant::DICTIONARY) {
+ result = alloc_node<DictionaryNode>();
+ } else {
+ ConstantNode *c = alloc_node<ConstantNode>();
+ Variant::CallError err;
+ c->value = Variant::construct(p_type.builtin_type, NULL, 0, err);
+ result = c;
+ }
+ } else {
+ ConstantNode *c = alloc_node<ConstantNode>();
+ c->value = Variant();
+ result = c;
+ }
+
+ result->line = p_line;
+
+ return result;
+}
+
GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
+#ifdef DEBUG_ENABLED
+ if (p_node->get_datatype().has_type && p_node->type != Node::TYPE_ARRAY && p_node->type != Node::TYPE_DICTIONARY) {
+#else
if (p_node->get_datatype().has_type) {
+#endif
return p_node->get_datatype();
}
@@ -6043,8 +6314,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
}
if (!valid) {
- _set_error("Invalid cast. Cannot convert from '" + source_type.to_string() +
- "' to '" + cn->cast_type.to_string() + "'.",
+ _set_error("Invalid cast. Cannot convert from \"" + source_type.to_string() +
+ "\" to \"" + cn->cast_type.to_string() + "\".",
cn->line);
return DataType();
}
@@ -6073,11 +6344,11 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
DataType signal_type = _reduce_node_type(op->arguments[1]);
// TODO: Check if signal exists when it's a constant
if (base_type.has_type && base_type.kind == DataType::BUILTIN && base_type.builtin_type != Variant::NIL && base_type.builtin_type != Variant::OBJECT) {
- _set_error("First argument of 'yield()' must be an object.", op->line);
+ _set_error("The first argument of \"yield()\" must be an object.", op->line);
return DataType();
}
if (signal_type.has_type && (signal_type.kind != DataType::BUILTIN || signal_type.builtin_type != Variant::STRING)) {
- _set_error("Second argument of 'yield()' must be a string.", op->line);
+ _set_error("The second argument of \"yield()\" must be a string.", op->line);
return DataType();
}
}
@@ -6097,15 +6368,15 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
if (check_types && type_type.has_type) {
if (!type_type.is_meta_type && (type_type.kind != DataType::NATIVE || !ClassDB::is_parent_class(type_type.native_type, "Script"))) {
- _set_error("Invalid 'is' test: right operand is not a type (not a native type nor a script).", op->line);
+ _set_error("Invalid \"is\" test: the right operand isn't a type (neither a native type nor a script).", op->line);
return DataType();
}
type_type.is_meta_type = false; // Test the actual type
if (!_is_type_compatible(type_type, value_type) && !_is_type_compatible(value_type, type_type)) {
if (op->op == OperatorNode::OP_IS) {
- _set_error("A value of type '" + value_type.to_string() + "' will never be an instance of '" + type_type.to_string() + "'.", op->line);
+ _set_error("A value of type \"" + value_type.to_string() + "\" will never be an instance of \"" + type_type.to_string() + "\".", op->line);
} else {
- _set_error("A value of type '" + value_type.to_string() + "' will never be of type '" + type_type.to_string() + "'.", op->line);
+ _set_error("A value of type \"" + value_type.to_string() + "\" will never be of type \"" + type_type.to_string() + "\".", op->line);
}
return DataType();
}
@@ -6133,8 +6404,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
node_type = _get_operation_type(var_op, argument_type, argument_type, valid);
if (check_types && !valid) {
- _set_error("Invalid operand type ('" + argument_type.to_string() +
- "') to unary operator '" + Variant::get_operator_name(var_op) + "'.",
+ _set_error("Invalid operand type (\"" + argument_type.to_string() +
+ "\") to unary operator \"" + Variant::get_operator_name(var_op) + "\".",
op->line, op->column);
return DataType();
}
@@ -6178,14 +6449,14 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
node_type = _get_operation_type(var_op, argument_a_type, argument_b_type, valid);
if (check_types && !valid) {
- _set_error("Invalid operand types ('" + argument_a_type.to_string() + "' and '" +
- argument_b_type.to_string() + "') to operator '" + Variant::get_operator_name(var_op) + "'.",
+ _set_error("Invalid operand types (\"" + argument_a_type.to_string() + "\" and \"" +
+ argument_b_type.to_string() + "\") to operator \"" + Variant::get_operator_name(var_op) + "\".",
op->line, op->column);
return DataType();
}
#ifdef DEBUG_ENABLED
- if (var_op == Variant::OP_DIVIDE && argument_a_type.has_type && argument_a_type.kind == DataType::BUILTIN && argument_a_type.builtin_type == Variant::INT &&
- argument_b_type.has_type && argument_b_type.kind == DataType::BUILTIN && argument_b_type.builtin_type == Variant::INT) {
+ if (var_op == Variant::OP_DIVIDE && argument_a_type.kind == DataType::BUILTIN && argument_a_type.builtin_type == Variant::INT &&
+ argument_b_type.kind == DataType::BUILTIN && argument_b_type.builtin_type == Variant::INT) {
_add_warning(GDScriptWarning::INTEGER_DIVISION, op->line);
}
#endif // DEBUG_ENABLED
@@ -6194,12 +6465,14 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
// Ternary operators
case OperatorNode::OP_TERNARY_IF: {
if (op->arguments.size() != 3) {
- _set_error("Parser bug: ternary operation without 3 arguments");
+ _set_error("Parser bug: ternary operation without 3 arguments.");
ERR_FAIL_V(DataType());
}
DataType true_type = _reduce_node_type(op->arguments[1]);
DataType false_type = _reduce_node_type(op->arguments[2]);
+ // Check arguments[0] errors.
+ _reduce_node_type(op->arguments[0]);
// If types are equal, then the expression is of the same type
// If they are compatible, return the broader type
@@ -6227,7 +6500,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
case OperatorNode::OP_ASSIGN_BIT_XOR:
case OperatorNode::OP_INIT_ASSIGN: {
- _set_error("Assignment inside expression is not allowed (parser bug?).", op->line);
+ _set_error("Assignment inside an expression isn't allowed (parser bug?).", op->line);
return DataType();
} break;
@@ -6263,8 +6536,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
if (valid) {
result = _type_from_variant(res);
} else if (check_types) {
- _set_error("Can't get index '" + String(member_id->name.operator String()) + "' on base '" +
- base_type.to_string() + "'.",
+ _set_error("Can't get index \"" + String(member_id->name.operator String()) + "\" on base \"" +
+ base_type.to_string() + "\".",
op->line);
return DataType();
}
@@ -6351,11 +6624,12 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
case Variant::COLOR: {
error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING;
} break;
- default: {}
+ default: {
+ }
}
}
if (error) {
- _set_error("Invalid index type (" + index_type.to_string() + ") for base '" + base_type.to_string() + "'.",
+ _set_error("Invalid index type (" + index_type.to_string() + ") for base \"" + base_type.to_string() + "\".",
op->line);
return DataType();
}
@@ -6388,8 +6662,8 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
node_type = _type_from_variant(res);
node_type.is_constant = false;
} else if (check_types) {
- _set_error("Can't get index '" + String(cn->value) + "' on base '" +
- base_type.to_string() + "'.",
+ _set_error("Can't get index \"" + String(cn->value) + "\" on base \"" +
+ base_type.to_string() + "\".",
op->line);
return DataType();
}
@@ -6399,11 +6673,11 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
_mark_line_as_unsafe(op->line);
}
} else if (!for_completion && (index_type.kind != DataType::BUILTIN || index_type.builtin_type != Variant::STRING)) {
- _set_error("Only strings can be used as index in the base type '" + base_type.to_string() + "'.", op->line);
+ _set_error("Only strings can be used as an index in the base type \"" + base_type.to_string() + "\".", op->line);
return DataType();
}
}
- if (check_types && !node_type.has_type) {
+ if (check_types && !node_type.has_type && base_type.kind == DataType::BUILTIN) {
// Can infer indexing type for some variant types
DataType result;
result.has_type = true;
@@ -6416,7 +6690,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
case Variant::REAL:
case Variant::NODE_PATH:
case Variant::_RID: {
- _set_error("Can't index on a value of type '" + base_type.to_string() + "'.", op->line);
+ _set_error("Can't index on a value of type \"" + base_type.to_string() + "\".", op->line);
return DataType();
} break;
// Return int
@@ -6469,10 +6743,12 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
}
}
} break;
- default: {}
+ default: {
+ }
}
- p_node->set_datatype(_resolve_type(node_type, p_node->line));
+ node_type = _resolve_type(node_type, p_node->line);
+ p_node->set_datatype(node_type);
return node_type;
}
@@ -6589,8 +6865,7 @@ bool GDScriptParser::_get_function_signature(DataType &p_base_type, const String
}
if (!ClassDB::class_exists(native)) {
if (!check_types) return false;
- ERR_EXPLAIN("Parser bug: Class '" + String(native) + "' not found.");
- ERR_FAIL_V(false);
+ ERR_FAIL_V_MSG(false, "Parser bug: Class '" + String(native) + "' not found.");
}
MethodBind *method = ClassDB::get_method(native, p_function);
@@ -6814,7 +7089,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
if (check_types) {
if (!tmp.has_method(callee_name)) {
- _set_error("Method '" + callee_name + "' is not declared on base '" + base_type.to_string() + "'.", p_call->line);
+ _set_error("The method \"" + callee_name + "\" isn't declared on base \"" + base_type.to_string() + "\".", p_call->line);
return DataType();
}
@@ -6874,7 +7149,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
if (!valid) {
#ifdef DEBUG_ENABLED
if (p_call->arguments[0]->type == Node::TYPE_SELF) {
- _set_error("Method '" + callee_name + "' is not declared in the current class.", p_call->line);
+ _set_error("The method \"" + callee_name + "\" isn't declared in the current class.", p_call->line);
return DataType();
}
DataType tmp_type;
@@ -6894,14 +7169,12 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
#ifdef DEBUG_ENABLED
if (current_function && !for_completion && !is_static && p_call->arguments[0]->type == Node::TYPE_SELF && current_function->_static) {
- if (current_function && current_function->_static && p_call->arguments[0]->type == Node::TYPE_SELF) {
- _set_error("Can't call non-static function from a static function.", p_call->line);
- return DataType();
- }
+ _set_error("Can't call non-static function from a static function.", p_call->line);
+ return DataType();
}
if (check_types && !is_static && !is_initializer && base_type.is_meta_type) {
- _set_error("Non-static function '" + String(callee_name) + "' can only be called from an instance.", p_call->line);
+ _set_error("Non-static function \"" + String(callee_name) + "\" can only be called from an instance.", p_call->line);
return DataType();
}
@@ -6926,11 +7199,11 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
}
if (arg_count < arg_types.size() - default_args_count) {
- _set_error("Too few arguments for '" + callee_name + "()' call. Expected at least " + itos(arg_types.size() - default_args_count) + ".", p_call->line);
+ _set_error("Too few arguments for \"" + callee_name + "()\" call. Expected at least " + itos(arg_types.size() - default_args_count) + ".", p_call->line);
return return_type;
}
if (!is_vararg && arg_count > arg_types.size()) {
- _set_error("Too many arguments for '" + callee_name + "()' call. Expected at most " + itos(arg_types.size()) + ".", p_call->line);
+ _set_error("Too many arguments for \"" + callee_name + "()\" call. Expected at most " + itos(arg_types.size()) + ".", p_call->line);
return return_type;
}
@@ -6952,7 +7225,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_function_call_type(const Operat
} else if (!_is_type_compatible(arg_types[i - arg_diff], par_type, true)) {
// Supertypes are acceptable for dynamic compliance
if (!_is_type_compatible(par_type, arg_types[i - arg_diff])) {
- _set_error("At '" + callee_name + "()' call, argument " + itos(i - arg_diff + 1) + ". Assigned type (" +
+ _set_error("At \"" + callee_name + "()\" call, argument " + itos(i - arg_diff + 1) + ". Assigned type (" +
par_type.to_string() + ") doesn't match the function argument's type (" +
arg_types[i - arg_diff].to_string() + ").",
p_call->line);
@@ -7100,8 +7373,7 @@ bool GDScriptParser::_get_member_type(const DataType &p_base_type, const StringN
}
if (!ClassDB::class_exists(native)) {
if (!check_types) return false;
- ERR_EXPLAIN("Parser bug: Class '" + String(native) + "' not found.");
- ERR_FAIL_V(false);
+ ERR_FAIL_V_MSG(false, "Parser bug: Class \"" + String(native) + "\" not found.");
}
bool valid = false;
@@ -7271,7 +7543,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType
Ref<GDScript> gds = scr;
if (gds.is_valid()) {
if (!gds->is_valid()) {
- _set_error("Class '" + p_identifier + "' could not be fully loaded (script error or cyclic dependency).");
+ _set_error("The class \"" + p_identifier + "\" couldn't be fully loaded (script error or cyclic dependency).");
return DataType();
}
result.kind = DataType::GDSCRIPT;
@@ -7280,7 +7552,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType
}
return result;
}
- _set_error("Class '" + p_identifier + "' was found in global scope but its script could not be loaded.");
+ _set_error("The class \"" + p_identifier + "\" was found in global scope, but its script couldn't be loaded.");
return DataType();
}
@@ -7323,7 +7595,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType
Ref<GDScript> gds = singleton;
if (gds.is_valid()) {
if (!gds->is_valid()) {
- _set_error("Couldn't fully load singleton script '" + p_identifier + "' (possible cyclic reference or parse error).", p_line);
+ _set_error("Couldn't fully load the singleton script \"" + p_identifier + "\" (possible cyclic reference or parse error).", p_line);
return DataType();
}
result.kind = DataType::GDSCRIPT;
@@ -7335,7 +7607,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_identifier_type(const DataType
}
// This means looking in the current class, which type is always known
- _set_error("Identifier '" + p_identifier.operator String() + "' is not declared in the current scope.", p_line);
+ _set_error("The identifier \"" + p_identifier.operator String() + "\" isn't declared in the current scope.", p_line);
}
#ifdef DEBUG_ENABLED
@@ -7367,7 +7639,7 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
DataType expr = _resolve_type(c.expression->get_datatype(), c.expression->line);
if (check_types && !_is_type_compatible(cont, expr)) {
- _set_error("Constant value type (" + expr.to_string() + ") is not compatible with declared type (" + cont.to_string() + ").",
+ _set_error("The constant value type (" + expr.to_string() + ") isn't compatible with declared type (" + cont.to_string() + ").",
c.expression->line);
return;
}
@@ -7378,7 +7650,7 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
DataType tmp;
if (_get_member_type(p_class->base_type, E->key(), tmp)) {
- _set_error("Member '" + String(E->key()) + "' already exists in parent class.", c.expression->line);
+ _set_error("The member \"" + String(E->key()) + "\" already exists in a parent class.", c.expression->line);
return;
}
}
@@ -7400,7 +7672,7 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
DataType tmp;
if (_get_member_type(p_class->base_type, v.identifier, tmp)) {
- _set_error("Member '" + String(v.identifier) + "' already exists in parent class.", v.line);
+ _set_error("The member \"" + String(v.identifier) + "\" already exists in a parent class.", v.line);
return;
}
@@ -7417,13 +7689,13 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
} else {
// Try with implicit conversion
if (v.data_type.kind != DataType::BUILTIN || !_is_type_compatible(v.data_type, expr_type, true)) {
- _set_error("Assigned expression type (" + expr_type.to_string() + ") doesn't match the variable's type (" +
+ _set_error("The assigned expression's type (" + expr_type.to_string() + ") doesn't match the variable's type (" +
v.data_type.to_string() + ").",
v.line);
return;
}
- // Replace assignment with implict conversion
+ // Replace assignment with implicit conversion
BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
convert->line = v.line;
convert->function = GDScriptFunctions::TYPE_CONVERT;
@@ -7446,43 +7718,19 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
if (v.data_type.infer_type) {
if (!expr_type.has_type) {
- _set_error("Assigned value does not have a set type, variable type cannot be inferred.", v.line);
+ _set_error("The assigned value doesn't have a set type; the variable type can't be inferred.", v.line);
return;
}
v.data_type = expr_type;
v.data_type.is_constant = false;
}
- } else if (v.data_type.has_type && v.data_type.kind == DataType::BUILTIN) {
- // Create default value based on the type
- IdentifierNode *id = alloc_node<IdentifierNode>();
- id->line = v.line;
- id->name = v.identifier;
-
- ConstantNode *init = alloc_node<ConstantNode>();
- init->line = v.line;
- Variant::CallError err;
- init->value = Variant::construct(v.data_type.builtin_type, NULL, 0, err);
-
- OperatorNode *op = alloc_node<OperatorNode>();
- op->line = v.line;
- op->op = OperatorNode::OP_INIT_ASSIGN;
- op->arguments.push_back(id);
- op->arguments.push_back(init);
-
- p_class->initializer->statements.push_front(op);
- v.initial_assignment = op;
-#ifdef DEBUG_ENABLED
- NewLineNode *nl = alloc_node<NewLineNode>();
- nl->line = v.line - 1;
- p_class->initializer->statements.push_front(nl);
-#endif
}
// Check export hint
if (v.data_type.has_type && v._export.type != Variant::NIL) {
DataType export_type = _type_from_property(v._export);
if (!_is_type_compatible(v.data_type, export_type, true)) {
- _set_error("Export hint type (" + export_type.to_string() + ") doesn't match the variable's type (" +
+ _set_error("The export hint's type (" + export_type.to_string() + ") doesn't match the variable's type (" +
v.data_type.to_string() + ").",
v.line);
return;
@@ -7501,15 +7749,15 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
if (setter->get_required_argument_count() != 1 &&
!(setter->get_required_argument_count() == 0 && setter->default_values.size() > 0)) {
- _set_error("Setter function needs to receive exactly 1 argument. See '" + setter->name +
- "()' definition at line " + itos(setter->line) + ".",
+ _set_error("The setter function needs to receive exactly 1 argument. See \"" + setter->name +
+ "()\" definition at line " + itos(setter->line) + ".",
v.line);
return;
}
if (!_is_type_compatible(v.data_type, setter->argument_types[0])) {
- _set_error("Setter argument type (" + setter->argument_types[0].to_string() +
- ") doesn't match the variable's type (" + v.data_type.to_string() + "). See '" +
- setter->name + "()' definition at line " + itos(setter->line) + ".",
+ _set_error("The setter argument's type (" + setter->argument_types[0].to_string() +
+ ") doesn't match the variable's type (" + v.data_type.to_string() + "). See \"" +
+ setter->name + "()\" definition at line " + itos(setter->line) + ".",
v.line);
return;
}
@@ -7520,15 +7768,15 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
FunctionNode *getter = p_class->functions[j];
if (getter->get_required_argument_count() != 0) {
- _set_error("Getter function can't receive arguments. See '" + getter->name +
- "()' definition at line " + itos(getter->line) + ".",
+ _set_error("The getter function can't receive arguments. See \"" + getter->name +
+ "()\" definition at line " + itos(getter->line) + ".",
v.line);
return;
}
if (!_is_type_compatible(v.data_type, getter->get_datatype())) {
- _set_error("Getter return type (" + getter->get_datatype().to_string() +
+ _set_error("The getter return type (" + getter->get_datatype().to_string() +
") doesn't match the variable's type (" + v.data_type.to_string() +
- "). See '" + getter->name + "()' definition at line " + itos(getter->line) + ".",
+ "). See \"" + getter->name + "()\" definition at line " + itos(getter->line) + ".",
v.line);
return;
}
@@ -7542,23 +7790,23 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
for (int j = 0; j < p_class->static_functions.size(); j++) {
if (v.setter == p_class->static_functions[j]->name) {
FunctionNode *setter = p_class->static_functions[j];
- _set_error("Setter can't be a static function. See '" + setter->name + "()' definition at line " + itos(setter->line) + ".", v.line);
+ _set_error("The setter can't be a static function. See \"" + setter->name + "()\" definition at line " + itos(setter->line) + ".", v.line);
return;
}
if (v.getter == p_class->static_functions[j]->name) {
FunctionNode *getter = p_class->static_functions[j];
- _set_error("Getter can't be a static function. See '" + getter->name + "()' definition at line " + itos(getter->line) + ".", v.line);
+ _set_error("The getter can't be a static function. See \"" + getter->name + "()\" definition at line " + itos(getter->line) + ".", v.line);
return;
}
}
if (!found_setter && v.setter != StringName()) {
- _set_error("Setter function is not defined.", v.line);
+ _set_error("The setter function isn't defined.", v.line);
return;
}
if (!found_getter && v.getter != StringName()) {
- _set_error("Getter function is not defined.", v.line);
+ _set_error("The getter function isn't defined.", v.line);
return;
}
}
@@ -7605,7 +7853,7 @@ void GDScriptParser::_check_function_types(FunctionNode *p_function) {
if (!_is_type_compatible(p_function->argument_types[i], def_type, true)) {
String arg_name = p_function->arguments[i];
_set_error("Value type (" + def_type.to_string() + ") doesn't match the type of argument '" +
- arg_name + "' (" + p_function->arguments[i] + ")",
+ arg_name + "' (" + p_function->arguments[i] + ").",
p_function->line);
}
}
@@ -7614,6 +7862,11 @@ void GDScriptParser::_check_function_types(FunctionNode *p_function) {
if (p_function->arguments_usage[i] == 0 && !p_function->arguments[i].operator String().begins_with("_")) {
_add_warning(GDScriptWarning::UNUSED_ARGUMENT, p_function->line, p_function->name, p_function->arguments[i].operator String());
}
+ for (int j = 0; j < current_class->variables.size(); j++) {
+ if (current_class->variables[j].identifier == p_function->arguments[i]) {
+ _add_warning(GDScriptWarning::SHADOWED_VARIABLE, p_function->line, p_function->arguments[i], itos(current_class->variables[j].line));
+ }
+ }
#endif // DEBUG_ENABLED
}
@@ -7663,21 +7916,21 @@ void GDScriptParser::_check_function_types(FunctionNode *p_function) {
}
}
parent_signature += ")";
- _set_error("Function signature doesn't match the parent. Parent signature is: '" + parent_signature + "'.", p_function->line);
+ _set_error("The function signature doesn't match the parent. Parent signature is: \"" + parent_signature + "\".", p_function->line);
return;
}
}
#endif // DEBUG_ENABLED
} else {
if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) {
- _set_error("Constructor cannot return a value.", p_function->line);
+ _set_error("The constructor can't return a value.", p_function->line);
return;
}
}
if (p_function->return_type.has_type && (p_function->return_type.kind != DataType::BUILTIN || p_function->return_type.builtin_type != Variant::NIL)) {
if (!p_function->body->has_return) {
- _set_error("Non-void function must return a value in all possible paths.", p_function->line);
+ _set_error("A non-void function must return a value in all possible paths.", p_function->line);
return;
}
}
@@ -7687,6 +7940,17 @@ void GDScriptParser::_check_function_types(FunctionNode *p_function) {
p_function->return_type.has_type = false;
p_function->return_type.may_yield = true;
}
+
+#ifdef DEBUG_ENABLED
+ for (Map<StringName, LocalVarNode *>::Element *E = p_function->body->variables.front(); E; E = E->next()) {
+ LocalVarNode *lv = E->get();
+ for (int i = 0; i < current_class->variables.size(); i++) {
+ if (current_class->variables[i].identifier == lv->name) {
+ _add_warning(GDScriptWarning::SHADOWED_VARIABLE, lv->line, lv->name, itos(current_class->variables[i].line));
+ }
+ }
+ }
+#endif // DEBUG_ENABLED
}
void GDScriptParser::_check_class_blocks_types(ClassNode *p_class) {
@@ -7795,14 +8059,14 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
if (_is_type_compatible(assign_type, lv->datatype)) {
_mark_line_as_unsafe(lv->line);
} else {
- // Try implict conversion
+ // Try implicit conversion
if (lv->datatype.kind != DataType::BUILTIN || !_is_type_compatible(lv->datatype, assign_type, true)) {
- _set_error("Assigned value type (" + assign_type.to_string() + ") doesn't match the variable's type (" +
+ _set_error("The assigned value type (" + assign_type.to_string() + ") doesn't match the variable's type (" +
lv->datatype.to_string() + ").",
lv->line);
return;
}
- // Replace assignment with implict conversion
+ // Replace assignment with implicit conversion
BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
convert->line = lv->line;
convert->function = GDScriptFunctions::TYPE_CONVERT;
@@ -7829,7 +8093,7 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
}
if (lv->datatype.infer_type) {
if (!assign_type.has_type) {
- _set_error("Assigned value does not have a set type, variable type cannot be inferred.", lv->line);
+ _set_error("The assigned value doesn't have a set type; the variable type can't be inferred.", lv->line);
return;
}
lv->datatype = assign_type;
@@ -7880,7 +8144,7 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
}
}
if (lh_type.is_constant) {
- _set_error("Cannot assign a new value to a constant.", op->line);
+ _set_error("Can't assign a new value to a constant.", op->line);
return;
}
}
@@ -7899,8 +8163,8 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
rh_type = _get_operation_type(oper, lh_type, arg_type, valid);
if (check_types && !valid) {
- _set_error("Invalid operand types ('" + lh_type.to_string() + "' and '" + arg_type.to_string() +
- "') to assignment operator '" + Variant::get_operator_name(oper) + "'.",
+ _set_error("Invalid operand types (\"" + lh_type.to_string() + "\" and \"" + arg_type.to_string() +
+ "\") to assignment operator \"" + Variant::get_operator_name(oper) + "\".",
op->line);
return;
}
@@ -7926,14 +8190,14 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
if (_is_type_compatible(rh_type, lh_type)) {
_mark_line_as_unsafe(op->line);
} else {
- // Try implict conversion
+ // Try implicit conversion
if (lh_type.kind != DataType::BUILTIN || !_is_type_compatible(lh_type, rh_type, true)) {
- _set_error("Assigned value type (" + rh_type.to_string() + ") doesn't match the variable's type (" +
+ _set_error("The assigned value's type (" + rh_type.to_string() + ") doesn't match the variable's type (" +
lh_type.to_string() + ").",
op->line);
return;
}
- // Replace assignment with implict conversion
+ // Replace assignment with implicit conversion
BuiltInFunctionNode *convert = alloc_node<BuiltInFunctionNode>();
convert->line = op->line;
convert->function = GDScriptFunctions::TYPE_CONVERT;
@@ -8013,18 +8277,18 @@ void GDScriptParser::_check_block_types(BlockNode *p_block) {
if (function_type.kind == DataType::BUILTIN && function_type.builtin_type == Variant::NIL) {
// Return void, should not have arguments
if (cf->arguments.size() > 0) {
- _set_error("Void function cannot return a value.", cf->line, cf->column);
+ _set_error("A void function cannot return a value.", cf->line, cf->column);
return;
}
} else {
// Return something, cannot be empty
if (cf->arguments.size() == 0) {
- _set_error("Non-void function must return a value.", cf->line, cf->column);
+ _set_error("A non-void function must return a value.", cf->line, cf->column);
return;
}
if (!_is_type_compatible(function_type, ret_type)) {
- _set_error("Returned value type (" + ret_type.to_string() + ") doesn't match the function return type (" +
+ _set_error("The returned value type (" + ret_type.to_string() + ") doesn't match the function return type (" +
function_type.to_string() + ").",
cf->line, cf->column);
return;
@@ -8116,6 +8380,9 @@ void GDScriptParser::_add_warning(int p_code, int p_line, const String &p_symbol
}
void GDScriptParser::_add_warning(int p_code, int p_line, const Vector<String> &p_symbols) {
+ if (GLOBAL_GET("debug/gdscript/warnings/exclude_addons").booleanize() && base_path.begins_with("res://addons/")) {
+ return;
+ }
if (tokenizer->is_ignoring_warnings() || !GLOBAL_GET("debug/gdscript/warnings/enable").booleanize()) {
return;
}
@@ -8161,6 +8428,10 @@ int GDScriptParser::get_error_column() const {
return error_column;
}
+bool GDScriptParser::has_error() const {
+ return error_set;
+}
+
Error GDScriptParser::_parse(const String &p_base_path) {
base_path = p_base_path;
@@ -8177,7 +8448,7 @@ Error GDScriptParser::_parse(const String &p_base_path) {
if (tokenizer->get_token() == GDScriptTokenizer::TK_ERROR) {
error_set = false;
- _set_error("Parse Error: " + tokenizer->get_token_error());
+ _set_error("Parse error: " + tokenizer->get_token_error());
}
if (error_set && !for_completion) {
@@ -8325,8 +8596,8 @@ void GDScriptParser::clear() {
validating = false;
for_completion = false;
error_set = false;
- tab_level.clear();
- tab_level.push_back(0);
+ indent_level.clear();
+ indent_level.push_back(IndentLevel(0, 0));
error_line = 0;
error_column = 0;
pending_newline = -1;
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 809bff8f20..93557d745d 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -149,6 +149,7 @@ public:
bool tool;
StringName name;
bool extends_used;
+ bool classname_used;
StringName extends_file;
Vector<StringName> extends_class;
DataType base_type;
@@ -198,6 +199,7 @@ public:
tool = false;
type = TYPE_CLASS;
extends_used = false;
+ classname_used = false;
end_line = -1;
owner = NULL;
}
@@ -479,7 +481,12 @@ public:
struct AssertNode : public Node {
Node *condition;
- AssertNode() { type = TYPE_ASSERT; }
+ Node *message;
+ AssertNode() :
+ condition(0),
+ message(0) {
+ type = TYPE_ASSERT;
+ }
};
struct BreakpointNode : public Node {
@@ -545,7 +552,27 @@ private:
int pending_newline;
- List<int> tab_level;
+ struct IndentLevel {
+ int indent;
+ int tabs;
+
+ bool is_mixed(IndentLevel other) {
+ return (
+ (indent == other.indent && tabs != other.tabs) ||
+ (indent > other.indent && tabs < other.tabs) ||
+ (indent < other.indent && tabs > other.tabs));
+ }
+
+ IndentLevel() :
+ indent(0),
+ tabs(0) {}
+
+ IndentLevel(int p_indent, int p_tabs) :
+ indent(p_indent),
+ tabs(p_tabs) {}
+ };
+
+ List<IndentLevel> indent_level;
String base_path;
String self_path;
@@ -580,7 +607,7 @@ private:
#endif // DEBUG_ENABLED
bool _recover_from_completion();
- bool _parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete = false);
+ bool _parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete = false, bool p_parsing_constant = false);
bool _enter_indent_block(BlockNode *p_block = NULL);
bool _parse_newline();
Node *_parse_expression(Node *p_parent, bool p_static, bool p_allow_assign = false, bool p_parsing_constant = false);
@@ -597,7 +624,7 @@ private:
void _parse_class(ClassNode *p_class);
bool _end_statement();
- void _determine_inheritance(ClassNode *p_class);
+ void _determine_inheritance(ClassNode *p_class, bool p_recursive = true);
bool _parse_type(DataType &r_type, bool p_can_be_void = false);
DataType _resolve_type(const DataType &p_source, int p_line);
DataType _type_from_variant(const Variant &p_value) const;
@@ -608,6 +635,7 @@ private:
bool _get_function_signature(DataType &p_base_type, const StringName &p_function, DataType &r_return_type, List<DataType> &r_arg_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) const;
bool _get_member_type(const DataType &p_base_type, const StringName &p_member, DataType &r_member_type) const;
bool _is_type_compatible(const DataType &p_container, const DataType &p_expression, bool p_allow_implicit_conversion = false) const;
+ Node *_get_default_value_for_type(const DataType &p_type, int p_line = -1);
DataType _reduce_node_type(Node *p_node);
DataType _reduce_function_call_type(const OperatorNode *p_call);
@@ -630,6 +658,7 @@ private:
Error _parse(const String &p_base_path);
public:
+ bool has_error() const;
String get_error() const;
int get_error_line() const;
int get_error_column() const;
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index abc739d645..761a3de37c 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -339,7 +339,8 @@ StringName GDScriptTokenizer::get_token_literal(int p_offset) const {
return "null";
case Variant::BOOL:
return value ? "true" : "false";
- default: {}
+ default: {
+ }
}
}
case TK_OP_AND:
@@ -356,8 +357,7 @@ StringName GDScriptTokenizer::get_token_literal(int p_offset) const {
}
}
}
- ERR_EXPLAIN("Failed to get token literal");
- ERR_FAIL_V("");
+ ERR_FAIL_V_MSG("", "Failed to get token literal.");
}
static bool _is_text_char(CharType c) {
@@ -375,6 +375,11 @@ static bool _is_hex(CharType c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
+static bool _is_bin(CharType c) {
+
+ return (c == '0' || c == '1');
+}
+
void GDScriptTokenizerText::_make_token(Token p_type) {
TokenData &tk = tk_rb[tk_rb_pos];
@@ -445,11 +450,11 @@ void GDScriptTokenizerText::_make_error(const String &p_error) {
tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
}
-void GDScriptTokenizerText::_make_newline(int p_spaces) {
+void GDScriptTokenizerText::_make_newline(int p_indentation, int p_tabs) {
TokenData &tk = tk_rb[tk_rb_pos];
tk.type = TK_NEWLINE;
- tk.constant = p_spaces;
+ tk.constant = Vector2(p_indentation, p_tabs);
tk.line = line;
tk.col = column;
tk_rb_pos = (tk_rb_pos + 1) % TK_RB_SIZE;
@@ -506,18 +511,6 @@ void GDScriptTokenizerText::_advance() {
case ' ':
INCPOS(1);
continue;
- case '\n': {
- line++;
- INCPOS(1);
- column = 1;
- int i = 0;
- while (GETCHAR(i) == ' ' || GETCHAR(i) == '\t') {
- i++;
- }
-
- _make_newline(i);
- return;
- }
case '#': { // line comment skip
#ifdef DEBUG_ENABLED
String comment;
@@ -534,27 +527,45 @@ void GDScriptTokenizerText::_advance() {
}
}
#ifdef DEBUG_ENABLED
- if (comment.begins_with("#warning-ignore:")) {
- String code = comment.get_slice(":", 1);
+ String comment_content = comment.trim_prefix("#").trim_prefix(" ");
+ if (comment_content.begins_with("warning-ignore:")) {
+ String code = comment_content.get_slice(":", 1);
warning_skips.push_back(Pair<int, String>(line, code.strip_edges().to_lower()));
- } else if (comment.begins_with("#warning-ignore-all:")) {
- String code = comment.get_slice(":", 1);
+ } else if (comment_content.begins_with("warning-ignore-all:")) {
+ String code = comment_content.get_slice(":", 1);
warning_global_skips.insert(code.strip_edges().to_lower());
- } else if (comment.strip_edges() == "#warnings-disable") {
+ } else if (comment_content.strip_edges() == "warnings-disable") {
ignore_warnings = true;
}
#endif // DEBUG_ENABLED
+ FALLTHROUGH;
+ }
+ case '\n': {
+ line++;
INCPOS(1);
+ bool used_spaces = false;
+ int tabs = 0;
column = 1;
- line++;
int i = 0;
- while (GETCHAR(i) == ' ' || GETCHAR(i) == '\t') {
- i++;
+ while (true) {
+ if (GETCHAR(i) == ' ') {
+ i++;
+ used_spaces = true;
+ } else if (GETCHAR(i) == '\t') {
+ if (used_spaces) {
+ _make_error("Spaces used before tabs on a line");
+ return;
+ }
+ i++;
+ tabs++;
+ } else {
+ break; // not indentation anymore
+ }
}
- _make_newline(i);
- return;
- } break;
+ _make_newline(i, tabs);
+ return;
+ }
case '/': {
switch (GETCHAR(1)) {
@@ -812,12 +823,8 @@ void GDScriptTokenizerText::_advance() {
_make_error("Unterminated String");
return;
}
- if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
- _make_error("Malformed hex constant in string");
- return;
- }
- CharType v;
+ CharType v = 0;
if (c >= '0' && c <= '9') {
v = c - '0';
} else if (c >= 'a' && c <= 'f') {
@@ -827,8 +834,8 @@ void GDScriptTokenizerText::_advance() {
v = c - 'A';
v += 10;
} else {
- ERR_PRINT("BUG");
- v = 0;
+ _make_error("Malformed hex constant in string");
+ return;
}
res <<= 4;
@@ -875,6 +882,7 @@ void GDScriptTokenizerText::_advance() {
bool period_found = false;
bool exponent_found = false;
bool hexa_found = false;
+ bool bin_found = false;
bool sign_found = false;
String str;
@@ -885,23 +893,38 @@ void GDScriptTokenizerText::_advance() {
if (period_found || exponent_found) {
_make_error("Invalid numeric constant at '.'");
return;
+ } else if (bin_found) {
+ _make_error("Invalid binary constant at '.'");
+ return;
+ } else if (hexa_found) {
+ _make_error("Invalid hexadecimal constant at '.'");
+ return;
}
period_found = true;
} else if (GETCHAR(i) == 'x') {
- if (hexa_found || str.length() != 1 || !((i == 1 && str[0] == '0') || (i == 2 && str[1] == '0' && str[0] == '-'))) {
+ if (hexa_found || bin_found || str.length() != 1 || !((i == 1 && str[0] == '0') || (i == 2 && str[1] == '0' && str[0] == '-'))) {
_make_error("Invalid numeric constant at 'x'");
return;
}
hexa_found = true;
+ } else if (hexa_found && _is_hex(GETCHAR(i))) {
+
+ } else if (!hexa_found && GETCHAR(i) == 'b') {
+ if (bin_found || str.length() != 1 || !((i == 1 && str[0] == '0') || (i == 2 && str[1] == '0' && str[0] == '-'))) {
+ _make_error("Invalid numeric constant at 'b'");
+ return;
+ }
+ bin_found = true;
} else if (!hexa_found && GETCHAR(i) == 'e') {
- if (hexa_found || exponent_found) {
+ if (exponent_found || bin_found) {
_make_error("Invalid numeric constant at 'e'");
return;
}
exponent_found = true;
} else if (_is_number(GETCHAR(i))) {
//all ok
- } else if (hexa_found && _is_hex(GETCHAR(i))) {
+
+ } else if (bin_found && _is_bin(GETCHAR(i))) {
} else if ((GETCHAR(i) == '-' || GETCHAR(i) == '+') && exponent_found) {
if (sign_found) {
@@ -928,6 +951,9 @@ void GDScriptTokenizerText::_advance() {
if (hexa_found) {
int64_t val = str.hex_to_int64();
_make_constant(val);
+ } else if (bin_found) {
+ int64_t val = str.bin_to_int64();
+ _make_constant(val);
} else if (period_found || exponent_found) {
double val = str.to_double();
_make_constant(val);
@@ -1131,7 +1157,17 @@ int GDScriptTokenizerText::get_token_line_indent(int p_offset) const {
int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
ERR_FAIL_COND_V(tk_rb[ofs].type != TK_NEWLINE, 0);
- return tk_rb[ofs].constant;
+ return tk_rb[ofs].constant.operator Vector2().x;
+}
+
+int GDScriptTokenizerText::get_token_line_tab_indent(int p_offset) const {
+
+ ERR_FAIL_COND_V(p_offset <= -MAX_LOOKAHEAD, 0);
+ ERR_FAIL_COND_V(p_offset >= MAX_LOOKAHEAD, 0);
+
+ int ofs = (TK_RB_SIZE + tk_rb_pos + p_offset - MAX_LOOKAHEAD - 1) % TK_RB_SIZE;
+ ERR_FAIL_COND_V(tk_rb[ofs].type != TK_NEWLINE, 0);
+ return tk_rb[ofs].constant.operator Vector2().y;
}
String GDScriptTokenizerText::get_token_error(int p_offset) const {
@@ -1162,10 +1198,8 @@ Error GDScriptTokenizerBuffer::set_code_buffer(const Vector<uint8_t> &p_buffer)
ERR_FAIL_COND_V(p_buffer.size() < 24 || p_buffer[0] != 'G' || p_buffer[1] != 'D' || p_buffer[2] != 'S' || p_buffer[3] != 'C', ERR_INVALID_DATA);
int version = decode_uint32(&buf[4]);
- if (version > BYTECODE_VERSION) {
- ERR_EXPLAIN("Bytecode is too New! Please use a newer engine version.");
- ERR_FAIL_COND_V(version > BYTECODE_VERSION, ERR_INVALID_DATA);
- }
+ ERR_FAIL_COND_V_MSG(version > BYTECODE_VERSION, ERR_INVALID_DATA, "Bytecode is too recent! Please use a newer engine version.");
+
int identifier_count = decode_uint32(&buf[8]);
int constant_count = decode_uint32(&buf[12]);
int line_count = decode_uint32(&buf[16]);
@@ -1278,7 +1312,7 @@ Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code)
} break;
case TK_CONSTANT: {
- Variant c = tt.get_token_constant();
+ const Variant &c = tt.get_token_constant();
if (!constant_map.has(c)) {
int idx = constant_map.size();
constant_map[c] = idx;
@@ -1302,7 +1336,8 @@ Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code)
ERR_FAIL_V(Vector<uint8_t>());
} break;
- default: {}
+ default: {
+ }
};
token_array.push_back(token);
@@ -1370,7 +1405,7 @@ Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code)
int len;
// Objects cannot be constant, never encode objects
Error err = encode_variant(E->get(), NULL, len, false);
- ERR_FAIL_COND_V(err != OK, Vector<uint8_t>());
+ ERR_FAIL_COND_V_MSG(err != OK, Vector<uint8_t>(), "Error when trying to encode Variant.");
int pos = buf.size();
buf.resize(pos + len);
encode_variant(E->get(), &buf.write[pos], len, false);
diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h
index 7b977ff67c..58749012b7 100644
--- a/modules/gdscript/gdscript_tokenizer.h
+++ b/modules/gdscript/gdscript_tokenizer.h
@@ -168,6 +168,7 @@ public:
virtual int get_token_line(int p_offset = 0) const = 0;
virtual int get_token_column(int p_offset = 0) const = 0;
virtual int get_token_line_indent(int p_offset = 0) const = 0;
+ virtual int get_token_line_tab_indent(int p_offset = 0) const = 0;
virtual String get_token_error(int p_offset = 0) const = 0;
virtual void advance(int p_amount = 1) = 0;
#ifdef DEBUG_ENABLED
@@ -205,7 +206,7 @@ class GDScriptTokenizerText : public GDScriptTokenizer {
};
void _make_token(Token p_type);
- void _make_newline(int p_spaces = 0);
+ void _make_newline(int p_indentation = 0, int p_tabs = 0);
void _make_identifier(const StringName &p_identifier);
void _make_built_in_func(GDScriptFunctions::Function p_func);
void _make_constant(const Variant &p_constant);
@@ -222,6 +223,7 @@ class GDScriptTokenizerText : public GDScriptTokenizer {
int tk_rb_pos;
String last_error;
bool error_flag;
+
#ifdef DEBUG_ENABLED
Vector<Pair<int, String> > warning_skips;
Set<String> warning_global_skips;
@@ -239,6 +241,7 @@ public:
virtual int get_token_line(int p_offset = 0) const;
virtual int get_token_column(int p_offset = 0) const;
virtual int get_token_line_indent(int p_offset = 0) const;
+ virtual int get_token_line_tab_indent(int p_offset = 0) const;
virtual const Variant &get_token_constant(int p_offset = 0) const;
virtual String get_token_error(int p_offset = 0) const;
virtual void advance(int p_amount = 1);
@@ -277,6 +280,7 @@ public:
virtual int get_token_line(int p_offset = 0) const;
virtual int get_token_column(int p_offset = 0) const;
virtual int get_token_line_indent(int p_offset = 0) const;
+ virtual int get_token_line_tab_indent(int p_offset = 0) const { return 0; }
virtual const Variant &get_token_constant(int p_offset = 0) const;
virtual String get_token_error(int p_offset = 0) const;
virtual void advance(int p_amount = 1);
diff --git a/modules/gdscript/icons/icon_g_d_script.svg b/modules/gdscript/icons/icon_g_d_script.svg
new file mode 100644
index 0000000000..953bb9ae9e
--- /dev/null
+++ b/modules/gdscript/icons/icon_g_d_script.svg
@@ -0,0 +1,5 @@
+<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+<g transform="translate(0 -1036.4)">
+<path transform="translate(0 1036.4)" d="m7 1l-0.56445 2.2578a5 5 0 0 0 -0.68945 0.2793l-1.9883-1.1934-1.4141 1.4141 1.1953 1.9941a5 5 0 0 0 -0.28516 0.68555l-2.2539 0.5625v2l2.2578 0.56445a5 5 0 0 0 0.2793 0.6875l-1.1934 1.9902 1.4141 1.4141 1.9941-1.1953a5 5 0 0 0 0.68555 0.28516l0.5625 2.2539h2l0.56445-2.2578a5 5 0 0 0 0.6875 -0.2793l1.9902 1.1934 1.4141-1.4141-1.1953-1.9941a5 5 0 0 0 0.28516 -0.68555l2.2539-0.5625v-2l-2.2578-0.56445a5 5 0 0 0 -0.2793 -0.6875l1.1934-1.9902-1.4141-1.4141-1.9941 1.1953a5 5 0 0 0 -0.68555 -0.28516l-0.5625-2.2539h-2zm1 5a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2 -2 2 2 0 0 1 2 -2z" fill="#e0e0e0"/>
+</g>
+</svg>
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp
new file mode 100644
index 0000000000..d63f786fcb
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp
@@ -0,0 +1,740 @@
+/*************************************************************************/
+/* gdscript_extend_parser.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "gdscript_extend_parser.h"
+#include "../gdscript.h"
+#include "core/io/json.h"
+#include "gdscript_language_protocol.h"
+#include "gdscript_workspace.h"
+
+void ExtendGDScriptParser::update_diagnostics() {
+
+ diagnostics.clear();
+
+ if (has_error()) {
+ lsp::Diagnostic diagnostic;
+ diagnostic.severity = lsp::DiagnosticSeverity::Error;
+ diagnostic.message = get_error();
+ diagnostic.source = "gdscript";
+ diagnostic.code = -1;
+ lsp::Range range;
+ lsp::Position pos;
+ int line = LINE_NUMBER_TO_INDEX(get_error_line());
+ const String &line_text = get_lines()[line];
+ pos.line = line;
+ pos.character = line_text.length() - line_text.strip_edges(true, false).length();
+ range.start = pos;
+ range.end = range.start;
+ range.end.character = line_text.strip_edges(false).length();
+ diagnostic.range = range;
+ diagnostics.push_back(diagnostic);
+ }
+
+ const List<GDScriptWarning> &warnings = get_warnings();
+ for (const List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) {
+ const GDScriptWarning &warning = E->get();
+ lsp::Diagnostic diagnostic;
+ diagnostic.severity = lsp::DiagnosticSeverity::Warning;
+ diagnostic.message = warning.get_message();
+ diagnostic.source = "gdscript";
+ diagnostic.code = warning.code;
+ lsp::Range range;
+ lsp::Position pos;
+ int line = LINE_NUMBER_TO_INDEX(warning.line);
+ const String &line_text = get_lines()[line];
+ pos.line = line;
+ pos.character = line_text.length() - line_text.strip_edges(true, false).length();
+ range.start = pos;
+ range.end = pos;
+ range.end.character = line_text.strip_edges(false).length();
+ diagnostic.range = range;
+ diagnostics.push_back(diagnostic);
+ }
+}
+
+void ExtendGDScriptParser::update_symbols() {
+
+ members.clear();
+
+ const GDScriptParser::Node *head = get_parse_tree();
+ if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) {
+
+ parse_class_symbol(gdclass, class_symbol);
+
+ for (int i = 0; i < class_symbol.children.size(); i++) {
+ const lsp::DocumentSymbol &symbol = class_symbol.children[i];
+ members.set(symbol.name, &symbol);
+
+ // cache level one inner classes
+ if (symbol.kind == lsp::SymbolKind::Class) {
+ ClassMembers inner_class;
+ for (int j = 0; j < symbol.children.size(); j++) {
+ const lsp::DocumentSymbol &s = symbol.children[j];
+ inner_class.set(s.name, &s);
+ }
+ inner_classes.set(symbol.name, inner_class);
+ }
+ }
+ }
+}
+
+void ExtendGDScriptParser::update_document_links(const String &p_code) {
+ document_links.clear();
+
+ GDScriptTokenizerText tokenizer;
+ FileAccessRef fs = FileAccess::create(FileAccess::ACCESS_RESOURCES);
+ tokenizer.set_code(p_code);
+ while (true) {
+ if (tokenizer.get_token() == GDScriptTokenizer::TK_EOF) {
+ break;
+ } else if (tokenizer.get_token() == GDScriptTokenizer::TK_CONSTANT) {
+ const Variant &const_val = tokenizer.get_token_constant();
+ if (const_val.get_type() == Variant::STRING) {
+ String path = const_val;
+ bool exists = fs->file_exists(path);
+ if (!exists) {
+ path = get_path().get_base_dir() + "/" + path;
+ exists = fs->file_exists(path);
+ }
+ if (exists) {
+ String value = const_val;
+ lsp::DocumentLink link;
+ link.target = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path);
+ link.range.start.line = LINE_NUMBER_TO_INDEX(tokenizer.get_token_line());
+ link.range.end.line = link.range.start.line;
+ link.range.end.character = LINE_NUMBER_TO_INDEX(tokenizer.get_token_column());
+ link.range.start.character = link.range.end.character - value.length();
+ document_links.push_back(link);
+ }
+ }
+ }
+ tokenizer.advance();
+ }
+}
+
+void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol) {
+
+ const String uri = get_uri();
+
+ r_symbol.uri = uri;
+ r_symbol.script_path = path;
+ r_symbol.children.clear();
+ r_symbol.name = p_class->name;
+ if (r_symbol.name.empty())
+ r_symbol.name = path.get_file();
+ r_symbol.kind = lsp::SymbolKind::Class;
+ r_symbol.deprecated = false;
+ r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_class->line);
+ r_symbol.range.start.character = p_class->column;
+ r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_class->end_line);
+ r_symbol.selectionRange.start.line = r_symbol.range.start.line;
+ r_symbol.detail = "class " + r_symbol.name;
+ bool is_root_class = &r_symbol == &class_symbol;
+ r_symbol.documentation = parse_documentation(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->line), is_root_class);
+
+ for (int i = 0; i < p_class->variables.size(); ++i) {
+
+ const GDScriptParser::ClassNode::Member &m = p_class->variables[i];
+
+ lsp::DocumentSymbol symbol;
+ symbol.name = m.identifier;
+ symbol.kind = lsp::SymbolKind::Variable;
+ symbol.deprecated = false;
+ const int line = LINE_NUMBER_TO_INDEX(m.line);
+ symbol.range.start.line = line;
+ symbol.range.start.character = lines[line].length() - lines[line].strip_edges(true, false).length();
+ symbol.range.end.line = line;
+ symbol.range.end.character = lines[line].length();
+ symbol.selectionRange.start.line = symbol.range.start.line;
+ if (m._export.type != Variant::NIL) {
+ symbol.detail += "export ";
+ }
+ symbol.detail += "var " + m.identifier;
+ if (m.data_type.kind != GDScriptParser::DataType::UNRESOLVED) {
+ symbol.detail += ": " + m.data_type.to_string();
+ }
+ if (m.default_value.get_type() != Variant::NIL) {
+ symbol.detail += " = " + JSON::print(m.default_value);
+ }
+
+ symbol.documentation = parse_documentation(line);
+ symbol.uri = uri;
+ symbol.script_path = path;
+
+ r_symbol.children.push_back(symbol);
+ }
+
+ for (int i = 0; i < p_class->_signals.size(); ++i) {
+ const GDScriptParser::ClassNode::Signal &signal = p_class->_signals[i];
+
+ lsp::DocumentSymbol symbol;
+ symbol.name = signal.name;
+ symbol.kind = lsp::SymbolKind::Event;
+ symbol.deprecated = false;
+ const int line = LINE_NUMBER_TO_INDEX(signal.line);
+ symbol.range.start.line = line;
+ symbol.range.start.character = lines[line].length() - lines[line].strip_edges(true, false).length();
+ symbol.range.end.line = symbol.range.start.line;
+ symbol.range.end.character = lines[line].length();
+ symbol.selectionRange.start.line = symbol.range.start.line;
+ symbol.documentation = parse_documentation(line);
+ symbol.uri = uri;
+ symbol.script_path = path;
+ symbol.detail = "signal " + signal.name + "(";
+ for (int j = 0; j < signal.arguments.size(); j++) {
+ if (j > 0) {
+ symbol.detail += ", ";
+ }
+ symbol.detail += signal.arguments[j];
+ }
+ symbol.detail += ")";
+
+ r_symbol.children.push_back(symbol);
+ }
+
+ for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
+ lsp::DocumentSymbol symbol;
+ const GDScriptParser::ClassNode::Constant &c = E->value();
+ const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression);
+ ERR_FAIL_COND(!node);
+ symbol.name = E->key();
+ symbol.kind = lsp::SymbolKind::Constant;
+ symbol.deprecated = false;
+ const int line = LINE_NUMBER_TO_INDEX(E->get().expression->line);
+ symbol.range.start.line = line;
+ symbol.range.start.character = E->get().expression->column;
+ symbol.range.end.line = symbol.range.start.line;
+ symbol.range.end.character = lines[line].length();
+ symbol.selectionRange.start.line = symbol.range.start.line;
+ symbol.documentation = parse_documentation(line);
+ symbol.uri = uri;
+ symbol.script_path = path;
+
+ symbol.detail = "const " + symbol.name;
+ if (c.type.kind != GDScriptParser::DataType::UNRESOLVED) {
+ symbol.detail += ": " + c.type.to_string();
+ }
+
+ String value_text;
+ if (node->value.get_type() == Variant::OBJECT) {
+ RES res = node->value;
+ if (res.is_valid() && !res->get_path().empty()) {
+ value_text = "preload(\"" + res->get_path() + "\")";
+ if (symbol.documentation.empty()) {
+ if (Map<String, ExtendGDScriptParser *>::Element *S = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(res->get_path())) {
+ symbol.documentation = S->get()->class_symbol.documentation;
+ }
+ }
+ } else {
+ value_text = JSON::print(node->value);
+ }
+ } else {
+ value_text = JSON::print(node->value);
+ }
+ if (!value_text.empty()) {
+ symbol.detail += " = " + value_text;
+ }
+
+ r_symbol.children.push_back(symbol);
+ }
+
+ for (int i = 0; i < p_class->functions.size(); ++i) {
+ const GDScriptParser::FunctionNode *func = p_class->functions[i];
+ lsp::DocumentSymbol symbol;
+ parse_function_symbol(func, symbol);
+ r_symbol.children.push_back(symbol);
+ }
+
+ for (int i = 0; i < p_class->static_functions.size(); ++i) {
+ const GDScriptParser::FunctionNode *func = p_class->static_functions[i];
+ lsp::DocumentSymbol symbol;
+ parse_function_symbol(func, symbol);
+ r_symbol.children.push_back(symbol);
+ }
+
+ for (int i = 0; i < p_class->subclasses.size(); ++i) {
+ const GDScriptParser::ClassNode *subclass = p_class->subclasses[i];
+ lsp::DocumentSymbol symbol;
+ parse_class_symbol(subclass, symbol);
+ r_symbol.children.push_back(symbol);
+ }
+}
+
+void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol) {
+
+ const String uri = get_uri();
+
+ r_symbol.name = p_func->name;
+ r_symbol.kind = lsp::SymbolKind::Function;
+ r_symbol.detail = "func " + p_func->name + "(";
+ r_symbol.deprecated = false;
+ const int line = LINE_NUMBER_TO_INDEX(p_func->line);
+ r_symbol.range.start.line = line;
+ r_symbol.range.start.character = p_func->column;
+ r_symbol.range.end.line = MAX(p_func->body->end_line - 2, r_symbol.range.start.line);
+ r_symbol.range.end.character = lines[r_symbol.range.end.line].length();
+ r_symbol.selectionRange.start.line = r_symbol.range.start.line;
+ r_symbol.documentation = parse_documentation(line);
+ r_symbol.uri = uri;
+ r_symbol.script_path = path;
+
+ String arguments;
+ for (int i = 0; i < p_func->arguments.size(); i++) {
+ lsp::DocumentSymbol symbol;
+ symbol.kind = lsp::SymbolKind::Variable;
+ symbol.name = p_func->arguments[i];
+ symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->body->line);
+ symbol.range.start.character = p_func->body->column;
+ symbol.range.end = symbol.range.start;
+ symbol.uri = uri;
+ symbol.script_path = path;
+ r_symbol.children.push_back(symbol);
+ if (i > 0) {
+ arguments += ", ";
+ }
+ arguments += String(p_func->arguments[i]);
+ if (p_func->argument_types[i].kind != GDScriptParser::DataType::UNRESOLVED) {
+ arguments += ": " + p_func->argument_types[i].to_string();
+ }
+ int default_value_idx = i - (p_func->arguments.size() - p_func->default_values.size());
+ if (default_value_idx >= 0) {
+ const GDScriptParser::ConstantNode *const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(p_func->default_values[default_value_idx]);
+ if (const_node == NULL) {
+ const GDScriptParser::OperatorNode *operator_node = dynamic_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[default_value_idx]);
+ if (operator_node) {
+ const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(operator_node->next);
+ }
+ }
+
+ if (const_node) {
+ String value = JSON::print(const_node->value);
+ arguments += " = " + value;
+ }
+ }
+ }
+ r_symbol.detail += arguments + ")";
+ if (p_func->return_type.kind != GDScriptParser::DataType::UNRESOLVED) {
+ r_symbol.detail += " -> " + p_func->return_type.to_string();
+ }
+
+ for (const Map<StringName, LocalVarNode *>::Element *E = p_func->body->variables.front(); E; E = E->next()) {
+ lsp::DocumentSymbol symbol;
+ const GDScriptParser::LocalVarNode *var = E->value();
+ symbol.name = E->key();
+ symbol.kind = lsp::SymbolKind::Variable;
+ symbol.range.start.line = LINE_NUMBER_TO_INDEX(E->get()->line);
+ symbol.range.start.character = E->get()->column;
+ symbol.range.end.line = symbol.range.start.line;
+ symbol.range.end.character = lines[symbol.range.end.line].length();
+ symbol.uri = uri;
+ symbol.script_path = path;
+ symbol.detail = "var " + symbol.name;
+ if (var->datatype.kind != GDScriptParser::DataType::UNRESOLVED) {
+ symbol.detail += ": " + var->datatype.to_string();
+ }
+ symbol.documentation = parse_documentation(line);
+ r_symbol.children.push_back(symbol);
+ }
+}
+
+String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) {
+ ERR_FAIL_INDEX_V(p_line, lines.size(), String());
+
+ List<String> doc_lines;
+
+ if (!p_docs_down) { // inline comment
+ String inline_comment = lines[p_line];
+ int comment_start = inline_comment.find("#");
+ if (comment_start != -1) {
+ inline_comment = inline_comment.substr(comment_start, inline_comment.length()).strip_edges();
+ if (inline_comment.length() > 1) {
+ doc_lines.push_back(inline_comment.substr(1, inline_comment.length()));
+ }
+ }
+ }
+
+ int step = p_docs_down ? 1 : -1;
+ int start_line = p_docs_down ? p_line : p_line - 1;
+ for (int i = start_line; true; i += step) {
+
+ if (i < 0 || i >= lines.size()) break;
+
+ String line_comment = lines[i].strip_edges(true, false);
+ if (line_comment.begins_with("#")) {
+ line_comment = line_comment.substr(1, line_comment.length());
+ if (p_docs_down) {
+ doc_lines.push_back(line_comment);
+ } else {
+ doc_lines.push_front(line_comment);
+ }
+ } else {
+ break;
+ }
+ }
+
+ String doc;
+ for (List<String>::Element *E = doc_lines.front(); E; E = E->next()) {
+ doc += E->get() + "\n";
+ }
+ return doc;
+}
+
+String ExtendGDScriptParser::get_text_for_completion(const lsp::Position &p_cursor) const {
+
+ String longthing;
+ int len = lines.size();
+ for (int i = 0; i < len; i++) {
+
+ if (i == p_cursor.line) {
+ longthing += lines[i].substr(0, p_cursor.character);
+ longthing += String::chr(0xFFFF); //not unicode, represents the cursor
+ longthing += lines[i].substr(p_cursor.character, lines[i].size());
+ } else {
+
+ longthing += lines[i];
+ }
+
+ if (i != len - 1)
+ longthing += "\n";
+ }
+
+ return longthing;
+}
+
+String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol, bool p_func_requred) const {
+ String longthing;
+ int len = lines.size();
+ for (int i = 0; i < len; i++) {
+
+ if (i == p_cursor.line) {
+ String line = lines[i];
+ String first_part = line.substr(0, p_cursor.character);
+ String last_part = line.substr(p_cursor.character + 1, lines[i].length());
+ if (!p_symbol.empty()) {
+ String left_cursor_text;
+ for (int c = p_cursor.character - 1; c >= 0; c--) {
+ left_cursor_text = line.substr(c, p_cursor.character - c);
+ if (p_symbol.begins_with(left_cursor_text)) {
+ first_part = line.substr(0, c);
+ first_part += p_symbol;
+ break;
+ }
+ }
+ }
+
+ longthing += first_part;
+ longthing += String::chr(0xFFFF); //not unicode, represents the cursor
+ if (p_func_requred) {
+ longthing += "("; // tell the parser this is a function call
+ }
+ longthing += last_part;
+ } else {
+
+ longthing += lines[i];
+ }
+
+ if (i != len - 1)
+ longthing += "\n";
+ }
+
+ return longthing;
+}
+
+String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const {
+
+ ERR_FAIL_INDEX_V(p_position.line, lines.size(), "");
+ String line = lines[p_position.line];
+ ERR_FAIL_INDEX_V(p_position.character, line.size(), "");
+
+ int start_pos = p_position.character;
+ for (int c = p_position.character; c >= 0; c--) {
+ start_pos = c;
+ CharType ch = line[c];
+ bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_';
+ if (!valid_char) {
+ break;
+ }
+ }
+
+ int end_pos = p_position.character;
+ for (int c = p_position.character; c < line.length(); c++) {
+ CharType ch = line[c];
+ bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_';
+ if (!valid_char) {
+ break;
+ }
+ end_pos = c;
+ }
+ if (start_pos < end_pos) {
+ p_offset.x = start_pos - p_position.character;
+ p_offset.y = end_pos - p_position.character;
+ return line.substr(start_pos + 1, end_pos - start_pos);
+ }
+
+ return "";
+}
+
+String ExtendGDScriptParser::get_uri() const {
+ return GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path);
+}
+
+const lsp::DocumentSymbol *ExtendGDScriptParser::search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent) const {
+ const lsp::DocumentSymbol *ret = NULL;
+ if (p_line < p_parent.range.start.line) {
+ return ret;
+ } else if (p_parent.range.start.line == p_line) {
+ return &p_parent;
+ } else {
+ for (int i = 0; i < p_parent.children.size(); i++) {
+ ret = search_symbol_defined_at_line(p_line, p_parent.children[i]);
+ if (ret) {
+ break;
+ }
+ }
+ }
+ return ret;
+}
+
+const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int p_line) const {
+ if (p_line <= 0) {
+ return &class_symbol;
+ }
+ return search_symbol_defined_at_line(p_line, class_symbol);
+}
+
+const lsp::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String &p_name, const String &p_subclass) const {
+
+ if (p_subclass.empty()) {
+ const lsp::DocumentSymbol *const *ptr = members.getptr(p_name);
+ if (ptr) {
+ return *ptr;
+ }
+ } else {
+ if (const ClassMembers *_class = inner_classes.getptr(p_subclass)) {
+ const lsp::DocumentSymbol *const *ptr = _class->getptr(p_name);
+ if (ptr) {
+ return *ptr;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+const List<lsp::DocumentLink> &ExtendGDScriptParser::get_document_links() const {
+ return document_links;
+}
+
+const Array &ExtendGDScriptParser::get_member_completions() {
+
+ if (member_completions.empty()) {
+
+ const String *name = members.next(NULL);
+ while (name) {
+
+ const lsp::DocumentSymbol *symbol = members.get(*name);
+ lsp::CompletionItem item = symbol->make_completion_item();
+ item.data = JOIN_SYMBOLS(path, *name);
+ member_completions.push_back(item.to_json());
+
+ name = members.next(name);
+ }
+
+ const String *_class = inner_classes.next(NULL);
+ while (_class) {
+
+ const ClassMembers *inner_class = inner_classes.getptr(*_class);
+ const String *member_name = inner_class->next(NULL);
+ while (member_name) {
+ const lsp::DocumentSymbol *symbol = inner_class->get(*member_name);
+ lsp::CompletionItem item = symbol->make_completion_item();
+ item.data = JOIN_SYMBOLS(path, JOIN_SYMBOLS(*_class, *member_name));
+ member_completions.push_back(item.to_json());
+
+ member_name = inner_class->next(member_name);
+ }
+
+ _class = inner_classes.next(_class);
+ }
+ }
+
+ return member_completions;
+}
+
+Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::FunctionNode *p_func) const {
+ Dictionary func;
+ ERR_FAIL_NULL_V(p_func, func);
+ func["name"] = p_func->name;
+ func["return_type"] = p_func->return_type.to_string();
+ func["rpc_mode"] = p_func->rpc_mode;
+ Array arguments;
+ for (int i = 0; i < p_func->arguments.size(); i++) {
+ Dictionary arg;
+ arg["name"] = p_func->arguments[i];
+ arg["type"] = p_func->argument_types[i].to_string();
+ int default_value_idx = i - (p_func->arguments.size() - p_func->default_values.size());
+ if (default_value_idx >= 0) {
+ const GDScriptParser::ConstantNode *const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(p_func->default_values[default_value_idx]);
+ if (const_node == NULL) {
+ const GDScriptParser::OperatorNode *operator_node = dynamic_cast<const GDScriptParser::OperatorNode *>(p_func->default_values[default_value_idx]);
+ if (operator_node) {
+ const_node = dynamic_cast<const GDScriptParser::ConstantNode *>(operator_node->next);
+ }
+ }
+ if (const_node) {
+ arg["default_value"] = const_node->value;
+ }
+ }
+ arguments.push_back(arg);
+ }
+ if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_func->line))) {
+ func["signature"] = symbol->detail;
+ func["description"] = symbol->documentation;
+ }
+ func["arguments"] = arguments;
+ return func;
+}
+
+Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode *p_class) const {
+ Dictionary class_api;
+
+ ERR_FAIL_NULL_V(p_class, class_api);
+
+ class_api["name"] = String(p_class->name);
+ class_api["path"] = path;
+ Array extends_class;
+ for (int i = 0; i < p_class->extends_class.size(); i++) {
+ extends_class.append(String(p_class->extends_class[i]));
+ }
+ class_api["extends_class"] = extends_class;
+ class_api["extends_file"] = String(p_class->extends_file);
+ class_api["icon"] = String(p_class->icon_path);
+
+ if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(p_class->line))) {
+ class_api["signature"] = symbol->detail;
+ class_api["description"] = symbol->documentation;
+ }
+
+ Array subclasses;
+ for (int i = 0; i < p_class->subclasses.size(); i++) {
+ subclasses.push_back(dump_class_api(p_class->subclasses[i]));
+ }
+ class_api["sub_classes"] = subclasses;
+
+ Array constants;
+ for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_class->constant_expressions.front(); E; E = E->next()) {
+
+ const GDScriptParser::ClassNode::Constant &c = E->value();
+ const GDScriptParser::ConstantNode *node = dynamic_cast<const GDScriptParser::ConstantNode *>(c.expression);
+ ERR_FAIL_COND_V(!node, class_api);
+
+ Dictionary api;
+ api["name"] = E->key();
+ api["value"] = node->value;
+ api["data_type"] = node->datatype.to_string();
+ if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(node->line))) {
+ api["signature"] = symbol->detail;
+ api["description"] = symbol->documentation;
+ }
+ constants.push_back(api);
+ }
+ class_api["constants"] = constants;
+
+ Array members;
+ for (int i = 0; i < p_class->variables.size(); ++i) {
+ const GDScriptParser::ClassNode::Member &m = p_class->variables[i];
+ Dictionary api;
+ api["name"] = m.identifier;
+ api["data_type"] = m.data_type.to_string();
+ api["default_value"] = m.default_value;
+ api["setter"] = String(m.setter);
+ api["getter"] = String(m.getter);
+ api["export"] = m._export.type != Variant::NIL;
+ if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.line))) {
+ api["signature"] = symbol->detail;
+ api["description"] = symbol->documentation;
+ }
+ members.push_back(api);
+ }
+ class_api["members"] = members;
+
+ Array signals;
+ for (int i = 0; i < p_class->_signals.size(); ++i) {
+ const GDScriptParser::ClassNode::Signal &signal = p_class->_signals[i];
+ Dictionary api;
+ api["name"] = signal.name;
+ Array args;
+ for (int j = 0; j < signal.arguments.size(); j++) {
+ args.append(signal.arguments[j]);
+ }
+ api["arguments"] = args;
+ if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(signal.line))) {
+ api["signature"] = symbol->detail;
+ api["description"] = symbol->documentation;
+ }
+ signals.push_back(api);
+ }
+ class_api["signals"] = signals;
+
+ Array methods;
+ for (int i = 0; i < p_class->functions.size(); ++i) {
+ methods.append(dump_function_api(p_class->functions[i]));
+ }
+ class_api["methods"] = methods;
+
+ Array static_functions;
+ for (int i = 0; i < p_class->static_functions.size(); ++i) {
+ static_functions.append(dump_function_api(p_class->functions[i]));
+ }
+ class_api["static_functions"] = static_functions;
+
+ return class_api;
+}
+
+Dictionary ExtendGDScriptParser::generate_api() const {
+
+ Dictionary api;
+ const GDScriptParser::Node *head = get_parse_tree();
+ if (const GDScriptParser::ClassNode *gdclass = dynamic_cast<const GDScriptParser::ClassNode *>(head)) {
+ api = dump_class_api(gdclass);
+ }
+ return api;
+}
+
+Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) {
+ path = p_path;
+ lines = p_code.split("\n");
+
+ Error err = GDScriptParser::parse(p_code, p_path.get_base_dir(), false, p_path, false, NULL, false);
+ update_diagnostics();
+ update_symbols();
+ update_document_links(p_code);
+ return err;
+}
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h
new file mode 100644
index 0000000000..a6e0ca5534
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_extend_parser.h
@@ -0,0 +1,101 @@
+/*************************************************************************/
+/* gdscript_extend_parser.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_EXTEND_PARSER_H
+#define GDSCRIPT_EXTEND_PARSER_H
+
+#include "../gdscript_parser.h"
+#include "core/variant.h"
+#include "lsp.hpp"
+
+#ifndef LINE_NUMBER_TO_INDEX
+#define LINE_NUMBER_TO_INDEX(p_line) ((p_line)-1)
+#endif
+
+#ifndef SYMBOL_SEPERATOR
+#define SYMBOL_SEPERATOR "::"
+#endif
+
+#ifndef JOIN_SYMBOLS
+#define JOIN_SYMBOLS(p_path, name) ((p_path) + SYMBOL_SEPERATOR + (name))
+#endif
+
+typedef HashMap<String, const lsp::DocumentSymbol *> ClassMembers;
+
+class ExtendGDScriptParser : public GDScriptParser {
+
+ String path;
+ Vector<String> lines;
+
+ lsp::DocumentSymbol class_symbol;
+ Vector<lsp::Diagnostic> diagnostics;
+ List<lsp::DocumentLink> document_links;
+ ClassMembers members;
+ HashMap<String, ClassMembers> inner_classes;
+
+ void update_diagnostics();
+
+ void update_symbols();
+ void update_document_links(const String &p_code);
+ void parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol);
+ void parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol);
+
+ Dictionary dump_function_api(const GDScriptParser::FunctionNode *p_func) const;
+ Dictionary dump_class_api(const GDScriptParser::ClassNode *p_class) const;
+
+ String parse_documentation(int p_line, bool p_docs_down = false);
+ const lsp::DocumentSymbol *search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent) const;
+
+ Array member_completions;
+
+public:
+ _FORCE_INLINE_ const String &get_path() const { return path; }
+ _FORCE_INLINE_ const Vector<String> &get_lines() const { return lines; }
+ _FORCE_INLINE_ const lsp::DocumentSymbol &get_symbols() const { return class_symbol; }
+ _FORCE_INLINE_ const Vector<lsp::Diagnostic> &get_diagnostics() const { return diagnostics; }
+ _FORCE_INLINE_ const ClassMembers &get_members() const { return members; }
+ _FORCE_INLINE_ const HashMap<String, ClassMembers> &get_inner_classes() const { return inner_classes; }
+
+ String get_text_for_completion(const lsp::Position &p_cursor) const;
+ String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "", bool p_func_requred = false) const;
+ String get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const;
+ String get_uri() const;
+
+ const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line) const;
+ const lsp::DocumentSymbol *get_member_symbol(const String &p_name, const String &p_subclass = "") const;
+ const List<lsp::DocumentLink> &get_document_links() const;
+
+ const Array &get_member_completions();
+ Dictionary generate_api() const;
+
+ Error parse(const String &p_code, const String &p_path);
+};
+
+#endif
diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp
new file mode 100644
index 0000000000..90646f73ba
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp
@@ -0,0 +1,235 @@
+/*************************************************************************/
+/* gdscript_language_protocol.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "gdscript_language_protocol.h"
+#include "core/io/json.h"
+#include "core/os/copymem.h"
+#include "core/project_settings.h"
+#include "editor/editor_node.h"
+
+GDScriptLanguageProtocol *GDScriptLanguageProtocol::singleton = NULL;
+
+void GDScriptLanguageProtocol::on_data_received(int p_id) {
+ lastest_client_id = p_id;
+ Ref<WebSocketPeer> peer = server->get_peer(p_id);
+ PoolByteArray data;
+ if (OK == peer->get_packet_buffer(data)) {
+ String message;
+ message.parse_utf8((const char *)data.read().ptr(), data.size());
+ if (message.begins_with("Content-Length:")) return;
+ String output = process_message(message);
+ if (!output.empty()) {
+ CharString charstr = output.utf8();
+ peer->put_packet((const uint8_t *)charstr.ptr(), charstr.length());
+ }
+ }
+}
+
+void GDScriptLanguageProtocol::on_client_connected(int p_id, const String &p_protocal) {
+ clients.set(p_id, server->get_peer(p_id));
+}
+
+void GDScriptLanguageProtocol::on_client_disconnected(int p_id, bool p_was_clean_close) {
+ clients.erase(p_id);
+}
+
+String GDScriptLanguageProtocol::process_message(const String &p_text) {
+ String ret = process_string(p_text);
+ if (ret.empty()) {
+ return ret;
+ } else {
+ return format_output(ret);
+ }
+}
+
+String GDScriptLanguageProtocol::format_output(const String &p_text) {
+
+ String header = "Content-Length: ";
+ CharString charstr = p_text.utf8();
+ size_t len = charstr.length();
+ header += itos(len);
+ header += "\r\n\r\n";
+
+ return header + p_text;
+}
+
+void GDScriptLanguageProtocol::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("initialize", "params"), &GDScriptLanguageProtocol::initialize);
+ ClassDB::bind_method(D_METHOD("initialized", "params"), &GDScriptLanguageProtocol::initialized);
+ ClassDB::bind_method(D_METHOD("on_data_received"), &GDScriptLanguageProtocol::on_data_received);
+ ClassDB::bind_method(D_METHOD("on_client_connected"), &GDScriptLanguageProtocol::on_client_connected);
+ ClassDB::bind_method(D_METHOD("on_client_disconnected"), &GDScriptLanguageProtocol::on_client_disconnected);
+ ClassDB::bind_method(D_METHOD("notify_all_clients", "p_method", "p_params"), &GDScriptLanguageProtocol::notify_all_clients, DEFVAL(Variant()));
+ ClassDB::bind_method(D_METHOD("notify_client", "p_method", "p_params", "p_client"), &GDScriptLanguageProtocol::notify_client, DEFVAL(Variant()), DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("is_smart_resolve_enabled"), &GDScriptLanguageProtocol::is_smart_resolve_enabled);
+ ClassDB::bind_method(D_METHOD("get_text_document"), &GDScriptLanguageProtocol::get_text_document);
+ ClassDB::bind_method(D_METHOD("get_workspace"), &GDScriptLanguageProtocol::get_workspace);
+ ClassDB::bind_method(D_METHOD("is_initialized"), &GDScriptLanguageProtocol::is_initialized);
+}
+
+Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) {
+
+ lsp::InitializeResult ret;
+
+ String root_uri = p_params["rootUri"];
+ String root = p_params["rootPath"];
+ bool is_same_workspace;
+#ifndef WINDOWS_ENABLED
+ is_same_workspace = root.to_lower() == workspace->root.to_lower();
+#else
+ is_same_workspace = root.replace("\\", "/").to_lower() == workspace->root.to_lower();
+#endif
+
+ if (root_uri.length() && is_same_workspace) {
+ workspace->root_uri = root_uri;
+ } else {
+
+ workspace->root_uri = "file://" + workspace->root;
+
+ Dictionary params;
+ params["path"] = workspace->root;
+ Dictionary request = make_notification("gdscrip_client/changeWorkspace", params);
+ if (Ref<WebSocketPeer> *peer = clients.getptr(lastest_client_id)) {
+ String msg = JSON::print(request);
+ msg = format_output(msg);
+ CharString charstr = msg.utf8();
+ (*peer)->put_packet((const uint8_t *)charstr.ptr(), charstr.length());
+ }
+ }
+
+ if (!_initialized) {
+ workspace->initialize();
+ text_document->initialize();
+ _initialized = true;
+ }
+
+ return ret.to_json();
+}
+
+void GDScriptLanguageProtocol::initialized(const Variant &p_params) {
+
+ lsp::GodotCapabilities capabilities;
+
+ DocData *doc = EditorHelp::get_doc_data();
+ for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) {
+
+ lsp::GodotNativeClassInfo gdclass;
+ gdclass.name = E->get().name;
+ gdclass.class_doc = &(E->get());
+ if (ClassDB::ClassInfo *ptr = ClassDB::classes.getptr(StringName(E->get().name))) {
+ gdclass.class_info = ptr;
+ }
+ capabilities.native_classes.push_back(gdclass);
+ }
+
+ notify_client("gdscript/capabilities", capabilities.to_json());
+}
+
+void GDScriptLanguageProtocol::poll() {
+ server->poll();
+}
+
+Error GDScriptLanguageProtocol::start(int p_port) {
+ if (server == NULL) {
+ server = dynamic_cast<WebSocketServer *>(ClassDB::instance("WebSocketServer"));
+ ERR_FAIL_COND_V(!server, FAILED);
+ server->set_buffers(8192, 1024, 8192, 1024); // 8mb should be way more than enough
+ server->connect("data_received", this, "on_data_received");
+ server->connect("client_connected", this, "on_client_connected");
+ server->connect("client_disconnected", this, "on_client_disconnected");
+ }
+ return server->listen(p_port);
+}
+
+void GDScriptLanguageProtocol::stop() {
+ const int *ptr = clients.next(NULL);
+ while (ptr) {
+ clients.get(*ptr)->close();
+ ptr = clients.next(ptr);
+ }
+ server->stop();
+ clients.clear();
+}
+
+void GDScriptLanguageProtocol::notify_all_clients(const String &p_method, const Variant &p_params) {
+
+ Dictionary message = make_notification(p_method, p_params);
+ String msg = JSON::print(message);
+ msg = format_output(msg);
+ CharString charstr = msg.utf8();
+ const int *p_id = clients.next(NULL);
+ while (p_id != NULL) {
+ Ref<WebSocketPeer> peer = clients.get(*p_id);
+ (*peer)->put_packet((const uint8_t *)charstr.ptr(), charstr.length());
+ p_id = clients.next(p_id);
+ }
+}
+
+void GDScriptLanguageProtocol::notify_client(const String &p_method, const Variant &p_params, int p_client) {
+
+ if (p_client == -1) {
+ p_client = lastest_client_id;
+ }
+
+ Ref<WebSocketPeer> *peer = clients.getptr(p_client);
+ ERR_FAIL_COND(peer == NULL);
+
+ Dictionary message = make_notification(p_method, p_params);
+ String msg = JSON::print(message);
+ msg = format_output(msg);
+ CharString charstr = msg.utf8();
+
+ (*peer)->put_packet((const uint8_t *)charstr.ptr(), charstr.length());
+}
+
+bool GDScriptLanguageProtocol::is_smart_resolve_enabled() const {
+ return bool(_EDITOR_GET("network/language_server/enable_smart_resolve"));
+}
+
+bool GDScriptLanguageProtocol::is_goto_native_symbols_enabled() const {
+ return bool(_EDITOR_GET("network/language_server/show_native_symbols_in_editor"));
+}
+
+GDScriptLanguageProtocol::GDScriptLanguageProtocol() {
+ server = NULL;
+ singleton = this;
+ _initialized = false;
+ workspace.instance();
+ text_document.instance();
+ set_scope("textDocument", text_document.ptr());
+ set_scope("completionItem", text_document.ptr());
+ set_scope("workspace", workspace.ptr());
+ workspace->root = ProjectSettings::get_singleton()->get_resource_path();
+}
+
+GDScriptLanguageProtocol::~GDScriptLanguageProtocol() {
+ memdelete(server);
+ server = NULL;
+}
diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h
new file mode 100644
index 0000000000..136b45fd78
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_language_protocol.h
@@ -0,0 +1,93 @@
+/*************************************************************************/
+/* gdscript_language_protocol.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_PROTOCAL_SERVER_H
+#define GDSCRIPT_PROTOCAL_SERVER_H
+
+#include "gdscript_text_document.h"
+#include "gdscript_workspace.h"
+#include "lsp.hpp"
+#include "modules/jsonrpc/jsonrpc.h"
+#include "modules/websocket/websocket_peer.h"
+#include "modules/websocket/websocket_server.h"
+
+class GDScriptLanguageProtocol : public JSONRPC {
+ GDCLASS(GDScriptLanguageProtocol, JSONRPC)
+
+ enum LSPErrorCode {
+ RequestCancelled = -32800,
+ ContentModified = -32801,
+ };
+
+ static GDScriptLanguageProtocol *singleton;
+
+ HashMap<int, Ref<WebSocketPeer> > clients;
+ WebSocketServer *server;
+ int lastest_client_id;
+
+ Ref<GDScriptTextDocument> text_document;
+ Ref<GDScriptWorkspace> workspace;
+
+ void on_data_received(int p_id);
+ void on_client_connected(int p_id, const String &p_protocal);
+ void on_client_disconnected(int p_id, bool p_was_clean_close);
+
+ String process_message(const String &p_text);
+ String format_output(const String &p_text);
+
+ bool _initialized;
+
+protected:
+ static void _bind_methods();
+
+ Dictionary initialize(const Dictionary &p_params);
+ void initialized(const Variant &p_params);
+
+public:
+ _FORCE_INLINE_ static GDScriptLanguageProtocol *get_singleton() { return singleton; }
+ _FORCE_INLINE_ Ref<GDScriptWorkspace> get_workspace() { return workspace; }
+ _FORCE_INLINE_ Ref<GDScriptTextDocument> get_text_document() { return text_document; }
+ _FORCE_INLINE_ bool is_initialized() const { return _initialized; }
+
+ void poll();
+ Error start(int p_port);
+ void stop();
+
+ void notify_all_clients(const String &p_method, const Variant &p_params = Variant());
+ void notify_client(const String &p_method, const Variant &p_params = Variant(), int p_client = -1);
+
+ bool is_smart_resolve_enabled() const;
+ bool is_goto_native_symbols_enabled() const;
+
+ GDScriptLanguageProtocol();
+ ~GDScriptLanguageProtocol();
+};
+
+#endif
diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp
new file mode 100644
index 0000000000..8d58b99e02
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_language_server.cpp
@@ -0,0 +1,89 @@
+/*************************************************************************/
+/* gdscript_language_server.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "gdscript_language_server.h"
+#include "core/os/file_access.h"
+#include "core/os/os.h"
+#include "editor/editor_node.h"
+
+GDScriptLanguageServer::GDScriptLanguageServer() {
+ thread = NULL;
+ thread_exit = false;
+ _EDITOR_DEF("network/language_server/remote_port", 6008);
+ _EDITOR_DEF("network/language_server/enable_smart_resolve", false);
+ _EDITOR_DEF("network/language_server/show_native_symbols_in_editor", false);
+}
+
+void GDScriptLanguageServer::_notification(int p_what) {
+
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ start();
+ break;
+ case NOTIFICATION_EXIT_TREE:
+ stop();
+ break;
+ }
+}
+
+void GDScriptLanguageServer::thread_main(void *p_userdata) {
+ GDScriptLanguageServer *self = static_cast<GDScriptLanguageServer *>(p_userdata);
+ while (!self->thread_exit) {
+ // Poll 20 times per second
+ self->protocol.poll();
+ OS::get_singleton()->delay_usec(50000);
+ }
+}
+
+void GDScriptLanguageServer::start() {
+ int port = (int)_EDITOR_GET("network/language_server/remote_port");
+ if (protocol.start(port) == OK) {
+ EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR);
+ ERR_FAIL_COND(thread != NULL || thread_exit);
+ thread_exit = false;
+ thread = Thread::create(GDScriptLanguageServer::thread_main, this);
+ }
+}
+
+void GDScriptLanguageServer::stop() {
+ ERR_FAIL_COND(NULL == thread || thread_exit);
+ thread_exit = true;
+ Thread::wait_to_finish(thread);
+ memdelete(thread);
+ thread = NULL;
+ protocol.stop();
+ EditorNode::get_log()->add_message("--- GDScript language server stopped ---", EditorLog::MSG_TYPE_EDITOR);
+}
+
+void register_lsp_types() {
+ ClassDB::register_class<GDScriptLanguageProtocol>();
+ ClassDB::register_class<GDScriptTextDocument>();
+ ClassDB::register_class<GDScriptWorkspace>();
+}
diff --git a/modules/gdscript/language_server/gdscript_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h
new file mode 100644
index 0000000000..83c2320d98
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_language_server.h
@@ -0,0 +1,60 @@
+/*************************************************************************/
+/* gdscript_language_server.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_LANGUAGE_SERVER_H
+#define GDSCRIPT_LANGUAGE_SERVER_H
+
+#include "../gdscript_parser.h"
+#include "editor/editor_plugin.h"
+#include "gdscript_language_protocol.h"
+
+class GDScriptLanguageServer : public EditorPlugin {
+ GDCLASS(GDScriptLanguageServer, EditorPlugin);
+
+ GDScriptLanguageProtocol protocol;
+
+ Thread *thread;
+ bool thread_exit;
+ static void thread_main(void *p_userdata);
+
+private:
+ void _notification(int p_what);
+ void _iteration();
+
+public:
+ Error parse_script_file(const String &p_path);
+ GDScriptLanguageServer();
+ void start();
+ void stop();
+};
+
+void register_lsp_types();
+
+#endif // GDSCRIPT_LANGUAGE_SERVER_H
diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp
new file mode 100644
index 0000000000..1ca2a50c21
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_text_document.cpp
@@ -0,0 +1,423 @@
+/*************************************************************************/
+/* gdscript_text_document.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "gdscript_text_document.h"
+#include "../gdscript.h"
+#include "core/os/os.h"
+#include "editor/editor_settings.h"
+#include "editor/plugins/script_text_editor.h"
+#include "gdscript_extend_parser.h"
+#include "gdscript_language_protocol.h"
+
+void GDScriptTextDocument::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen);
+ ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange);
+ ClassDB::bind_method(D_METHOD("nativeSymbol"), &GDScriptTextDocument::nativeSymbol);
+ ClassDB::bind_method(D_METHOD("documentSymbol"), &GDScriptTextDocument::documentSymbol);
+ ClassDB::bind_method(D_METHOD("completion"), &GDScriptTextDocument::completion);
+ ClassDB::bind_method(D_METHOD("resolve"), &GDScriptTextDocument::resolve);
+ ClassDB::bind_method(D_METHOD("foldingRange"), &GDScriptTextDocument::foldingRange);
+ ClassDB::bind_method(D_METHOD("codeLens"), &GDScriptTextDocument::codeLens);
+ ClassDB::bind_method(D_METHOD("documentLink"), &GDScriptTextDocument::documentLink);
+ ClassDB::bind_method(D_METHOD("colorPresentation"), &GDScriptTextDocument::colorPresentation);
+ ClassDB::bind_method(D_METHOD("hover"), &GDScriptTextDocument::hover);
+ ClassDB::bind_method(D_METHOD("definition"), &GDScriptTextDocument::definition);
+ ClassDB::bind_method(D_METHOD("show_native_symbol_in_editor"), &GDScriptTextDocument::show_native_symbol_in_editor);
+}
+
+void GDScriptTextDocument::didOpen(const Variant &p_param) {
+ lsp::TextDocumentItem doc = load_document_item(p_param);
+ sync_script_content(doc.uri, doc.text);
+}
+
+void GDScriptTextDocument::didChange(const Variant &p_param) {
+ lsp::TextDocumentItem doc = load_document_item(p_param);
+ Dictionary dict = p_param;
+ Array contentChanges = dict["contentChanges"];
+ for (int i = 0; i < contentChanges.size(); ++i) {
+ lsp::TextDocumentContentChangeEvent evt;
+ evt.load(contentChanges[i]);
+ doc.text = evt.text;
+ }
+ sync_script_content(doc.uri, doc.text);
+}
+
+lsp::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_param) {
+ lsp::TextDocumentItem doc;
+ Dictionary params = p_param;
+ doc.load(params["textDocument"]);
+ return doc;
+}
+
+void GDScriptTextDocument::notify_client_show_symbol(const lsp::DocumentSymbol *symbol) {
+ ERR_FAIL_NULL(symbol);
+ GDScriptLanguageProtocol::get_singleton()->notify_client("gdscript/show_native_symbol", symbol->to_json(true));
+}
+
+void GDScriptTextDocument::initialize() {
+
+ if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
+
+ const HashMap<StringName, ClassMembers> &native_members = GDScriptLanguageProtocol::get_singleton()->get_workspace()->native_members;
+
+ const StringName *class_ptr = native_members.next(NULL);
+ while (class_ptr) {
+
+ const ClassMembers &members = native_members.get(*class_ptr);
+
+ const String *name = members.next(NULL);
+ while (name) {
+
+ const lsp::DocumentSymbol *symbol = members.get(*name);
+ lsp::CompletionItem item = symbol->make_completion_item();
+ item.data = JOIN_SYMBOLS(String(*class_ptr), *name);
+ native_member_completions.push_back(item.to_json());
+
+ name = members.next(name);
+ }
+
+ class_ptr = native_members.next(class_ptr);
+ }
+ }
+}
+
+Variant GDScriptTextDocument::nativeSymbol(const Dictionary &p_params) {
+
+ Variant ret;
+
+ lsp::NativeSymbolInspectParams params;
+ params.load(p_params);
+
+ if (const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_native_symbol(params)) {
+ ret = symbol->to_json(true);
+ notify_client_show_symbol(symbol);
+ }
+
+ return ret;
+}
+
+Array GDScriptTextDocument::documentSymbol(const Dictionary &p_params) {
+ Dictionary params = p_params["textDocument"];
+ String uri = params["uri"];
+ String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(uri);
+ Array arr;
+ if (const Map<String, ExtendGDScriptParser *>::Element *parser = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(path)) {
+ Vector<lsp::DocumentedSymbolInformation> list;
+ parser->get()->get_symbols().symbol_tree_as_list(uri, list);
+ for (int i = 0; i < list.size(); i++) {
+ arr.push_back(list[i].to_json());
+ }
+ }
+ return arr;
+}
+
+Array GDScriptTextDocument::completion(const Dictionary &p_params) {
+
+ Array arr;
+
+ lsp::CompletionParams params;
+ params.load(p_params);
+ Dictionary request_data = params.to_json();
+
+ List<ScriptCodeCompletionOption> options;
+ GDScriptLanguageProtocol::get_singleton()->get_workspace()->completion(params, &options);
+
+ if (!options.empty()) {
+
+ int i = 0;
+ arr.resize(options.size());
+
+ for (const List<ScriptCodeCompletionOption>::Element *E = options.front(); E; E = E->next()) {
+
+ const ScriptCodeCompletionOption &option = E->get();
+ lsp::CompletionItem item;
+ item.label = option.display;
+ item.data = request_data;
+
+ switch (option.kind) {
+ case ScriptCodeCompletionOption::KIND_ENUM:
+ item.kind = lsp::CompletionItemKind::Enum;
+ break;
+ case ScriptCodeCompletionOption::KIND_CLASS:
+ item.kind = lsp::CompletionItemKind::Class;
+ break;
+ case ScriptCodeCompletionOption::KIND_MEMBER:
+ item.kind = lsp::CompletionItemKind::Property;
+ break;
+ case ScriptCodeCompletionOption::KIND_FUNCTION:
+ item.kind = lsp::CompletionItemKind::Method;
+ break;
+ case ScriptCodeCompletionOption::KIND_SIGNAL:
+ item.kind = lsp::CompletionItemKind::Event;
+ break;
+ case ScriptCodeCompletionOption::KIND_CONSTANT:
+ item.kind = lsp::CompletionItemKind::Constant;
+ break;
+ case ScriptCodeCompletionOption::KIND_VARIABLE:
+ item.kind = lsp::CompletionItemKind::Variable;
+ break;
+ case ScriptCodeCompletionOption::KIND_FILE_PATH:
+ item.kind = lsp::CompletionItemKind::File;
+ break;
+ case ScriptCodeCompletionOption::KIND_NODE_PATH:
+ item.kind = lsp::CompletionItemKind::Snippet;
+ break;
+ case ScriptCodeCompletionOption::KIND_PLAIN_TEXT:
+ item.kind = lsp::CompletionItemKind::Text;
+ break;
+ }
+
+ arr[i] = item.to_json();
+ i++;
+ }
+ } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
+
+ arr = native_member_completions.duplicate();
+
+ for (Map<String, ExtendGDScriptParser *>::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.front(); E; E = E->next()) {
+
+ ExtendGDScriptParser *script = E->get();
+ const Array &items = script->get_member_completions();
+
+ const int start_size = arr.size();
+ arr.resize(start_size + items.size());
+ for (int i = start_size; i < arr.size(); i++) {
+ arr[i] = items[i - start_size];
+ }
+ }
+ }
+ return arr;
+}
+
+Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {
+
+ lsp::CompletionItem item;
+ item.load(p_params);
+
+ lsp::CompletionParams params;
+ Variant data = p_params["data"];
+
+ const lsp::DocumentSymbol *symbol = NULL;
+
+ if (data.get_type() == Variant::DICTIONARY) {
+
+ params.load(p_params["data"]);
+ symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params, item.label, item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function);
+
+ } else if (data.get_type() == Variant::STRING) {
+
+ String query = data;
+
+ Vector<String> param_symbols = query.split(SYMBOL_SEPERATOR, false);
+
+ if (param_symbols.size() >= 2) {
+
+ String class_ = param_symbols[0];
+ StringName class_name = class_;
+ String member_name = param_symbols[param_symbols.size() - 1];
+ String inner_class_name;
+ if (param_symbols.size() >= 3) {
+ inner_class_name = param_symbols[1];
+ }
+
+ if (const ClassMembers *members = GDScriptLanguageProtocol::get_singleton()->get_workspace()->native_members.getptr(class_name)) {
+ if (const lsp::DocumentSymbol *const *member = members->getptr(member_name)) {
+ symbol = *member;
+ }
+ }
+
+ if (!symbol) {
+ if (const Map<String, ExtendGDScriptParser *>::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(class_name)) {
+ symbol = E->get()->get_member_symbol(member_name, inner_class_name);
+ }
+ }
+ }
+ }
+
+ if (symbol) {
+ item.documentation = symbol->render();
+ }
+
+ if ((item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function) && !item.label.ends_with("):")) {
+ item.insertText = item.label + "(";
+ if (symbol && symbol->detail.find(",") == -1) {
+ item.insertText += ")";
+ }
+ } else if (item.kind == lsp::CompletionItemKind::Event) {
+ if (params.context.triggerKind == lsp::CompletionTriggerKind::TriggerCharacter && (params.context.triggerCharacter == "(")) {
+ const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\"";
+ item.insertText = quote_style + item.label + quote_style;
+ }
+ }
+
+ return item.to_json(true);
+}
+
+Array GDScriptTextDocument::foldingRange(const Dictionary &p_params) {
+ Array arr;
+ return arr;
+}
+
+Array GDScriptTextDocument::codeLens(const Dictionary &p_params) {
+ Array arr;
+ return arr;
+}
+
+Array GDScriptTextDocument::documentLink(const Dictionary &p_params) {
+ Array ret;
+
+ lsp::DocumentLinkParams params;
+ params.load(p_params);
+
+ List<lsp::DocumentLink> links;
+ GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_document_links(params.textDocument.uri, links);
+ for (const List<lsp::DocumentLink>::Element *E = links.front(); E; E = E->next()) {
+ ret.push_back(E->get().to_json());
+ }
+ return ret;
+}
+
+Array GDScriptTextDocument::colorPresentation(const Dictionary &p_params) {
+ Array arr;
+ return arr;
+}
+
+Variant GDScriptTextDocument::hover(const Dictionary &p_params) {
+
+ lsp::TextDocumentPositionParams params;
+ params.load(p_params);
+
+ const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params);
+ if (symbol) {
+
+ lsp::Hover hover;
+ hover.contents = symbol->render();
+ return hover.to_json();
+
+ } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
+
+ Dictionary ret;
+ Array contents;
+ List<const lsp::DocumentSymbol *> list;
+ GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(params, list);
+ for (List<const lsp::DocumentSymbol *>::Element *E = list.front(); E; E = E->next()) {
+ if (const lsp::DocumentSymbol *s = E->get()) {
+ contents.push_back(s->render().value);
+ }
+ }
+ ret["contents"] = contents;
+ return ret;
+ }
+
+ return Variant();
+}
+
+Array GDScriptTextDocument::definition(const Dictionary &p_params) {
+ Array arr;
+
+ lsp::TextDocumentPositionParams params;
+ params.load(p_params);
+
+ const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params);
+ if (symbol) {
+ lsp::Location location;
+ location.uri = symbol->uri;
+ location.range = symbol->range;
+
+ const String &path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(symbol->uri);
+ if (file_checker->file_exists(path)) {
+ arr.push_back(location.to_json());
+ } else if (!symbol->native_class.empty()) {
+ if (GDScriptLanguageProtocol::get_singleton()->is_goto_native_symbols_enabled()) {
+ String id;
+ switch (symbol->kind) {
+ case lsp::SymbolKind::Class:
+ id = "class_name:" + symbol->name;
+ break;
+ case lsp::SymbolKind::Constant:
+ id = "class_constant:" + symbol->native_class + ":" + symbol->name;
+ break;
+ case lsp::SymbolKind::Property:
+ case lsp::SymbolKind::Variable:
+ id = "class_property:" + symbol->native_class + ":" + symbol->name;
+ break;
+ case lsp::SymbolKind::Enum:
+ id = "class_enum:" + symbol->native_class + ":" + symbol->name;
+ break;
+ case lsp::SymbolKind::Method:
+ case lsp::SymbolKind::Function:
+ id = "class_method:" + symbol->native_class + ":" + symbol->name;
+ break;
+ default:
+ id = "class_global:" + symbol->native_class + ":" + symbol->name;
+ break;
+ }
+ call_deferred("show_native_symbol_in_editor", id);
+ } else {
+ notify_client_show_symbol(symbol);
+ }
+ }
+ } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
+
+ List<const lsp::DocumentSymbol *> list;
+ GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(params, list);
+ for (List<const lsp::DocumentSymbol *>::Element *E = list.front(); E; E = E->next()) {
+
+ if (const lsp::DocumentSymbol *s = E->get()) {
+ if (!s->uri.empty()) {
+ lsp::Location location;
+ location.uri = s->uri;
+ location.range = s->range;
+ arr.push_back(location.to_json());
+ }
+ }
+ }
+ }
+
+ return arr;
+}
+
+GDScriptTextDocument::GDScriptTextDocument() {
+ file_checker = FileAccess::create(FileAccess::ACCESS_RESOURCES);
+}
+
+GDScriptTextDocument::~GDScriptTextDocument() {
+ memdelete(file_checker);
+}
+
+void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) {
+ String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path);
+ GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content);
+}
+
+void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) {
+ ScriptEditor::get_singleton()->call_deferred("_help_class_goto", p_symbol_id);
+ OS::get_singleton()->move_window_to_foreground();
+}
diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h
new file mode 100644
index 0000000000..235e2c3f6e
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_text_document.h
@@ -0,0 +1,75 @@
+/*************************************************************************/
+/* gdscript_text_document.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_TEXT_DOCUMENT_H
+#define GDSCRIPT_TEXT_DOCUMENT_H
+
+#include "core/os/file_access.h"
+#include "core/reference.h"
+#include "lsp.hpp"
+
+class GDScriptTextDocument : public Reference {
+ GDCLASS(GDScriptTextDocument, Reference)
+protected:
+ static void _bind_methods();
+
+ FileAccess *file_checker;
+
+ void didOpen(const Variant &p_param);
+ void didChange(const Variant &p_param);
+
+ void sync_script_content(const String &p_path, const String &p_content);
+ void show_native_symbol_in_editor(const String &p_symbol_id);
+
+ Array native_member_completions;
+
+private:
+ lsp::TextDocumentItem load_document_item(const Variant &p_param);
+ void notify_client_show_symbol(const lsp::DocumentSymbol *symbol);
+
+public:
+ Variant nativeSymbol(const Dictionary &p_params);
+ Array documentSymbol(const Dictionary &p_params);
+ Array completion(const Dictionary &p_params);
+ Dictionary resolve(const Dictionary &p_params);
+ Array foldingRange(const Dictionary &p_params);
+ Array codeLens(const Dictionary &p_params);
+ Array documentLink(const Dictionary &p_params);
+ Array colorPresentation(const Dictionary &p_params);
+ Variant hover(const Dictionary &p_params);
+ Array definition(const Dictionary &p_params);
+
+ void initialize();
+
+ GDScriptTextDocument();
+ virtual ~GDScriptTextDocument();
+};
+
+#endif
diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp
new file mode 100644
index 0000000000..f9a974bad3
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_workspace.cpp
@@ -0,0 +1,534 @@
+/*************************************************************************/
+/* gdscript_workspace.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "gdscript_workspace.h"
+#include "../gdscript.h"
+#include "../gdscript_parser.h"
+#include "core/project_settings.h"
+#include "core/script_language.h"
+#include "editor/editor_help.h"
+#include "gdscript_language_protocol.h"
+
+void GDScriptWorkspace::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("symbol"), &GDScriptWorkspace::symbol);
+ ClassDB::bind_method(D_METHOD("parse_script", "p_path", "p_content"), &GDScriptWorkspace::parse_script);
+ ClassDB::bind_method(D_METHOD("parse_local_script", "p_path"), &GDScriptWorkspace::parse_local_script);
+ ClassDB::bind_method(D_METHOD("get_file_path", "p_uri"), &GDScriptWorkspace::get_file_path);
+ ClassDB::bind_method(D_METHOD("get_file_uri", "p_path"), &GDScriptWorkspace::get_file_uri);
+ ClassDB::bind_method(D_METHOD("publish_diagnostics", "p_path"), &GDScriptWorkspace::publish_diagnostics);
+ ClassDB::bind_method(D_METHOD("generate_script_api", "p_path"), &GDScriptWorkspace::generate_script_api);
+}
+
+void GDScriptWorkspace::remove_cache_parser(const String &p_path) {
+ Map<String, ExtendGDScriptParser *>::Element *parser = parse_results.find(p_path);
+ Map<String, ExtendGDScriptParser *>::Element *script = scripts.find(p_path);
+ if (parser && script) {
+ if (script->get() && script->get() == script->get()) {
+ memdelete(script->get());
+ } else {
+ memdelete(script->get());
+ memdelete(parser->get());
+ }
+ parse_results.erase(p_path);
+ scripts.erase(p_path);
+ } else if (parser) {
+ memdelete(parser->get());
+ parse_results.erase(p_path);
+ } else if (script) {
+ memdelete(script->get());
+ scripts.erase(p_path);
+ }
+}
+
+const lsp::DocumentSymbol *GDScriptWorkspace::get_native_symbol(const String &p_class, const String &p_member) const {
+
+ StringName class_name = p_class;
+ StringName empty;
+
+ while (class_name != empty) {
+ if (const Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.find(class_name)) {
+ const lsp::DocumentSymbol &class_symbol = E->value();
+
+ if (p_member.empty()) {
+ return &class_symbol;
+ } else {
+ for (int i = 0; i < class_symbol.children.size(); i++) {
+ const lsp::DocumentSymbol &symbol = class_symbol.children[i];
+ if (symbol.name == p_member) {
+ return &symbol;
+ }
+ }
+ }
+ }
+ class_name = ClassDB::get_parent_class(class_name);
+ }
+
+ return NULL;
+}
+
+const lsp::DocumentSymbol *GDScriptWorkspace::get_script_symbol(const String &p_path) const {
+ const Map<String, ExtendGDScriptParser *>::Element *S = scripts.find(p_path);
+ if (S) {
+ return &(S->get()->get_symbols());
+ }
+ return NULL;
+}
+
+void GDScriptWorkspace::reload_all_workspace_scripts() {
+ List<String> paths;
+ list_script_files("res://", paths);
+ for (List<String>::Element *E = paths.front(); E; E = E->next()) {
+ const String &path = E->get();
+ Error err;
+ String content = FileAccess::get_file_as_string(path, &err);
+ ERR_CONTINUE(err != OK);
+ err = parse_script(path, content);
+
+ if (err != OK) {
+ Map<String, ExtendGDScriptParser *>::Element *S = parse_results.find(path);
+ String err_msg = "Failed parse script " + path;
+ if (S) {
+ err_msg += "\n" + S->get()->get_error();
+ }
+ ERR_CONTINUE_MSG(err != OK, err_msg);
+ }
+ }
+}
+
+void GDScriptWorkspace::list_script_files(const String &p_root_dir, List<String> &r_files) {
+ Error err;
+ DirAccessRef dir = DirAccess::open(p_root_dir, &err);
+ if (OK == err) {
+ dir->list_dir_begin();
+ String file_name = dir->get_next();
+ while (file_name.length()) {
+ if (dir->current_is_dir() && file_name != "." && file_name != ".." && file_name != "./") {
+ list_script_files(p_root_dir.plus_file(file_name), r_files);
+ } else if (file_name.ends_with(".gd")) {
+ String script_file = p_root_dir.plus_file(file_name);
+ r_files.push_back(script_file);
+ }
+ file_name = dir->get_next();
+ }
+ }
+}
+
+ExtendGDScriptParser *GDScriptWorkspace::get_parse_successed_script(const String &p_path) {
+ const Map<String, ExtendGDScriptParser *>::Element *S = scripts.find(p_path);
+ if (!S) {
+ parse_local_script(p_path);
+ S = scripts.find(p_path);
+ }
+ if (S) {
+ return S->get();
+ }
+ return NULL;
+}
+
+ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &p_path) {
+ const Map<String, ExtendGDScriptParser *>::Element *S = parse_results.find(p_path);
+ if (!S) {
+ parse_local_script(p_path);
+ S = parse_results.find(p_path);
+ }
+ if (S) {
+ return S->get();
+ }
+ return NULL;
+}
+
+Array GDScriptWorkspace::symbol(const Dictionary &p_params) {
+ String query = p_params["query"];
+ Array arr;
+ if (!query.empty()) {
+ for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) {
+ Vector<lsp::DocumentedSymbolInformation> script_symbols;
+ E->get()->get_symbols().symbol_tree_as_list(E->key(), script_symbols);
+ for (int i = 0; i < script_symbols.size(); ++i) {
+ if (query.is_subsequence_ofi(script_symbols[i].name)) {
+ arr.push_back(script_symbols[i].to_json());
+ }
+ }
+ }
+ }
+ return arr;
+}
+
+Error GDScriptWorkspace::initialize() {
+ if (initialized) return OK;
+
+ DocData *doc = EditorHelp::get_doc_data();
+ for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) {
+
+ const DocData::ClassDoc &class_data = E->value();
+ lsp::DocumentSymbol class_symbol;
+ String class_name = E->key();
+ class_symbol.name = class_name;
+ class_symbol.native_class = class_name;
+ class_symbol.kind = lsp::SymbolKind::Class;
+ class_symbol.detail = String("<Native> class ") + class_name;
+ if (!class_data.inherits.empty()) {
+ class_symbol.detail += " extends " + class_data.inherits;
+ }
+ class_symbol.documentation = class_data.brief_description + "\n" + class_data.description;
+
+ for (int i = 0; i < class_data.constants.size(); i++) {
+ const DocData::ConstantDoc &const_data = class_data.constants[i];
+ lsp::DocumentSymbol symbol;
+ symbol.name = const_data.name;
+ symbol.native_class = class_name;
+ symbol.kind = lsp::SymbolKind::Constant;
+ symbol.detail = "const " + class_name + "." + const_data.name;
+ if (const_data.enumeration.length()) {
+ symbol.detail += ": " + const_data.enumeration;
+ }
+ symbol.detail += " = " + const_data.value;
+ symbol.documentation = const_data.description;
+ class_symbol.children.push_back(symbol);
+ }
+
+ Vector<DocData::PropertyDoc> properties;
+ properties.append_array(class_data.properties);
+ const int theme_prop_start_idx = properties.size();
+ properties.append_array(class_data.theme_properties);
+
+ for (int i = 0; i < class_data.properties.size(); i++) {
+ const DocData::PropertyDoc &data = class_data.properties[i];
+ lsp::DocumentSymbol symbol;
+ symbol.name = data.name;
+ symbol.native_class = class_name;
+ symbol.kind = lsp::SymbolKind::Property;
+ symbol.detail = String(i >= theme_prop_start_idx ? "<Theme> var" : "var") + " " + class_name + "." + data.name;
+ if (data.enumeration.length()) {
+ symbol.detail += ": " + data.enumeration;
+ } else {
+ symbol.detail += ": " + data.type;
+ }
+ symbol.documentation = data.description;
+ class_symbol.children.push_back(symbol);
+ }
+
+ Vector<DocData::MethodDoc> methods_signals;
+ methods_signals.append_array(class_data.methods);
+ const int signal_start_idx = methods_signals.size();
+ methods_signals.append_array(class_data.signals);
+
+ for (int i = 0; i < methods_signals.size(); i++) {
+ const DocData::MethodDoc &data = methods_signals[i];
+
+ lsp::DocumentSymbol symbol;
+ symbol.name = data.name;
+ symbol.native_class = class_name;
+ symbol.kind = i >= signal_start_idx ? lsp::SymbolKind::Event : lsp::SymbolKind::Method;
+
+ String params = "";
+ bool arg_default_value_started = false;
+ for (int j = 0; j < data.arguments.size(); j++) {
+ const DocData::ArgumentDoc &arg = data.arguments[j];
+ if (!arg_default_value_started && !arg.default_value.empty()) {
+ arg_default_value_started = true;
+ }
+ String arg_str = arg.name + ": " + arg.type;
+ if (arg_default_value_started) {
+ arg_str += " = " + arg.default_value;
+ }
+ if (j < data.arguments.size() - 1) {
+ arg_str += ", ";
+ }
+ params += arg_str;
+ }
+ if (data.qualifiers.find("vararg") != -1) {
+ params += params.empty() ? "..." : ", ...";
+ }
+
+ String return_type = data.return_type;
+ if (return_type.empty()) {
+ return_type = "void";
+ }
+ symbol.detail = "func " + class_name + "." + data.name + "(" + params + ") -> " + return_type;
+ symbol.documentation = data.description;
+ class_symbol.children.push_back(symbol);
+ }
+
+ native_symbols.insert(class_name, class_symbol);
+ }
+
+ reload_all_workspace_scripts();
+
+ if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
+ for (Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.front(); E; E = E->next()) {
+ ClassMembers members;
+ const lsp::DocumentSymbol &class_symbol = E->get();
+ for (int i = 0; i < class_symbol.children.size(); i++) {
+ const lsp::DocumentSymbol &symbol = class_symbol.children[i];
+ members.set(symbol.name, &symbol);
+ }
+ native_members.set(E->key(), members);
+ }
+
+ // cache member completions
+ for (Map<String, ExtendGDScriptParser *>::Element *S = scripts.front(); S; S = S->next()) {
+ S->get()->get_member_completions();
+ }
+ }
+
+ return OK;
+}
+
+Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_content) {
+
+ ExtendGDScriptParser *parser = memnew(ExtendGDScriptParser);
+ Error err = parser->parse(p_content, p_path);
+ Map<String, ExtendGDScriptParser *>::Element *last_parser = parse_results.find(p_path);
+ Map<String, ExtendGDScriptParser *>::Element *last_script = scripts.find(p_path);
+
+ if (err == OK) {
+
+ remove_cache_parser(p_path);
+ parse_results[p_path] = parser;
+ scripts[p_path] = parser;
+
+ } else {
+ if (last_parser && last_script && last_parser->get() != last_script->get()) {
+ memdelete(last_parser->get());
+ }
+ parse_results[p_path] = parser;
+ }
+
+ publish_diagnostics(p_path);
+
+ return err;
+}
+
+Error GDScriptWorkspace::parse_local_script(const String &p_path) {
+ Error err;
+ String content = FileAccess::get_file_as_string(p_path, &err);
+ if (err == OK) {
+ err = parse_script(p_path, content);
+ }
+ return err;
+}
+
+String GDScriptWorkspace::get_file_path(const String &p_uri) const {
+ String path = p_uri;
+ path = path.replace(root_uri + "/", "res://");
+ path = path.http_unescape();
+ return path;
+}
+
+String GDScriptWorkspace::get_file_uri(const String &p_path) const {
+ String uri = p_path;
+ uri = uri.replace("res://", root_uri + "/");
+ return uri;
+}
+
+void GDScriptWorkspace::publish_diagnostics(const String &p_path) {
+ Dictionary params;
+ Array errors;
+ const Map<String, ExtendGDScriptParser *>::Element *ele = parse_results.find(p_path);
+ if (ele) {
+ const Vector<lsp::Diagnostic> &list = ele->get()->get_diagnostics();
+ errors.resize(list.size());
+ for (int i = 0; i < list.size(); ++i) {
+ errors[i] = list[i].to_json();
+ }
+ }
+ params["diagnostics"] = errors;
+ params["uri"] = get_file_uri(p_path);
+ GDScriptLanguageProtocol::get_singleton()->notify_client("textDocument/publishDiagnostics", params);
+}
+
+void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<ScriptCodeCompletionOption> *r_options) {
+
+ String path = get_file_path(p_params.textDocument.uri);
+ String call_hint;
+ bool forced = false;
+
+ if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
+ String code = parser->get_text_for_completion(p_params.position);
+ GDScriptLanguage::get_singleton()->complete_code(code, path, NULL, r_options, forced, call_hint);
+ }
+}
+
+const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_requred) {
+
+ const lsp::DocumentSymbol *symbol = NULL;
+
+ String path = get_file_path(p_doc_pos.textDocument.uri);
+ if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
+
+ String symbol_identifier = p_symbol_name;
+ Vector<String> identifier_parts = symbol_identifier.split("(");
+ if (identifier_parts.size()) {
+ symbol_identifier = identifier_parts[0];
+ }
+
+ lsp::Position pos = p_doc_pos.position;
+ if (symbol_identifier.empty()) {
+ Vector2i offset;
+ symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset);
+ pos.character += offset.y;
+ }
+
+ if (!symbol_identifier.empty()) {
+
+ if (ScriptServer::is_global_class(symbol_identifier)) {
+
+ String class_path = ScriptServer::get_global_class_path(symbol_identifier);
+ symbol = get_script_symbol(class_path);
+
+ } else {
+
+ ScriptLanguage::LookupResult ret;
+ if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_requred), symbol_identifier, path, NULL, ret)) {
+
+ if (ret.type == ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION) {
+
+ String target_script_path = path;
+ if (!ret.script.is_null()) {
+ target_script_path = ret.script->get_path();
+ }
+
+ if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) {
+ symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location));
+ }
+
+ } else {
+
+ String member = ret.class_member;
+ if (member.empty() && symbol_identifier != ret.class_name) {
+ member = symbol_identifier;
+ }
+ symbol = get_native_symbol(ret.class_name, member);
+ }
+ } else {
+ symbol = parser->get_member_symbol(symbol_identifier);
+ }
+ }
+ }
+ }
+
+ return symbol;
+}
+
+void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list) {
+
+ String path = get_file_path(p_doc_pos.textDocument.uri);
+ if (const ExtendGDScriptParser *parser = get_parse_result(path)) {
+
+ String symbol_identifier;
+ Vector2i offset;
+ symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset);
+
+ const StringName *class_ptr = native_members.next(NULL);
+ while (class_ptr) {
+ const ClassMembers &members = native_members.get(*class_ptr);
+ if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) {
+ r_list.push_back(*symbol);
+ }
+ class_ptr = native_members.next(class_ptr);
+ }
+
+ for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) {
+ const ExtendGDScriptParser *script = E->get();
+ const ClassMembers &members = script->get_members();
+ if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) {
+ r_list.push_back(*symbol);
+ }
+
+ const HashMap<String, ClassMembers> &inner_classes = script->get_inner_classes();
+ const String *_class = inner_classes.next(NULL);
+ while (_class) {
+
+ const ClassMembers *inner_class = inner_classes.getptr(*_class);
+ if (const lsp::DocumentSymbol *const *symbol = inner_class->getptr(symbol_identifier)) {
+ r_list.push_back(*symbol);
+ }
+
+ _class = inner_classes.next(_class);
+ }
+ }
+ }
+}
+
+const lsp::DocumentSymbol *GDScriptWorkspace::resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params) {
+
+ if (Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.find(p_params.native_class)) {
+ const lsp::DocumentSymbol &symbol = E->get();
+ if (p_params.symbol_name.empty() || p_params.symbol_name == symbol.name) {
+ return &symbol;
+ }
+
+ for (int i = 0; i < symbol.children.size(); ++i) {
+ if (symbol.children[i].name == p_params.symbol_name) {
+ return &(symbol.children[i]);
+ }
+ }
+ }
+
+ return NULL;
+}
+
+void GDScriptWorkspace::resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list) {
+ if (const ExtendGDScriptParser *parser = get_parse_successed_script(get_file_path(p_uri))) {
+ const List<lsp::DocumentLink> &links = parser->get_document_links();
+ for (const List<lsp::DocumentLink>::Element *E = links.front(); E; E = E->next()) {
+ r_list.push_back(E->get());
+ }
+ }
+}
+
+Dictionary GDScriptWorkspace::generate_script_api(const String &p_path) {
+ Dictionary api;
+ if (const ExtendGDScriptParser *parser = get_parse_successed_script(p_path)) {
+ api = parser->generate_api();
+ }
+ return api;
+}
+
+GDScriptWorkspace::GDScriptWorkspace() {
+ ProjectSettings::get_singleton()->get_resource_path();
+}
+
+GDScriptWorkspace::~GDScriptWorkspace() {
+ Set<String> cached_parsers;
+
+ for (Map<String, ExtendGDScriptParser *>::Element *E = parse_results.front(); E; E = E->next()) {
+ cached_parsers.insert(E->key());
+ }
+
+ for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) {
+ cached_parsers.insert(E->key());
+ }
+
+ for (Set<String>::Element *E = cached_parsers.front(); E; E = E->next()) {
+ remove_cache_parser(E->get());
+ }
+}
diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h
new file mode 100644
index 0000000000..a416ae1075
--- /dev/null
+++ b/modules/gdscript/language_server/gdscript_workspace.h
@@ -0,0 +1,91 @@
+/*************************************************************************/
+/* gdscript_workspace.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GDSCRIPT_WORKSPACE_H
+#define GDSCRIPT_WORKSPACE_H
+
+#include "../gdscript_parser.h"
+#include "core/variant.h"
+#include "gdscript_extend_parser.h"
+#include "lsp.hpp"
+
+class GDScriptWorkspace : public Reference {
+ GDCLASS(GDScriptWorkspace, Reference);
+
+protected:
+ static void _bind_methods();
+ void remove_cache_parser(const String &p_path);
+ bool initialized = false;
+ Map<StringName, lsp::DocumentSymbol> native_symbols;
+
+ const lsp::DocumentSymbol *get_native_symbol(const String &p_class, const String &p_member = "") const;
+ const lsp::DocumentSymbol *get_script_symbol(const String &p_path) const;
+
+ void reload_all_workspace_scripts();
+
+ ExtendGDScriptParser *get_parse_successed_script(const String &p_path);
+ ExtendGDScriptParser *get_parse_result(const String &p_path);
+
+ void list_script_files(const String &p_root_dir, List<String> &r_files);
+
+public:
+ String root;
+ String root_uri;
+
+ Map<String, ExtendGDScriptParser *> scripts;
+ Map<String, ExtendGDScriptParser *> parse_results;
+ HashMap<StringName, ClassMembers> native_members;
+
+public:
+ Array symbol(const Dictionary &p_params);
+
+public:
+ Error initialize();
+
+ Error parse_script(const String &p_path, const String &p_content);
+ Error parse_local_script(const String &p_path);
+
+ String get_file_path(const String &p_uri) const;
+ String get_file_uri(const String &p_path) const;
+
+ void publish_diagnostics(const String &p_path);
+ void completion(const lsp::CompletionParams &p_params, List<ScriptCodeCompletionOption> *r_options);
+
+ const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_requred = false);
+ void resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list);
+ const lsp::DocumentSymbol *resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params);
+ void resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list);
+ Dictionary generate_script_api(const String &p_path);
+
+ GDScriptWorkspace();
+ ~GDScriptWorkspace();
+};
+
+#endif
diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp
new file mode 100644
index 0000000000..35471d63d6
--- /dev/null
+++ b/modules/gdscript/language_server/lsp.hpp
@@ -0,0 +1,1662 @@
+/*************************************************************************/
+/* lsp.hpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GODOT_LSP_H
+#define GODOT_LSP_H
+
+#include "core/class_db.h"
+#include "core/list.h"
+#include "editor/doc/doc_data.h"
+
+namespace lsp {
+
+typedef String DocumentUri;
+
+/** Format BBCode documentation from DocData to markdown */
+static String marked_documentation(const String &p_bbcode);
+
+/**
+ * Text documents are identified using a URI. On the protocol level, URIs are passed as strings.
+ */
+struct TextDocumentIdentifier {
+ /**
+ * The text document's URI.
+ */
+ DocumentUri uri;
+
+ _FORCE_INLINE_ void load(const Dictionary &p_params) {
+ uri = p_params["uri"];
+ }
+
+ _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary dict;
+ dict["uri"] = uri;
+ return dict;
+ }
+};
+
+/**
+ * Position in a text document expressed as zero-based line and zero-based character offset.
+ * A position is between two characters like an ‘insert’ cursor in a editor.
+ * Special values like for example -1 to denote the end of a line are not supported.
+ */
+struct Position {
+ /**
+ * Line position in a document (zero-based).
+ */
+ int line = 0;
+
+ /**
+ * Character offset on a line in a document (zero-based). Assuming that the line is
+ * represented as a string, the `character` value represents the gap between the
+ * `character` and `character + 1`.
+ *
+ * If the character value is greater than the line length it defaults back to the
+ * line length.
+ */
+ int character = 0;
+
+ _FORCE_INLINE_ void load(const Dictionary &p_params) {
+ line = p_params["line"];
+ character = p_params["character"];
+ }
+
+ _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary dict;
+ dict["line"] = line;
+ dict["character"] = character;
+ return dict;
+ }
+};
+
+/**
+ * A range in a text document expressed as (zero-based) start and end positions.
+ * A range is comparable to a selection in an editor. Therefore the end position is exclusive.
+ * If you want to specify a range that contains a line including the line ending character(s) then use an end position denoting the start of the next line.
+ */
+struct Range {
+ /**
+ * The range's start position.
+ */
+ Position start;
+
+ /**
+ * The range's end position.
+ */
+ Position end;
+
+ _FORCE_INLINE_ void load(const Dictionary &p_params) {
+ start.load(p_params["start"]);
+ end.load(p_params["end"]);
+ }
+
+ _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary dict;
+ dict["start"] = start.to_json();
+ dict["end"] = end.to_json();
+ return dict;
+ }
+};
+
+/**
+ * Represents a location inside a resource, such as a line inside a text file.
+ */
+struct Location {
+ DocumentUri uri;
+ Range range;
+
+ _FORCE_INLINE_ void load(const Dictionary &p_params) {
+ uri = p_params["uri"];
+ range.load(p_params["range"]);
+ }
+
+ _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary dict;
+ dict["uri"] = uri;
+ dict["range"] = range.to_json();
+ return dict;
+ }
+};
+
+/**
+ * Represents a link between a source and a target location.
+ */
+struct LocationLink {
+
+ /**
+ * Span of the origin of this link.
+ *
+ * Used as the underlined span for mouse interaction. Defaults to the word range at
+ * the mouse position.
+ */
+ Range *originSelectionRange = NULL;
+
+ /**
+ * The target resource identifier of this link.
+ */
+ String targetUri;
+
+ /**
+ * The full target range of this link. If the target for example is a symbol then target range is the
+ * range enclosing this symbol not including leading/trailing whitespace but everything else
+ * like comments. This information is typically used to highlight the range in the editor.
+ */
+ Range targetRange;
+
+ /**
+ * The range that should be selected and revealed when this link is being followed, e.g the name of a function.
+ * Must be contained by the the `targetRange`. See also `DocumentSymbol#range`
+ */
+ Range targetSelectionRange;
+};
+
+/**
+ * A parameter literal used in requests to pass a text document and a position inside that document.
+ */
+struct TextDocumentPositionParams {
+ /**
+ * The text document.
+ */
+ TextDocumentIdentifier textDocument;
+
+ /**
+ * The position inside the text document.
+ */
+ Position position;
+
+ _FORCE_INLINE_ void load(const Dictionary &p_params) {
+ textDocument.load(p_params["textDocument"]);
+ position.load(p_params["position"]);
+ }
+
+ _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary dict;
+ dict["textDocument"] = textDocument.to_json();
+ dict["position"] = position.to_json();
+ return dict;
+ }
+};
+
+struct DocumentLinkParams {
+ /**
+ * The document to provide document links for.
+ */
+ TextDocumentIdentifier textDocument;
+
+ _FORCE_INLINE_ void load(const Dictionary &p_params) {
+ textDocument.load(p_params["textDocument"]);
+ }
+};
+
+/**
+ * A document link is a range in a text document that links to an internal or external resource, like another
+ * text document or a web site.
+ */
+struct DocumentLink {
+
+ /**
+ * The range this link applies to.
+ */
+ Range range;
+
+ /**
+ * The uri this link points to. If missing a resolve request is sent later.
+ */
+ DocumentUri target;
+
+ Dictionary to_json() const {
+ Dictionary dict;
+ dict["range"] = range.to_json();
+ dict["target"] = target;
+ return dict;
+ }
+};
+
+/**
+ * A textual edit applicable to a text document.
+ */
+struct TextEdit {
+ /**
+ * The range of the text document to be manipulated. To insert
+ * text into a document create a range where start === end.
+ */
+ Range range;
+
+ /**
+ * The string to be inserted. For delete operations use an
+ * empty string.
+ */
+ String newText;
+};
+
+/**
+ * Represents a reference to a command.
+ * Provides a title which will be used to represent a command in the UI.
+ * Commands are identified by a string identifier.
+ * The recommended way to handle commands is to implement their execution on the server side if the client and server provides the corresponding capabilities.
+ * Alternatively the tool extension code could handle the command. The protocol currently doesn’t specify a set of well-known commands.
+ */
+struct Command {
+ /**
+ * Title of the command, like `save`.
+ */
+ String title;
+ /**
+ * The identifier of the actual command handler.
+ */
+ String command;
+ /**
+ * Arguments that the command handler should be
+ * invoked with.
+ */
+ Array arguments;
+
+ Dictionary to_json() const {
+ Dictionary dict;
+ dict["title"] = title;
+ dict["command"] = command;
+ if (arguments.size()) dict["arguments"] = arguments;
+ return dict;
+ }
+};
+
+// Use namespace instead of enumeration to follow the LSP specifications
+// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not
+
+namespace TextDocumentSyncKind {
+/**
+ * Documents should not be synced at all.
+ */
+static const int None = 0;
+
+/**
+ * Documents are synced by always sending the full content
+ * of the document.
+ */
+static const int Full = 1;
+
+/**
+ * Documents are synced by sending the full content on open.
+ * After that only incremental updates to the document are
+ * send.
+ */
+static const int Incremental = 2;
+}; // namespace TextDocumentSyncKind
+
+/**
+ * Completion options.
+ */
+struct CompletionOptions {
+ /**
+ * The server provides support to resolve additional
+ * information for a completion item.
+ */
+ bool resolveProvider = true;
+
+ /**
+ * The characters that trigger completion automatically.
+ */
+ Vector<String> triggerCharacters;
+
+ CompletionOptions() {
+ triggerCharacters.push_back(".");
+ triggerCharacters.push_back("$");
+ triggerCharacters.push_back("'");
+ triggerCharacters.push_back("\"");
+ triggerCharacters.push_back("(");
+ triggerCharacters.push_back(",");
+ }
+
+ Dictionary to_json() const {
+ Dictionary dict;
+ dict["resolveProvider"] = resolveProvider;
+ dict["triggerCharacters"] = triggerCharacters;
+ return dict;
+ }
+};
+
+/**
+ * Signature help options.
+ */
+struct SignatureHelpOptions {
+ /**
+ * The characters that trigger signature help
+ * automatically.
+ */
+ Vector<String> triggerCharacters;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["triggerCharacters"] = triggerCharacters;
+ return dict;
+ }
+};
+
+/**
+ * Code Lens options.
+ */
+struct CodeLensOptions {
+ /**
+ * Code lens has a resolve provider as well.
+ */
+ bool resolveProvider = false;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["resolveProvider"] = resolveProvider;
+ return dict;
+ }
+};
+
+/**
+ * Rename options
+ */
+struct RenameOptions {
+ /**
+ * Renames should be checked and tested before being executed.
+ */
+ bool prepareProvider = false;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["prepareProvider"] = prepareProvider;
+ return dict;
+ }
+};
+
+/**
+ * Document link options.
+ */
+struct DocumentLinkOptions {
+ /**
+ * Document links have a resolve provider as well.
+ */
+ bool resolveProvider = false;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["resolveProvider"] = resolveProvider;
+ return dict;
+ }
+};
+
+/**
+ * Execute command options.
+ */
+struct ExecuteCommandOptions {
+ /**
+ * The commands to be executed on the server
+ */
+ Vector<String> commands;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["commands"] = commands;
+ return dict;
+ }
+};
+
+/**
+ * Save options.
+ */
+struct SaveOptions {
+ /**
+ * The client is supposed to include the content on save.
+ */
+ bool includeText = true;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["includeText"] = includeText;
+ return dict;
+ }
+};
+
+/**
+ * Color provider options.
+ */
+struct ColorProviderOptions {
+ Dictionary to_json() {
+ Dictionary dict;
+ return dict;
+ }
+};
+
+/**
+ * Folding range provider options.
+ */
+struct FoldingRangeProviderOptions {
+ Dictionary to_json() {
+ Dictionary dict;
+ return dict;
+ }
+};
+
+struct TextDocumentSyncOptions {
+ /**
+ * Open and close notifications are sent to the server. If omitted open close notification should not
+ * be sent.
+ */
+ bool openClose = true;
+
+ /**
+ * Change notifications are sent to the server. See TextDocumentSyncKind.None, TextDocumentSyncKind.Full
+ * and TextDocumentSyncKind.Incremental. If omitted it defaults to TextDocumentSyncKind.None.
+ */
+ int change = TextDocumentSyncKind::Full;
+
+ /**
+ * If present will save notifications are sent to the server. If omitted the notification should not be
+ * sent.
+ */
+ bool willSave = false;
+
+ /**
+ * If present will save wait until requests are sent to the server. If omitted the request should not be
+ * sent.
+ */
+ bool willSaveWaitUntil = false;
+
+ /**
+ * If present save notifications are sent to the server. If omitted the notification should not be
+ * sent.
+ */
+ SaveOptions save;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["willSaveWaitUntil"] = willSaveWaitUntil;
+ dict["willSave"] = willSave;
+ dict["openClose"] = openClose;
+ dict["change"] = change;
+ dict["save"] = save.to_json();
+ return dict;
+ }
+};
+
+/**
+ * Static registration options to be returned in the initialize request.
+ */
+struct StaticRegistrationOptions {
+ /**
+ * The id used to register the request. The id can be used to deregister
+ * the request again. See also Registration#id.
+ */
+ String id;
+};
+
+/**
+ * Format document on type options.
+ */
+struct DocumentOnTypeFormattingOptions {
+ /**
+ * A character on which formatting should be triggered, like `}`.
+ */
+ String firstTriggerCharacter;
+
+ /**
+ * More trigger characters.
+ */
+ Vector<String> moreTriggerCharacter;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["firstTriggerCharacter"] = firstTriggerCharacter;
+ dict["moreTriggerCharacter"] = moreTriggerCharacter;
+ return dict;
+ }
+};
+
+struct TextDocumentItem {
+ /**
+ * The text document's URI.
+ */
+ DocumentUri uri;
+
+ /**
+ * The text document's language identifier.
+ */
+ String languageId;
+
+ /**
+ * The version number of this document (it will increase after each
+ * change, including undo/redo).
+ */
+ int version;
+
+ /**
+ * The content of the opened text document.
+ */
+ String text;
+
+ void load(const Dictionary &p_dict) {
+ uri = p_dict["uri"];
+ languageId = p_dict["languageId"];
+ version = p_dict["version"];
+ text = p_dict["text"];
+ }
+
+ Dictionary to_json() const {
+ Dictionary dict;
+ dict["uri"] = uri;
+ dict["languageId"] = languageId;
+ dict["version"] = version;
+ dict["text"] = text;
+ return dict;
+ }
+};
+
+/**
+ * An event describing a change to a text document. If range and rangeLength are omitted
+ * the new text is considered to be the full content of the document.
+ */
+struct TextDocumentContentChangeEvent {
+ /**
+ * The range of the document that changed.
+ */
+ Range range;
+
+ /**
+ * The length of the range that got replaced.
+ */
+ int rangeLength;
+
+ /**
+ * The new text of the range/document.
+ */
+ String text;
+
+ void load(const Dictionary &p_params) {
+ text = p_params["text"];
+ rangeLength = p_params["rangeLength"];
+ range.load(p_params["range"]);
+ }
+};
+
+// Use namespace instead of enumeration to follow the LSP specifications
+namespace DiagnosticSeverity {
+/**
+ * Reports an error.
+ */
+static const int Error = 1;
+/**
+ * Reports a warning.
+ */
+static const int Warning = 2;
+/**
+ * Reports an information.
+ */
+static const int Information = 3;
+/**
+ * Reports a hint.
+ */
+static const int Hint = 4;
+}; // namespace DiagnosticSeverity
+
+/**
+ * Represents a related message and source code location for a diagnostic. This should be
+ * used to point to code locations that cause or related to a diagnostics, e.g when duplicating
+ * a symbol in a scope.
+ */
+struct DiagnosticRelatedInformation {
+ /**
+ * The location of this related diagnostic information.
+ */
+ Location location;
+
+ /**
+ * The message of this related diagnostic information.
+ */
+ String message;
+
+ Dictionary to_json() const {
+ Dictionary dict;
+ dict["location"] = location.to_json(),
+ dict["message"] = message;
+ return dict;
+ }
+};
+
+/**
+ * Represents a diagnostic, such as a compiler error or warning.
+ * Diagnostic objects are only valid in the scope of a resource.
+ */
+struct Diagnostic {
+ /**
+ * The range at which the message applies.
+ */
+ Range range;
+
+ /**
+ * The diagnostic's severity. Can be omitted. If omitted it is up to the
+ * client to interpret diagnostics as error, warning, info or hint.
+ */
+ int severity;
+
+ /**
+ * The diagnostic's code, which might appear in the user interface.
+ */
+ int code;
+
+ /**
+ * A human-readable string describing the source of this
+ * diagnostic, e.g. 'typescript' or 'super lint'.
+ */
+ String source;
+
+ /**
+ * The diagnostic's message.
+ */
+ String message;
+
+ /**
+ * An array of related diagnostic information, e.g. when symbol-names within
+ * a scope collide all definitions can be marked via this property.
+ */
+ Vector<DiagnosticRelatedInformation> relatedInformation;
+
+ Dictionary to_json() const {
+ Dictionary dict;
+ dict["range"] = range.to_json();
+ dict["code"] = code;
+ dict["severity"] = severity;
+ dict["message"] = message;
+ dict["source"] = source;
+ if (!relatedInformation.empty()) {
+ Array arr;
+ arr.resize(relatedInformation.size());
+ for (int i = 0; i < relatedInformation.size(); i++) {
+ arr[i] = relatedInformation[i].to_json();
+ }
+ dict["relatedInformation"] = arr;
+ }
+ return dict;
+ }
+};
+
+// Use namespace instead of enumeration to follow the LSP specifications
+/**
+ * Describes the content type that a client supports in various
+ * result literals like `Hover`, `ParameterInfo` or `CompletionItem`.
+ *
+ * Please note that `MarkupKinds` must not start with a `$`. This kinds
+ * are reserved for internal usage.
+ */
+namespace MarkupKind {
+static const String PlainText = "plaintext";
+static const String Markdown = "markdown";
+}; // namespace MarkupKind
+
+/**
+ * A `MarkupContent` literal represents a string value which content is interpreted base on its
+ * kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds.
+ *
+ * If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues.
+ * See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting
+ *
+ * Here is an example how such a string can be constructed using JavaScript / TypeScript:
+ * ```typescript
+ * let markdown: MarkdownContent = {
+ * kind: MarkupKind.Markdown,
+ * value: [
+ * '# Header',
+ * 'Some text',
+ * '```typescript',
+ * 'someCode();',
+ * '```'
+ * ].join('\n')
+ * };
+ * ```
+ *
+ * *Please Note* that clients might sanitize the return markdown. A client could decide to
+ * remove HTML from the markdown to avoid script execution.
+ */
+struct MarkupContent {
+ /**
+ * The type of the Markup
+ */
+ String kind;
+
+ /**
+ * The content itself
+ */
+ String value;
+
+ MarkupContent() {
+ kind = MarkupKind::Markdown;
+ }
+
+ MarkupContent(const String &p_value) {
+ value = p_value;
+ kind = MarkupKind::Markdown;
+ }
+
+ Dictionary to_json() const {
+ Dictionary dict;
+ dict["kind"] = kind;
+ dict["value"] = value;
+ return dict;
+ }
+};
+
+// Use namespace instead of enumeration to follow the LSP specifications
+// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not
+// And here C++ compilers are unhappy with our enumeration name like Color, File, Reference etc.
+/**
+ * The kind of a completion entry.
+ */
+namespace CompletionItemKind {
+static const int Text = 1;
+static const int Method = 2;
+static const int Function = 3;
+static const int Constructor = 4;
+static const int Field = 5;
+static const int Variable = 6;
+static const int Class = 7;
+static const int Interface = 8;
+static const int Module = 9;
+static const int Property = 10;
+static const int Unit = 11;
+static const int Value = 12;
+static const int Enum = 13;
+static const int Keyword = 14;
+static const int Snippet = 15;
+static const int Color = 16;
+static const int File = 17;
+static const int Reference = 18;
+static const int Folder = 19;
+static const int EnumMember = 20;
+static const int Constant = 21;
+static const int Struct = 22;
+static const int Event = 23;
+static const int Operator = 24;
+static const int TypeParameter = 25;
+}; // namespace CompletionItemKind
+
+// Use namespace instead of enumeration to follow the LSP specifications
+/**
+ * Defines whether the insert text in a completion item should be interpreted as
+ * plain text or a snippet.
+ */
+namespace InsertTextFormat {
+/**
+ * The primary text to be inserted is treated as a plain string.
+ */
+static const int PlainText = 1;
+
+/**
+ * The primary text to be inserted is treated as a snippet.
+ *
+ * A snippet can define tab stops and placeholders with `$1`, `$2`
+ * and `${3:foo}`. `$0` defines the final tab stop, it defaults to
+ * the end of the snippet. Placeholders with equal identifiers are linked,
+ * that is typing in one will update others too.
+ */
+static const int Snippet = 2;
+}; // namespace InsertTextFormat
+
+struct CompletionItem {
+ /**
+ * The label of this completion item. By default
+ * also the text that is inserted when selecting
+ * this completion.
+ */
+ String label;
+
+ /**
+ * The kind of this completion item. Based of the kind
+ * an icon is chosen by the editor. The standardized set
+ * of available values is defined in `CompletionItemKind`.
+ */
+ int kind;
+
+ /**
+ * A human-readable string with additional information
+ * about this item, like type or symbol information.
+ */
+ String detail;
+
+ /**
+ * A human-readable string that represents a doc-comment.
+ */
+ MarkupContent documentation;
+
+ /**
+ * Indicates if this item is deprecated.
+ */
+ bool deprecated = false;
+
+ /**
+ * Select this item when showing.
+ *
+ * *Note* that only one completion item can be selected and that the
+ * tool / client decides which item that is. The rule is that the *first*
+ * item of those that match best is selected.
+ */
+ bool preselect = false;
+
+ /**
+ * A string that should be used when comparing this item
+ * with other items. When `falsy` the label is used.
+ */
+ String sortText;
+
+ /**
+ * A string that should be used when filtering a set of
+ * completion items. When `falsy` the label is used.
+ */
+ String filterText;
+
+ /**
+ * A string that should be inserted into a document when selecting
+ * this completion. When `falsy` the label is used.
+ *
+ * The `insertText` is subject to interpretation by the client side.
+ * Some tools might not take the string literally. For example
+ * VS Code when code complete is requested in this example `con<cursor position>`
+ * and a completion item with an `insertText` of `console` is provided it
+ * will only insert `sole`. Therefore it is recommended to use `textEdit` instead
+ * since it avoids additional client side interpretation.
+ *
+ * @deprecated Use textEdit instead.
+ */
+ String insertText;
+
+ /**
+ * The format of the insert text. The format applies to both the `insertText` property
+ * and the `newText` property of a provided `textEdit`.
+ */
+ int insertTextFormat;
+
+ /**
+ * An edit which is applied to a document when selecting this completion. When an edit is provided the value of
+ * `insertText` is ignored.
+ *
+ * *Note:* The range of the edit must be a single line range and it must contain the position at which completion
+ * has been requested.
+ */
+ TextEdit textEdit;
+
+ /**
+ * An optional array of additional text edits that are applied when
+ * selecting this completion. Edits must not overlap (including the same insert position)
+ * with the main edit nor with themselves.
+ *
+ * Additional text edits should be used to change text unrelated to the current cursor position
+ * (for example adding an import statement at the top of the file if the completion item will
+ * insert an unqualified type).
+ */
+ Vector<TextEdit> additionalTextEdits;
+
+ /**
+ * An optional set of characters that when pressed while this completion is active will accept it first and
+ * then type that character. *Note* that all commit characters should have `length=1` and that superfluous
+ * characters will be ignored.
+ */
+ Vector<String> commitCharacters;
+
+ /**
+ * An optional command that is executed *after* inserting this completion. *Note* that
+ * additional modifications to the current document should be described with the
+ * additionalTextEdits-property.
+ */
+ Command command;
+
+ /**
+ * A data entry field that is preserved on a completion item between
+ * a completion and a completion resolve request.
+ */
+ Variant data;
+
+ _FORCE_INLINE_ Dictionary to_json(bool resolved = false) const {
+ Dictionary dict;
+ dict["label"] = label;
+ dict["kind"] = kind;
+ dict["data"] = data;
+ if (resolved) {
+ dict["insertText"] = insertText;
+ dict["detail"] = detail;
+ dict["documentation"] = documentation.to_json();
+ dict["deprecated"] = deprecated;
+ dict["preselect"] = preselect;
+ dict["sortText"] = sortText;
+ dict["filterText"] = filterText;
+ if (commitCharacters.size()) dict["commitCharacters"] = commitCharacters;
+ dict["command"] = command.to_json();
+ }
+ return dict;
+ }
+
+ void load(const Dictionary &p_dict) {
+ if (p_dict.has("label")) label = p_dict["label"];
+ if (p_dict.has("kind")) kind = p_dict["kind"];
+ if (p_dict.has("detail")) detail = p_dict["detail"];
+ if (p_dict.has("documentation")) {
+ Variant doc = p_dict["documentation"];
+ if (doc.get_type() == Variant::STRING) {
+ documentation.value = doc;
+ } else if (doc.get_type() == Variant::DICTIONARY) {
+ Dictionary v = doc;
+ documentation.value = v["value"];
+ }
+ }
+ if (p_dict.has("deprecated")) deprecated = p_dict["deprecated"];
+ if (p_dict.has("preselect")) preselect = p_dict["preselect"];
+ if (p_dict.has("sortText")) sortText = p_dict["sortText"];
+ if (p_dict.has("filterText")) filterText = p_dict["filterText"];
+ if (p_dict.has("insertText")) insertText = p_dict["insertText"];
+ if (p_dict.has("data")) data = p_dict["data"];
+ }
+};
+
+/**
+ * Represents a collection of [completion items](#CompletionItem) to be presented
+ * in the editor.
+ */
+struct CompletionList {
+ /**
+ * This list it not complete. Further typing should result in recomputing
+ * this list.
+ */
+ bool isIncomplete;
+
+ /**
+ * The completion items.
+ */
+ Vector<CompletionItem> items;
+};
+
+// Use namespace instead of enumeration to follow the LSP specifications
+// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not
+// And here C++ compilers are unhappy with our enumeration name like String, Array, Object etc
+/**
+ * A symbol kind.
+ */
+namespace SymbolKind {
+static const int File = 0;
+static const int Module = 1;
+static const int Namespace = 2;
+static const int Package = 3;
+static const int Class = 4;
+static const int Method = 5;
+static const int Property = 6;
+static const int Field = 7;
+static const int Constructor = 8;
+static const int Enum = 9;
+static const int Interface = 10;
+static const int Function = 11;
+static const int Variable = 12;
+static const int Constant = 13;
+static const int String = 14;
+static const int Number = 15;
+static const int Boolean = 16;
+static const int Array = 17;
+static const int Object = 18;
+static const int Key = 19;
+static const int Null = 20;
+static const int EnumMember = 21;
+static const int Struct = 22;
+static const int Event = 23;
+static const int Operator = 24;
+static const int TypeParameter = 25;
+}; // namespace SymbolKind
+
+/**
+ * Represents information about programming constructs like variables, classes,
+ * interfaces etc.
+ */
+struct SymbolInformation {
+ /**
+ * The name of this symbol.
+ */
+ String name;
+
+ /**
+ * The kind of this symbol.
+ */
+ int kind = SymbolKind::File;
+
+ /**
+ * Indicates if this symbol is deprecated.
+ */
+ bool deprecated = false;
+
+ /**
+ * The location of this symbol. The location's range is used by a tool
+ * to reveal the location in the editor. If the symbol is selected in the
+ * tool the range's start information is used to position the cursor. So
+ * the range usually spans more then the actual symbol's name and does
+ * normally include things like visibility modifiers.
+ *
+ * The range doesn't have to denote a node range in the sense of a abstract
+ * syntax tree. It can therefore not be used to re-construct a hierarchy of
+ * the symbols.
+ */
+ Location location;
+
+ /**
+ * The name of the symbol containing this symbol. This information is for
+ * user interface purposes (e.g. to render a qualifier in the user interface
+ * if necessary). It can't be used to re-infer a hierarchy for the document
+ * symbols.
+ */
+ String containerName;
+
+ _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary dict;
+ dict["name"] = name;
+ dict["kind"] = kind;
+ dict["deprecated"] = deprecated;
+ dict["location"] = location.to_json();
+ dict["containerName"] = containerName;
+ return dict;
+ }
+};
+
+struct DocumentedSymbolInformation : public SymbolInformation {
+ /**
+ * A human-readable string with additional information
+ */
+ String detail;
+
+ /**
+ * A human-readable string that represents a doc-comment.
+ */
+ String documentation;
+};
+
+/**
+ * Represents programming constructs like variables, classes, interfaces etc. that appear in a document. Document symbols can be
+ * hierarchical and they have two ranges: one that encloses its definition and one that points to its most interesting range,
+ * e.g. the range of an identifier.
+ */
+struct DocumentSymbol {
+
+ /**
+ * The name of this symbol. Will be displayed in the user interface and therefore must not be
+ * an empty string or a string only consisting of white spaces.
+ */
+ String name;
+
+ /**
+ * More detail for this symbol, e.g the signature of a function.
+ */
+ String detail;
+
+ /**
+ * Documentation for this symbol
+ */
+ String documentation;
+
+ /**
+ * Class name for the native symbols
+ */
+ String native_class;
+
+ /**
+ * The kind of this symbol.
+ */
+ int kind = SymbolKind::File;
+
+ /**
+ * Indicates if this symbol is deprecated.
+ */
+ bool deprecated = false;
+
+ /**
+ * The range enclosing this symbol not including leading/trailing whitespace but everything else
+ * like comments. This information is typically used to determine if the clients cursor is
+ * inside the symbol to reveal in the symbol in the UI.
+ */
+ Range range;
+
+ /**
+ * The range that should be selected and revealed when this symbol is being picked, e.g the name of a function.
+ * Must be contained by the `range`.
+ */
+ Range selectionRange;
+
+ DocumentUri uri;
+ String script_path;
+
+ /**
+ * Children of this symbol, e.g. properties of a class.
+ */
+ Vector<DocumentSymbol> children;
+
+ Dictionary to_json(bool with_doc = false) const {
+ Dictionary dict;
+ dict["name"] = name;
+ dict["detail"] = detail;
+ dict["kind"] = kind;
+ dict["deprecated"] = deprecated;
+ dict["range"] = range.to_json();
+ dict["selectionRange"] = selectionRange.to_json();
+ if (with_doc) {
+ dict["documentation"] = documentation;
+ dict["native_class"] = native_class;
+ }
+ Array arr;
+ arr.resize(children.size());
+ for (int i = 0; i < children.size(); i++) {
+ arr[i] = children[i].to_json(with_doc);
+ }
+ dict["children"] = arr;
+ return dict;
+ }
+
+ void symbol_tree_as_list(const String &p_uri, Vector<DocumentedSymbolInformation> &r_list, const String &p_container = "", bool p_join_name = false) const {
+ DocumentedSymbolInformation si;
+ if (p_join_name && !p_container.empty()) {
+ si.name = p_container + ">" + name;
+ } else {
+ si.name = name;
+ }
+ si.kind = kind;
+ si.containerName = p_container;
+ si.deprecated = deprecated;
+ si.location.uri = p_uri;
+ si.location.range = range;
+ si.detail = detail;
+ si.documentation = documentation;
+ r_list.push_back(si);
+ for (int i = 0; i < children.size(); i++) {
+ children[i].symbol_tree_as_list(p_uri, r_list, si.name, p_join_name);
+ }
+ }
+
+ _FORCE_INLINE_ MarkupContent render() const {
+ MarkupContent markdown;
+ if (detail.length()) {
+ markdown.value = "\t" + detail + "\n\n";
+ }
+ if (documentation.length()) {
+ markdown.value += marked_documentation(documentation) + "\n\n";
+ }
+ if (script_path.length()) {
+ markdown.value += "Defined in [" + script_path + "](" + uri + ")";
+ }
+ return markdown;
+ }
+
+ _FORCE_INLINE_ CompletionItem make_completion_item(bool resolved = false) const {
+
+ lsp::CompletionItem item;
+ item.label = name;
+
+ if (resolved) {
+ item.documentation = render();
+ }
+
+ switch (kind) {
+ case lsp::SymbolKind::Enum:
+ item.kind = lsp::CompletionItemKind::Enum;
+ break;
+ case lsp::SymbolKind::Class:
+ item.kind = lsp::CompletionItemKind::Class;
+ break;
+ case lsp::SymbolKind::Property:
+ item.kind = lsp::CompletionItemKind::Property;
+ break;
+ case lsp::SymbolKind::Method:
+ case lsp::SymbolKind::Function:
+ item.kind = lsp::CompletionItemKind::Method;
+ break;
+ case lsp::SymbolKind::Event:
+ item.kind = lsp::CompletionItemKind::Event;
+ break;
+ case lsp::SymbolKind::Constant:
+ item.kind = lsp::CompletionItemKind::Constant;
+ break;
+ case lsp::SymbolKind::Variable:
+ item.kind = lsp::CompletionItemKind::Variable;
+ break;
+ case lsp::SymbolKind::File:
+ item.kind = lsp::CompletionItemKind::File;
+ break;
+ default:
+ item.kind = lsp::CompletionItemKind::Text;
+ break;
+ }
+
+ return item;
+ }
+};
+
+struct NativeSymbolInspectParams {
+
+ String native_class;
+ String symbol_name;
+
+ void load(const Dictionary &p_params) {
+ native_class = p_params["native_class"];
+ symbol_name = p_params["symbol_name"];
+ }
+};
+
+/**
+ * Enum of known range kinds
+ */
+namespace FoldingRangeKind {
+/**
+ * Folding range for a comment
+ */
+static const String Comment = "comment";
+/**
+ * Folding range for a imports or includes
+ */
+static const String Imports = "imports";
+/**
+ * Folding range for a region (e.g. `#region`)
+ */
+static const String Region = "region";
+} // namespace FoldingRangeKind
+
+/**
+ * Represents a folding range.
+ */
+struct FoldingRange {
+
+ /**
+ * The zero-based line number from where the folded range starts.
+ */
+ int startLine = 0;
+
+ /**
+ * The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line.
+ */
+ int startCharacter = 0;
+
+ /**
+ * The zero-based line number where the folded range ends.
+ */
+ int endLine = 0;
+
+ /**
+ * The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line.
+ */
+ int endCharacter = 0;
+
+ /**
+ * Describes the kind of the folding range such as `comment' or 'region'. The kind
+ * is used to categorize folding ranges and used by commands like 'Fold all comments'. See
+ * [FoldingRangeKind](#FoldingRangeKind) for an enumeration of standardized kinds.
+ */
+ String kind = FoldingRangeKind::Region;
+
+ _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary dict;
+ dict["startLine"] = startLine;
+ dict["startCharacter"] = startCharacter;
+ dict["endLine"] = endLine;
+ dict["endCharacter"] = endCharacter;
+ return dict;
+ }
+};
+
+// Use namespace instead of enumeration to follow the LSP specifications
+/**
+ * How a completion was triggered
+ */
+namespace CompletionTriggerKind {
+/**
+ * Completion was triggered by typing an identifier (24x7 code
+ * complete), manual invocation (e.g Ctrl+Space) or via API.
+ */
+static const int Invoked = 1;
+
+/**
+ * Completion was triggered by a trigger character specified by
+ * the `triggerCharacters` properties of the `CompletionRegistrationOptions`.
+ */
+static const int TriggerCharacter = 2;
+
+/**
+ * Completion was re-triggered as the current completion list is incomplete.
+ */
+static const int TriggerForIncompleteCompletions = 3;
+} // namespace CompletionTriggerKind
+
+/**
+ * Contains additional information about the context in which a completion request is triggered.
+ */
+struct CompletionContext {
+ /**
+ * How the completion was triggered.
+ */
+ int triggerKind = CompletionTriggerKind::TriggerCharacter;
+
+ /**
+ * The trigger character (a single character) that has trigger code complete.
+ * Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter`
+ */
+ String triggerCharacter;
+
+ void load(const Dictionary &p_params) {
+ triggerKind = int(p_params["triggerKind"]);
+ triggerCharacter = p_params["triggerCharacter"];
+ }
+};
+
+struct CompletionParams : public TextDocumentPositionParams {
+
+ /**
+ * The completion context. This is only available if the client specifies
+ * to send this using `ClientCapabilities.textDocument.completion.contextSupport === true`
+ */
+ CompletionContext context;
+
+ void load(const Dictionary &p_params) {
+ TextDocumentPositionParams::load(p_params);
+ context.load(p_params["context"]);
+ }
+};
+
+/**
+ * The result of a hover request.
+ */
+struct Hover {
+ /**
+ * The hover's content
+ */
+ MarkupContent contents;
+
+ /**
+ * An optional range is a range inside a text document
+ * that is used to visualize a hover, e.g. by changing the background color.
+ */
+ Range range;
+
+ _FORCE_INLINE_ Dictionary to_json() const {
+ Dictionary dict;
+ dict["range"] = range.to_json();
+ dict["contents"] = contents.to_json();
+ return dict;
+ }
+};
+
+struct ServerCapabilities {
+ /**
+ * Defines how text documents are synced. Is either a detailed structure defining each notification or
+ * for backwards compatibility the TextDocumentSyncKind number. If omitted it defaults to `TextDocumentSyncKind.None`.
+ */
+ TextDocumentSyncOptions textDocumentSync;
+
+ /**
+ * The server provides hover support.
+ */
+ bool hoverProvider = true;
+
+ /**
+ * The server provides completion support.
+ */
+ CompletionOptions completionProvider;
+
+ /**
+ * The server provides signature help support.
+ */
+ SignatureHelpOptions signatureHelpProvider;
+
+ /**
+ * The server provides goto definition support.
+ */
+ bool definitionProvider = true;
+
+ /**
+ * The server provides Goto Type Definition support.
+ *
+ * Since 3.6.0
+ */
+ bool typeDefinitionProvider = false;
+
+ /**
+ * The server provides Goto Implementation support.
+ *
+ * Since 3.6.0
+ */
+ bool implementationProvider = false;
+
+ /**
+ * The server provides find references support.
+ */
+ bool referencesProvider = false;
+
+ /**
+ * The server provides document highlight support.
+ */
+ bool documentHighlightProvider = false;
+
+ /**
+ * The server provides document symbol support.
+ */
+ bool documentSymbolProvider = true;
+
+ /**
+ * The server provides workspace symbol support.
+ */
+ bool workspaceSymbolProvider = true;
+
+ /**
+ * The server provides code actions. The `CodeActionOptions` return type is only
+ * valid if the client signals code action literal support via the property
+ * `textDocument.codeAction.codeActionLiteralSupport`.
+ */
+ bool codeActionProvider = false;
+
+ /**
+ * The server provides code lens.
+ */
+ CodeLensOptions codeLensProvider;
+
+ /**
+ * The server provides document formatting.
+ */
+ bool documentFormattingProvider = false;
+
+ /**
+ * The server provides document range formatting.
+ */
+ bool documentRangeFormattingProvider = false;
+
+ /**
+ * The server provides document formatting on typing.
+ */
+ DocumentOnTypeFormattingOptions documentOnTypeFormattingProvider;
+
+ /**
+ * The server provides rename support. RenameOptions may only be
+ * specified if the client states that it supports
+ * `prepareSupport` in its initial `initialize` request.
+ */
+ RenameOptions renameProvider;
+
+ /**
+ * The server provides document link support.
+ */
+ DocumentLinkOptions documentLinkProvider;
+
+ /**
+ * The server provides color provider support.
+ *
+ * Since 3.6.0
+ */
+ ColorProviderOptions colorProvider;
+
+ /**
+ * The server provides folding provider support.
+ *
+ * Since 3.10.0
+ */
+ FoldingRangeProviderOptions foldingRangeProvider;
+
+ /**
+ * The server provides go to declaration support.
+ *
+ * Since 3.14.0
+ */
+ bool declarationProvider = true;
+
+ /**
+ * The server provides execute command support.
+ */
+ ExecuteCommandOptions executeCommandProvider;
+
+ _FORCE_INLINE_ Dictionary to_json() {
+ Dictionary dict;
+ dict["textDocumentSync"] = (int)textDocumentSync.change;
+ dict["completionProvider"] = completionProvider.to_json();
+ dict["signatureHelpProvider"] = signatureHelpProvider.to_json();
+ dict["codeLensProvider"] = false; // codeLensProvider.to_json();
+ dict["documentOnTypeFormattingProvider"] = documentOnTypeFormattingProvider.to_json();
+ dict["renameProvider"] = renameProvider.to_json();
+ dict["documentLinkProvider"] = documentLinkProvider.to_json();
+ dict["colorProvider"] = false; // colorProvider.to_json();
+ dict["foldingRangeProvider"] = false; //foldingRangeProvider.to_json();
+ dict["executeCommandProvider"] = executeCommandProvider.to_json();
+ dict["hoverProvider"] = hoverProvider;
+ dict["definitionProvider"] = definitionProvider;
+ dict["typeDefinitionProvider"] = typeDefinitionProvider;
+ dict["implementationProvider"] = implementationProvider;
+ dict["referencesProvider"] = referencesProvider;
+ dict["documentHighlightProvider"] = documentHighlightProvider;
+ dict["documentSymbolProvider"] = documentSymbolProvider;
+ dict["workspaceSymbolProvider"] = workspaceSymbolProvider;
+ dict["codeActionProvider"] = codeActionProvider;
+ dict["documentFormattingProvider"] = documentFormattingProvider;
+ dict["documentRangeFormattingProvider"] = documentRangeFormattingProvider;
+ dict["declarationProvider"] = declarationProvider;
+ return dict;
+ }
+};
+
+struct InitializeResult {
+ /**
+ * The capabilities the language server provides.
+ */
+ ServerCapabilities capabilities;
+
+ _FORCE_INLINE_ Dictionary to_json() {
+ Dictionary dict;
+ dict["capabilities"] = capabilities.to_json();
+ return dict;
+ }
+};
+
+struct GodotNativeClassInfo {
+
+ String name;
+ const DocData::ClassDoc *class_doc = NULL;
+ const ClassDB::ClassInfo *class_info = NULL;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ dict["name"] = name;
+ dict["inherits"] = class_doc->inherits;
+ return dict;
+ }
+};
+
+/** Features not included in the standard lsp specifications */
+struct GodotCapabilities {
+
+ /**
+ * Native class list
+ */
+ List<GodotNativeClassInfo> native_classes;
+
+ Dictionary to_json() {
+ Dictionary dict;
+ Array classes;
+ for (List<GodotNativeClassInfo>::Element *E = native_classes.front(); E; E = E->next()) {
+ classes.push_back(E->get().to_json());
+ }
+ dict["native_classes"] = classes;
+ return dict;
+ }
+};
+
+/** Format BBCode documentation from DocData to markdown */
+static String marked_documentation(const String &p_bbcode) {
+
+ String markdown = p_bbcode.strip_edges();
+
+ Vector<String> lines = markdown.split("\n");
+ bool in_code_block = false;
+ int code_block_indent = -1;
+
+ markdown = "";
+ for (int i = 0; i < lines.size(); i++) {
+ String line = lines[i];
+ int block_start = line.find("[codeblock]");
+ if (block_start != -1) {
+ code_block_indent = block_start;
+ in_code_block = true;
+ line = "\n";
+ } else if (in_code_block) {
+ line = "\t" + line.substr(code_block_indent, line.length());
+ }
+
+ if (in_code_block && line.find("[/codeblock]") != -1) {
+ line = "\n";
+ in_code_block = false;
+ }
+
+ if (!in_code_block) {
+ line = line.strip_edges();
+ line = line.replace("[code]", "`");
+ line = line.replace("[/code]", "`");
+ line = line.replace("[i]", "*");
+ line = line.replace("[/i]", "*");
+ line = line.replace("[b]", "**");
+ line = line.replace("[/b]", "**");
+ line = line.replace("[u]", "__");
+ line = line.replace("[/u]", "__");
+ line = line.replace("[method ", "`");
+ line = line.replace("[member ", "`");
+ line = line.replace("[signal ", "`");
+ line = line.replace("[enum ", "`");
+ line = line.replace("[constant ", "`");
+ line = line.replace("[", "`");
+ line = line.replace("]", "`");
+ }
+
+ if (!in_code_block && i < lines.size() - 1) {
+ line += "\n\n";
+ } else if (i < lines.size() - 1) {
+ line += "\n";
+ }
+ markdown += line;
+ }
+ return markdown;
+}
+
+} // namespace lsp
+
+#endif
diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp
index b8a13ed91b..94b9e8c2d9 100644
--- a/modules/gdscript/register_types.cpp
+++ b/modules/gdscript/register_types.cpp
@@ -32,8 +32,8 @@
#include "core/io/file_access_encrypted.h"
#include "core/io/resource_loader.h"
+#include "core/os/dir_access.h"
#include "core/os/file_access.h"
-#include "editor/gdscript_highlighter.h"
#include "gdscript.h"
#include "gdscript_tokenizer.h"
@@ -46,6 +46,12 @@ Ref<ResourceFormatSaverGDScript> resource_saver_gd;
#include "editor/editor_export.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
+#include "editor/gdscript_highlighter.h"
+
+#ifndef GDSCRIPT_NO_LSP
+#include "core/engine.h"
+#include "language_server/gdscript_language_server.h"
+#endif // !GDSCRIPT_NO_LSP
class EditorExportGDScript : public EditorExportPlugin {
@@ -117,6 +123,9 @@ public:
file = FileAccess::get_file_as_array(tmp_path);
add_file(p_path.get_basename() + ".gde", file, true);
+ // Clean up temporary file.
+ DirAccess::remove_file_or_error(tmp_path);
+
} else {
add_file(p_path.get_basename() + ".gdc", file, true);
@@ -130,9 +139,16 @@ static void _editor_init() {
Ref<EditorExportGDScript> gd_export;
gd_export.instance();
EditorExport::get_singleton()->add_export_plugin(gd_export);
+
+#ifndef GDSCRIPT_NO_LSP
+ register_lsp_types();
+ GDScriptLanguageServer *lsp_plugin = memnew(GDScriptLanguageServer);
+ EditorNode::get_singleton()->add_editor_plugin(lsp_plugin);
+ Engine::get_singleton()->add_singleton(Engine::Singleton("GDScriptLanguageProtocol", GDScriptLanguageProtocol::get_singleton()));
+#endif // !GDSCRIPT_NO_LSP
}
-#endif
+#endif // TOOLS_ENABLED
void register_gdscript_types() {
@@ -151,7 +167,7 @@ void register_gdscript_types() {
#ifdef TOOLS_ENABLED
ScriptEditor::register_create_syntax_highlighter_function(GDScriptSyntaxHighlighter::create);
EditorNode::add_init_callback(_editor_init);
-#endif
+#endif // TOOLS_ENABLED
}
void unregister_gdscript_types() {