1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-04 12:00:25 +00:00

Merge pull request #106198 from SatLess/User-Func-Autocomplete

Add code completion for user-defined methods when overriding in GDScript
This commit is contained in:
Rémi Verschelde
2025-06-09 00:44:22 +02:00
11 changed files with 124 additions and 9 deletions

View File

@@ -3519,9 +3519,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<GDScriptParser::FunctionNode *>(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: {
@@ -3542,17 +3562,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<MethodInfo> 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);

View File

@@ -1609,7 +1609,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 {
@@ -1660,15 +1660,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<FunctionNode>();
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;
@@ -1678,7 +1693,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<SuiteNode>();
@@ -1687,13 +1701,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);
@@ -3621,7 +3640,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;

View File

@@ -862,6 +862,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;
@@ -1508,7 +1509,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);

View File

@@ -200,6 +200,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;
@@ -285,6 +291,11 @@ public:
const Vector<int> &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;

View File

@@ -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

View File

@@ -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:"},
]

View File

@@ -0,0 +1,3 @@
extends "res://completion/class_a.notest.gd"
func

View File

@@ -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):"},
]

View File

@@ -0,0 +1,3 @@
extends "res://completion/class_a.notest.gd"
static func

View File

@@ -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:"},
]

View File

@@ -0,0 +1,3 @@
extends "res://completion/class_a.notest.gd"
func _