Add CompleteDone hook, with namespace insertion for C#
Add a new vim hook on CompleteDone. This hook is called when a completions is selected. When forcing semantic completion with the keybind, C# completions can return a list of importable types. These types are from namespaces which havn't been imported, and thus are not valid to use without also adding their namespace's import statement. This change makes YCM automatically insert the necessary using statement to import that namespace on completion completion. In the case there are multiple possible namespaces, it prompts you to choose one.
This commit is contained in:
parent
7e9333a9c2
commit
dd27184970
@ -84,6 +84,9 @@ function! youcompleteme#Enable()
|
||||
autocmd InsertLeave * call s:OnInsertLeave()
|
||||
autocmd InsertEnter * call s:OnInsertEnter()
|
||||
autocmd VimLeave * call s:OnVimLeave()
|
||||
if pyeval( 'vimsupport.VimVersionAtLeast("7.3.598")' )
|
||||
autocmd CompleteDone * call s:OnCompleteDone()
|
||||
endif
|
||||
augroup END
|
||||
|
||||
" Calling these once solves the problem of BufReadPre/BufRead/BufEnter not
|
||||
@ -359,6 +362,11 @@ function! s:OnVimLeave()
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:OnCompleteDone()
|
||||
py ycm_state.OnCompleteDone()
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:OnBufferReadPre(filename)
|
||||
let threshold = g:ycm_disable_for_files_larger_than_kb * 1024
|
||||
|
||||
|
@ -40,7 +40,7 @@ class CompletionRequest( BaseRequest ):
|
||||
return self._response_future.done()
|
||||
|
||||
|
||||
def Response( self ):
|
||||
def RawResponse( self ):
|
||||
if not self._response_future:
|
||||
return []
|
||||
try:
|
||||
@ -50,13 +50,16 @@ class CompletionRequest( BaseRequest ):
|
||||
for e in errors:
|
||||
HandleServerException( MakeServerException( e ) )
|
||||
|
||||
return _ConvertCompletionResponseToVimDatas( response )
|
||||
return JsonFromFuture( self._response_future )[ 'completions' ]
|
||||
except Exception as e:
|
||||
HandleServerException( e )
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def Response( self ):
|
||||
return _ConvertCompletionDatasToVimDatas( self.RawResponse() )
|
||||
|
||||
|
||||
def _ConvertCompletionDataToVimData( completion_data ):
|
||||
# see :h complete-items for a description of the dictionary fields
|
||||
vim_data = {
|
||||
@ -77,6 +80,6 @@ def _ConvertCompletionDataToVimData( completion_data ):
|
||||
return vim_data
|
||||
|
||||
|
||||
def _ConvertCompletionResponseToVimDatas( response_data ):
|
||||
def _ConvertCompletionDatasToVimDatas( response_data ):
|
||||
return [ _ConvertCompletionDataToVimData( x )
|
||||
for x in response_data[ 'completions' ] ]
|
||||
for x in response_data ]
|
||||
|
147
python/ycm/tests/postcomplete_tests.py
Normal file
147
python/ycm/tests/postcomplete_tests.py
Normal file
@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2013 Google Inc.
|
||||
#
|
||||
# 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 mock import MagicMock
|
||||
from nose.tools import eq_
|
||||
from hamcrest import assert_that, empty
|
||||
from ycm import vimsupport
|
||||
from ycm.youcompleteme import YouCompleteMe
|
||||
|
||||
def HasPostCompletionAction_TrueOnCsharp_test():
|
||||
vimsupport.CurrentFiletypes = MagicMock( return_value = [ "cs" ] )
|
||||
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
|
||||
eq_( True, ycm_state.HasPostCompletionAction() )
|
||||
|
||||
|
||||
def HasPostCompletionAction_FalseOnOtherFiletype_test():
|
||||
vimsupport.CurrentFiletypes = MagicMock( return_value = [ "txt" ] )
|
||||
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
|
||||
eq_( False, ycm_state.HasPostCompletionAction() )
|
||||
|
||||
|
||||
def GetRequiredNamespaceImport_ReturnEmptyForNoExtraData_test():
|
||||
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
|
||||
|
||||
eq_( "", ycm_state.GetRequiredNamespaceImport( {} ) )
|
||||
|
||||
|
||||
def GetRequiredNamespaceImport_ReturnNamespaceFromExtraData_test():
|
||||
namespace = "A_NAMESPACE"
|
||||
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
|
||||
|
||||
eq_( namespace, ycm_state.GetRequiredNamespaceImport(
|
||||
_BuildCompletion( namespace )
|
||||
))
|
||||
|
||||
|
||||
def FilterMatchingCompletions_MatchIsReturned_test():
|
||||
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
|
||||
vimsupport.TextBeforeCursor = MagicMock( return_value = " Test" )
|
||||
completions = [ _BuildCompletion( "A" ) ]
|
||||
|
||||
result = ycm_state.FilterMatchingCompletions( completions )
|
||||
|
||||
eq_( list( result ), completions )
|
||||
|
||||
|
||||
def FilterMatchingCompletions_ShortTextDoesntRaise_test():
|
||||
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
|
||||
vimsupport.TextBeforeCursor = MagicMock( return_value = "X" )
|
||||
completions = [ _BuildCompletion( "A" ) ]
|
||||
|
||||
ycm_state.FilterMatchingCompletions( completions )
|
||||
|
||||
|
||||
def FilterMatchingCompletions_ExactMatchIsReturned_test():
|
||||
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
|
||||
vimsupport.TextBeforeCursor = MagicMock( return_value = "Test" )
|
||||
completions = [ _BuildCompletion( "A" ) ]
|
||||
|
||||
result = ycm_state.FilterMatchingCompletions( completions )
|
||||
|
||||
eq_( list( result ), completions )
|
||||
|
||||
|
||||
def FilterMatchingCompletions_NonMatchIsntReturned_test():
|
||||
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
|
||||
vimsupport.TextBeforeCursor = MagicMock( return_value = " Quote" )
|
||||
completions = [ _BuildCompletion( "A" ) ]
|
||||
|
||||
result = ycm_state.FilterMatchingCompletions( completions )
|
||||
|
||||
assert_that( list( result ), empty() )
|
||||
|
||||
|
||||
def PostComplete_EmptyDoesntInsertNamespace_test():
|
||||
ycm_state = _SetupForCompletionDone( [] )
|
||||
|
||||
ycm_state.OnCompleteDone()
|
||||
|
||||
assert not vimsupport.InsertNamespace.called
|
||||
|
||||
def PostComplete_ExistingWithoutNamespaceDoesntInsertNamespace_test():
|
||||
completions = [ _BuildCompletion( None ) ]
|
||||
ycm_state = _SetupForCompletionDone( completions )
|
||||
|
||||
ycm_state.OnCompleteDone()
|
||||
|
||||
assert not vimsupport.InsertNamespace.called
|
||||
|
||||
|
||||
def PostComplete_ValueDoesInsertNamespace_test():
|
||||
namespace = "A_NAMESPACE"
|
||||
completions = [ _BuildCompletion( namespace ) ]
|
||||
ycm_state = _SetupForCompletionDone( completions )
|
||||
|
||||
ycm_state.OnCompleteDone()
|
||||
|
||||
vimsupport.InsertNamespace.assert_called_once_with( namespace )
|
||||
|
||||
def PostComplete_InsertSecondNamespaceIfSelected_test():
|
||||
namespace = "A_NAMESPACE"
|
||||
namespace2 = "ANOTHER_NAMESPACE"
|
||||
completions = [
|
||||
_BuildCompletion( namespace ),
|
||||
_BuildCompletion( namespace2 ),
|
||||
]
|
||||
ycm_state = _SetupForCompletionDone( completions )
|
||||
vimsupport.PresentDialog = MagicMock( return_value = 1 )
|
||||
|
||||
ycm_state.OnCompleteDone()
|
||||
|
||||
vimsupport.InsertNamespace.assert_called_once_with( namespace2 )
|
||||
|
||||
|
||||
def _SetupForCompletionDone( completions ):
|
||||
vimsupport.CurrentFiletypes = MagicMock( return_value = [ "cs" ] )
|
||||
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
|
||||
request = MagicMock();
|
||||
request.Done = MagicMock( return_value = True )
|
||||
request.RawResponse = MagicMock( return_value = completions )
|
||||
ycm_state._latest_completion_request = request
|
||||
vimsupport.InsertNamespace = MagicMock()
|
||||
vimsupport.TextBeforeCursor = MagicMock( return_value = " Test" )
|
||||
return ycm_state
|
||||
|
||||
|
||||
def _BuildCompletion( namespace ):
|
||||
return {
|
||||
'extra_data': { 'required_namespace_import' : namespace },
|
||||
'insertion_text': 'Test'
|
||||
}
|
@ -21,6 +21,7 @@ import vim
|
||||
import os
|
||||
import tempfile
|
||||
import json
|
||||
import re
|
||||
from ycmd.utils import ToUtf8IfNeeded
|
||||
from ycmd import user_options_store
|
||||
|
||||
@ -453,6 +454,14 @@ def FiletypesForBuffer( buffer_object ):
|
||||
return GetBufferOption( buffer_object, 'ft' ).split( '.' )
|
||||
|
||||
|
||||
def VariableExists( variable ):
|
||||
return GetBoolValue( "exists( '{0}' )".format( EscapeForVim( variable ) ) )
|
||||
|
||||
|
||||
def SetVariableValue( variable, value ):
|
||||
vim.command( "let {0} = '{1}'".format( variable, EscapeForVim( value ) ) )
|
||||
|
||||
|
||||
def GetVariableValue( variable ):
|
||||
return vim.eval( variable )
|
||||
|
||||
@ -509,3 +518,27 @@ def ReplaceChunk( start, end, replacement_text, line_delta, char_delta,
|
||||
|
||||
new_line_delta = replacement_lines_count - source_lines_count
|
||||
return ( new_line_delta, new_char_delta )
|
||||
|
||||
|
||||
def InsertNamespace( namespace ):
|
||||
if VariableExists( 'g:ycm_cs_insert_namespace_function' ):
|
||||
function = GetVariableValue( 'g:ycm_cs_insert_namespace_function' )
|
||||
SetVariableValue( "g:ycm_namespace", namespace )
|
||||
vim.eval( function )
|
||||
else:
|
||||
pattern = '^\s*using\(\s\+[a-zA-Z0-9]\+\s\+=\)\?\s\+[a-zA-Z0-9.]\+\s*;\s*'
|
||||
line = SearchInCurrentBuffer( pattern )
|
||||
existing_line = LineTextInCurrentBuffer( line )
|
||||
existing_indent = re.sub( r"\S.*", "", existing_line )
|
||||
new_line = "{0}using {1};\n\n".format( existing_indent, namespace )
|
||||
replace_pos = { 'line_num': line + 1, 'column_num': 1 }
|
||||
ReplaceChunk( replace_pos, replace_pos, new_line, 0, 0 )
|
||||
PostVimMessage( "Add namespace: {0}".format( namespace ) )
|
||||
|
||||
|
||||
def SearchInCurrentBuffer( pattern ):
|
||||
return GetIntValue( "search('{0}', 'Wcnb')".format( EscapeForVim( pattern )))
|
||||
|
||||
|
||||
def LineTextInCurrentBuffer( line ):
|
||||
return vim.current.buffer[ line ]
|
||||
|
@ -292,6 +292,61 @@ class YouCompleteMe( object ):
|
||||
SendEventNotificationAsync( 'CurrentIdentifierFinished' )
|
||||
|
||||
|
||||
def OnCompleteDone( self ):
|
||||
if not self.HasPostCompletionAction():
|
||||
return
|
||||
|
||||
latest_completion_request = self.GetCurrentCompletionRequest()
|
||||
if not latest_completion_request.Done():
|
||||
return
|
||||
|
||||
completions = latest_completion_request.RawResponse()
|
||||
completions = list( self.FilterMatchingCompletions( completions ) )
|
||||
if not completions:
|
||||
return
|
||||
|
||||
namespaces = [ self.GetRequiredNamespaceImport( c )
|
||||
for c in completions ]
|
||||
namespaces = [ n for n in namespaces if n ]
|
||||
if not namespaces:
|
||||
return
|
||||
|
||||
if len( namespaces ) > 1:
|
||||
choices = [ "{0}: {1}".format( i + 1, n )
|
||||
for i,n in enumerate( namespaces ) ]
|
||||
choice = vimsupport.PresentDialog(
|
||||
"Insert which namespace:", choices )
|
||||
if choice < 0:
|
||||
return
|
||||
namespace = namespaces[ choice ]
|
||||
else:
|
||||
namespace = namespaces[ 0 ]
|
||||
|
||||
vimsupport.InsertNamespace( namespace )
|
||||
|
||||
|
||||
def HasPostCompletionAction( self ):
|
||||
filetype = vimsupport.CurrentFiletypes()[ 0 ]
|
||||
return filetype == 'cs'
|
||||
|
||||
|
||||
def FilterMatchingCompletions( self, completions ):
|
||||
text = vimsupport.TextBeforeCursor() # No support for multiple line completions
|
||||
for completion in completions:
|
||||
word = completion[ "insertion_text" ]
|
||||
for i in [ None, -1 ]:
|
||||
if text[ -1 * len( word ) + ( i or 0 ) : i ] == word:
|
||||
yield completion
|
||||
break
|
||||
|
||||
|
||||
def GetRequiredNamespaceImport( self, completion ):
|
||||
if ( "extra_data" not in completion
|
||||
or "required_namespace_import" not in completion[ "extra_data" ] ):
|
||||
return ""
|
||||
return completion[ "extra_data" ][ "required_namespace_import" ]
|
||||
|
||||
|
||||
def DiagnosticsForCurrentFileReady( self ):
|
||||
return bool( self._latest_file_parse_request and
|
||||
self._latest_file_parse_request.Done() )
|
||||
|
Loading…
Reference in New Issue
Block a user