You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-05 12:10:55 +00:00
sort code completions with rules
Fixups Add levenshtein distance for comparisons, remove kind sort order, try to improve as many different use cases as possible Trying again to improve code completion Sort code autocompletion options by similarity based on input To make it really brief, uses a combination `String.similiary`, the category system introduced in a previous PR, and some filtering to yield more predictable results, instead of scattering every completion option at seemingly random. It also gives much higher priority to strings that contain the base in full, closer to the beginning or are perfect matches. Also moves CodeCompletionOptionCompare to code_edit.cpp Co-Authored-By: Micky <66727710+Mickeon@users.noreply.github.com> Co-Authored-By: Eric M <41730826+EricEzaM@users.noreply.github.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
completion_option.kind = (ScriptLanguage::CodeCompletionKind)p_type;
|
||||
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.icon = p_icon;
|
||||
completion_option.default_value = p_value;
|
||||
completion_option.location = p_location;
|
||||
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["font_color"] = code_completion_options[i].font_color;
|
||||
option["icon"] = code_completion_options[i].icon;
|
||||
option["location"] = code_completion_options[i].location;
|
||||
option["default_value"] = code_completion_options[i].default_value;
|
||||
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["font_color"] = code_completion_options[p_index].font_color;
|
||||
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;
|
||||
return option;
|
||||
}
|
||||
@@ -2424,9 +2426,14 @@ 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_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("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("get_code_completion_options"), &CodeEdit::get_code_completion_options);
|
||||
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["icon"] = E.icon;
|
||||
option["default_value"] = E.default_value;
|
||||
option["location"] = E.location;
|
||||
completion_options_sources[i] = option;
|
||||
i++;
|
||||
}
|
||||
@@ -2977,6 +2985,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
|
||||
option.insert_text = completion_options[i].get("insert_text");
|
||||
option.font_color = completion_options[i].get("font_color");
|
||||
option.icon = completion_options[i].get("icon");
|
||||
option.location = completion_options[i].get("location");
|
||||
option.default_value = completion_options[i].get("default_value");
|
||||
|
||||
int offset = 0;
|
||||
@@ -3063,7 +3072,7 @@ 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();
|
||||
@@ -3075,23 +3084,16 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
|
||||
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;
|
||||
String string_to_complete_lower = string_to_complete.to_lower();
|
||||
|
||||
for (ScriptLanguage::CodeCompletionOption &option : code_completion_option_sources) {
|
||||
option.matches.clear();
|
||||
if (single_quote && option.display.is_quoted()) {
|
||||
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) {
|
||||
String quote = single_quote ? "'" : "\"";
|
||||
@@ -3104,6 +3106,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
|
||||
}
|
||||
|
||||
if (string_to_complete.length() == 0) {
|
||||
option.get_option_characteristics(string_to_complete);
|
||||
code_completion_options.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);
|
||||
@@ -3111,139 +3114,73 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* This code works the same as:
|
||||
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];
|
||||
|
||||
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];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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<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;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
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;
|
||||
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++;
|
||||
|
||||
/* 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));
|
||||
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);
|
||||
}
|
||||
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));
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
option.matches.append_array(ssq_lower_matches);
|
||||
completion_options_subseq_casei.push_back(option);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
code_completion_options.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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. */
|
||||
if (code_completion_options.size() == 0) {
|
||||
cancel_code_completion();
|
||||
@@ -3256,6 +3193,8 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
|
||||
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_current_selected = 0;
|
||||
code_completion_force_item_center = -1;
|
||||
@@ -3384,3 +3323,26 @@ 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user