From a18807d31e81fc56c3831473e249089b7dbcb590 Mon Sep 17 00:00:00 2001 From: Strahinja Val Markovic Date: Sun, 5 Jan 2014 11:48:05 -0800 Subject: [PATCH] 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. --- python/ycm/base.py | 53 +++++++++++++++++++++++++++-------- python/ycm/tests/base_test.py | 48 +++++++++++++++++++++++++++++-- 2 files changed, 87 insertions(+), 14 deletions(-) diff --git a/python/ycm/base.py b/python/ycm/base.py index 2d0c2f7d..05314dd5 100644 --- a/python/ycm/base.py +++ b/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 . -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(): diff --git a/python/ycm/tests/base_test.py b/python/ycm/tests/base_test.py index 24c97c8c..f022486a 100644 --- a/python/ycm/tests/base_test.py +++ b/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' ) )