diff --git a/core/object/script_language.h b/core/object/script_language.h index f82b58439fb3..5081a49adc02 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -322,9 +322,8 @@ class ScriptLanguage : public Object { }; enum CodeCompletionLocation { - LOCATION_LOCAL = 0, - LOCATION_PARENT_MASK = 1 << 8, - LOCATION_OTHER_USER_CODE = 1 << 9, + LOCATION_LOCAL = 1 << 0, + LOCATION_BASE = 1 << 1, LOCATION_OTHER = 1 << 10, }; @@ -346,6 +345,13 @@ class ScriptLanguage : public Object { kind = p_kind; location = p_location; } + + bool is_basic_identifier() const { + return (kind == ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE || + kind == ScriptLanguage::CODE_COMPLETION_KIND_MEMBER || + kind == ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION || + kind == ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL); + } }; virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List *r_options, bool &r_force, String &r_call_hint) { return ERR_UNAVAILABLE; } diff --git a/core/object/script_language_extension.cpp b/core/object/script_language_extension.cpp index 19c6fc3383e1..7be9afaffd93 100644 --- a/core/object/script_language_extension.cpp +++ b/core/object/script_language_extension.cpp @@ -166,8 +166,9 @@ void ScriptLanguageExtension::_bind_methods() { BIND_ENUM_CONSTANT(LOOKUP_RESULT_MAX); BIND_ENUM_CONSTANT(LOCATION_LOCAL); - BIND_ENUM_CONSTANT(LOCATION_PARENT_MASK); - BIND_ENUM_CONSTANT(LOCATION_OTHER_USER_CODE); + BIND_ENUM_CONSTANT(LOCATION_BASE); + //BIND_ENUM_CONSTANT(LOCATION_PARENT_MASK); + //BIND_ENUM_CONSTANT(LOCATION_OTHER_USER_CODE); BIND_ENUM_CONSTANT(LOCATION_OTHER); BIND_ENUM_CONSTANT(CODE_COMPLETION_KIND_CLASS); diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 28d687488ced..b62cf8b62bea 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -948,7 +948,7 @@ void CodeTextEditor::_complete_request() { } else if (e.insert_text.begins_with("#") || e.insert_text.begins_with("//")) { 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, (CodeEdit::CodeCompletionLocation)e.location); } text_editor->update_code_completion_options(forced); } diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 6b4e7184d99c..0ccc6a6c7bed 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -756,7 +756,8 @@ void ScriptTextEditor::_code_complete_script(const String &p_code, Listget_language()->complete_code(p_code, script->get_path(), base, r_options, r_force, hint); - r_options->sort_custom_inplace(); + // Editor will sort later, no point doing it now? + //r_options->sort_custom_inplace(); if (err == OK) { code_editor->get_text_editor()->set_code_hint(hint); diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index 1d96376748c0..1dca82123bd4 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -259,51 +259,4 @@ class ScriptTextEditor : public ScriptEditorBase { ~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 diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index f88ac581cae7..675aac0429d2 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -495,89 +495,6 @@ struct GDScriptCompletionIdentifier { const GDScriptParser::ExpressionNode *assigned_expression = nullptr; }; -// LOCATION METHODS -// These methods are used to populate the `CodeCompletionOption::location` integer. -// For these methods, the location is based on the depth in the inheritance chain that the property -// appears. For example, if you are completing code in a class that inherits Node2D, a property found on Node2D -// will have a "better" (lower) location "score" than a property that is found on CanvasItem. - -static int _get_property_location(StringName p_class, StringName p_property) { - if (!ClassDB::has_property(p_class, p_property)) { - return ScriptLanguage::LOCATION_OTHER; - } - - int depth = 0; - StringName class_test = p_class; - while (class_test && !ClassDB::has_property(class_test, p_property, true)) { - class_test = ClassDB::get_parent_class(class_test); - depth++; - } - - return depth | ScriptLanguage::LOCATION_PARENT_MASK; -} - -static int _get_constant_location(StringName p_class, StringName p_constant) { - if (!ClassDB::has_integer_constant(p_class, p_constant)) { - return ScriptLanguage::LOCATION_OTHER; - } - - int depth = 0; - StringName class_test = p_class; - while (class_test && !ClassDB::has_integer_constant(class_test, p_constant, true)) { - class_test = ClassDB::get_parent_class(class_test); - depth++; - } - - return depth | ScriptLanguage::LOCATION_PARENT_MASK; -} - -static int _get_signal_location(StringName p_class, StringName p_signal) { - if (!ClassDB::has_signal(p_class, p_signal)) { - return ScriptLanguage::LOCATION_OTHER; - } - - int depth = 0; - StringName class_test = p_class; - while (class_test && !ClassDB::has_signal(class_test, p_signal, true)) { - class_test = ClassDB::get_parent_class(class_test); - depth++; - } - - return depth | ScriptLanguage::LOCATION_PARENT_MASK; -} - -static int _get_method_location(StringName p_class, StringName p_method) { - if (!ClassDB::has_method(p_class, p_method)) { - return ScriptLanguage::LOCATION_OTHER; - } - - int depth = 0; - StringName class_test = p_class; - while (class_test && !ClassDB::has_method(class_test, p_method, true)) { - class_test = ClassDB::get_parent_class(class_test); - depth++; - } - - return depth | ScriptLanguage::LOCATION_PARENT_MASK; -} - -static int _get_enum_constant_location(StringName p_class, StringName p_enum_constant) { - if (!ClassDB::get_integer_constant_enum(p_class, p_enum_constant)) { - return ScriptLanguage::LOCATION_OTHER; - } - - int depth = 0; - StringName class_test = p_class; - while (class_test && !ClassDB::get_integer_constant_enum(class_test, p_enum_constant, true)) { - class_test = ClassDB::get_parent_class(class_test); - depth++; - } - - return depth | ScriptLanguage::LOCATION_PARENT_MASK; -} - -// END LOCATION METHODS - static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg = true) { if (p_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) { String enum_name = p_info.class_name; @@ -872,7 +789,7 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio List global_classes; ScriptServer::get_global_class_list(&global_classes); for (const StringName &E : global_classes) { - ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_OTHER_USER_CODE); + ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS); r_result.insert(option.display, option); } @@ -884,24 +801,25 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio if (!info.is_singleton || info.path.get_extension().to_lower() != "gd") { continue; } - ScriptLanguage::CodeCompletionOption option(info.name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_OTHER_USER_CODE); + ScriptLanguage::CodeCompletionOption option(info.name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS); r_result.insert(option.display, option); } } -static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, HashMap &r_result) { +static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, HashMap &r_result, int p_recursion_depth = 0) { for (int i = 0; i < p_suite->locals.size(); i++) { ScriptLanguage::CodeCompletionOption option; + ScriptLanguage::CodeCompletionLocation location = p_recursion_depth == 0 ? ScriptLanguage::LOCATION_LOCAL : ScriptLanguage::LOCATION_BASE; 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; } 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); } 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); } } @@ -916,7 +834,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, int classes_processed = 0; while (clss) { 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 + classes_processed <= 1 ? ScriptLanguage::LOCATION_LOCAL : ScriptLanguage::LOCATION_BASE; const GDScriptParser::ClassNode::Member &member = clss->members[i]; ScriptLanguage::CodeCompletionOption option; switch (member.type) { @@ -1020,24 +938,21 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base List members; scr->get_script_property_list(&members); for (const PropertyInfo &E : members) { - int location = p_recursion_depth + _get_property_location(scr->get_class_name(), E.class_name); - ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); + ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, ScriptLanguage::LOCATION_BASE); r_result.insert(option.display, option); } } HashMap constants; scr->get_constants(&constants); for (const KeyValue &E : constants) { - int location = p_recursion_depth + _get_constant_location(scr->get_class_name(), E.key); - ScriptLanguage::CodeCompletionOption option(E.key.operator String(), ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); + ScriptLanguage::CodeCompletionOption option(E.key.operator String(), ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, ScriptLanguage::LOCATION_BASE); r_result.insert(option.display, option); } List signals; scr->get_script_signal_list(&signals); for (const MethodInfo &E : signals) { - int location = p_recursion_depth + _get_signal_location(scr->get_class_name(), E.name); - ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); + ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, ScriptLanguage::LOCATION_BASE); r_result.insert(option.display, option); } } @@ -1048,8 +963,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base if (E.name.begins_with("@")) { continue; } - int location = p_recursion_depth + _get_method_location(scr->get_class_name(), E.name); - ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location); + ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, ScriptLanguage::LOCATION_BASE); if (E.arguments.size()) { option.insert_text += "("; } else { @@ -1079,16 +993,14 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base List constants; ClassDB::get_integer_constant_list(type, &constants); for (const String &E : constants) { - int location = p_recursion_depth + _get_constant_location(type, StringName(E)); - ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); + ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, ScriptLanguage::LOCATION_BASE); r_result.insert(option.display, option); } List signals; ClassDB::get_signal_list(type, &signals); for (const MethodInfo &E : signals) { - int location = p_recursion_depth + _get_signal_location(type, StringName(E.name)); - ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); + ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, ScriptLanguage::LOCATION_BASE); r_result.insert(option.display, option); } @@ -1102,8 +1014,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base if (E.name.contains("/")) { continue; } - int location = p_recursion_depth + _get_property_location(type, E.class_name); - ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); + ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, ScriptLanguage::LOCATION_BASE); r_result.insert(option.display, option); } } @@ -1120,8 +1031,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base if (E.name.begins_with("_")) { continue; } - int location = p_recursion_depth + _get_method_location(type, E.name); - ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location); + ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, ScriptLanguage::LOCATION_BASE); if (E.arguments.size()) { option.insert_text += "("; } else { @@ -1283,7 +1193,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context List global_classes; ScriptServer::get_global_class_list(&global_classes); for (const StringName &E : global_classes) { - ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_OTHER_USER_CODE); + ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_OTHER); r_result.insert(option.display, option); } } @@ -2404,8 +2314,7 @@ static void _find_enumeration_candidates(GDScriptParser::CompletionContext &p_co ClassDB::get_enum_constants(class_name, enum_name, &enum_constants); for (const StringName &E : enum_constants) { String candidate = class_name + "." + E; - int location = _get_enum_constant_location(class_name, E); - ScriptLanguage::CodeCompletionOption option(candidate, ScriptLanguage::CODE_COMPLETION_KIND_ENUM, location); + ScriptLanguage::CodeCompletionOption option(candidate, ScriptLanguage::CODE_COMPLETION_KIND_ENUM); r_result.insert(option.display, option); } } diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index c977d9d2fbe1..3259e66be27a 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -1889,7 +1889,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 &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 &p_icon, const Variant &p_value, CodeCompletionLocation p_location) { ScriptLanguage::CodeCompletionOption completion_option; completion_option.kind = (ScriptLanguage::CodeCompletionKind)p_type; completion_option.display = p_display_text; @@ -1897,6 +1897,7 @@ void CodeEdit::add_code_completion_option(CodeCompletionKind p_type, const Strin completion_option.font_color = p_text_color; completion_option.icon = p_icon; completion_option.default_value = p_value; + completion_option.location = (ScriptLanguage::CodeCompletionLocation)p_location; code_completion_option_submitted.push_back(completion_option); } @@ -2279,9 +2280,13 @@ void CodeEdit::_bind_methods() { BIND_ENUM_CONSTANT(KIND_FILE_PATH); BIND_ENUM_CONSTANT(KIND_PLAIN_TEXT); + BIND_ENUM_CONSTANT(LOCATION_LOCAL); + BIND_ENUM_CONSTANT(LOCATION_BASE); + 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("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()), 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()), 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("get_code_completion_options"), &CodeEdit::get_code_completion_options); ClassDB::bind_method(D_METHOD("get_code_completion_option", "index"), &CodeEdit::get_code_completion_option); @@ -2918,20 +2923,17 @@ void CodeEdit::_filter_code_completion_candidates_impl() { } /* 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 == "'"; code_completion_options.clear(); code_completion_base = string_to_complete; - Vector completion_options_casei; - Vector completion_options_substr; Vector completion_options_substr_casei; - Vector completion_options_subseq; - Vector completion_options_subseq_casei; int max_width = 0; String string_to_complete_lower = string_to_complete.to_lower(); + for (ScriptLanguage::CodeCompletionOption &option : code_completion_option_sources) { if (single_quote && option.display.is_quoted()) { option.display = option.display.unquote().quote("'"); @@ -2960,78 +2962,26 @@ void CodeEdit::_filter_code_completion_candidates_impl() { 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 - */ + // if (option.display.similarity(string_to_complete) < 0.1) { + // continue; + // } 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]; - const char32_t *sst = &string_to_complete[0]; - const char32_t *sst_lower = &display_lower[0]; - Vector> ssq_matches; - int ssq_match_start = 0; - int ssq_match_len = 0; Vector> 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; - } - - // Check subsequence. - if (*ssq == *tgt) { - ssq++; - if (ssq_match_len == 0) { - ssq_match_start = i; - } - ssq_match_len++; - } else if (ssq_match_len > 0) { - ssq_matches.push_back(Pair(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; - } - } 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++; @@ -3045,33 +2995,10 @@ void CodeEdit::_filter_code_completion_candidates_impl() { } } - /* 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(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(sst_start, string_to_complete.length())); - completion_options_substr.push_back(option); - } else { - if (ssq_match_len > 0) { - ssq_matches.push_back(Pair(ssq_match_start, ssq_match_len)); - } - option.matches.append_array(ssq_matches); - completion_options_subseq.push_back(option); - } - if (font.is_valid()) { - max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); - } - } else if (!*ssq_lower) { // Matched the whole subsequence in s_lower. + 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(sst_lower_start, string_to_complete.length())); - completion_options_casei.push_back(option); - } else if (sst_lower_start > 0) { // Matched substring in s_lower. + if (sst_lower_start >= 0) { option.matches.push_back(Pair(sst_lower_start, string_to_complete.length())); completion_options_substr_casei.push_back(option); } else { @@ -3079,7 +3006,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { ssq_lower_matches.push_back(Pair(ssq_lower_match_start, ssq_lower_match_len)); } option.matches.append_array(ssq_lower_matches); - completion_options_subseq_casei.push_back(option); + completion_options_substr_casei.push_back(option); } if (font.is_valid()) { max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); @@ -3087,9 +3014,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { } } - code_completion_options.append_array(completion_options_casei); - code_completion_options.append_array(completion_options_subseq); - code_completion_options.append_array(completion_options_subseq_casei); + code_completion_options.append_array(completion_options_substr_casei); /* No options to complete, cancel. */ if (code_completion_options.size() == 0) { @@ -3103,6 +3028,9 @@ void CodeEdit::_filter_code_completion_candidates_impl() { return; } + CodeCompletionOptionCompare::base = string_to_complete; + code_completion_options.sort_custom(); + code_completion_longest_line = MIN(max_width, code_completion_max_width * font_size); code_completion_current_selected = 0; code_completion_force_item_center = -1; @@ -3231,3 +3159,102 @@ CodeEdit::CodeEdit() { CodeEdit::~CodeEdit() { } + +String CodeCompletionOptionCompare::base; + +int levenshtein_distance(const String &source, const String &target) { + if (source.size() > target.size()) { + return levenshtein_distance(target, source); + } + + const int min_size = source.size(); + const int max_size = target.size(); + Vector lev_dist; + lev_dist.resize(min_size + 1); + + for (int i = 0; i <= min_size; ++i) { + lev_dist.write[i] = i; + } + + for (int j = 1; j <= max_size; ++j) { + int previous_diagonal = lev_dist[0]; + lev_dist.write[0] += 1; + + for (int i = 1; i <= min_size; ++i) { + const int previous_diagonal_save = lev_dist[i]; + if (source[i - 1] == target[j - 1]) { + lev_dist.write[i] = previous_diagonal; + } else { + lev_dist.write[i] = MIN(MIN(lev_dist[i - 1], lev_dist[i]), previous_diagonal) + 1; + } + previous_diagonal = previous_diagonal_save; + } + } + + return lev_dist[min_size]; +} + +// Return true if l should come before r +bool CodeCompletionOptionCompare::operator()(const ScriptLanguage::CodeCompletionOption &l, const ScriptLanguage::CodeCompletionOption &r) const { + // Get position of exact match + int l_exact_idx = l.display.findn(base); + int r_exact_idx = r.display.findn(base); + + bool l_exact = l_exact_idx != -1; + bool r_exact = r_exact_idx != -1; + + // Compare position to length of base to discard exact matches which are very far from the start. + // Other wise you have things like MyLongVeryLongNameVec3 being ranked before Vector3 for search 'vec3' + if ((l_exact && l_exact_idx <= base.length() * 2) || (r_exact && l_exact_idx <= base.length() * 2)) { + if (l_exact && r_exact) { + if (l.location != r.location) { + return l.location < r.location; + } + if (l_exact_idx != r_exact_idx) { + return l_exact_idx < r_exact_idx; + } + } + + if (l_exact != r_exact) { + return l_exact; + } + } + + const int l_lev = levenshtein_distance(base, l.display); + const int r_lev = levenshtein_distance(base, r.display); + + if (ABS(l_lev - r_lev) > 8) { + return l_lev < r_lev; + } + + if (l.location != r.location) { + return l.location < r.location; + } + + bool all_match_groups_same = true; + int matches_shared_size = MIN(l.matches.size(), r.matches.size()); + for (int i = 0; i < matches_shared_size; ++i) { + if (l.matches[i].first != r.matches[i].first || l.matches[i].second != r.matches[i].second) { + all_match_groups_same = false; + break; + } + } + + if (matches_shared_size > 0 && !all_match_groups_same) { + for (int i = 0; i < matches_shared_size; ++i) { + if (l.matches[i].first != r.matches[i].first) { + return l.matches[i].first < r.matches[i].first; + } + } + } + + if (!l.matches.is_empty() && !r.matches.is_empty()) { + float l_match_score = (float)(l.matches[0].first + 1) / l.matches[0].second; + float r_match_score = (float)(r.matches[0].first + 1) / r.matches[0].second; + if (!Math::is_equal_approx(l_match_score, r_match_score, 0.2f)) { + return l_match_score < r_match_score; + } + } + + return l_lev < r_lev; +} diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 55fc5aa2aeb3..cb815d2324dc 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -52,6 +52,13 @@ class CodeEdit : public TextEdit { KIND_PLAIN_TEXT, }; + /* /core/object/script_language.h - ScriptLanguage::CodeCompletionLocation */ + enum CodeCompletionLocation { + LOCATION_LOCAL = 1 << 1, + LOCATION_BASE = 1 << 2, + LOCATION_OTHER = 1 << 10 + }; + private: /* Indent management */ int indent_size = 4; @@ -404,7 +411,7 @@ class CodeEdit : public TextEdit { 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 &p_icon = Ref(), 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 &p_icon = Ref(), const Variant &p_value = Variant::NIL, CodeCompletionLocation p_location = LOCATION_OTHER); void update_code_completion_options(bool p_forced = false); TypedArray get_code_completion_options() const; @@ -433,5 +440,13 @@ class CodeEdit : public TextEdit { }; VARIANT_ENUM_CAST(CodeEdit::CodeCompletionKind); +VARIANT_ENUM_CAST(CodeEdit::CodeCompletionLocation); + +// The custom comparer which will sort completion options. +struct CodeCompletionOptionCompare { + static String base; + + _FORCE_INLINE_ bool operator()(const ScriptLanguage::CodeCompletionOption &l, const ScriptLanguage::CodeCompletionOption &r) const; +}; #endif // CODE_EDIT_H