From fc4df4b17d352f8b22b92273211e9602e3f70f2e Mon Sep 17 00:00:00 2001 From: Sat <124409014+SatLess@users.noreply.github.com> Date: Thu, 8 May 2025 06:22:05 -0300 Subject: [PATCH] Added working version for user-defined function autocompletion --- modules/gdscript/gdscript_editor.cpp | 29 +++++++++++++++++-- modules/gdscript/gdscript_parser.cpp | 29 +++++++++++++++---- modules/gdscript/gdscript_parser.h | 3 +- modules/gdscript/gdscript_tokenizer.h | 11 +++++++ .../scripts/completion/class_a.notest.gd | 15 ++++++++++ .../override_function_no_underscore.cfg | 13 +++++++++ .../common/override_function_no_underscore.gd | 3 ++ .../common/override_function_static.cfg | 14 +++++++++ .../common/override_function_static.gd | 3 ++ .../common/override_function_underscore.cfg | 10 +++++++ .../common/override_function_underscore.gd | 3 ++ 11 files changed, 124 insertions(+), 9 deletions(-) create mode 100644 modules/gdscript/tests/scripts/completion/common/override_function_no_underscore.cfg create mode 100644 modules/gdscript/tests/scripts/completion/common/override_function_no_underscore.gd create mode 100644 modules/gdscript/tests/scripts/completion/common/override_function_static.cfg create mode 100644 modules/gdscript/tests/scripts/completion/common/override_function_static.gd create mode 100644 modules/gdscript/tests/scripts/completion/common/override_function_underscore.cfg create mode 100644 modules/gdscript/tests/scripts/completion/common/override_function_underscore.gd diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 0a2c9f3fb46..e937601a360 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -3521,9 +3521,29 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } break; case GDScriptParser::COMPLETION_OVERRIDE_METHOD: { GDScriptParser::DataType native_type = completion_context.current_class->base_type; + GDScriptParser::FunctionNode *function_node = static_cast(completion_context.node); + bool is_static = function_node != nullptr && function_node->is_static; while (native_type.is_set() && native_type.kind != GDScriptParser::DataType::NATIVE) { switch (native_type.kind) { case GDScriptParser::DataType::CLASS: { + for (const GDScriptParser::ClassNode::Member &member : native_type.class_type->members) { + if (member.type != GDScriptParser::ClassNode::Member::FUNCTION) { + continue; + } + + if (options.has(member.function->identifier->name) || completion_context.current_class->has_function(member.function->identifier->name)) { + continue; + } + + if (is_static != member.function->is_static) { + continue; + } + + String display_name = member.function->identifier->name; + display_name += member.function->signature + ":"; + ScriptLanguage::CodeCompletionOption option(display_name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); + options.insert(member.function->identifier->name, option); // Insert name instead of display to track duplicates. + } native_type = native_type.class_type->base_type; } break; default: { @@ -3544,17 +3564,20 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c bool use_type_hint = EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints").operator bool(); List virtual_methods; - ClassDB::get_virtual_methods(class_name, &virtual_methods); - - { + if (is_static) { // Not truly a virtual method, but can also be "overridden". MethodInfo static_init("_static_init"); static_init.return_val.type = Variant::NIL; static_init.flags |= METHOD_FLAG_STATIC | METHOD_FLAG_VIRTUAL; virtual_methods.push_back(static_init); + } else { + ClassDB::get_virtual_methods(class_name, &virtual_methods); } for (const MethodInfo &mi : virtual_methods) { + if (options.has(mi.name) || completion_context.current_class->has_function(mi.name)) { + continue; + } String method_hint = mi.name; if (method_hint.contains_char(':')) { method_hint = method_hint.get_slicec(':', 0); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index a225d2fa6f3..6c659fdd38f 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -1615,7 +1615,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_abstract, bool p_ return enum_node; } -void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type) { +void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type, int p_signature_start) { if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) { bool default_used = false; do { @@ -1666,15 +1666,30 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod current_class->has_static_data = true; } +#ifdef TOOLS_ENABLED + if (p_type == "function" && p_signature_start != -1) { + int signature_end_pos = tokenizer->get_current_position() - 1; + String source_code = tokenizer->get_source_code(); + p_function->signature = source_code.substr(p_signature_start, signature_end_pos - p_signature_start); + } +#endif // TOOLS_ENABLED + // TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens. consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after %s declaration.)", p_type)); } GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_abstract, bool p_is_static) { FunctionNode *function = alloc_node(); + function->is_static = p_is_static; make_completion_context(COMPLETION_OVERRIDE_METHOD, function); +#ifdef TOOLS_ENABLED + // The signature is something like `(a: int, b: int = 0) -> void`. + // We start one token earlier, since the parser looks one token ahead. + const int signature_start_pos = tokenizer->get_current_position(); +#endif // TOOLS_ENABLED + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) { complete_extents(function); return nullptr; @@ -1684,7 +1699,6 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_abstract, current_function = function; function->identifier = parse_identifier(); - function->is_static = p_is_static; SuiteNode *body = alloc_node(); @@ -1693,13 +1707,18 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_abstract, push_multiline(true); consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after function name.)"); - parse_function_signature(function, body, "function"); + +#ifdef TOOLS_ENABLED + parse_function_signature(function, body, "function", signature_start_pos); +#else // !TOOLS_ENABLED + parse_function_signature(function, body, "function", -1); +#endif // TOOLS_ENABLED current_suite = previous_suite; #ifdef TOOLS_ENABLED function->min_local_doc_line = previous.end_line + 1; -#endif +#endif // TOOLS_ENABLED function->body = parse_suite("function declaration", body); @@ -3619,7 +3638,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p SuiteNode *previous_suite = current_suite; current_suite = body; - parse_function_signature(function, body, "lambda"); + parse_function_signature(function, body, "lambda", -1); current_suite = previous_suite; diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index ee246c762ad..8b073afc3be 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -863,6 +863,7 @@ public: #ifdef TOOLS_ENABLED MemberDocData doc_data; int min_local_doc_line = 0; + String signature; // For autocompletion. #endif // TOOLS_ENABLED bool resolved_signature = false; @@ -1510,7 +1511,7 @@ private: EnumNode *parse_enum(bool p_is_abstract, bool p_is_static); ParameterNode *parse_parameter(); FunctionNode *parse_function(bool p_is_abstract, bool p_is_static); - void parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type); + void parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type, int p_signature_start); SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr, bool p_for_lambda = false); // Annotations AnnotationNode *parse_annotation(uint32_t p_valid_targets); diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index cfaa2aae053..bd78bcd4388 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -201,6 +201,12 @@ public: static String get_token_name(Token::Type p_token_type); +#ifdef TOOLS_ENABLED + // This is a temporary solution, as Tokens are not able to store their position, only lines and columns. + virtual int get_current_position() const { return 0; } + virtual String get_source_code() const { return ""; } +#endif // TOOLS_ENABLED + virtual int get_cursor_line() const = 0; virtual int get_cursor_column() const = 0; virtual void set_cursor_position(int p_line, int p_column) = 0; @@ -287,6 +293,11 @@ public: const Vector &get_continuation_lines() const { return continuation_lines; } +#ifdef TOOLS_ENABLED + virtual int get_current_position() const override { return position; } + virtual String get_source_code() const override { return source; } +#endif // TOOLS_ENABLED + virtual int get_cursor_line() const override; virtual int get_cursor_column() const override; virtual void set_cursor_position(int p_line, int p_column) override; diff --git a/modules/gdscript/tests/scripts/completion/class_a.notest.gd b/modules/gdscript/tests/scripts/completion/class_a.notest.gd index 47c64dc674a..a343aae2031 100644 --- a/modules/gdscript/tests/scripts/completion/class_a.notest.gd +++ b/modules/gdscript/tests/scripts/completion/class_a.notest.gd @@ -6,3 +6,18 @@ var property_of_a func func_of_a(): pass + + +func _func_of_a_underscore(): + pass + + +static func func_of_a_static(): + pass + +func func_of_a_args(a: int): + pass + +func func_of_a_callable(call := func(): + var x_of_a = 10): + pass diff --git a/modules/gdscript/tests/scripts/completion/common/override_function_no_underscore.cfg b/modules/gdscript/tests/scripts/completion/common/override_function_no_underscore.cfg new file mode 100644 index 00000000000..f02d0c5b7f7 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/override_function_no_underscore.cfg @@ -0,0 +1,13 @@ +scene="res://completion/get_node/get_node.tscn" +[output] +include=[ + ; GDScript: class_a.notest.gd + {"display": "func_of_a():"}, + {"display": "func_of_a_args(a: int):"}, + {"display": "func_of_a_callable(call := func(): + var x_of_a = 10):"} +] +exclude=[ + ; GDScript: class_a.notest.gd + {"display": "_static_init() -> void:"}, +] diff --git a/modules/gdscript/tests/scripts/completion/common/override_function_no_underscore.gd b/modules/gdscript/tests/scripts/completion/common/override_function_no_underscore.gd new file mode 100644 index 00000000000..347e7a44166 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/override_function_no_underscore.gd @@ -0,0 +1,3 @@ +extends "res://completion/class_a.notest.gd" + +func ➡ diff --git a/modules/gdscript/tests/scripts/completion/common/override_function_static.cfg b/modules/gdscript/tests/scripts/completion/common/override_function_static.cfg new file mode 100644 index 00000000000..90040ef57a5 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/override_function_static.cfg @@ -0,0 +1,14 @@ +scene="res://completion/get_node/get_node.tscn" +[output] +include=[ + ; GDScript: class_a.notest.gd + {"display": "_static_init() -> void:"}, + {"display": "func_of_a_static():"}, +] +exclude=[ + ; GDScript: class_a.notest.gd + {"display": "func_of_a():"}, + {"display": "func_of_a_args(a: int):"}, + {"display": "func_of_a_callable(call := func(): + var x_of_a = 10):"}, +] diff --git a/modules/gdscript/tests/scripts/completion/common/override_function_static.gd b/modules/gdscript/tests/scripts/completion/common/override_function_static.gd new file mode 100644 index 00000000000..80b5932af11 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/override_function_static.gd @@ -0,0 +1,3 @@ +extends "res://completion/class_a.notest.gd" + +static func ➡ diff --git a/modules/gdscript/tests/scripts/completion/common/override_function_underscore.cfg b/modules/gdscript/tests/scripts/completion/common/override_function_underscore.cfg new file mode 100644 index 00000000000..94b5e3b45da --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/override_function_underscore.cfg @@ -0,0 +1,10 @@ +scene="res://completion/get_node/get_node.tscn" +[output] +include=[ + ; GDScript: class_a.notest.gd + {"display": "_func_of_a_underscore():"}, +] +exclude=[ + ; GDScript: class_a.notest.gd + {"display": "_static_init() -> void:"}, +] diff --git a/modules/gdscript/tests/scripts/completion/common/override_function_underscore.gd b/modules/gdscript/tests/scripts/completion/common/override_function_underscore.gd new file mode 100644 index 00000000000..9fd39e27b01 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/override_function_underscore.gd @@ -0,0 +1,3 @@ +extends "res://completion/class_a.notest.gd" + +func _➡