Browse Source

Candidate overlap with buffer text improved

Now, "foobar.h" will be changed to insert "foo" if the text after the cursor is
"bar.h". This already worked for "foobar" and "bar", but the overlap search
would stop before a non-word character. This has now been resolved.
Strahinja Val Markovic 11 years ago
parent
commit
a18807d31e
2 changed files with 87 additions and 14 deletions
  1. 41 12
      python/ycm/base.py
  2. 46 2
      python/ycm/tests/base_test.py

+ 41 - 12
python/ycm/base.py

@@ -17,7 +17,6 @@
 # You should have received a copy of the GNU General Public License
 # along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.
 
-import re
 import vim
 from ycm import vimsupport
 from ycm import utils
@@ -124,18 +123,14 @@ def AdjustCandidateInsertionText( candidates ):
   to implement and is probably not worth doing.
   """
 
-  def NewCandidateInsertionText( to_insert, word_after_cursor ):
-    if to_insert.endswith( word_after_cursor ):
-      return to_insert[ : - len( word_after_cursor ) ]
+  def NewCandidateInsertionText( to_insert, text_after_cursor ):
+    overlap_len = OverlapLength( to_insert, text_after_cursor )
+    if overlap_len:
+      return to_insert[ :-overlap_len ]
     return to_insert
 
-  match = re.search( r'^(\w+)', vimsupport.TextAfterCursor() )
-  if not match:
-    return candidates
-
   new_candidates = []
-
-  word_after_cursor = match.group( 1 )
+  text_after_cursor = vimsupport.TextAfterCursor()
   for candidate in candidates:
     if type( candidate ) is dict:
       new_candidate = candidate.copy()
@@ -145,17 +140,51 @@ def AdjustCandidateInsertionText( candidates ):
 
       new_candidate[ 'word' ] = NewCandidateInsertionText(
         new_candidate[ 'word' ],
-        word_after_cursor )
+        text_after_cursor )
 
       new_candidates.append( new_candidate )
 
     elif type( candidate ) is str:
       new_candidates.append(
         { 'abbr': candidate,
-          'word': NewCandidateInsertionText( candidate, word_after_cursor ) } )
+          'word': NewCandidateInsertionText( candidate, text_after_cursor ) } )
   return new_candidates
 
 
+def OverlapLength( left_string, right_string ):
+  """Returns the length of the overlap between two strings.
+  Example: "foo baro" and "baro zoo" -> 4
+  """
+  left_string_length = len( left_string )
+  right_string_length = len( right_string )
+
+  if not left_string_length or not right_string_length:
+    return 0
+
+  # Truncate the longer string.
+  if left_string_length > right_string_length:
+    left_string = left_string[ -right_string_length: ]
+  elif left_string_length < right_string_length:
+    right_string = right_string[ :left_string_length ]
+
+  if left_string == right_string:
+    return min( left_string_length, right_string_length )
+
+  # Start by looking for a single character match
+  # and increase length until no match is found.
+  best = 0
+  length = 1
+  while True:
+    pattern = left_string[ -length: ]
+    found = right_string.find( pattern )
+    if found < 0:
+      return best
+    length += found
+    if left_string[ -length: ] == right_string[ :length ]:
+      best = length
+      length += 1
+
+
 COMPATIBLE_WITH_CORE_VERSION = 7
 
 def CompatibleWithYcmCore():

+ 46 - 2
python/ycm/tests/base_test.py

@@ -49,6 +49,16 @@ def AdjustCandidateInsertionText_WhitespaceInTextAfterCursor_test():
        base.AdjustCandidateInsertionText( [ 'foobar' ] ) )
 
 
+def AdjustCandidateInsertionText_MoreThanWordMatchingAfterCursor_test():
+  vimsupport.TextAfterCursor = MagicMock( return_value = 'bar.h' )
+  eq_( [ { 'abbr': 'foobar.h', 'word': 'foo' } ],
+       base.AdjustCandidateInsertionText( [ 'foobar.h' ] ) )
+
+  vimsupport.TextAfterCursor = MagicMock( return_value = 'bar(zoo' )
+  eq_( [ { 'abbr': 'foobar(zoo', 'word': 'foo' } ],
+       base.AdjustCandidateInsertionText( [ 'foobar(zoo' ] ) )
+
+
 def AdjustCandidateInsertionText_NotSuffix_test():
   vimsupport.TextAfterCursor = MagicMock( return_value = 'bar' )
   eq_( [ { 'abbr': 'foofoo', 'word': 'foofoo' } ],
@@ -57,8 +67,8 @@ def AdjustCandidateInsertionText_NotSuffix_test():
 
 def AdjustCandidateInsertionText_NothingAfterCursor_test():
   vimsupport.TextAfterCursor = MagicMock( return_value = '' )
-  eq_( [ 'foofoo',
-         'zobar' ],
+  eq_( [ { 'abbr': 'foofoo', 'word': 'foofoo' },
+         { 'abbr': 'zobar', 'word': 'zobar' }, ],
        base.AdjustCandidateInsertionText( [ 'foofoo',
                                             'zobar' ] ) )
 
@@ -88,3 +98,37 @@ def AdjustCandidateInsertionText_DontTouchAbbr_test():
   eq_( [ { 'abbr': '1234', 'word': 'foo' } ],
        base.AdjustCandidateInsertionText(
          [ { 'abbr': '1234', 'word': 'foobar' } ] ) )
+
+
+def OverlapLength_Basic_test():
+  eq_( 3, base.OverlapLength( 'foo bar', 'bar zoo' ) )
+  eq_( 3, base.OverlapLength( 'foobar', 'barzoo' ) )
+
+
+def OverlapLength_OneCharOverlap_test():
+  eq_( 1, base.OverlapLength( 'foo b', 'b zoo' ) )
+
+
+def OverlapLength_SameStrings_test():
+  eq_( 6, base.OverlapLength( 'foobar', 'foobar' ) )
+
+
+def OverlapLength_Substring_test():
+  eq_( 6, base.OverlapLength( 'foobar', 'foobarzoo' ) )
+  eq_( 6, base.OverlapLength( 'zoofoobar', 'foobar' ) )
+
+
+def OverlapLength_LongestOverlap_test():
+  eq_( 7, base.OverlapLength( 'bar foo foo', 'foo foo bar' ) )
+
+
+def OverlapLength_EmptyInput_test():
+  eq_( 0, base.OverlapLength( '', 'goobar' ) )
+  eq_( 0, base.OverlapLength( 'foobar', '' ) )
+  eq_( 0, base.OverlapLength( '', '' ) )
+
+
+def OverlapLength_NoOverlap_test():
+  eq_( 0, base.OverlapLength( 'foobar', 'goobar' ) )
+  eq_( 0, base.OverlapLength( 'foobar', '(^($@#$#@' ) )
+  eq_( 0, base.OverlapLength( 'foo bar zoo', 'foo zoo bar' ) )