Better completion in the middle of a word
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 commit resolves 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. Fixes #374.
This commit is contained in:
parent
8fe41c7c73
commit
e740bac1f6
@ -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
|
||||
|
@ -18,6 +18,7 @@
|
||||
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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():
|
||||
|
@ -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 []
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
90
python/ycm/tests/base_test.py
Normal file
90
python/ycm/tests/base_test.py
Normal file
@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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' } ] ) )
|
@ -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 )
|
||||
|
Loading…
Reference in New Issue
Block a user