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
|
endif
|
||||||
endwhile
|
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
|
let s:searched_and_results_found = len( l:results ) != 0
|
||||||
return { 'words' : l:results, 'refresh' : 'always' }
|
return { 'words' : l:results, 'refresh' : 'always' }
|
||||||
endfunction
|
endfunction
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
|
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import vim
|
import vim
|
||||||
from ycm import vimsupport
|
from ycm import vimsupport
|
||||||
from ycm import utils
|
from ycm import utils
|
||||||
@ -70,6 +71,55 @@ def CurrentIdentifierFinished():
|
|||||||
return line[ : current_column ].isspace()
|
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
|
COMPATIBLE_WITH_CORE_VERSION = 4
|
||||||
|
|
||||||
def CompatibleWithYcmCore():
|
def CompatibleWithYcmCore():
|
||||||
|
@ -45,6 +45,7 @@ class OmniCompleter( Completer ):
|
|||||||
return super( OmniCompleter, self ).ShouldUseNow( start_column )
|
return super( OmniCompleter, self ).ShouldUseNow( start_column )
|
||||||
return self.ShouldUseNowInner( start_column )
|
return self.ShouldUseNowInner( start_column )
|
||||||
|
|
||||||
|
|
||||||
def ShouldUseNowInner( self, start_column ):
|
def ShouldUseNowInner( self, start_column ):
|
||||||
if not self.omnifunc:
|
if not self.omnifunc:
|
||||||
return False
|
return False
|
||||||
@ -58,6 +59,7 @@ class OmniCompleter( Completer ):
|
|||||||
else:
|
else:
|
||||||
return self.CandidatesForQueryAsyncInner( query, unused_start_column )
|
return self.CandidatesForQueryAsyncInner( query, unused_start_column )
|
||||||
|
|
||||||
|
|
||||||
def CandidatesForQueryAsyncInner( self, query, unused_start_column ):
|
def CandidatesForQueryAsyncInner( self, query, unused_start_column ):
|
||||||
if not self.omnifunc:
|
if not self.omnifunc:
|
||||||
self.stored_candidates = None
|
self.stored_candidates = None
|
||||||
@ -104,6 +106,7 @@ class OmniCompleter( Completer ):
|
|||||||
else:
|
else:
|
||||||
return self.CandidatesFromStoredRequestInner()
|
return self.CandidatesFromStoredRequestInner()
|
||||||
|
|
||||||
|
|
||||||
def CandidatesFromStoredRequestInner( self ):
|
def CandidatesFromStoredRequestInner( self ):
|
||||||
return self.stored_candidates if self.stored_candidates else []
|
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
|
"""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. """
|
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 = MagicMock()
|
||||||
vim_mock.eval = MagicMock( return_value = '' )
|
vim_mock.eval = MagicMock( side_effect = VimEval )
|
||||||
sys.modules[ 'vim' ] = vim_mock
|
sys.modules[ 'vim' ] = vim_mock
|
||||||
return 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 ]
|
return vim.current.window.cursor[ 1 ]
|
||||||
|
|
||||||
|
|
||||||
|
def TextAfterCursor():
|
||||||
|
"""Returns the text after CurrentColumn."""
|
||||||
|
return vim.current.line[ CurrentColumn(): ]
|
||||||
|
|
||||||
|
|
||||||
def GetUnsavedBuffers():
|
def GetUnsavedBuffers():
|
||||||
def BufferModified( buffer_number ):
|
def BufferModified( buffer_number ):
|
||||||
to_eval = 'getbufvar({0}, "&mod")'.format( buffer_number )
|
to_eval = 'getbufvar({0}, "&mod")'.format( buffer_number )
|
||||||
|
Loading…
Reference in New Issue
Block a user