diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim
index 642fbbf9..94a83ab8 100644
--- a/autoload/youcompleteme.vim
+++ b/autoload/youcompleteme.vim
@@ -484,7 +484,7 @@ function! s:CompletionsForQuery( query, use_filetype_completer,
endif
endwhile
- let l:results = pyeval( 'completer.CandidatesFromStoredRequest()' )
+ let l:results = pyeval( 'base.AdjustCandidateInsertionText( completer.CandidatesFromStoredRequest() )' )
let s:searched_and_results_found = len( l:results ) != 0
return { 'words' : l:results, 'refresh' : 'always' }
endfunction
diff --git a/python/ycm/base.py b/python/ycm/base.py
index 5adb66cf..cecd8a9d 100644
--- a/python/ycm/base.py
+++ b/python/ycm/base.py
@@ -18,6 +18,7 @@
# along with YouCompleteMe. If not, see .
import os
+import re
import vim
from ycm import vimsupport
from ycm import utils
@@ -70,6 +71,55 @@ def CurrentIdentifierFinished():
return line[ : current_column ].isspace()
+def AdjustCandidateInsertionText( candidates ):
+ """This function adjusts the candidate insertion text to take into account the
+ text that's currently in front of the cursor.
+
+ For instance ('|' represents the cursor):
+ 1. Buffer state: 'foo.|bar'
+ 2. A completion candidate of 'zoobar' is shown and the user selects it.
+ 3. Buffer state: 'foo.zoobar|bar' instead of 'foo.zoo|bar' which is what the
+ user wanted.
+
+ This function changes candidates to resolve that issue.
+
+ It could be argued that the user actually wants the final buffer state to be
+ 'foo.zoobar|' (the cursor at the end), but that would be much more difficult
+ 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 ) ]
+ return to_insert
+
+ match = re.search( r'^(\w+)', vimsupport.TextAfterCursor() )
+ if not match:
+ return candidates
+
+ new_candidates = []
+
+ word_after_cursor = match.group( 1 )
+ for candidate in candidates:
+ if type( candidate ) is dict:
+ new_candidate = candidate.copy()
+
+ if not 'abbr' in new_candidate:
+ new_candidate[ 'abbr' ] = new_candidate[ 'word' ]
+
+ new_candidate[ 'word' ] = NewCandidateInsertionText(
+ new_candidate[ 'word' ],
+ word_after_cursor )
+
+ new_candidates.append( new_candidate )
+
+ elif type( candidate ) is str:
+ new_candidates.append(
+ { 'abbr': candidate,
+ 'word': NewCandidateInsertionText( candidate, word_after_cursor ) } )
+ return new_candidates
+
+
COMPATIBLE_WITH_CORE_VERSION = 4
def CompatibleWithYcmCore():
diff --git a/python/ycm/completers/all/omni_completer.py b/python/ycm/completers/all/omni_completer.py
index 17052647..66b6fff6 100644
--- a/python/ycm/completers/all/omni_completer.py
+++ b/python/ycm/completers/all/omni_completer.py
@@ -45,6 +45,7 @@ class OmniCompleter( Completer ):
return super( OmniCompleter, self ).ShouldUseNow( start_column )
return self.ShouldUseNowInner( start_column )
+
def ShouldUseNowInner( self, start_column ):
if not self.omnifunc:
return False
@@ -58,6 +59,7 @@ class OmniCompleter( Completer ):
else:
return self.CandidatesForQueryAsyncInner( query, unused_start_column )
+
def CandidatesForQueryAsyncInner( self, query, unused_start_column ):
if not self.omnifunc:
self.stored_candidates = None
@@ -104,6 +106,7 @@ class OmniCompleter( Completer ):
else:
return self.CandidatesFromStoredRequestInner()
+
def CandidatesFromStoredRequestInner( self ):
return self.stored_candidates if self.stored_candidates else []
diff --git a/python/ycm/test_utils.py b/python/ycm/test_utils.py
index 4b48ef60..ab07013f 100644
--- a/python/ycm/test_utils.py
+++ b/python/ycm/test_utils.py
@@ -24,7 +24,13 @@ def MockVimModule():
"""The 'vim' module is something that is only present when running inside the
Vim Python interpreter, so we replace it with a MagicMock for tests. """
+ def VimEval( value ):
+ if value == "g:ycm_min_num_of_chars_for_completion":
+ return 0
+ return ''
+
vim_mock = MagicMock()
- vim_mock.eval = MagicMock( return_value = '' )
+ vim_mock.eval = MagicMock( side_effect = VimEval )
sys.modules[ 'vim' ] = vim_mock
return vim_mock
+
diff --git a/python/ycm/tests/base_test.py b/python/ycm/tests/base_test.py
new file mode 100644
index 00000000..24c97c8c
--- /dev/null
+++ b/python/ycm/tests/base_test.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2013 Strahinja Val Markovic
+#
+# This file is part of YouCompleteMe.
+#
+# YouCompleteMe is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# YouCompleteMe is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with YouCompleteMe. If not, see .
+
+from nose.tools import eq_
+from mock import MagicMock
+from ycm.test_utils import MockVimModule
+vim_mock = MockVimModule()
+from ycm import base
+from ycm import vimsupport
+
+
+def AdjustCandidateInsertionText_Basic_test():
+ vimsupport.TextAfterCursor = MagicMock( return_value = 'bar' )
+ eq_( [ { 'abbr': 'foobar', 'word': 'foo' } ],
+ base.AdjustCandidateInsertionText( [ 'foobar' ] ) )
+
+
+def AdjustCandidateInsertionText_ParenInTextAfterCursor_test():
+ vimsupport.TextAfterCursor = MagicMock( return_value = 'bar(zoo' )
+ eq_( [ { 'abbr': 'foobar', 'word': 'foo' } ],
+ base.AdjustCandidateInsertionText( [ 'foobar' ] ) )
+
+
+def AdjustCandidateInsertionText_PlusInTextAfterCursor_test():
+ vimsupport.TextAfterCursor = MagicMock( return_value = 'bar+zoo' )
+ eq_( [ { 'abbr': 'foobar', 'word': 'foo' } ],
+ base.AdjustCandidateInsertionText( [ 'foobar' ] ) )
+
+
+def AdjustCandidateInsertionText_WhitespaceInTextAfterCursor_test():
+ vimsupport.TextAfterCursor = MagicMock( return_value = 'bar zoo' )
+ eq_( [ { 'abbr': 'foobar', 'word': 'foo' } ],
+ base.AdjustCandidateInsertionText( [ 'foobar' ] ) )
+
+
+def AdjustCandidateInsertionText_NotSuffix_test():
+ vimsupport.TextAfterCursor = MagicMock( return_value = 'bar' )
+ eq_( [ { 'abbr': 'foofoo', 'word': 'foofoo' } ],
+ base.AdjustCandidateInsertionText( [ 'foofoo' ] ) )
+
+
+def AdjustCandidateInsertionText_NothingAfterCursor_test():
+ vimsupport.TextAfterCursor = MagicMock( return_value = '' )
+ eq_( [ 'foofoo',
+ 'zobar' ],
+ base.AdjustCandidateInsertionText( [ 'foofoo',
+ 'zobar' ] ) )
+
+
+def AdjustCandidateInsertionText_MultipleStrings_test():
+ vimsupport.TextAfterCursor = MagicMock( return_value = 'bar' )
+ eq_( [ { 'abbr': 'foobar', 'word': 'foo' },
+ { 'abbr': 'zobar', 'word': 'zo' },
+ { 'abbr': 'qbar', 'word': 'q' },
+ { 'abbr': 'bar', 'word': '' },
+ ],
+ base.AdjustCandidateInsertionText( [ 'foobar',
+ 'zobar',
+ 'qbar',
+ 'bar' ] ) )
+
+
+def AdjustCandidateInsertionText_DictInput_test():
+ vimsupport.TextAfterCursor = MagicMock( return_value = 'bar' )
+ eq_( [ { 'abbr': 'foobar', 'word': 'foo' } ],
+ base.AdjustCandidateInsertionText(
+ [ { 'word': 'foobar' } ] ) )
+
+
+def AdjustCandidateInsertionText_DontTouchAbbr_test():
+ vimsupport.TextAfterCursor = MagicMock( return_value = 'bar' )
+ eq_( [ { 'abbr': '1234', 'word': 'foo' } ],
+ base.AdjustCandidateInsertionText(
+ [ { 'abbr': '1234', 'word': 'foobar' } ] ) )
diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py
index 15532f7d..40082676 100644
--- a/python/ycm/vimsupport.py
+++ b/python/ycm/vimsupport.py
@@ -41,6 +41,11 @@ def CurrentColumn():
return vim.current.window.cursor[ 1 ]
+def TextAfterCursor():
+ """Returns the text after CurrentColumn."""
+ return vim.current.line[ CurrentColumn(): ]
+
+
def GetUnsavedBuffers():
def BufferModified( buffer_number ):
to_eval = 'getbufvar({0}, "&mod")'.format( buffer_number )