瀏覽代碼

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

[Autocomplete] Avoid prepending literals when the character has already been typed
Thaddeus Crews 3 月之前
父節點
當前提交
c977b597b8

+ 73 - 23
modules/gdscript/gdscript_editor.cpp

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

+ 11 - 0
modules/gdscript/tests/scripts/completion/argument_options/string_literals/add_node_path_tween_prepended.cfg

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

+ 8 - 0
modules/gdscript/tests/scripts/completion/argument_options/string_literals/add_node_path_tween_prepended.gd

@@ -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, ^"➡")

+ 9 - 0
modules/gdscript/tests/scripts/completion/argument_options/string_literals/add_string_name_input_event_prepended.cfg

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

+ 3 - 0
modules/gdscript/tests/scripts/completion/argument_options/string_literals/add_string_name_input_event_prepended.gd

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