From 1b8d0a16b7df53aa0f43f046fe8fcc4b446df69e Mon Sep 17 00:00:00 2001 From: George Marques Date: Mon, 13 Jun 2016 14:06:03 -0300 Subject: [PATCH 1/4] Add similarity comparison to String Uses the Sorensen-Dice coefficient to calculate similarity. This also adds String.bigrams() as a convenience function needed by the comparison. --- core/ustring.cpp | 44 +++++++++++++++++++++++++++++++++++++++++++ core/ustring.h | 2 ++ core/variant_call.cpp | 4 ++++ 3 files changed, 50 insertions(+) diff --git a/core/ustring.cpp b/core/ustring.cpp index 485f7f1b62c..ea9a9d903e5 100644 --- a/core/ustring.cpp +++ b/core/ustring.cpp @@ -2810,6 +2810,50 @@ bool String::_base_is_subsequence_of(const String& p_string, bool case_insensiti return false; } +Vector String::bigrams() const { + int n_pairs = length() - 1; + Vector b; + if(n_pairs <= 0) { + return b; + } + b.resize(n_pairs); + for(int i = 0; i < n_pairs; i++) { + b[i] = substr(i,2); + } + return b; +} + +// Similarity according to Sorensen-Dice coefficient +float String::similarity(const String& p_string) const { + if(operator==(p_string)) { + // Equal strings are totally similar + return 1.0f; + } + if (length() < 2 || p_string.length() < 2) { + // No way to calculate similarity without a single bigram + return 0.0f; + } + + Vector src_bigrams = bigrams(); + Vector tgt_bigrams = p_string.bigrams(); + + int src_size = src_bigrams.size(); + int tgt_size = tgt_bigrams.size(); + + float sum = src_size + tgt_size; + float inter = 0; + for (int i = 0; i < src_size; i++) { + for (int j = 0; j < tgt_size; j++) { + if (src_bigrams[i] == tgt_bigrams[j]) { + inter++; + break; + } + } + } + + return (2.0f * inter)/sum; +} + static bool _wildcard_match(const CharType* p_pattern, const CharType* p_string,bool p_case_sensitive) { switch (*p_pattern) { case '\0': diff --git a/core/ustring.h b/core/ustring.h index 8aceb0748c5..692cb4e37d4 100644 --- a/core/ustring.h +++ b/core/ustring.h @@ -123,6 +123,8 @@ public: bool ends_with(const String& p_string) const; bool is_subsequence_of(const String& p_string) const; bool is_subsequence_ofi(const String& p_string) const; + Vector bigrams() const; + float similarity(const String& p_string) const; String replace_first(String p_key,String p_with) const; String replace(String p_key,String p_with) const; String replacen(String p_key,String p_with) const; diff --git a/core/variant_call.cpp b/core/variant_call.cpp index 00551385825..683b1611d86 100644 --- a/core/variant_call.cpp +++ b/core/variant_call.cpp @@ -249,6 +249,8 @@ static void _call_##m_type##_##m_method(Variant& r_ret,Variant& p_self,const Var VCALL_LOCALMEM1R(String,ends_with); VCALL_LOCALMEM1R(String,is_subsequence_of); VCALL_LOCALMEM1R(String,is_subsequence_ofi); + VCALL_LOCALMEM0R(String,bigrams); + VCALL_LOCALMEM1R(String,similarity); VCALL_LOCALMEM2R(String,replace); VCALL_LOCALMEM2R(String,replacen); VCALL_LOCALMEM2R(String,insert); @@ -1274,6 +1276,8 @@ _VariantCall::addfunc(Variant::m_vtype,Variant::m_ret,_SCS(#m_method),VCALL(m_cl ADDFUNC1(STRING,BOOL,String,ends_with,STRING,"text",varray()); ADDFUNC1(STRING,BOOL,String,is_subsequence_of,STRING,"text",varray()); ADDFUNC1(STRING,BOOL,String,is_subsequence_ofi,STRING,"text",varray()); + ADDFUNC0(STRING,STRING_ARRAY,String,bigrams,varray()); + ADDFUNC1(STRING,REAL,String,similarity,STRING,"text",varray()); ADDFUNC2(STRING,STRING,String,replace,STRING,"what",STRING,"forwhat",varray()); ADDFUNC2(STRING,STRING,String,replacen,STRING,"what",STRING,"forwhat",varray()); From d3dff93e33bb61bde1cc8c311c38c3ba356b2c7f Mon Sep 17 00:00:00 2001 From: George Marques Date: Mon, 13 Jun 2016 14:12:12 -0300 Subject: [PATCH 2/4] Add docs for String.bigrams() and String.similarity(text) --- doc/base/classes.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/base/classes.xml b/doc/base/classes.xml index b773d85b02f..e687fb5ccf8 100644 --- a/doc/base/classes.xml +++ b/doc/base/classes.xml @@ -37268,6 +37268,13 @@ This method controls whether the position between two cached points is interpola Return true if the strings begins with the given string. + + + + + Return the bigrams (pairs of consecutive letters) of this string. + + @@ -37627,6 +37634,15 @@ This method controls whether the position between two cached points is interpola + + + + + + + Return the similarity index of the text compared to this string. 1 means totally similar and 0 means totally dissimilar. + + From 831ae2d510b4ae87a1ff5f828ab817640269dca2 Mon Sep 17 00:00:00 2001 From: George Marques Date: Mon, 13 Jun 2016 16:40:28 -0300 Subject: [PATCH 3/4] Fix TextEdit cursor position after undo remove text It was going to where the text started, now it goes to where the text ends. --- scene/gui/text_edit.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index c08247095a1..5774b02beb7 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -3837,8 +3837,13 @@ void TextEdit::undo() { } } - cursor_set_line(undo_stack_pos->get().from_line); - cursor_set_column(undo_stack_pos->get().from_column); + if (undo_stack_pos->get().type == TextOperation::TYPE_REMOVE) { + cursor_set_line(undo_stack_pos->get().to_line); + cursor_set_column(undo_stack_pos->get().to_column); + } else { + cursor_set_line(undo_stack_pos->get().from_line); + cursor_set_column(undo_stack_pos->get().from_column); + } update(); } From 66e7ddb55b4c7e5f29acb0df83dafc69d091a8cc Mon Sep 17 00:00:00 2001 From: George Marques Date: Mon, 13 Jun 2016 16:55:26 -0300 Subject: [PATCH 4/4] Improve code completion search - List completion by subsequence match. - Case insensitive. - Sort completion list by similarity. --- scene/gui/text_edit.cpp | 72 ++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 41 deletions(-) diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 5774b02beb7..ec0573e396b 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -3840,6 +3840,7 @@ void TextEdit::undo() { if (undo_stack_pos->get().type == TextOperation::TYPE_REMOVE) { cursor_set_line(undo_stack_pos->get().to_line); cursor_set_column(undo_stack_pos->get().to_column); + _cancel_code_hint(); } else { cursor_set_line(undo_stack_pos->get().from_line); cursor_set_column(undo_stack_pos->get().from_column); @@ -3986,27 +3987,18 @@ void TextEdit::set_completion(bool p_enabled,const Vector& p_prefixes) { void TextEdit::_confirm_completion() { - String remaining=completion_current.substr(completion_base.length(),completion_current.length()-completion_base.length()); - String l = text[cursor.line]; - bool same=true; - //if what is going to be inserted is the same as what it is, don't change it - for(int i=0;i=l.length() || l[c]!=remaining[i]) { - same=false; - break; - } + begin_complex_operation(); + + _remove_text(cursor.line, cursor.column - completion_base.length(), cursor.line, cursor.column); + cursor_set_column(cursor.column - completion_base.length(), false); + insert_text_at_cursor(completion_current); + + if (completion_current.ends_with("(") && auto_brace_completion_enabled) { + insert_text_at_cursor(")"); + cursor.column--; } - if (same) - cursor_set_column(cursor.column+remaining.length()); - else { - insert_text_at_cursor(remaining); - if (remaining.ends_with("(") && auto_brace_completion_enabled) { - insert_text_at_cursor(")"); - cursor.column--; - } - } + end_complex_operation(); _cancel_completion(); } @@ -4110,30 +4102,29 @@ void TextEdit::_update_completion_candidates() { completion_index=0; completion_base=s; int ci_match=0; + Vector sim_cache; for(int i=0;i=completion_strings[i].length()) - break; - if (completion_current[j]!=completion_strings[i][j]) - break; - m++; + // Calculate the similarity to keep completions in good order + float similarity = s.similarity(completion_strings[i]); + int comp_size = completion_options.size(); + if (comp_size == 0) { + completion_options.push_back(completion_strings[i]); + sim_cache.push_back(similarity); + } else { + float comp_sim; + int pos = 0; + do { + comp_sim = sim_cache[pos++]; + } while(pos < comp_size && similarity <= comp_sim); + pos--; // Pos will be off by one + completion_options.insert(pos, completion_strings[i]); + sim_cache.insert(pos, similarity); } - if (m>ci_match) { - ci_match=m; - completion_index=completion_options.size()-1; - } - } } @@ -4146,7 +4137,8 @@ void TextEdit::_update_completion_candidates() { } - completion_current=completion_options[completion_index]; + // The top of the list is the best match + completion_current=completion_options[0]; #if 0 // even there's only one option, user still get the chance to choose using it or not if (completion_options.size()==1) { @@ -4158,8 +4150,6 @@ void TextEdit::_update_completion_candidates() { } #endif - if (completion_options.size()==1 && s==completion_options[0]) - _cancel_completion(); completion_enabled=true; }