Merge pull request #5196 from vnen/similarity-code-completion

Improve code completion search
This commit is contained in:
Rémi Verschelde 2016-06-20 13:37:24 +02:00 committed by GitHub
commit 3668768463
5 changed files with 104 additions and 43 deletions

View file

@ -2810,6 +2810,50 @@ bool String::_base_is_subsequence_of(const String& p_string, bool case_insensiti
return false; return false;
} }
Vector<String> String::bigrams() const {
int n_pairs = length() - 1;
Vector<String> 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<String> src_bigrams = bigrams();
Vector<String> 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) { static bool _wildcard_match(const CharType* p_pattern, const CharType* p_string,bool p_case_sensitive) {
switch (*p_pattern) { switch (*p_pattern) {
case '\0': case '\0':

View file

@ -123,6 +123,8 @@ public:
bool ends_with(const String& p_string) const; bool ends_with(const String& p_string) const;
bool is_subsequence_of(const String& p_string) const; bool is_subsequence_of(const String& p_string) const;
bool is_subsequence_ofi(const String& p_string) const; bool is_subsequence_ofi(const String& p_string) const;
Vector<String> bigrams() const;
float similarity(const String& p_string) const;
String replace_first(String p_key,String p_with) const; String replace_first(String p_key,String p_with) const;
String replace(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; String replacen(String p_key,String p_with) const;

View file

@ -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,ends_with);
VCALL_LOCALMEM1R(String,is_subsequence_of); VCALL_LOCALMEM1R(String,is_subsequence_of);
VCALL_LOCALMEM1R(String,is_subsequence_ofi); VCALL_LOCALMEM1R(String,is_subsequence_ofi);
VCALL_LOCALMEM0R(String,bigrams);
VCALL_LOCALMEM1R(String,similarity);
VCALL_LOCALMEM2R(String,replace); VCALL_LOCALMEM2R(String,replace);
VCALL_LOCALMEM2R(String,replacen); VCALL_LOCALMEM2R(String,replacen);
VCALL_LOCALMEM2R(String,insert); VCALL_LOCALMEM2R(String,insert);
@ -1281,6 +1283,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,ends_with,STRING,"text",varray());
ADDFUNC1(STRING,BOOL,String,is_subsequence_of,STRING,"text",varray()); ADDFUNC1(STRING,BOOL,String,is_subsequence_of,STRING,"text",varray());
ADDFUNC1(STRING,BOOL,String,is_subsequence_ofi,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,replace,STRING,"what",STRING,"forwhat",varray());
ADDFUNC2(STRING,STRING,String,replacen,STRING,"what",STRING,"forwhat",varray()); ADDFUNC2(STRING,STRING,String,replacen,STRING,"what",STRING,"forwhat",varray());

View file

@ -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 true if the strings begins with the given string.
</description> </description>
</method> </method>
<method name="bigrams">
<return type="StringArray">
</return>
<description>
Return the bigrams (pairs of consecutive letters) of this string.
</description>
</method>
<method name="c_escape"> <method name="c_escape">
<return type="String"> <return type="String">
</return> </return>
@ -37627,6 +37634,15 @@ This method controls whether the position between two cached points is interpola
<description> <description>
</description> </description>
</method> </method>
<method name="similarity">
<return type="float">
</return>
<argument index="0" name="text" type="String">
</argument>
<description>
Return the similarity index of the text compared to this string. 1 means totally similar and 0 means totally dissimilar.
</description>
</method>
<method name="split"> <method name="split">
<return type="StringArray"> <return type="StringArray">
</return> </return>

View file

@ -3851,8 +3851,14 @@ void TextEdit::undo() {
} }
} }
cursor_set_line(undo_stack_pos->get().from_line); if (undo_stack_pos->get().type == TextOperation::TYPE_REMOVE) {
cursor_set_column(undo_stack_pos->get().from_column); 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);
}
update(); update();
} }
@ -3995,27 +4001,18 @@ void TextEdit::set_completion(bool p_enabled,const Vector<String>& p_prefixes) {
void TextEdit::_confirm_completion() { void TextEdit::_confirm_completion() {
String remaining=completion_current.substr(completion_base.length(),completion_current.length()-completion_base.length()); begin_complex_operation();
String l = text[cursor.line];
bool same=true; _remove_text(cursor.line, cursor.column - completion_base.length(), cursor.line, cursor.column);
//if what is going to be inserted is the same as what it is, don't change it cursor_set_column(cursor.column - completion_base.length(), false);
for(int i=0;i<remaining.length();i++) { insert_text_at_cursor(completion_current);
int c=i+cursor.column;
if (c>=l.length() || l[c]!=remaining[i]) { if (completion_current.ends_with("(") && auto_brace_completion_enabled) {
same=false; insert_text_at_cursor(")");
break; cursor.column--;
}
} }
if (same) end_complex_operation();
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--;
}
}
_cancel_completion(); _cancel_completion();
} }
@ -4119,30 +4116,29 @@ void TextEdit::_update_completion_candidates() {
completion_index=0; completion_index=0;
completion_base=s; completion_base=s;
int ci_match=0; int ci_match=0;
Vector<float> sim_cache;
for(int i=0;i<completion_strings.size();i++) { for(int i=0;i<completion_strings.size();i++) {
if (completion_strings[i].begins_with(s)) { if (s.is_subsequence_ofi(completion_strings[i])) {
// don't remove duplicates if no input is provided // don't remove duplicates if no input is provided
if (completion_options.find(completion_strings[i]) != -1 && s != "") { if (s != "" && completion_options.find(completion_strings[i]) != -1) {
continue; continue;
} }
completion_options.push_back(completion_strings[i]); // Calculate the similarity to keep completions in good order
int m=0; float similarity = s.similarity(completion_strings[i]);
int max=MIN(completion_current.length(),completion_strings[i].length()); int comp_size = completion_options.size();
if (max<ci_match) if (comp_size == 0) {
continue; completion_options.push_back(completion_strings[i]);
for(int j=0;j<max;j++) { sim_cache.push_back(similarity);
} else {
if (j>=completion_strings[i].length()) float comp_sim;
break; int pos = 0;
if (completion_current[j]!=completion_strings[i][j]) do {
break; comp_sim = sim_cache[pos++];
m++; } 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;
}
} }
} }
@ -4155,7 +4151,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 0 // even there's only one option, user still get the chance to choose using it or not
if (completion_options.size()==1) { if (completion_options.size()==1) {
@ -4167,8 +4164,6 @@ void TextEdit::_update_completion_candidates() {
} }
#endif #endif
if (completion_options.size()==1 && s==completion_options[0])
_cancel_completion();
completion_enabled=true; completion_enabled=true;
} }