2
0
Эх сурвалжийг харах

Merge pull request #75746 from ajreckof/order_autocomplete

Sort code autocompletion with rules
Rémi Verschelde 2 жил өмнө
parent
commit
577ab3c565

+ 46 - 1
core/object/script_language.cpp

@@ -34,7 +34,6 @@
 #include "core/core_string_names.h"
 #include "core/core_string_names.h"
 #include "core/debugger/engine_debugger.h"
 #include "core/debugger/engine_debugger.h"
 #include "core/debugger/script_debugger.h"
 #include "core/debugger/script_debugger.h"
-#include "core/variant/typed_array.h"
 
 
 #include <stdint.h>
 #include <stdint.h>
 
 
@@ -461,6 +460,52 @@ void ScriptLanguage::get_core_type_words(List<String> *p_core_type_words) const
 void ScriptLanguage::frame() {
 void ScriptLanguage::frame() {
 }
 }
 
 
+TypedArray<int> ScriptLanguage::CodeCompletionOption::get_option_characteristics(const String &p_base) {
+	// Return characacteristics of the match found by order of importance.
+	// Matches will be ranked by a lexicographical order on the vector returned by this function.
+	// The lower values indicate better matches and that they should go before in the order of appearance.
+	if (last_matches == matches) {
+		return charac;
+	}
+	charac.clear();
+	// Ensure base is not empty and at the same time that matches is not empty too.
+	if (p_base.length() == 0) {
+		last_matches = matches;
+		charac.push_back(location);
+		return charac;
+	}
+	charac.push_back(matches.size());
+	charac.push_back((matches[0].first == 0) ? 0 : 1);
+	charac.push_back(location);
+	const char32_t *target_char = &p_base[0];
+	int bad_case = 0;
+	for (const Pair<int, int> &match_segment : matches) {
+		const char32_t *string_to_complete_char = &display[match_segment.first];
+		for (int j = 0; j < match_segment.second; j++, string_to_complete_char++, target_char++) {
+			if (*string_to_complete_char != *target_char) {
+				bad_case++;
+			}
+		}
+	}
+	charac.push_back(bad_case);
+	charac.push_back(matches[0].first);
+	last_matches = matches;
+	return charac;
+}
+
+void ScriptLanguage::CodeCompletionOption::clear_characteristics() {
+	charac = TypedArray<int>();
+}
+
+TypedArray<int> ScriptLanguage::CodeCompletionOption::get_option_cached_characteristics() const {
+	// Only returns the cached value and warns if it was not updated since the last change of matches.
+	if (last_matches != matches) {
+		WARN_PRINT("Characteristics are not up to date.");
+	}
+
+	return charac;
+}
+
 bool PlaceHolderScriptInstance::set(const StringName &p_name, const Variant &p_value) {
 bool PlaceHolderScriptInstance::set(const StringName &p_name, const Variant &p_value) {
 	if (script->is_placeholder_fallback_enabled()) {
 	if (script->is_placeholder_fallback_enabled()) {
 		return false;
 		return false;

+ 12 - 2
core/object/script_language.h

@@ -35,6 +35,7 @@
 #include "core/io/resource.h"
 #include "core/io/resource.h"
 #include "core/templates/pair.h"
 #include "core/templates/pair.h"
 #include "core/templates/rb_map.h"
 #include "core/templates/rb_map.h"
+#include "core/variant/typed_array.h"
 
 
 class ScriptLanguage;
 class ScriptLanguage;
 template <typename T>
 template <typename T>
@@ -305,8 +306,8 @@ public:
 	virtual Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) { return ERR_UNAVAILABLE; }
 	virtual Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) { return ERR_UNAVAILABLE; }
 	virtual bool overrides_external_editor() { return false; }
 	virtual bool overrides_external_editor() { return false; }
 
 
-	/* Keep enum in Sync with:                               */
-	/* /scene/gui/code_edit.h - CodeEdit::CodeCompletionKind */
+	// Keep enums in sync with:
+	// scene/gui/code_edit.h - CodeEdit::CodeCompletionKind
 	enum CodeCompletionKind {
 	enum CodeCompletionKind {
 		CODE_COMPLETION_KIND_CLASS,
 		CODE_COMPLETION_KIND_CLASS,
 		CODE_COMPLETION_KIND_FUNCTION,
 		CODE_COMPLETION_KIND_FUNCTION,
@@ -321,6 +322,7 @@ public:
 		CODE_COMPLETION_KIND_MAX
 		CODE_COMPLETION_KIND_MAX
 	};
 	};
 
 
+	// scene/gui/code_edit.h - CodeEdit::CodeCompletionLocation
 	enum CodeCompletionLocation {
 	enum CodeCompletionLocation {
 		LOCATION_LOCAL = 0,
 		LOCATION_LOCAL = 0,
 		LOCATION_PARENT_MASK = 1 << 8,
 		LOCATION_PARENT_MASK = 1 << 8,
@@ -336,6 +338,7 @@ public:
 		Ref<Resource> icon;
 		Ref<Resource> icon;
 		Variant default_value;
 		Variant default_value;
 		Vector<Pair<int, int>> matches;
 		Vector<Pair<int, int>> matches;
+		Vector<Pair<int, int>> last_matches;
 		int location = LOCATION_OTHER;
 		int location = LOCATION_OTHER;
 
 
 		CodeCompletionOption() {}
 		CodeCompletionOption() {}
@@ -346,6 +349,13 @@ public:
 			kind = p_kind;
 			kind = p_kind;
 			location = p_location;
 			location = p_location;
 		}
 		}
+
+		TypedArray<int> get_option_characteristics(const String &p_base);
+		void clear_characteristics();
+		TypedArray<int> get_option_cached_characteristics() const;
+
+	private:
+		TypedArray<int> charac;
 	};
 	};
 
 
 	virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<CodeCompletionOption> *r_options, bool &r_force, String &r_call_hint) { return ERR_UNAVAILABLE; }
 	virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<CodeCompletionOption> *r_options, bool &r_force, String &r_call_hint) { return ERR_UNAVAILABLE; }

+ 14 - 0
doc/classes/CodeEdit.xml

@@ -49,8 +49,10 @@
 			<param index="3" name="text_color" type="Color" default="Color(1, 1, 1, 1)" />
 			<param index="3" name="text_color" type="Color" default="Color(1, 1, 1, 1)" />
 			<param index="4" name="icon" type="Resource" default="null" />
 			<param index="4" name="icon" type="Resource" default="null" />
 			<param index="5" name="value" type="Variant" default="0" />
 			<param index="5" name="value" type="Variant" default="0" />
+			<param index="6" name="location" type="int" default="1024" />
 			<description>
 			<description>
 				Submits an item to the queue of potential candidates for the autocomplete menu. Call [method update_code_completion_options] to update the list.
 				Submits an item to the queue of potential candidates for the autocomplete menu. Call [method update_code_completion_options] to update the list.
+				[param location] indicates location of the option relative to the location of the code completion query. See [enum CodeEdit.CodeCompletionLocation] for how to set this value.
 				[b]Note:[/b] This list will replace all current candidates.
 				[b]Note:[/b] This list will replace all current candidates.
 			</description>
 			</description>
 		</method>
 		</method>
@@ -560,6 +562,18 @@
 		<constant name="KIND_PLAIN_TEXT" value="9" enum="CodeCompletionKind">
 		<constant name="KIND_PLAIN_TEXT" value="9" enum="CodeCompletionKind">
 			Marks the option as unclassified or plain text.
 			Marks the option as unclassified or plain text.
 		</constant>
 		</constant>
+		<constant name="LOCATION_LOCAL" value="0" enum="CodeCompletionLocation">
+			The option is local to the location of the code completion query - e.g. a local variable. Subsequent value of location represent options from the outer class, the exact value represent how far they are (in terms of inner classes).
+		</constant>
+		<constant name="LOCATION_PARENT_MASK" value="256" enum="CodeCompletionLocation">
+			The option is from the containing class or a parent class, relative to the location of the code completion query. Perform a bitwise OR with the class depth (e.g. 0 for the local class, 1 for the parent, 2 for the grandparent, etc) to store the depth of an option in the class or a parent class.
+		</constant>
+		<constant name="LOCATION_OTHER_USER_CODE" value="512" enum="CodeCompletionLocation">
+			The option is from user code which is not local and not in a derived class (e.g. Autoload Singletons).
+		</constant>
+		<constant name="LOCATION_OTHER" value="1024" enum="CodeCompletionLocation">
+			The option is from other engine code, not covered by the other enum constants - e.g. built-in classes.
+		</constant>
 	</constants>
 	</constants>
 	<theme_items>
 	<theme_items>
 		<theme_item name="background_color" data_type="color" type="Color" default="Color(0, 0, 0, 0)">
 		<theme_item name="background_color" data_type="color" type="Color" default="Color(0, 0, 0, 0)">

+ 1 - 1
doc/classes/ScriptLanguageExtension.xml

@@ -358,7 +358,7 @@
 		<constant name="LOOKUP_RESULT_MAX" value="9" enum="LookupResultType">
 		<constant name="LOOKUP_RESULT_MAX" value="9" enum="LookupResultType">
 		</constant>
 		</constant>
 		<constant name="LOCATION_LOCAL" value="0" enum="CodeCompletionLocation">
 		<constant name="LOCATION_LOCAL" value="0" enum="CodeCompletionLocation">
-			The option is local to the location of the code completion query - e.g. a local variable.
+			The option is local to the location of the code completion query - e.g. a local variable. Subsequent value of location represent options from the outer class, the exact value represent how far they are (in terms of inner classes).
 		</constant>
 		</constant>
 		<constant name="LOCATION_PARENT_MASK" value="256" enum="CodeCompletionLocation">
 		<constant name="LOCATION_PARENT_MASK" value="256" enum="CodeCompletionLocation">
 			The option is from the containing class or a parent class, relative to the location of the code completion query. Perform a bitwise OR with the class depth (e.g. 0 for the local class, 1 for the parent, 2 for the grandparent, etc) to store the depth of an option in the class or a parent class.
 			The option is from the containing class or a parent class, relative to the location of the code completion query. Perform a bitwise OR with the class depth (e.g. 0 for the local class, 1 for the parent, 2 for the grandparent, etc) to store the depth of an option in the class or a parent class.

+ 1 - 1
editor/code_editor.cpp

@@ -943,7 +943,7 @@ void CodeTextEditor::_complete_request() {
 		} else if (e.insert_text.begins_with("#") || e.insert_text.begins_with("//")) {
 		} else if (e.insert_text.begins_with("#") || e.insert_text.begins_with("//")) {
 			font_color = completion_comment_color;
 			font_color = completion_comment_color;
 		}
 		}
-		text_editor->add_code_completion_option((CodeEdit::CodeCompletionKind)e.kind, e.display, e.insert_text, font_color, _get_completion_icon(e), e.default_value);
+		text_editor->add_code_completion_option((CodeEdit::CodeCompletionKind)e.kind, e.display, e.insert_text, font_color, _get_completion_icon(e), e.default_value, e.location);
 	}
 	}
 	text_editor->update_code_completion_options(forced);
 	text_editor->update_code_completion_options(forced);
 }
 }

+ 0 - 2
editor/plugins/script_text_editor.cpp

@@ -753,8 +753,6 @@ void ScriptTextEditor::_code_complete_script(const String &p_code, List<ScriptLa
 	String hint;
 	String hint;
 	Error err = script->get_language()->complete_code(p_code, script->get_path(), base, r_options, r_force, hint);
 	Error err = script->get_language()->complete_code(p_code, script->get_path(), base, r_options, r_force, hint);
 
 
-	r_options->sort_custom_inplace<CodeCompletionOptionCompare>();
-
 	if (err == OK) {
 	if (err == OK) {
 		code_editor->get_text_editor()->set_code_hint(hint);
 		code_editor->get_text_editor()->set_code_hint(hint);
 	}
 	}

+ 0 - 47
editor/plugins/script_text_editor.h

@@ -259,51 +259,4 @@ public:
 	~ScriptTextEditor();
 	~ScriptTextEditor();
 };
 };
 
 
-const int KIND_COUNT = 10;
-// The order in which to sort code completion options.
-const ScriptLanguage::CodeCompletionKind KIND_SORT_ORDER[KIND_COUNT] = {
-	ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE,
-	ScriptLanguage::CODE_COMPLETION_KIND_MEMBER,
-	ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION,
-	ScriptLanguage::CODE_COMPLETION_KIND_ENUM,
-	ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL,
-	ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT,
-	ScriptLanguage::CODE_COMPLETION_KIND_CLASS,
-	ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH,
-	ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH,
-	ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT,
-};
-
-// The custom comparer which will sort completion options.
-struct CodeCompletionOptionCompare {
-	_FORCE_INLINE_ bool operator()(const ScriptLanguage::CodeCompletionOption &l, const ScriptLanguage::CodeCompletionOption &r) const {
-		if (l.location == r.location) {
-			// If locations are same, sort on kind
-			if (l.kind == r.kind) {
-				// If kinds are same, sort alphanumeric
-				return l.display < r.display;
-			}
-
-			// Sort kinds based on the const sorting array defined above. Lower index = higher priority.
-			int l_index = -1;
-			int r_index = -1;
-			for (int i = 0; i < KIND_COUNT; i++) {
-				const ScriptLanguage::CodeCompletionKind kind = KIND_SORT_ORDER[i];
-				l_index = kind == l.kind ? i : l_index;
-				r_index = kind == r.kind ? i : r_index;
-
-				if (l_index != -1 && r_index != -1) {
-					return l_index < r_index;
-				}
-			}
-
-			// This return should never be hit unless something goes wrong.
-			// l and r should always have a Kind which is in the sort order array.
-			return l.display < r.display;
-		}
-
-		return l.location < r.location;
-	}
-};
-
 #endif // SCRIPT_TEXT_EDITOR_H
 #endif // SCRIPT_TEXT_EDITOR_H

+ 8 - 7
modules/gdscript/gdscript_editor.cpp

@@ -906,19 +906,20 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio
 	}
 	}
 }
 }
 
 
-static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) {
+static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth = 0) {
 	for (int i = 0; i < p_suite->locals.size(); i++) {
 	for (int i = 0; i < p_suite->locals.size(); i++) {
 		ScriptLanguage::CodeCompletionOption option;
 		ScriptLanguage::CodeCompletionOption option;
+		int location = p_recursion_depth == 0 ? ScriptLanguage::LOCATION_LOCAL : (p_recursion_depth | ScriptLanguage::LOCATION_PARENT_MASK);
 		if (p_suite->locals[i].type == GDScriptParser::SuiteNode::Local::CONSTANT) {
 		if (p_suite->locals[i].type == GDScriptParser::SuiteNode::Local::CONSTANT) {
-			option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, ScriptLanguage::LOCATION_LOCAL);
+			option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location);
 			option.default_value = p_suite->locals[i].constant->initializer->reduced_value;
 			option.default_value = p_suite->locals[i].constant->initializer->reduced_value;
 		} else {
 		} else {
-			option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE, ScriptLanguage::LOCATION_LOCAL);
+			option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE, location);
 		}
 		}
 		r_result.insert(option.display, option);
 		r_result.insert(option.display, option);
 	}
 	}
 	if (p_suite->parent_block) {
 	if (p_suite->parent_block) {
-		_find_identifiers_in_suite(p_suite->parent_block, r_result);
+		_find_identifiers_in_suite(p_suite->parent_block, r_result, p_recursion_depth + 1);
 	}
 	}
 }
 }
 
 
@@ -933,7 +934,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
 		int classes_processed = 0;
 		int classes_processed = 0;
 		while (clss) {
 		while (clss) {
 			for (int i = 0; i < clss->members.size(); i++) {
 			for (int i = 0; i < clss->members.size(); i++) {
-				const int location = (classes_processed + p_recursion_depth) | ScriptLanguage::LOCATION_PARENT_MASK;
+				const int location = p_recursion_depth == 0 ? classes_processed : (p_recursion_depth | ScriptLanguage::LOCATION_PARENT_MASK);
 				const GDScriptParser::ClassNode::Member &member = clss->members[i];
 				const GDScriptParser::ClassNode::Member &member = clss->members[i];
 				ScriptLanguage::CodeCompletionOption option;
 				ScriptLanguage::CodeCompletionOption option;
 				switch (member.type) {
 				switch (member.type) {
@@ -1025,7 +1026,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
 	while (!base_type.has_no_type()) {
 	while (!base_type.has_no_type()) {
 		switch (base_type.kind) {
 		switch (base_type.kind) {
 			case GDScriptParser::DataType::CLASS: {
 			case GDScriptParser::DataType::CLASS: {
-				_find_identifiers_in_class(base_type.class_type, p_only_functions, base_type.is_meta_type, false, r_result, p_recursion_depth + 1);
+				_find_identifiers_in_class(base_type.class_type, p_only_functions, base_type.is_meta_type, false, r_result, p_recursion_depth);
 				// This already finds all parent identifiers, so we are done.
 				// This already finds all parent identifiers, so we are done.
 				base_type = GDScriptParser::DataType();
 				base_type = GDScriptParser::DataType();
 			} break;
 			} break;
@@ -1205,7 +1206,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context
 	}
 	}
 
 
 	if (p_context.current_class) {
 	if (p_context.current_class) {
-		_find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth + 1);
+		_find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth);
 	}
 	}
 
 
 	List<StringName> functions;
 	List<StringName> functions;

+ 95 - 133
scene/gui/code_edit.cpp

@@ -142,13 +142,12 @@ void CodeEdit::_notification(int p_what) {
 					Point2 match_pos = Point2(code_completion_rect.position.x + icon_area_size.x + theme_cache.code_completion_icon_separation, code_completion_rect.position.y + i * row_height);
 					Point2 match_pos = Point2(code_completion_rect.position.x + icon_area_size.x + theme_cache.code_completion_icon_separation, code_completion_rect.position.y + i * row_height);
 
 
 					for (int j = 0; j < code_completion_options[l].matches.size(); j++) {
 					for (int j = 0; j < code_completion_options[l].matches.size(); j++) {
-						Pair<int, int> match = code_completion_options[l].matches[j];
-						int match_offset = theme_cache.font->get_string_size(code_completion_options[l].display.substr(0, match.first), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width;
-						int match_len = theme_cache.font->get_string_size(code_completion_options[l].display.substr(match.first, match.second), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width;
+						Pair<int, int> match_segment = code_completion_options[l].matches[j];
+						int match_offset = theme_cache.font->get_string_size(code_completion_options[l].display.substr(0, match_segment.first), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width;
+						int match_len = theme_cache.font->get_string_size(code_completion_options[l].display.substr(match_segment.first, match_segment.second), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width;
 
 
 						draw_rect(Rect2(match_pos + Point2(match_offset, 0), Size2(match_len, row_height)), theme_cache.code_completion_existing_color);
 						draw_rect(Rect2(match_pos + Point2(match_offset, 0), Size2(match_len, row_height)), theme_cache.code_completion_existing_color);
 					}
 					}
-
 					tl->draw(ci, title_pos, code_completion_options[l].font_color);
 					tl->draw(ci, title_pos, code_completion_options[l].font_color);
 				}
 				}
 
 
@@ -2031,7 +2030,7 @@ void CodeEdit::request_code_completion(bool p_force) {
 	}
 	}
 }
 }
 
 
-void CodeEdit::add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color, const Ref<Resource> &p_icon, const Variant &p_value) {
+void CodeEdit::add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color, const Ref<Resource> &p_icon, const Variant &p_value, int p_location) {
 	ScriptLanguage::CodeCompletionOption completion_option;
 	ScriptLanguage::CodeCompletionOption completion_option;
 	completion_option.kind = (ScriptLanguage::CodeCompletionKind)p_type;
 	completion_option.kind = (ScriptLanguage::CodeCompletionKind)p_type;
 	completion_option.display = p_display_text;
 	completion_option.display = p_display_text;
@@ -2039,6 +2038,7 @@ void CodeEdit::add_code_completion_option(CodeCompletionKind p_type, const Strin
 	completion_option.font_color = p_text_color;
 	completion_option.font_color = p_text_color;
 	completion_option.icon = p_icon;
 	completion_option.icon = p_icon;
 	completion_option.default_value = p_value;
 	completion_option.default_value = p_value;
+	completion_option.location = p_location;
 	code_completion_option_submitted.push_back(completion_option);
 	code_completion_option_submitted.push_back(completion_option);
 }
 }
 
 
@@ -2063,6 +2063,7 @@ TypedArray<Dictionary> CodeEdit::get_code_completion_options() const {
 		option["insert_text"] = code_completion_options[i].insert_text;
 		option["insert_text"] = code_completion_options[i].insert_text;
 		option["font_color"] = code_completion_options[i].font_color;
 		option["font_color"] = code_completion_options[i].font_color;
 		option["icon"] = code_completion_options[i].icon;
 		option["icon"] = code_completion_options[i].icon;
+		option["location"] = code_completion_options[i].location;
 		option["default_value"] = code_completion_options[i].default_value;
 		option["default_value"] = code_completion_options[i].default_value;
 		completion_options[i] = option;
 		completion_options[i] = option;
 	}
 	}
@@ -2081,6 +2082,7 @@ Dictionary CodeEdit::get_code_completion_option(int p_index) const {
 	option["insert_text"] = code_completion_options[p_index].insert_text;
 	option["insert_text"] = code_completion_options[p_index].insert_text;
 	option["font_color"] = code_completion_options[p_index].font_color;
 	option["font_color"] = code_completion_options[p_index].font_color;
 	option["icon"] = code_completion_options[p_index].icon;
 	option["icon"] = code_completion_options[p_index].icon;
+	option["location"] = code_completion_options[p_index].location;
 	option["default_value"] = code_completion_options[p_index].default_value;
 	option["default_value"] = code_completion_options[p_index].default_value;
 	return option;
 	return option;
 }
 }
@@ -2424,9 +2426,14 @@ void CodeEdit::_bind_methods() {
 	BIND_ENUM_CONSTANT(KIND_FILE_PATH);
 	BIND_ENUM_CONSTANT(KIND_FILE_PATH);
 	BIND_ENUM_CONSTANT(KIND_PLAIN_TEXT);
 	BIND_ENUM_CONSTANT(KIND_PLAIN_TEXT);
 
 
+	BIND_ENUM_CONSTANT(LOCATION_LOCAL);
+	BIND_ENUM_CONSTANT(LOCATION_PARENT_MASK);
+	BIND_ENUM_CONSTANT(LOCATION_OTHER_USER_CODE)
+	BIND_ENUM_CONSTANT(LOCATION_OTHER);
+
 	ClassDB::bind_method(D_METHOD("get_text_for_code_completion"), &CodeEdit::get_text_for_code_completion);
 	ClassDB::bind_method(D_METHOD("get_text_for_code_completion"), &CodeEdit::get_text_for_code_completion);
 	ClassDB::bind_method(D_METHOD("request_code_completion", "force"), &CodeEdit::request_code_completion, DEFVAL(false));
 	ClassDB::bind_method(D_METHOD("request_code_completion", "force"), &CodeEdit::request_code_completion, DEFVAL(false));
-	ClassDB::bind_method(D_METHOD("add_code_completion_option", "type", "display_text", "insert_text", "text_color", "icon", "value"), &CodeEdit::add_code_completion_option, DEFVAL(Color(1, 1, 1)), DEFVAL(Ref<Resource>()), DEFVAL(Variant::NIL));
+	ClassDB::bind_method(D_METHOD("add_code_completion_option", "type", "display_text", "insert_text", "text_color", "icon", "value", "location"), &CodeEdit::add_code_completion_option, DEFVAL(Color(1, 1, 1)), DEFVAL(Ref<Resource>()), DEFVAL(Variant::NIL), DEFVAL(LOCATION_OTHER));
 	ClassDB::bind_method(D_METHOD("update_code_completion_options", "force"), &CodeEdit::update_code_completion_options);
 	ClassDB::bind_method(D_METHOD("update_code_completion_options", "force"), &CodeEdit::update_code_completion_options);
 	ClassDB::bind_method(D_METHOD("get_code_completion_options"), &CodeEdit::get_code_completion_options);
 	ClassDB::bind_method(D_METHOD("get_code_completion_options"), &CodeEdit::get_code_completion_options);
 	ClassDB::bind_method(D_METHOD("get_code_completion_option", "index"), &CodeEdit::get_code_completion_option);
 	ClassDB::bind_method(D_METHOD("get_code_completion_option", "index"), &CodeEdit::get_code_completion_option);
@@ -2954,6 +2961,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
 			option["font_color"] = E.font_color;
 			option["font_color"] = E.font_color;
 			option["icon"] = E.icon;
 			option["icon"] = E.icon;
 			option["default_value"] = E.default_value;
 			option["default_value"] = E.default_value;
+			option["location"] = E.location;
 			completion_options_sources[i] = option;
 			completion_options_sources[i] = option;
 			i++;
 			i++;
 		}
 		}
@@ -2977,6 +2985,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
 			option.insert_text = completion_options[i].get("insert_text");
 			option.insert_text = completion_options[i].get("insert_text");
 			option.font_color = completion_options[i].get("font_color");
 			option.font_color = completion_options[i].get("font_color");
 			option.icon = completion_options[i].get("icon");
 			option.icon = completion_options[i].get("icon");
+			option.location = completion_options[i].get("location");
 			option.default_value = completion_options[i].get("default_value");
 			option.default_value = completion_options[i].get("default_value");
 
 
 			int offset = 0;
 			int offset = 0;
@@ -3063,7 +3072,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
 	}
 	}
 
 
 	/* Filter Options. */
 	/* Filter Options. */
-	/* For now handle only tradional quoted strings. */
+	/* For now handle only traditional quoted strings. */
 	bool single_quote = in_string != -1 && first_quote_col > 0 && delimiters[in_string].start_key == "'";
 	bool single_quote = in_string != -1 && first_quote_col > 0 && delimiters[in_string].start_key == "'";
 
 
 	code_completion_options.clear();
 	code_completion_options.clear();
@@ -3075,23 +3084,16 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
 		return;
 		return;
 	}
 	}
 
 
-	Vector<ScriptLanguage::CodeCompletionOption> completion_options_casei;
-	Vector<ScriptLanguage::CodeCompletionOption> completion_options_substr;
-	Vector<ScriptLanguage::CodeCompletionOption> completion_options_substr_casei;
-	Vector<ScriptLanguage::CodeCompletionOption> completion_options_subseq;
-	Vector<ScriptLanguage::CodeCompletionOption> completion_options_subseq_casei;
-
 	int max_width = 0;
 	int max_width = 0;
 	String string_to_complete_lower = string_to_complete.to_lower();
 	String string_to_complete_lower = string_to_complete.to_lower();
+
 	for (ScriptLanguage::CodeCompletionOption &option : code_completion_option_sources) {
 	for (ScriptLanguage::CodeCompletionOption &option : code_completion_option_sources) {
+		option.matches.clear();
 		if (single_quote && option.display.is_quoted()) {
 		if (single_quote && option.display.is_quoted()) {
 			option.display = option.display.unquote().quote("'");
 			option.display = option.display.unquote().quote("'");
 		}
 		}
 
 
-		int offset = 0;
-		if (option.default_value.get_type() == Variant::COLOR) {
-			offset = line_height;
-		}
+		int offset = option.default_value.get_type() == Variant::COLOR ? line_height : 0;
 
 
 		if (in_string != -1) {
 		if (in_string != -1) {
 			String quote = single_quote ? "'" : "\"";
 			String quote = single_quote ? "'" : "\"";
@@ -3104,6 +3106,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
 		}
 		}
 
 
 		if (string_to_complete.length() == 0) {
 		if (string_to_complete.length() == 0) {
+			option.get_option_characteristics(string_to_complete);
 			code_completion_options.push_back(option);
 			code_completion_options.push_back(option);
 			if (theme_cache.font.is_valid()) {
 			if (theme_cache.font.is_valid()) {
 				max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
 				max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
@@ -3111,139 +3114,73 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
 			continue;
 			continue;
 		}
 		}
 
 
-		/* This code works the same as:
-
-		if (option.display.begins_with(s)) {
-			completion_options.push_back(option);
-		} else if (option.display.to_lower().begins_with(s.to_lower())) {
-			completion_options_casei.push_back(option);
-		} else if (s.is_subsequence_of(option.display)) {
-			completion_options_subseq.push_back(option);
-		} else if (s.is_subsequence_ofn(option.display)) {
-			completion_options_subseq_casei.push_back(option);
-		}
-
-		But is more performant due to being inlined and looping over the characters only once
-		*/
-
-		String display_lower = option.display.to_lower();
-
-		const char32_t *ssq = &string_to_complete[0];
-		const char32_t *ssq_lower = &string_to_complete_lower[0];
-
-		const char32_t *tgt = &option.display[0];
-		const char32_t *tgt_lower = &display_lower[0];
+		String target_lower = option.display.to_lower();
+		const char32_t *string_to_complete_char_lower = &string_to_complete_lower[0];
+		const char32_t *target_char_lower = &target_lower[0];
 
 
-		const char32_t *sst = &string_to_complete[0];
-		const char32_t *sst_lower = &display_lower[0];
-
-		Vector<Pair<int, int>> ssq_matches;
-		int ssq_match_start = 0;
-		int ssq_match_len = 0;
-
-		Vector<Pair<int, int>> ssq_lower_matches;
-		int ssq_lower_match_start = 0;
-		int ssq_lower_match_len = 0;
-
-		int sst_start = -1;
-		int sst_lower_start = -1;
-
-		for (int i = 0; *tgt; tgt++, tgt_lower++, i++) {
-			// Check substring.
-			if (*sst == *tgt) {
-				sst++;
-				if (sst_start == -1) {
-					sst_start = i;
-				}
-			} else if (sst_start != -1 && *sst) {
-				sst = &string_to_complete[0];
-				sst_start = -1;
+		Vector<Vector<Pair<int, int>>> all_possible_subsequence_matches;
+		for (int i = 0; *target_char_lower; i++, target_char_lower++) {
+			if (*target_char_lower == *string_to_complete_char_lower) {
+				all_possible_subsequence_matches.push_back({ { i, 1 } });
 			}
 			}
+		}
+		string_to_complete_char_lower++;
 
 
-			// Check subsequence.
-			if (*ssq == *tgt) {
-				ssq++;
-				if (ssq_match_len == 0) {
-					ssq_match_start = i;
+		for (int i = 1; *string_to_complete_char_lower && (all_possible_subsequence_matches.size() > 0); i++, string_to_complete_char_lower++) {
+			// find all occurrences of ssq_lower to avoid looking everywhere each time
+			Vector<int> all_ocurence;
+			for (int j = i; j < target_lower.length(); j++) {
+				if (target_lower[j] == *string_to_complete_char_lower) {
+					all_ocurence.push_back(j);
 				}
 				}
-				ssq_match_len++;
-			} else if (ssq_match_len > 0) {
-				ssq_matches.push_back(Pair<int, int>(ssq_match_start, ssq_match_len));
-				ssq_match_len = 0;
 			}
 			}
-
-			// Check lower substring.
-			if (*sst_lower == *tgt) {
-				sst_lower++;
-				if (sst_lower_start == -1) {
-					sst_lower_start = i;
+			Vector<Vector<Pair<int, int>>> next_subsequence_matches;
+			for (Vector<Pair<int, int>> &subsequence_matches : all_possible_subsequence_matches) {
+				Pair<int, int> match_last_segment = subsequence_matches[subsequence_matches.size() - 1];
+				int next_index = match_last_segment.first + match_last_segment.second;
+				// get the last index from current sequence
+				// and look for next char starting from that index
+				if (target_lower[next_index] == *string_to_complete_char_lower) {
+					Vector<Pair<int, int>> new_matches = subsequence_matches;
+					new_matches.write[new_matches.size() - 1].second++;
+					next_subsequence_matches.push_back(new_matches);
 				}
 				}
-			} else if (sst_lower_start != -1 && *sst_lower) {
-				sst_lower = &string_to_complete[0];
-				sst_lower_start = -1;
-			}
-
-			// Check lower subsequence.
-			if (*ssq_lower == *tgt_lower) {
-				ssq_lower++;
-				if (ssq_lower_match_len == 0) {
-					ssq_lower_match_start = i;
+				for (int index : all_ocurence) {
+					if (index > next_index) {
+						Vector<Pair<int, int>> new_matches = subsequence_matches;
+						new_matches.push_back({ index, 1 });
+						next_subsequence_matches.push_back(new_matches);
+					}
 				}
 				}
-				ssq_lower_match_len++;
-			} else if (ssq_lower_match_len > 0) {
-				ssq_lower_matches.push_back(Pair<int, int>(ssq_lower_match_start, ssq_lower_match_len));
-				ssq_lower_match_len = 0;
 			}
 			}
-		}
-
-		/* Matched the whole subsequence in s. */
-		if (!*ssq) { // Matched the whole subsequence in s.
-			option.matches.clear();
-
-			if (sst_start == 0) { // Matched substring in beginning of s.
-				option.matches.push_back(Pair<int, int>(sst_start, string_to_complete.length()));
-				code_completion_options.push_back(option);
-			} else if (sst_start > 0) { // Matched substring in s.
-				option.matches.push_back(Pair<int, int>(sst_start, string_to_complete.length()));
-				completion_options_substr.push_back(option);
-			} else {
-				if (ssq_match_len > 0) {
-					ssq_matches.push_back(Pair<int, int>(ssq_match_start, ssq_match_len));
+			all_possible_subsequence_matches = next_subsequence_matches;
+		}
+		// go through all possible matches to get the best one as defined by CodeCompletionOptionCompare
+		if (all_possible_subsequence_matches.size() > 0) {
+			option.matches = all_possible_subsequence_matches[0];
+			option.get_option_characteristics(string_to_complete);
+			all_possible_subsequence_matches = all_possible_subsequence_matches.slice(1);
+			if (all_possible_subsequence_matches.size() > 0) {
+				CodeCompletionOptionCompare compare;
+				ScriptLanguage::CodeCompletionOption compared_option = option;
+				compared_option.clear_characteristics();
+				for (Vector<Pair<int, int>> &matches : all_possible_subsequence_matches) {
+					compared_option.matches = matches;
+					compared_option.get_option_characteristics(string_to_complete);
+					if (compare(compared_option, option)) {
+						option = compared_option;
+						compared_option.clear_characteristics();
+					}
 				}
 				}
-				option.matches.append_array(ssq_matches);
-				completion_options_subseq.push_back(option);
 			}
 			}
-			if (theme_cache.font.is_valid()) {
-				max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
-			}
-		} else if (!*ssq_lower) { // Matched the whole subsequence in s_lower.
-			option.matches.clear();
 
 
-			if (sst_lower_start == 0) { // Matched substring in beginning of s_lower.
-				option.matches.push_back(Pair<int, int>(sst_lower_start, string_to_complete.length()));
-				completion_options_casei.push_back(option);
-			} else if (sst_lower_start > 0) { // Matched substring in s_lower.
-				option.matches.push_back(Pair<int, int>(sst_lower_start, string_to_complete.length()));
-				completion_options_substr_casei.push_back(option);
-			} else {
-				if (ssq_lower_match_len > 0) {
-					ssq_lower_matches.push_back(Pair<int, int>(ssq_lower_match_start, ssq_lower_match_len));
-				}
-				option.matches.append_array(ssq_lower_matches);
-				completion_options_subseq_casei.push_back(option);
-			}
+			code_completion_options.push_back(option);
 			if (theme_cache.font.is_valid()) {
 			if (theme_cache.font.is_valid()) {
 				max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
 				max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
 			}
 			}
 		}
 		}
 	}
 	}
 
 
-	code_completion_options.append_array(completion_options_casei);
-	code_completion_options.append_array(completion_options_substr);
-	code_completion_options.append_array(completion_options_substr_casei);
-	code_completion_options.append_array(completion_options_subseq);
-	code_completion_options.append_array(completion_options_subseq_casei);
-
 	/* No options to complete, cancel. */
 	/* No options to complete, cancel. */
 	if (code_completion_options.size() == 0) {
 	if (code_completion_options.size() == 0) {
 		cancel_code_completion();
 		cancel_code_completion();
@@ -3256,6 +3193,8 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
 		return;
 		return;
 	}
 	}
 
 
+	code_completion_options.sort_custom<CodeCompletionOptionCompare>();
+
 	code_completion_longest_line = MIN(max_width, theme_cache.code_completion_max_width * theme_cache.font_size);
 	code_completion_longest_line = MIN(max_width, theme_cache.code_completion_max_width * theme_cache.font_size);
 	code_completion_current_selected = 0;
 	code_completion_current_selected = 0;
 	code_completion_force_item_center = -1;
 	code_completion_force_item_center = -1;
@@ -3384,3 +3323,26 @@ CodeEdit::CodeEdit() {
 
 
 CodeEdit::~CodeEdit() {
 CodeEdit::~CodeEdit() {
 }
 }
+
+// Return true if l should come before r
+bool CodeCompletionOptionCompare::operator()(const ScriptLanguage::CodeCompletionOption &l, const ScriptLanguage::CodeCompletionOption &r) const {
+	// Check if we are not completing an empty string in this case there is no reason to get matches characteristics.
+
+	TypedArray<int> lcharac = l.get_option_cached_characteristics();
+	TypedArray<int> rcharac = r.get_option_cached_characteristics();
+
+	if (lcharac != rcharac) {
+		return lcharac < rcharac;
+	}
+
+	// to get here they need to have the same size so we can take the size of whichever we want
+	for (int i = 0; i < l.matches.size(); ++i) {
+		if (l.matches[i].first != r.matches[i].first) {
+			return l.matches[i].first < r.matches[i].first;
+		}
+		if (l.matches[i].second != r.matches[i].second) {
+			return l.matches[i].second > r.matches[i].second;
+		}
+	}
+	return l.display < r.display;
+}

+ 17 - 3
scene/gui/code_edit.h

@@ -37,8 +37,8 @@ class CodeEdit : public TextEdit {
 	GDCLASS(CodeEdit, TextEdit)
 	GDCLASS(CodeEdit, TextEdit)
 
 
 public:
 public:
-	/* Keep enum in sync with:                                           */
-	/* /core/object/script_language.h - ScriptLanguage::CodeCompletionKind */
+	// Keep enums in sync with:
+	// core/object/script_language.h - ScriptLanguage::CodeCompletionKind
 	enum CodeCompletionKind {
 	enum CodeCompletionKind {
 		KIND_CLASS,
 		KIND_CLASS,
 		KIND_FUNCTION,
 		KIND_FUNCTION,
@@ -52,6 +52,14 @@ public:
 		KIND_PLAIN_TEXT,
 		KIND_PLAIN_TEXT,
 	};
 	};
 
 
+	// core/object/script_language.h - ScriptLanguage::CodeCompletionLocation
+	enum CodeCompletionLocation {
+		LOCATION_LOCAL = 0,
+		LOCATION_PARENT_MASK = 1 << 8,
+		LOCATION_OTHER_USER_CODE = 1 << 9,
+		LOCATION_OTHER = 1 << 10,
+	};
+
 private:
 private:
 	/* Indent management */
 	/* Indent management */
 	int indent_size = 4;
 	int indent_size = 4;
@@ -427,7 +435,7 @@ public:
 
 
 	void request_code_completion(bool p_force = false);
 	void request_code_completion(bool p_force = false);
 
 
-	void add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color = Color(1, 1, 1), const Ref<Resource> &p_icon = Ref<Resource>(), const Variant &p_value = Variant::NIL);
+	void add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color = Color(1, 1, 1), const Ref<Resource> &p_icon = Ref<Resource>(), const Variant &p_value = Variant::NIL, int p_location = LOCATION_OTHER);
 	void update_code_completion_options(bool p_forced = false);
 	void update_code_completion_options(bool p_forced = false);
 
 
 	TypedArray<Dictionary> get_code_completion_options() const;
 	TypedArray<Dictionary> get_code_completion_options() const;
@@ -456,5 +464,11 @@ public:
 };
 };
 
 
 VARIANT_ENUM_CAST(CodeEdit::CodeCompletionKind);
 VARIANT_ENUM_CAST(CodeEdit::CodeCompletionKind);
+VARIANT_ENUM_CAST(CodeEdit::CodeCompletionLocation);
+
+// The custom comparer which will sort completion options.
+struct CodeCompletionOptionCompare {
+	_FORCE_INLINE_ bool operator()(const ScriptLanguage::CodeCompletionOption &l, const ScriptLanguage::CodeCompletionOption &r) const;
+};
 
 
 #endif // CODE_EDIT_H
 #endif // CODE_EDIT_H

+ 94 - 2
tests/scene/test_code_edit.h

@@ -3186,7 +3186,7 @@ TEST_CASE("[SceneTree][CodeEdit] completion") {
 			code_edit->set_code_completion_selected_index(1);
 			code_edit->set_code_completion_selected_index(1);
 			ERR_PRINT_ON;
 			ERR_PRINT_ON;
 			CHECK(code_edit->get_code_completion_selected_index() == 0);
 			CHECK(code_edit->get_code_completion_selected_index() == 0);
-			CHECK(code_edit->get_code_completion_option(0).size() == 6);
+			CHECK(code_edit->get_code_completion_option(0).size() == 7);
 			CHECK(code_edit->get_code_completion_options().size() == 1);
 			CHECK(code_edit->get_code_completion_options().size() == 1);
 
 
 			/* Check cancel closes completion. */
 			/* Check cancel closes completion. */
@@ -3197,7 +3197,7 @@ TEST_CASE("[SceneTree][CodeEdit] completion") {
 			CHECK(code_edit->get_code_completion_selected_index() == 0);
 			CHECK(code_edit->get_code_completion_selected_index() == 0);
 			code_edit->set_code_completion_selected_index(1);
 			code_edit->set_code_completion_selected_index(1);
 			CHECK(code_edit->get_code_completion_selected_index() == 1);
 			CHECK(code_edit->get_code_completion_selected_index() == 1);
-			CHECK(code_edit->get_code_completion_option(0).size() == 6);
+			CHECK(code_edit->get_code_completion_option(0).size() == 7);
 			CHECK(code_edit->get_code_completion_options().size() == 3);
 			CHECK(code_edit->get_code_completion_options().size() == 3);
 
 
 			/* Check data. */
 			/* Check data. */
@@ -3445,6 +3445,98 @@ TEST_CASE("[SceneTree][CodeEdit] completion") {
 		}
 		}
 	}
 	}
 
 
+	SUBCASE("[CodeEdit] autocomplete suggestion order") {
+		/* Favorize less fragmented suggestion. */
+		code_edit->clear();
+		code_edit->insert_text_at_caret("te");
+		code_edit->set_caret_column(2);
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "test", "test");
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "tset", "tset");
+		code_edit->update_code_completion_options();
+		code_edit->confirm_code_completion();
+		CHECK(code_edit->get_line(0) == "test");
+
+		/* Favorize suggestion starting from the string to complete (matching start). */
+		code_edit->clear();
+		code_edit->insert_text_at_caret("te");
+		code_edit->set_caret_column(2);
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "test", "test");
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "stest", "stest");
+		code_edit->update_code_completion_options();
+		code_edit->confirm_code_completion();
+		CHECK(code_edit->get_line(0) == "test");
+
+		/* Favorize less fragment to matching start. */
+		code_edit->clear();
+		code_edit->insert_text_at_caret("te");
+		code_edit->set_caret_column(2);
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "tset", "tset");
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "stest", "stest");
+		code_edit->update_code_completion_options();
+		code_edit->confirm_code_completion();
+		CHECK(code_edit->get_line(0) == "stest");
+
+		/* Favorize closer location. */
+		code_edit->clear();
+		code_edit->insert_text_at_caret("te");
+		code_edit->set_caret_column(2);
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "test", "test");
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "test_bis", "test_bis", Color(1, 1, 1), Ref<Resource>(), Variant::NIL, CodeEdit::LOCATION_LOCAL);
+		code_edit->update_code_completion_options();
+		code_edit->confirm_code_completion();
+		CHECK(code_edit->get_line(0) == "test_bis");
+
+		/* Favorize matching start to location. */
+		code_edit->clear();
+		code_edit->insert_text_at_caret("te");
+		code_edit->set_caret_column(2);
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "test", "test");
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "stest_bis", "test_bis", Color(1, 1, 1), Ref<Resource>(), Variant::NIL, CodeEdit::LOCATION_LOCAL);
+		code_edit->update_code_completion_options();
+		code_edit->confirm_code_completion();
+		CHECK(code_edit->get_line(0) == "test");
+
+		/* Favorize good capitalisation. */
+		code_edit->clear();
+		code_edit->insert_text_at_caret("te");
+		code_edit->set_caret_column(2);
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "test", "test");
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "Test", "Test");
+		code_edit->update_code_completion_options();
+		code_edit->confirm_code_completion();
+		CHECK(code_edit->get_line(0) == "test");
+
+		/* Favorize location to good capitalisation. */
+		code_edit->clear();
+		code_edit->insert_text_at_caret("te");
+		code_edit->set_caret_column(2);
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "test", "test");
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "Test", "Test", Color(1, 1, 1), Ref<Resource>(), Variant::NIL, CodeEdit::LOCATION_LOCAL);
+		code_edit->update_code_completion_options();
+		code_edit->confirm_code_completion();
+		CHECK(code_edit->get_line(0) == "Test");
+
+		/* Favorize string to complete being closest to the start of the suggestion (closest to start). */
+		code_edit->clear();
+		code_edit->insert_text_at_caret("te");
+		code_edit->set_caret_column(2);
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "stest", "stest");
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "sstest", "sstest");
+		code_edit->update_code_completion_options();
+		code_edit->confirm_code_completion();
+		CHECK(code_edit->get_line(0) == "stest");
+
+		/* Favorize good capitalisation to closest to start. */
+		code_edit->clear();
+		code_edit->insert_text_at_caret("te");
+		code_edit->set_caret_column(2);
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "sTest", "stest");
+		code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "sstest", "sstest");
+		code_edit->update_code_completion_options();
+		code_edit->confirm_code_completion();
+		CHECK(code_edit->get_line(0) == "sstest");
+	}
+
 	memdelete(code_edit);
 	memdelete(code_edit);
 }
 }