diff options
Diffstat (limited to 'modules/gdscript')
-rw-r--r-- | modules/gdscript/doc_classes/@GDScript.xml | 257 | ||||
-rw-r--r-- | modules/gdscript/editor/gdscript_highlighter.cpp | 69 | ||||
-rw-r--r-- | modules/gdscript/gdscript.cpp | 47 | ||||
-rw-r--r-- | modules/gdscript/gdscript.h | 5 | ||||
-rw-r--r-- | modules/gdscript/gdscript_editor.cpp | 80 | ||||
-rw-r--r-- | modules/gdscript/gdscript_parser.cpp | 2 | ||||
-rw-r--r-- | modules/gdscript/language_server/gdscript_language_protocol.cpp | 4 | ||||
-rw-r--r-- | modules/gdscript/language_server/gdscript_workspace.cpp | 4 | ||||
-rw-r--r-- | modules/gdscript/tests/gdscript_test_runner_suite.h | 34 |
9 files changed, 402 insertions, 100 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 10cf783e73..a44be04b2d 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -8,14 +8,15 @@ For the list of the global functions and constants see [@GlobalScope]. </description> <tutorials> + <link title="GDScript exports">$DOCS_URL/tutorials/scripting/gdscript/gdscript_exports.html</link> </tutorials> <methods> <method name="Color8"> <return type="Color" /> - <argument index="0" name="r8" type="int" /> - <argument index="1" name="g8" type="int" /> - <argument index="2" name="b8" type="int" /> - <argument index="3" name="a8" type="int" default="255" /> + <param index="0" name="r8" type="int" /> + <param index="1" name="g8" type="int" /> + <param index="2" name="b8" type="int" /> + <param index="3" name="a8" type="int" default="255" /> <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 @@ -29,8 +30,8 @@ </method> <method name="assert"> <return type="void" /> - <argument index="0" name="condition" type="bool" /> - <argument index="1" name="message" type="String" default="""" /> + <param index="0" name="condition" type="bool" /> + <param index="1" name="message" type="String" default="""" /> <description> Asserts that the [code]condition[/code] is [code]true[/code]. If the [code]condition[/code] is [code]false[/code], an error is generated. When running from the editor, the running project will also be paused until you resume it. This can be used as a stronger form of [method @GlobalScope.push_error] for reporting errors to project developers or add-on users. [b]Note:[/b] For performance reasons, the code inside [method assert] is only executed in debug builds or when running the project from the editor. Don't include code that has side effects in an [method assert] call. Otherwise, the project will behave differently when exported in release mode. @@ -47,7 +48,7 @@ </method> <method name="char"> <return type="String" /> - <argument index="0" name="char" type="int" /> + <param index="0" name="char" type="int" /> <description> Returns a character as a String of the given Unicode code point (which is compatible with ASCII code). [codeblock] @@ -59,8 +60,8 @@ </method> <method name="convert"> <return type="Variant" /> - <argument index="0" name="what" type="Variant" /> - <argument index="1" name="type" type="int" /> + <param index="0" name="what" type="Variant" /> + <param index="1" name="type" type="int" /> <description> Converts from a type to another in the best way possible. The [code]type[/code] parameter uses the [enum Variant.Type] values. [codeblock] @@ -75,7 +76,7 @@ </method> <method name="dict2inst"> <return type="Object" /> - <argument index="0" name="dictionary" type="Dictionary" /> + <param index="0" name="dictionary" type="Dictionary" /> <description> Converts a dictionary (previously created with [method inst2dict]) back to an instance. Useful for deserializing. </description> @@ -103,7 +104,7 @@ </method> <method name="inst2dict"> <return type="Dictionary" /> - <argument index="0" name="instance" type="Object" /> + <param index="0" name="instance" type="Object" /> <description> Returns the passed instance converted to a dictionary (useful for serializing). [codeblock] @@ -122,7 +123,7 @@ </method> <method name="len"> <return type="int" /> - <argument index="0" name="var" type="Variant" /> + <param index="0" name="var" type="Variant" /> <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. @@ -134,7 +135,7 @@ </method> <method name="load"> <return type="Resource" /> - <argument index="0" name="path" type="String" /> + <param index="0" name="path" type="String" /> <description> Loads a resource from the filesystem located at [code]path[/code]. The resource is loaded on the method call (unless it's referenced already elsewhere, e.g. in another script or in the scene), which might cause slight delay, especially when loading scenes. To avoid unnecessary delays when loading something multiple times, either store the resource in a variable or use [method preload]. [b]Note:[/b] Resource paths can be obtained by right-clicking on a resource in the FileSystem dock and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script. @@ -149,7 +150,7 @@ </method> <method name="preload"> <return type="Resource" /> - <argument index="0" name="path" type="String" /> + <param index="0" name="path" type="String" /> <description> Returns a [Resource] from the filesystem located at [code]path[/code]. The resource is loaded during script parsing, i.e. is loaded with the script and [method preload] effectively acts as a reference to that resource. Note that the method requires a constant path. If you want to load a resource from a dynamic/variable path, use [method load]. [b]Note:[/b] Resource paths can be obtained by right clicking on a resource in the Assets Panel and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script. @@ -237,8 +238,13 @@ </method> <method name="type_exists"> <return type="bool" /> - <argument index="0" name="type" type="StringName" /> + <param index="0" name="type" type="StringName" /> <description> + Returns whether the given [Object]-derived class exists in [ClassDB]. Note that [Variant] data types are not registered in [ClassDB]. + [codeblock] + type_exists("Sprite2D") # Returns true + type_exists("NonExistentClass") # Returns false + [/codeblock] </description> </method> </methods> @@ -262,157 +268,334 @@ <annotation name="@export"> <return type="void" /> <description> + Mark the following property as exported (editable in the Inspector dock and saved to disk). To control the type of the exported property use the type hint notation. + [codeblock] + @export var int_number = 5 + @export var float_number: float = 5 + [/codeblock] </description> </annotation> <annotation name="@export_category"> <return type="void" /> - <argument index="0" name="name" type="String" /> + <param index="0" name="name" type="String" /> <description> + Define a new category for the following exported properties. This helps to organize properties in the Inspector dock. + See also [constant PROPERTY_USAGE_CATEGORY]. + [codeblock] + @export_category("My Properties") + @export var number = 3 + @export var string = "" + [/codeblock] + [b]Note:[/b] Categories in the property list are supposed to indicate different base types, so the use of this annotation is not encouraged. See [annotation @export_group] and [annotation @export_subgroup] instead. </description> </annotation> <annotation name="@export_color_no_alpha"> <return type="void" /> <description> + Export a [Color] property without an alpha (fixed as [code]1.0[/code]). + See also [constant PROPERTY_HINT_COLOR_NO_ALPHA]. + [codeblock] + @export_color_no_alpha var modulate_color: Color + [/codeblock] </description> </annotation> <annotation name="@export_dir"> <return type="void" /> <description> + Export a [String] property as a path to a directory. The path will be limited to the project folder and its subfolders. See [annotation @export_global_dir] to allow picking from the entire filesystem. + See also [constant PROPERTY_HINT_DIR]. + [codeblock] + @export_dir var sprite_folder: String + [/codeblock] </description> </annotation> <annotation name="@export_enum" qualifiers="vararg"> <return type="void" /> - <argument index="0" name="names" type="String" /> + <param index="0" name="names" type="String" /> <description> + Export a [String] or integer property as an enumerated list of options. If the property is an integer field, then the index of the value is stored, in the same order the values are provided. You can add specific identifiers for allowed values using a colon. + See also [constant PROPERTY_HINT_ENUM]. + [codeblock] + @export_enum("Rebecca", "Mary", "Leah") var character_name: String + @export_enum("Warrior", "Magician", "Thief") var character_class: int + @export_enum("Walking:30", "Running:60", "Riding:200") var character_speed: int + [/codeblock] </description> </annotation> <annotation name="@export_exp_easing" qualifiers="vararg"> <return type="void" /> - <argument index="0" name="hints" type="String" default="""" /> + <param index="0" name="hints" type="String" default="""" /> <description> + Export a floating-point property with an easing editor widget. Additional hints can be provided to adjust the behavior of the widget. [code]"attenuation"[/code] flips the curve, which makes it more intuitive for editing attenuation properties. [code]"positive_only"[/code] limits values to only be greater than or equal to zero. + See also [constant PROPERTY_HINT_EXP_EASING]. + [codeblock] + @export_exp_easing var transition_speed + @export_exp_easing("attenuation") var fading_attenuation + @export_exp_easing("positive_only") var effect_power + [/codeblock] </description> </annotation> <annotation name="@export_file" qualifiers="vararg"> <return type="void" /> - <argument index="0" name="filter" type="String" default="""" /> + <param index="0" name="filter" type="String" default="""" /> <description> + Export a [String] property as a path to a file. The path will be limited to the project folder and its subfolders. See [annotation @export_global_file] to allow picking from the entire filesystem. + If [param filter] is provided, only matching files will be available for picking. + See also [constant PROPERTY_HINT_FILE]. + [codeblock] + @export_file var sound_effect_file: String + @export_file("*.txt") var notes_file: String + [/codeblock] </description> </annotation> <annotation name="@export_flags" qualifiers="vararg"> <return type="void" /> - <argument index="0" name="names" type="String" /> + <param index="0" name="names" type="String" /> <description> + Export an integer property as a bit flag field. This allows to store several "checked" or [code]true[/code] values with one property, and comfortably select them from the Inspector dock. + See also [constant PROPERTY_HINT_FLAGS]. + [codeblock] + @export_flags("Fire", "Water", "Earth", "Wind") var spell_elements = 0 + [/codeblock] </description> </annotation> <annotation name="@export_flags_2d_navigation"> <return type="void" /> <description> + Export an integer property as a bit flag field for 2D navigation layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/2d_navigation/layer_1]. + See also [constant PROPERTY_HINT_LAYERS_2D_NAVIGATION]. + [codeblock] + @export_flags_2d_navigation var navigation_layers: int + [/codeblock] </description> </annotation> <annotation name="@export_flags_2d_physics"> <return type="void" /> <description> + Export an integer property as a bit flag field for 2D physics layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/2d_physics/layer_1]. + See also [constant PROPERTY_HINT_LAYERS_2D_PHYSICS]. + [codeblock] + @export_flags_2d_physics var physics_layers: int + [/codeblock] </description> </annotation> <annotation name="@export_flags_2d_render"> <return type="void" /> <description> + Export an integer property as a bit flag field for 2D render layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/2d_render/layer_1]. + See also [constant PROPERTY_HINT_LAYERS_2D_RENDER]. + [codeblock] + @export_flags_2d_render var render_layers: int + [/codeblock] </description> </annotation> <annotation name="@export_flags_3d_navigation"> <return type="void" /> <description> + Export an integer property as a bit flag field for 3D navigation layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/3d_navigation/layer_1]. + See also [constant PROPERTY_HINT_LAYERS_3D_NAVIGATION]. + [codeblock] + @export_flags_3d_navigation var navigation_layers: int + [/codeblock] </description> </annotation> <annotation name="@export_flags_3d_physics"> <return type="void" /> <description> + Export an integer property as a bit flag field for 3D physics layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/3d_physics/layer_1]. + See also [constant PROPERTY_HINT_LAYERS_3D_PHYSICS]. + [codeblock] + @export_flags_3d_physics var physics_layers: int + [/codeblock] </description> </annotation> <annotation name="@export_flags_3d_render"> <return type="void" /> <description> + Export an integer property as a bit flag field for 3D render layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/3d_render/layer_1]. + See also [constant PROPERTY_HINT_LAYERS_3D_RENDER]. + [codeblock] + @export_flags_3d_render var render_layers: int + [/codeblock] </description> </annotation> <annotation name="@export_global_dir"> <return type="void" /> <description> + Export a [String] property as a path to a directory. The path can be picked from the entire filesystem. See [annotation @export_dir] to limit it to the project folder and its subfolders. + See also [constant PROPERTY_HINT_GLOBAL_DIR]. + [codeblock] + @export_global_dir var sprite_folder: String + [/codeblock] </description> </annotation> <annotation name="@export_global_file" qualifiers="vararg"> <return type="void" /> - <argument index="0" name="filter" type="String" default="""" /> + <param index="0" name="filter" type="String" default="""" /> <description> + Export a [String] property as a path to a file. The path can be picked from the entire filesystem. See [annotation @export_file] to limit it to the project folder and its subfolders. + If [param filter] is provided, only matching files will be available for picking. + See also [constant PROPERTY_HINT_GLOBAL_FILE]. + [codeblock] + @export_global_file var sound_effect_file: String + @export_global_file("*.txt") var notes_file: String + [/codeblock] </description> </annotation> <annotation name="@export_group"> <return type="void" /> - <argument index="0" name="name" type="String" /> - <argument index="1" name="prefix" type="String" default="""" /> + <param index="0" name="name" type="String" /> + <param index="1" name="prefix" type="String" default="""" /> <description> + Define a new group for the following exported properties. This helps to organize properties in the Inspector dock. Groups can be added with an optional [param prefix], which would make group to only consider properties that have this prefix. The grouping will break on the first property that doesn't have a prefix. The prefix is also removed from the property's name in the Inspector dock. + If no [param prefix] is provided, the every following property is added to the group. The group ends when then next group or category is defined. You can also force end a group by using this annotation with empty strings for paramters, [code]@export_group("", "")[/code]. + Groups cannot be nested, use [annotation @export_subgroup] to add subgroups to your groups. + See also [constant PROPERTY_USAGE_GROUP]. + [codeblock] + @export_group("My Properties") + @export var number = 3 + @export var string = "" + + @export_group("Prefixed Properties", "prefix_") + @export var prefix_number = 3 + @export var prefix_string = "" + + @export_group("", "") + @export var ungrouped_number = 3 + [/codeblock] </description> </annotation> <annotation name="@export_multiline"> <return type="void" /> <description> + Export a [String] property with a large [TextEdit] widget instead of a [LineEdit]. This adds support for multiline content and makes it easier to edit large amount of text stored in the property. + See also [constant PROPERTY_HINT_MULTILINE_TEXT]. + [codeblock] + @export_multiline var character_bio + [/codeblock] </description> </annotation> <annotation name="@export_node_path" qualifiers="vararg"> <return type="void" /> - <argument index="0" name="type" type="String" default="""" /> + <param index="0" name="type" type="String" default="""" /> <description> + Export a [NodePath] property with a filter for allowed node types. + See also [constant PROPERTY_HINT_NODE_PATH_VALID_TYPES]. + [codeblock] + @export_node_path(Button, TouchScreenButton) var some_button + [/codeblock] </description> </annotation> <annotation name="@export_placeholder"> <return type="void" /> + <param index="0" name="placeholder" type="String" /> <description> + Export a [String] property with a placeholder text displayed in the editor widget when no value is present. + See also [constant PROPERTY_HINT_PLACEHOLDER_TEXT]. + [codeblock] + @export_placeholder("Name in lowercase") var character_id: String + [/codeblock] </description> </annotation> <annotation name="@export_range" qualifiers="vararg"> <return type="void" /> - <argument index="0" name="min" type="float" /> - <argument index="1" name="max" type="float" /> - <argument index="2" name="step" type="float" default="1.0" /> - <argument index="3" name="extra_hints" type="String" default="""" /> - <description> + <param index="0" name="min" type="float" /> + <param index="1" name="max" type="float" /> + <param index="2" name="step" type="float" default="1.0" /> + <param index="3" name="extra_hints" type="String" default="""" /> + <description> + Export a numeric property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [code]EditorSettings.interface/inspector/default_float_step[/code] setting. + If hints [code]"or_greater"[/code] and [code]"or_lesser"[/code] are provided, the editor widget will not cap the value at range boundaries. The [code]"exp"[/code] hint will make the edited values on range to change exponentially. The [code]"no_slider"[/code] hint will hide the slider element of the editor widget. + Hints also allow to indicate the units for the edited value. Using [code]"radians"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock. [code]"degrees"[/code] allows to add a degree sign as a unit suffix. Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string. + See also [constant PROPERTY_HINT_RANGE]. + [codeblock] + @export_range(0, 20) var number + @export_range(-10, 20) var number + @export_range(-10, 20, 0.2) var number: float + + @export_range(0, 100, 1, "or_greater") var power_percent + @export_range(0, 100, 1, "or_greater", "or_lesser") var health_delta + + @export_range(-3.14, 3.14, 0.001, "radians") var angle_radians + @export_range(0, 360, 1, "degrees") var angle_degrees + @export_range(-8, 8, 2, "suffix:px") var target_offset + [/codeblock] </description> </annotation> <annotation name="@export_subgroup"> <return type="void" /> - <argument index="0" name="name" type="String" /> - <argument index="1" name="prefix" type="String" default="""" /> + <param index="0" name="name" type="String" /> + <param index="1" name="prefix" type="String" default="""" /> <description> + Define a new subgroup for the following exported properties. This helps to organize properties in the Inspector dock. Subgroups work exactly like groups, except they need a parent group to exist. See [annotation @export_group]. + See also [constant PROPERTY_USAGE_SUBGROUP]. + [codeblock] + @export_group("My Properties") + @export var number = 3 + @export var string = "" + + @export_subgroup("My Prefixed Properties", "prefix_") + @export var prefix_number = 3 + @export var prefix_string = "" + [/codeblock] + [b]Note:[/b] Subgroups cannot be nested, they only provide one extra level of depth. Just like the next group ends the previous group, so do the subsequent subgroups. </description> </annotation> <annotation name="@icon"> <return type="void" /> - <argument index="0" name="icon_path" type="String" /> + <param index="0" name="icon_path" type="String" /> <description> + Add a custom icon to the current script. The icon is displayed in the Scene dock for every node that the script is attached to. For named classes the icon is also displayed in various editor dialogs. + [codeblock] + @icon("res://path/to/class/icon.svg") + [/codeblock] + [b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported yet. </description> </annotation> <annotation name="@onready"> <return type="void" /> <description> + Mark the following property as assigned on [Node]'s ready state change. Values for these properties are no assigned immediately upon the node's creation, and instead are computed and stored right before [method Node._ready]. + [codeblock] + @onready var character_name: Label = $Label + [/codeblock] </description> </annotation> <annotation name="@rpc" qualifiers="vararg"> <return type="void" /> - <argument index="0" name="mode" type="String" default="""" /> - <argument index="1" name="sync" type="String" default="""" /> - <argument index="2" name="transfer_mode" type="String" default="""" /> - <argument index="3" name="transfer_channel" type="int" default="0" /> + <param index="0" name="mode" type="String" default="""" /> + <param index="1" name="sync" type="String" default="""" /> + <param index="2" name="transfer_mode" type="String" default="""" /> + <param index="3" name="transfer_channel" type="int" default="0" /> <description> + Mark the following method for remote procedure calls. See [url=$DOCS_URL/tutorials/networking/high_level_multiplayer.html]High-level multiplayer[/url]. + [codeblock] + @rpc() + [/codeblock] </description> </annotation> <annotation name="@tool"> <return type="void" /> <description> + Mark the current script as a tool script, allowing it to be loaded and executed by the editor. See [url=$DOCS_URL/tutorials/plugins/running_code_in_the_editor.html]Running code in the editor[/url]. + [codeblock] + @tool + extends Node + [/codeblock] </description> </annotation> <annotation name="@warning_ignore" qualifiers="vararg"> <return type="void" /> - <argument index="0" name="warning" type="String" /> + <param index="0" name="warning" type="String" /> <description> + Mark the following statement to ignore the specified warning. See [url=$DOCS_URL/tutorials/scripting/gdscript/warning_system.html]GDScript warning system[/url]. + [codeblock] + func test(): + print("hello") + return + @warning_ignore("unreachable_code") + print("unreachable") + [/codeblock] </description> </annotation> </annotations> diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index a23f19de85..e7299b4d3a 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -46,6 +46,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l bool prev_is_char = false; bool prev_is_number = false; + bool prev_is_binary_op = false; bool in_keyword = false; bool in_word = false; bool in_function_name = false; @@ -84,16 +85,17 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l const int line_length = str.length(); Color prev_color; - if (in_region != -1 && str.length() == 0) { + if (in_region != -1 && line_length == 0) { color_region_cache[p_line] = in_region; } - for (int j = 0; j < str.length(); j++) { + for (int j = 0; j < line_length; j++) { Dictionary highlighter_info; color = font_color; bool is_char = !is_symbol(str[j]); bool is_a_symbol = is_symbol(str[j]); bool is_number = is_digit(str[j]); + bool is_binary_op = false; /* color regions */ if (is_a_symbol || in_region != -1) { @@ -244,7 +246,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l is_hex_notation = false; } - // disallow anything not a 0 or 1 + // disallow anything not a 0 or 1 in binary notation if (is_bin_notation && (is_binary_digit(str[j]))) { is_number = true; } else if (is_bin_notation) { @@ -285,7 +287,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l if (!in_keyword && is_char && !prev_is_char) { int to = j; - while (to < str.length() && !is_symbol(str[to])) { + while (to < line_length && !is_symbol(str[to])) { to++; } @@ -318,12 +320,12 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_signal_declaration = true; } else { int k = j; - while (k < str.length() && !is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') { + while (k < line_length && !is_symbol(str[k]) && !is_whitespace(str[k])) { k++; } // check for space between name and bracket - while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) { + while (k < line_length && is_whitespace(str[k])) { k++; } @@ -336,7 +338,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l // Check for lambda. if (in_function_name && previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) { k = j - 1; - while (k > 0 && (str[k] == '\t' || str[k] == ' ')) { + while (k > 0 && is_whitespace(str[k])) { k--; } @@ -349,7 +351,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l if (!in_function_name && !in_member_variable && !in_keyword && !is_number && in_word) { int k = j; - while (k > 0 && !is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') { + while (k > 0 && !is_symbol(str[k]) && !is_whitespace(str[k])) { k--; } @@ -378,7 +380,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l if (in_variable_declaration || in_function_args) { int k = j; // Skip space - while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) { + while (k < line_length && is_whitespace(str[k])) { k++; } @@ -395,13 +397,41 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_member_variable = false; } - if (!in_node_path && in_region == -1 && (str[j] == '^')) { + if (j > 0 && (str[j] == '&' || str[j] == '^' || str[j] == '%' || str[j] == '+' || str[j] == '-' || str[j] == '~')) { + int k = j - 1; + while (k > 0 && is_whitespace(str[k])) { + k--; + } + if (!is_symbol(str[k]) || str[k] == '"' || str[k] == '\'' || str[k] == ')' || str[k] == ']' || str[k] == '}') { + is_binary_op = true; + } + } + + // Highlight '+' and '-' like numbers when unary + if ((str[j] == '+' || str[j] == '-' || str[j] == '~') && !is_binary_op) { + is_number = true; + is_a_symbol = false; + } + + // Keep symbol color for binary '&&'. In the case of '&&&' use StringName color for the last ampersand + if (!in_string_name && in_region == -1 && str[j] == '&' && !is_binary_op) { + if (j >= 2 && str[j - 1] == '&' && str[j - 2] != '&' && prev_is_binary_op) { + is_binary_op = true; + } else if (j == 0 || (j > 0 && str[j - 1] != '&') || prev_is_binary_op) { + in_string_name = true; + } + } else if (in_region != -1 || is_a_symbol) { + in_string_name = false; + } + + // '^^' has no special meaning, so unlike StringName, when binary, use NodePath color for the last caret + if (!in_node_path && in_region == -1 && str[j] == '^' && !is_binary_op && (j == 0 || (j > 0 && str[j - 1] != '^') || prev_is_binary_op)) { in_node_path = true; - } else if (in_region != -1 || (is_a_symbol && str[j] != '/' && str[j] != '%')) { + } else if (in_region != -1 || is_a_symbol) { in_node_path = false; } - if (!in_node_ref && in_region == -1 && (str[j] == '$' || str[j] == '%')) { + if (!in_node_ref && in_region == -1 && (str[j] == '$' || (str[j] == '%' && !is_binary_op))) { in_node_ref = true; } else if (in_region != -1 || (is_a_symbol && str[j] != '/' && str[j] != '%')) { in_node_ref = false; @@ -413,16 +443,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_annotation = false; } - if (!in_string_name && in_region == -1 && str[j] == '&') { - in_string_name = true; - } else if (in_region != -1 || is_a_symbol) { - in_string_name = false; - } - - if (in_node_path) { - next_type = NODE_PATH; - color = node_path_color; - } else if (in_node_ref) { + if (in_node_ref) { next_type = NODE_REF; color = node_ref_color; } else if (in_annotation) { @@ -431,6 +452,9 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } else if (in_string_name) { next_type = STRING_NAME; color = string_name_color; + } else if (in_node_path) { + next_type = NODE_PATH; + color = node_path_color; } else if (in_keyword) { next_type = KEYWORD; color = keyword_color; @@ -487,6 +511,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l prev_is_char = is_char; prev_is_number = is_number; + prev_is_binary_op = is_binary_op; if (color != prev_color) { prev_color = color; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index dc6bfbb034..cf2d6ae9f8 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -290,7 +290,9 @@ void GDScript::_get_script_method_list(List<MethodInfo> *r_list, bool p_include_ #endif mi.arguments.push_back(arginfo); } - +#ifdef TOOLS_ENABLED + mi.default_arguments.append_array(func->get_default_arg_values()); +#endif mi.return_val = func->get_return_type(); r_list->push_back(mi); } @@ -1536,6 +1538,47 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const } } +bool GDScriptInstance::property_can_revert(const StringName &p_name) const { + Variant name = p_name; + const Variant *args[1] = { &name }; + + const GDScript *sptr = script.ptr(); + while (sptr) { + HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_can_revert); + if (E) { + Callable::CallError err; + Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); + if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) { + return true; + } + } + sptr = sptr->_base; + } + + return false; +} + +bool GDScriptInstance::property_get_revert(const StringName &p_name, Variant &r_ret) const { + Variant name = p_name; + const Variant *args[1] = { &name }; + + const GDScript *sptr = script.ptr(); + while (sptr) { + HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_get_revert); + if (E) { + Callable::CallError err; + Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); + if (err.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) { + r_ret = ret; + return true; + } + } + sptr = sptr->_base; + } + + return false; +} + void GDScriptInstance::get_method_list(List<MethodInfo> *p_list) const { const GDScript *sptr = script.ptr(); while (sptr) { @@ -2246,6 +2289,8 @@ GDScriptLanguage::GDScriptLanguage() { strings._set = StaticCString::create("_set"); strings._get = StaticCString::create("_get"); strings._get_property_list = StaticCString::create("_get_property_list"); + strings._property_can_revert = StaticCString::create("_property_can_revert"); + strings._property_get_revert = StaticCString::create("_property_get_revert"); strings._script_source = StaticCString::create("script/source"); _debug_parse_err_line = -1; _debug_parse_err_file = ""; diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 5123cccddd..e4b12d4ddb 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -287,6 +287,9 @@ public: virtual void get_property_list(List<PropertyInfo> *p_properties) const; virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const; + virtual bool property_can_revert(const StringName &p_name) const; + virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const; + virtual void get_method_list(List<MethodInfo> *p_list) const; virtual bool has_method(const StringName &p_method) const; virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); @@ -423,6 +426,8 @@ public: StringName _set; StringName _get; StringName _get_property_list; + StringName _property_can_revert; + StringName _property_get_revert; StringName _script_source; } strings; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index d943974ce4..c18412bc63 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -660,7 +660,13 @@ static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx, bool } static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_function, int p_arg_idx) { - String arghint = p_function->get_datatype().to_string() + " " + p_function->identifier->name.operator String() + "("; + String arghint; + + if (p_function->get_datatype().builtin_type == Variant::NIL) { + arghint = "void " + p_function->identifier->name.operator String() + "("; + } else { + arghint = p_function->get_datatype().to_string() + " " + p_function->identifier->name.operator String() + "("; + } for (int i = 0; i < p_function->parameters.size(); i++) { if (i > 0) { @@ -671,7 +677,11 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio arghint += String::chr(0xFFFF); } const GDScriptParser::ParameterNode *par = p_function->parameters[i]; - arghint += par->identifier->name.operator String() + ": " + par->get_datatype().to_string(); + if (!par->get_datatype().is_hard_type()) { + arghint += par->identifier->name.operator String() + ": Variant"; + } else { + arghint += par->identifier->name.operator String() + ": " + par->get_datatype().to_string(); + } if (par->default_value) { String def_val = "<unknown>"; @@ -2517,39 +2527,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c GDScriptCompletionIdentifier connect_base; - if (Variant::has_utility_function(call->function_name)) { - MethodInfo info = Variant::get_utility_function_info(call->function_name); - r_arghint = _make_arguments_hint(info, p_argidx); - return; - } else if (GDScriptUtilityFunctions::function_exists(call->function_name)) { - MethodInfo info = GDScriptUtilityFunctions::get_function_info(call->function_name); - r_arghint = _make_arguments_hint(info, p_argidx); - return; - } else if (GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) { - // Complete constructor. - List<MethodInfo> constructors; - Variant::get_constructor_list(GDScriptParser::get_builtin_type(call->function_name), &constructors); - - int i = 0; - for (const MethodInfo &E : constructors) { - if (p_argidx >= E.arguments.size()) { - continue; - } - if (i > 0) { - r_arghint += "\n"; - } - r_arghint += _make_arguments_hint(E, p_argidx); - i++; - } - return; - } else if (call->is_super || callee_type == GDScriptParser::Node::IDENTIFIER) { - base = p_context.base; - - if (p_context.current_class) { - base_type = p_context.current_class->get_datatype(); - _static = !p_context.current_function || p_context.current_function->is_static; - } - } else if (callee_type == GDScriptParser::Node::SUBSCRIPT) { + if (callee_type == GDScriptParser::Node::SUBSCRIPT) { const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee); if (subscript->base != nullptr && subscript->base->type == GDScriptParser::Node::IDENTIFIER) { @@ -2589,6 +2567,38 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c _static = base_type.is_meta_type; } + } else if (Variant::has_utility_function(call->function_name)) { + MethodInfo info = Variant::get_utility_function_info(call->function_name); + r_arghint = _make_arguments_hint(info, p_argidx); + return; + } else if (GDScriptUtilityFunctions::function_exists(call->function_name)) { + MethodInfo info = GDScriptUtilityFunctions::get_function_info(call->function_name); + r_arghint = _make_arguments_hint(info, p_argidx); + return; + } else if (GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) { + // Complete constructor. + List<MethodInfo> constructors; + Variant::get_constructor_list(GDScriptParser::get_builtin_type(call->function_name), &constructors); + + int i = 0; + for (const MethodInfo &E : constructors) { + if (p_argidx >= E.arguments.size()) { + continue; + } + if (i > 0) { + r_arghint += "\n"; + } + r_arghint += _make_arguments_hint(E, p_argidx); + i++; + } + return; + } else if (call->is_super || callee_type == GDScriptParser::Node::IDENTIFIER) { + base = p_context.base; + + if (p_context.current_class) { + base_type = p_context.current_class->get_datatype(); + _static = !p_context.current_function || p_context.current_function->is_static; + } } else { return; } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 6f5397e1a3..6b6ad427a7 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -127,7 +127,7 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_global_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, varray(""), true); register_annotation(MethodInfo("@export_global_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_DIR, Variant::STRING>); register_annotation(MethodInfo("@export_multiline"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>); - register_annotation(MethodInfo("@export_placeholder"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>); + register_annotation(MethodInfo("@export_placeholder", PropertyInfo(Variant::STRING, "placeholder")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>); register_annotation(MethodInfo("@export_range", PropertyInfo(Variant::FLOAT, "min"), PropertyInfo(Variant::FLOAT, "max"), PropertyInfo(Variant::FLOAT, "step"), PropertyInfo(Variant::STRING, "extra_hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, varray(1.0, ""), true); register_annotation(MethodInfo("@export_exp_easing", PropertyInfo(Variant::STRING, "hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, varray(""), true); register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>); diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index c0d5856be5..39f4c976a4 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -184,7 +184,9 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { if (root_uri.length() && is_same_workspace) { workspace->root_uri = root_uri; } else { - workspace->root_uri = "file://" + workspace->root; + String r_root = workspace->root; + r_root = r_root.lstrip("/"); + workspace->root_uri = "file:///" + r_root; Dictionary params; params["path"] = workspace->root; diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index ded2a7b4d4..44b60369ab 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -500,10 +500,8 @@ Error GDScriptWorkspace::parse_local_script(const String &p_path) { String GDScriptWorkspace::get_file_path(const String &p_uri) const { String path = p_uri; - path = path.replace("///", "//"); - path = path.replace("%3A", ":"); - path = path.replacen(root_uri + "/", "res://"); path = path.uri_decode(); + path = path.replacen(root_uri + "/", "res://"); return path; } diff --git a/modules/gdscript/tests/gdscript_test_runner_suite.h b/modules/gdscript/tests/gdscript_test_runner_suite.h index 0722fb800e..90f6d7f7e8 100644 --- a/modules/gdscript/tests/gdscript_test_runner_suite.h +++ b/modules/gdscript/tests/gdscript_test_runner_suite.h @@ -69,6 +69,40 @@ func _init(): CHECK_MESSAGE(int(ref_counted->get_meta("result")) == 42, "The script should assign object metadata successfully."); } +TEST_CASE("[Modules][GDScript] Validate built-in API") { + GDScriptLanguage *lang = GDScriptLanguage::get_singleton(); + + // Validate methods. + List<MethodInfo> builtin_methods; + lang->get_public_functions(&builtin_methods); + + SUBCASE("[Modules][GDScript] Validate built-in methods") { + for (const MethodInfo &mi : builtin_methods) { + for (int j = 0; j < mi.arguments.size(); j++) { + PropertyInfo arg = mi.arguments[j]; + + TEST_COND((arg.name.is_empty() || arg.name.begins_with("_unnamed_arg")), + vformat("Unnamed argument in position %d of built-in method '%s'.", j, mi.name)); + } + } + } + + // Validate annotations. + List<MethodInfo> builtin_annotations; + lang->get_public_annotations(&builtin_annotations); + + SUBCASE("[Modules][GDScript] Validate built-in annotations") { + for (const MethodInfo &ai : builtin_annotations) { + for (int j = 0; j < ai.arguments.size(); j++) { + PropertyInfo arg = ai.arguments[j]; + + TEST_COND((arg.name.is_empty() || arg.name.begins_with("_unnamed_arg")), + vformat("Unnamed argument in position %d of built-in annotation '%s'.", j, ai.name)); + } + } + } +} + } // namespace GDScriptTests #endif // GDSCRIPT_TEST_RUNNER_SUITE_H |