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
commit c977b597b8
No known key found for this signature in database
GPG key ID: 8C6E5FEB5FC03CCC
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;
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 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()) {
switch (base_type.kind) {
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;
do {
@ -2939,8 +2940,8 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
return;
}
if (base_type.class_type->has_member(p_method)) {
const GDScriptParser::ClassNode::Member &member = base_type.class_type->get_member(p_method);
if (base_type.class_type->has_member(method)) {
const GDScriptParser::ClassNode::Member &member = base_type.class_type->get_member(method);
if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) {
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;
} break;
case GDScriptParser::DataType::SCRIPT: {
if (base_type.script_type->is_valid() && base_type.script_type->has_method(p_method)) {
r_arghint = _make_arguments_hint(base_type.script_type->get_method_info(p_method), p_argidx);
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(method), p_argidx);
return;
}
Ref<Script> base_script = base_type.script_type->get_base_script();
@ -2974,21 +2975,35 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
MethodInfo info;
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();
if (base.get_type() == Variant::OBJECT) {
Object *obj = base.operator Object *();
if (obj) {
List<String> options;
obj->get_argument_options(p_method, p_argidx, &options);
obj->get_argument_options(method, p_argidx, &options);
for (String &opt : options) {
// Handle user preference.
if (opt.is_quoted()) {
opt = opt.unquote().quote(quote_style);
if (use_string_names && info.arguments[p_argidx].type == Variant::STRING_NAME) {
opt = "&" + opt;
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;
}
} else {
opt = "&" + opt;
}
} else if (use_node_paths && info.arguments[p_argidx].type == Variant::NODE_PATH) {
opt = "^" + opt;
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;
}
} else {
opt = "^" + opt;
}
}
}
ScriptLanguage::CodeCompletionOption option(opt, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION);
@ -3007,13 +3022,13 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
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.
if (static_cast<GDScriptParser::CallNode *>(p_context.node)->arguments.is_empty()) {
if (p_call->arguments.is_empty()) {
base_type.kind = GDScriptParser::DataType::UNRESOLVED;
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) {
base_type.kind = GDScriptParser::DataType::UNRESOLVED;
break;
@ -3033,7 +3048,14 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
}
String name = E.name.quote(quote_style);
if (use_node_paths) {
name = "^" + 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) {
name = "^" + name;
}
} else {
name = "^" + name;
}
}
ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, ScriptLanguage::CodeCompletionLocation::LOCATION_LOCAL + n);
r_result.insert(option.display, option);
@ -3051,7 +3073,14 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) {
String name = member.get_name().quote(quote_style);
if (use_node_paths) {
name = "^" + 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) {
name = "^" + name;
}
} else {
name = "^" + name;
}
}
ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, ScriptLanguage::CodeCompletionLocation::LOCATION_LOCAL + n);
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);
if (use_node_paths) {
name = "^" + 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) {
name = "^" + name;
}
} else {
name = "^" + name;
}
}
ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER);
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
List<PropertyInfo> 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 path = ("/root/" + name).quote(quote_style);
if (use_node_paths) {
path = "^" + 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) {
path = "^" + path;
}
} else {
path = "^" + path;
}
}
ScriptLanguage::CodeCompletionOption option(path, ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH);
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
List<PropertyInfo> 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);
if (use_string_names) {
name = "&" + 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) {
name = "&" + name;
}
} else {
name = "&" + name;
}
}
ScriptLanguage::CodeCompletionOption option(name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT);
r_result.insert(option.display, option);
}
}
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;
_get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), list, SNAME("PackedScene"));
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;
base.get_method_list(&methods);
for (const MethodInfo &E : methods) {
if (E.name == p_method) {
if (E.name == method) {
r_arghint = _make_arguments_hint(E, p_argidx);
return;
}
@ -3358,7 +3408,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
GDScriptCompletionIdentifier ci;
ci.type = base_type;
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;
}