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

Merge pull request #107872 from Thought-Weaver/users/loganapple/editor-caret-fix

[Autocomplete] Avoid prepending literals when the character has already been typed
This commit is contained in:
Thaddeus Crews
2025-07-10 11:39:30 -05:00
5 changed files with 104 additions and 23 deletions

View File

@@ -2909,9 +2909,10 @@ static void _find_enumeration_candidates(GDScriptParser::CompletionContext &p_co
} }
} }
static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, int p_argidx, bool p_static, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, String &r_arghint) { static void _list_call_arguments(GDScriptParser::CompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const GDScriptParser::CallNode *p_call, int p_argidx, bool p_static, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, String &r_arghint) {
Variant base = p_base.value; Variant base = p_base.value;
GDScriptParser::DataType base_type = p_base.type; GDScriptParser::DataType base_type = p_base.type;
const StringName &method = p_call->function_name;
const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\"";
const bool use_string_names = EDITOR_GET("text_editor/completion/add_string_name_literals"); const bool use_string_names = EDITOR_GET("text_editor/completion/add_string_name_literals");
@@ -2920,7 +2921,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
while (base_type.is_set() && !base_type.is_variant()) { while (base_type.is_set() && !base_type.is_variant()) {
switch (base_type.kind) { switch (base_type.kind) {
case GDScriptParser::DataType::CLASS: { case GDScriptParser::DataType::CLASS: {
if (base_type.is_meta_type && p_method == SNAME("new")) { if (base_type.is_meta_type && method == SNAME("new")) {
const GDScriptParser::ClassNode *current = base_type.class_type; const GDScriptParser::ClassNode *current = base_type.class_type;
do { do {
@@ -2939,8 +2940,8 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
return; return;
} }
if (base_type.class_type->has_member(p_method)) { if (base_type.class_type->has_member(method)) {
const GDScriptParser::ClassNode::Member &member = base_type.class_type->get_member(p_method); const GDScriptParser::ClassNode::Member &member = base_type.class_type->get_member(method);
if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) { if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) {
r_arghint = _make_arguments_hint(member.function, p_argidx); r_arghint = _make_arguments_hint(member.function, p_argidx);
@@ -2951,8 +2952,8 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
base_type = base_type.class_type->base_type; base_type = base_type.class_type->base_type;
} break; } break;
case GDScriptParser::DataType::SCRIPT: { case GDScriptParser::DataType::SCRIPT: {
if (base_type.script_type->is_valid() && base_type.script_type->has_method(p_method)) { if (base_type.script_type->is_valid() && base_type.script_type->has_method(method)) {
r_arghint = _make_arguments_hint(base_type.script_type->get_method_info(p_method), p_argidx); r_arghint = _make_arguments_hint(base_type.script_type->get_method_info(method), p_argidx);
return; return;
} }
Ref<Script> base_script = base_type.script_type->get_base_script(); Ref<Script> base_script = base_type.script_type->get_base_script();
@@ -2974,22 +2975,36 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
MethodInfo info; MethodInfo info;
int method_args = 0; int method_args = 0;
if (ClassDB::get_method_info(class_name, p_method, &info)) { if (ClassDB::get_method_info(class_name, method, &info)) {
method_args = info.arguments.size(); method_args = info.arguments.size();
if (base.get_type() == Variant::OBJECT) { if (base.get_type() == Variant::OBJECT) {
Object *obj = base.operator Object *(); Object *obj = base.operator Object *();
if (obj) { if (obj) {
List<String> options; List<String> options;
obj->get_argument_options(p_method, p_argidx, &options); obj->get_argument_options(method, p_argidx, &options);
for (String &opt : options) { for (String &opt : options) {
// Handle user preference. // Handle user preference.
if (opt.is_quoted()) { if (opt.is_quoted()) {
opt = opt.unquote().quote(quote_style); opt = opt.unquote().quote(quote_style);
if (use_string_names && info.arguments[p_argidx].type == Variant::STRING_NAME) { if (use_string_names && info.arguments[p_argidx].type == Variant::STRING_NAME) {
if (p_call->arguments.size() > p_argidx && p_call->arguments[p_argidx] && p_call->arguments[p_argidx]->type == GDScriptParser::Node::LITERAL) {
GDScriptParser::LiteralNode *literal = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[p_argidx]);
if (literal->value.get_type() == Variant::STRING) {
opt = "&" + opt; opt = "&" + opt;
}
} else {
opt = "&" + opt;
}
} else if (use_node_paths && info.arguments[p_argidx].type == Variant::NODE_PATH) { } else if (use_node_paths && info.arguments[p_argidx].type == Variant::NODE_PATH) {
if (p_call->arguments.size() > p_argidx && p_call->arguments[p_argidx] && p_call->arguments[p_argidx]->type == GDScriptParser::Node::LITERAL) {
GDScriptParser::LiteralNode *literal = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[p_argidx]);
if (literal->value.get_type() == Variant::STRING) {
opt = "^" + opt; opt = "^" + opt;
} }
} else {
opt = "^" + opt;
}
}
} }
ScriptLanguage::CodeCompletionOption option(opt, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); ScriptLanguage::CodeCompletionOption option(opt, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION);
r_result.insert(option.display, option); r_result.insert(option.display, option);
@@ -3007,13 +3022,13 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
r_arghint = _make_arguments_hint(info, p_argidx); r_arghint = _make_arguments_hint(info, p_argidx);
} }
if (p_argidx == 1 && p_context.node && p_context.node->type == GDScriptParser::Node::CALL && ClassDB::is_parent_class(class_name, SNAME("Tween")) && p_method == SNAME("tween_property")) { if (p_argidx == 1 && p_call && ClassDB::is_parent_class(class_name, SNAME("Tween")) && method == SNAME("tween_property")) {
// Get tweened objects properties. // Get tweened objects properties.
if (static_cast<GDScriptParser::CallNode *>(p_context.node)->arguments.is_empty()) { if (p_call->arguments.is_empty()) {
base_type.kind = GDScriptParser::DataType::UNRESOLVED; base_type.kind = GDScriptParser::DataType::UNRESOLVED;
break; break;
} }
GDScriptParser::ExpressionNode *tweened_object = static_cast<GDScriptParser::CallNode *>(p_context.node)->arguments[0]; GDScriptParser::ExpressionNode *tweened_object = p_call->arguments[0];
if (!tweened_object) { if (!tweened_object) {
base_type.kind = GDScriptParser::DataType::UNRESOLVED; base_type.kind = GDScriptParser::DataType::UNRESOLVED;
break; break;
@@ -3033,8 +3048,15 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
} }
String name = E.name.quote(quote_style); String name = E.name.quote(quote_style);
if (use_node_paths) { if (use_node_paths) {
if (p_call->arguments.size() > p_argidx && p_call->arguments[p_argidx] && p_call->arguments[p_argidx]->type == GDScriptParser::Node::LITERAL) {
GDScriptParser::LiteralNode *literal = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[p_argidx]);
if (literal->value.get_type() == Variant::STRING) {
name = "^" + name; name = "^" + name;
} }
} else {
name = "^" + name;
}
}
ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, ScriptLanguage::CodeCompletionLocation::LOCATION_LOCAL + n); ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, ScriptLanguage::CodeCompletionLocation::LOCATION_LOCAL + n);
r_result.insert(option.display, option); r_result.insert(option.display, option);
} }
@@ -3051,8 +3073,15 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) {
String name = member.get_name().quote(quote_style); String name = member.get_name().quote(quote_style);
if (use_node_paths) { if (use_node_paths) {
if (p_call->arguments.size() > p_argidx && p_call->arguments[p_argidx] && p_call->arguments[p_argidx]->type == GDScriptParser::Node::LITERAL) {
GDScriptParser::LiteralNode *literal = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[p_argidx]);
if (literal->value.get_type() == Variant::STRING) {
name = "^" + name; name = "^" + name;
} }
} else {
name = "^" + name;
}
}
ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, ScriptLanguage::CodeCompletionLocation::LOCATION_LOCAL + n); ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, ScriptLanguage::CodeCompletionLocation::LOCATION_LOCAL + n);
r_result.insert(option.display, option); r_result.insert(option.display, option);
} }
@@ -3078,14 +3107,21 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
} }
String name = E.name.quote(quote_style); String name = E.name.quote(quote_style);
if (use_node_paths) { if (use_node_paths) {
if (p_call->arguments.size() > p_argidx && p_call->arguments[p_argidx] && p_call->arguments[p_argidx]->type == GDScriptParser::Node::LITERAL) {
GDScriptParser::LiteralNode *literal = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[p_argidx]);
if (literal->value.get_type() == Variant::STRING) {
name = "^" + name; name = "^" + name;
} }
} else {
name = "^" + name;
}
}
ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER); ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER);
r_result.insert(option.display, option); r_result.insert(option.display, option);
} }
} }
if (p_argidx == 0 && ClassDB::is_parent_class(class_name, SNAME("Node")) && (p_method == SNAME("get_node") || p_method == SNAME("has_node"))) { if (p_argidx == 0 && ClassDB::is_parent_class(class_name, SNAME("Node")) && (method == SNAME("get_node") || method == SNAME("has_node"))) {
// Get autoloads // Get autoloads
List<PropertyInfo> props; List<PropertyInfo> props;
ProjectSettings::get_singleton()->get_property_list(&props); ProjectSettings::get_singleton()->get_property_list(&props);
@@ -3098,14 +3134,21 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
String name = s.get_slicec('/', 1); String name = s.get_slicec('/', 1);
String path = ("/root/" + name).quote(quote_style); String path = ("/root/" + name).quote(quote_style);
if (use_node_paths) { if (use_node_paths) {
if (p_call->arguments.size() > p_argidx && p_call->arguments[p_argidx] && p_call->arguments[p_argidx]->type == GDScriptParser::Node::LITERAL) {
GDScriptParser::LiteralNode *literal = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[p_argidx]);
if (literal->value.get_type() == Variant::STRING) {
path = "^" + path; path = "^" + path;
} }
} else {
path = "^" + path;
}
}
ScriptLanguage::CodeCompletionOption option(path, ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH); ScriptLanguage::CodeCompletionOption option(path, ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH);
r_result.insert(option.display, option); r_result.insert(option.display, option);
} }
} }
if (p_argidx == 0 && method_args > 0 && ClassDB::is_parent_class(class_name, SNAME("InputEvent")) && p_method.operator String().contains("action")) { if (p_argidx == 0 && method_args > 0 && ClassDB::is_parent_class(class_name, SNAME("InputEvent")) && method.operator String().contains("action")) {
// Get input actions // Get input actions
List<PropertyInfo> props; List<PropertyInfo> props;
ProjectSettings::get_singleton()->get_property_list(&props); ProjectSettings::get_singleton()->get_property_list(&props);
@@ -3116,14 +3159,21 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
} }
String name = s.get_slicec('/', 1).quote(quote_style); String name = s.get_slicec('/', 1).quote(quote_style);
if (use_string_names) { if (use_string_names) {
if (p_call->arguments.size() > p_argidx && p_call->arguments[p_argidx] && p_call->arguments[p_argidx]->type == GDScriptParser::Node::LITERAL) {
GDScriptParser::LiteralNode *literal = static_cast<GDScriptParser::LiteralNode *>(p_call->arguments[p_argidx]);
if (literal->value.get_type() == Variant::STRING) {
name = "&" + name; name = "&" + name;
} }
} else {
name = "&" + name;
}
}
ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT); ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT);
r_result.insert(option.display, option); r_result.insert(option.display, option);
} }
} }
if (EDITOR_GET("text_editor/completion/complete_file_paths")) { if (EDITOR_GET("text_editor/completion/complete_file_paths")) {
if (p_argidx == 0 && p_method == SNAME("change_scene_to_file") && ClassDB::is_parent_class(class_name, SNAME("SceneTree"))) { if (p_argidx == 0 && method == SNAME("change_scene_to_file") && ClassDB::is_parent_class(class_name, SNAME("SceneTree"))) {
HashMap<String, ScriptLanguage::CodeCompletionOption> list; HashMap<String, ScriptLanguage::CodeCompletionOption> list;
_get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), list, SNAME("PackedScene")); _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), list, SNAME("PackedScene"));
for (const KeyValue<String, ScriptLanguage::CodeCompletionOption> &key_value_pair : list) { for (const KeyValue<String, ScriptLanguage::CodeCompletionOption> &key_value_pair : list) {
@@ -3147,7 +3197,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
List<MethodInfo> methods; List<MethodInfo> methods;
base.get_method_list(&methods); base.get_method_list(&methods);
for (const MethodInfo &E : methods) { for (const MethodInfo &E : methods) {
if (E.name == p_method) { if (E.name == method) {
r_arghint = _make_arguments_hint(E, p_argidx); r_arghint = _make_arguments_hint(E, p_argidx);
return; return;
} }
@@ -3358,7 +3408,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
GDScriptCompletionIdentifier ci; GDScriptCompletionIdentifier ci;
ci.type = base_type; ci.type = base_type;
ci.value = base; ci.value = base;
_find_call_arguments(p_context, ci, call->function_name, p_argidx, _static, r_result, r_arghint); _list_call_arguments(p_context, ci, call, p_argidx, _static, r_result, r_arghint);
r_forced = r_result.size() > 0; r_forced = r_result.size() > 0;
} }

View File

@@ -0,0 +1,11 @@
[input]
add_node_path_literals=true
[output]
include=[
{"insert_text": "\"property_of_a\""},
{"insert_text": "\"name\""},
]
exclude=[
{"insert_text": "^\"property_of_a\""},
{"insert_text": "^\"name\""},
]

View File

@@ -0,0 +1,8 @@
extends Node
const A = preload("res://completion/class_a.notest.gd")
func _ready() -> void:
var a := A.new()
var tween := get_tree().create_tween()
tween.tween_property(a, ^"")

View File

@@ -0,0 +1,9 @@
[input]
add_string_name_literals=true
[output]
include=[
{"insert_text": "\"test_input_action\""},
]
exclude=[
{"insert_text": "&\"test_input_action\""},
]

View File

@@ -0,0 +1,3 @@
func _input(event: InputEvent) -> void:
event.is_action_pressed(&"")
pass