Auto merge of #1636 - mispencer:CsAddImport, r=puremourning

Add required namespace in C# after completing

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.

Demo:
![c namespaceinsertdemo](https://cloud.githubusercontent.com/assets/1434266/9337289/b7994542-459b-11e5-8f52-e48af76a3aab.gif)

By default, the using statement is inserted after the last using statement. If there aren't any, it's inserted before the first line. This behavior is customization by setting a function to the vim global ``g:ycm_cs_insert_namespace_function``. I implemented this in pure vimscript largely so I could provide such customization...

Caveats: vim does not provide a way to get which completion was inserted. To get this, we store the completions, hook the CompleteDone event, and check than if the preceding text matches any of the completions. This is imperfect, and sometimes matches when it shouldn't. Also, the CompleteDone event requires 7.3.598 or newer.
This commit is contained in:
Homu 2015-09-14 00:48:30 +09:00
commit 0d426e4922
9 changed files with 677 additions and 42 deletions

View File

@ -167,7 +167,7 @@ that are conservatively turned off by default that you may want to turn on.
Please refer to the full Installation Guide below; the following commands are
provided on a best-effort basis and may not work for you.
Make sure you have Vim 7.3.584 with python2 support. Ubuntu 14.04 and later have
Make sure you have Vim 7.3.598 with python2 support. Ubuntu 14.04 and later have
a Vim that's recent enough. You can see the version of Vim installed by running
`vim --version`. If the version is too old, you may need to [compile Vim
from source][vim-build] (don't worry, it's easy).
@ -219,7 +219,7 @@ Please refer to the full Installation Guide below; the following commands are
provided on a best-effort basis and may not work for you. OpenBSD / FreeBSD are
not officially supported platforms by YCM.
Make sure you have Vim 7.3.584 with python2 support.
Make sure you have Vim 7.3.598 with python2 support.
OpenBSD 5.5 and later have a Vim that's recent enough. You can see the version of
Vim installed by running `vim --version`.
@ -277,19 +277,19 @@ process.
**Please follow the instructions carefully. Read EVERY WORD.**
1. **Ensure that your version of Vim is _at least_ 7.3.584 _and_ that it has
1. **Ensure that your version of Vim is _at least_ 7.3.598 _and_ that it has
support for python2 scripting**.
Inside Vim, type `:version`. Look at the first two to three lines of output;
it should say `Vi IMproved X.Y`, where X.Y is the major version of vim. If
your version is greater than 7.3, then you're all set. If your version is
7.3 then look below that where it says, `Included patches: 1-Z`, where Z
will be some number. That number needs to be 584 or higher.
will be some number. That number needs to be 598 or higher.
If your version of Vim is not recent enough, you may need to [compile Vim
from source][vim-build] (don't worry, it's easy).
After you have made sure that you have Vim 7.3.584+, type the following in
After you have made sure that you have Vim 7.3.598+, type the following in
Vim: `:echo has('python')`. The output should be 1. If it's 0, then get a
version of Vim with Python support.
@ -1364,6 +1364,23 @@ Default: `0`
let g:ycm_csharp_server_port = 0
### The `g:ycm_csharp_insert_namespace_expr` option
By default, when YCM inserts a namespace, it will insert the `using` statement
under the nearest `using` statement. You may prefer that the `using` statement is
inserted somewhere, for example, to preserve sorting. If so, you can set this
option to override this behaviour.
When this option is set, instead of inserting the `using` statement itself, YCM
will set the global variable `g:ycm_namespace_to_insert` to the namespace to
insert, and then evaluate this option's value as an expression. The option's
expression is responsible for inserting the namespace - the default insertion
will not occur.
Default: ''
let g:ycm_csharp_insert_namespace_expr = ''
### The `g:ycm_add_preview_to_completeopt` option
When this option is set to `1`, YCM will add the `preview` string to Vim's

View File

@ -84,6 +84,7 @@ function! youcompleteme#Enable()
autocmd InsertLeave * call s:OnInsertLeave()
autocmd InsertEnter * call s:OnInsertEnter()
autocmd VimLeave * call s:OnVimLeave()
autocmd CompleteDone * call s:OnCompleteDone()
augroup END
" Calling these once solves the problem of BufReadPre/BufRead/BufEnter not
@ -359,6 +360,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

View File

@ -73,23 +73,24 @@ Contents ~
26. The |g:ycm_auto_start_csharp_server| option
27. The |g:ycm_auto_stop_csharp_server| option
28. The |g:ycm_csharp_server_port| option
29. The |g:ycm_add_preview_to_completeopt| option
30. The |g:ycm_autoclose_preview_window_after_completion| option
31. The |g:ycm_autoclose_preview_window_after_insertion| option
32. The |g:ycm_max_diagnostics_to_display| option
33. The |g:ycm_key_list_select_completion| option
34. The |g:ycm_key_list_previous_completion| option
35. The |g:ycm_key_invoke_completion| option
36. The |g:ycm_key_detailed_diagnostics| option
37. The |g:ycm_global_ycm_extra_conf| option
38. The |g:ycm_confirm_extra_conf| option
39. The |g:ycm_extra_conf_globlist| option
40. The |g:ycm_filepath_completion_use_working_dir| option
41. The |g:ycm_semantic_triggers| option
42. The |g:ycm_cache_omnifunc| option
43. The |g:ycm_use_ultisnips_completer| option
44. The |g:ycm_goto_buffer_command| option
45. The |g:ycm_disable_for_files_larger_than_kb| option
29. The |g:ycm_csharp_insert_namespace_expr| option
30. The |g:ycm_add_preview_to_completeopt| option
31. The |g:ycm_autoclose_preview_window_after_completion| option
32. The |g:ycm_autoclose_preview_window_after_insertion| option
33. The |g:ycm_max_diagnostics_to_display| option
34. The |g:ycm_key_list_select_completion| option
35. The |g:ycm_key_list_previous_completion| option
36. The |g:ycm_key_invoke_completion| option
37. The |g:ycm_key_detailed_diagnostics| option
38. The |g:ycm_global_ycm_extra_conf| option
39. The |g:ycm_confirm_extra_conf| option
40. The |g:ycm_extra_conf_globlist| option
41. The |g:ycm_filepath_completion_use_working_dir| option
42. The |g:ycm_semantic_triggers| option
43. The |g:ycm_cache_omnifunc| option
44. The |g:ycm_use_ultisnips_completer| option
45. The |g:ycm_goto_buffer_command| option
46. The |g:ycm_disable_for_files_larger_than_kb| option
8. FAQ |youcompleteme-faq|
1. I used to be able to 'import vim' in '.ycm_extra_conf.py', but now can't |import-vim|
2. On very rare occasions Vim crashes when I tab through the completion menu |youcompleteme-on-very-rare-occasions-vim-crashes-when-i-tab-through-completion-menu|
@ -311,7 +312,7 @@ Ubuntu Linux x64 super-quick installation ~
Please refer to the full Installation Guide below; the following commands are
provided on a best-effort basis and may not work for you.
Make sure you have Vim 7.3.584 with python2 support. Ubuntu 14.04 and later
Make sure you have Vim 7.3.598 with python2 support. Ubuntu 14.04 and later
have a Vim that's recent enough. You can see the version of Vim installed by
running 'vim --version'. If the version is too old, you may need to compile Vim
from source [16] (don't worry, it's easy).
@ -368,7 +369,7 @@ Please refer to the full Installation Guide below; the following commands are
provided on a best-effort basis and may not work for you. OpenBSD / FreeBSD are
not officially supported platforms by YCM.
Make sure you have Vim 7.3.584 with python2 support.
Make sure you have Vim 7.3.598 with python2 support.
OpenBSD 5.5 and later have a Vim that's recent enough. You can see the version
of Vim installed by running 'vim --version'.
@ -429,19 +430,19 @@ process.
**Please follow the instructions carefully. Read EVERY WORD.**
1. **Ensure that your version of Vim is _at least_ 7.3.584 _and_ that it has
1. **Ensure that your version of Vim is _at least_ 7.3.598 _and_ that it has
support for python2 scripting**.
Inside Vim, type ':version'. Look at the first two to three lines of
output; it should say 'Vi IMproved X.Y', where X.Y is the major version
of vim. If your version is greater than 7.3, then you're all set. If your
version is 7.3 then look below that where it says, 'Included patches:
1-Z', where Z will be some number. That number needs to be 584 or higher.
1-Z', where Z will be some number. That number needs to be 598 or higher.
If your version of Vim is not recent enough, you may need to compile Vim
from source [16] (don't worry, it's easy).
After you have made sure that you have Vim 7.3.584+, type the following
After you have made sure that you have Vim 7.3.598+, type the following
in Vim: ":echo has('python')". The output should be 1. If it's 0, then
get a version of Vim with Python support.
@ -1580,6 +1581,24 @@ Default: '0'
let g:ycm_csharp_server_port = 0
<
-------------------------------------------------------------------------------
The *g:ycm_csharp_insert_namespace_expr* option
By default, when YCM inserts a namespace, it will insert the 'using' statement
under the nearest 'using' statement. You may prefer that the 'using' statement
is inserted somewhere, for example, to preserve sorting. If so, you can set
this option to override this behaviour.
When this option is set, instead of inserting the 'using' statement itself, YCM
will set the global variable 'g:ycm_namespace_to_insert' to the namespace to
insert, and then evaluate this option's value as an expression. The option's
expression is responsible for inserting the namespace - the default insertion
will not occur.
Default: ''
>
let g:ycm_csharp_insert_namespace_expr = ''
<
-------------------------------------------------------------------------------
The *g:ycm_add_preview_to_completeopt* option
When this option is set to '1', YCM will add the 'preview' string to Vim's

View File

@ -27,9 +27,9 @@ endfunction
if exists( "g:loaded_youcompleteme" )
call s:restore_cpo()
finish
elseif v:version < 703 || (v:version == 703 && !has('patch584'))
elseif v:version < 703 || (v:version == 703 && !has('patch598'))
echohl WarningMsg |
\ echomsg "YouCompleteMe unavailable: requires Vim 7.3.584+" |
\ echomsg "YouCompleteMe unavailable: requires Vim 7.3.598+" |
\ echohl None
call s:restore_cpo()
finish

View File

@ -41,7 +41,7 @@ class CompletionRequest( BaseRequest ):
return self._response_future.done()
def Response( self ):
def RawResponse( self ):
if not self._response_future:
return []
try:
@ -51,14 +51,17 @@ 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 _ConvertCompletionDataToVimData( completion_data ):
def Response( self ):
return _ConvertCompletionDatasToVimDatas( self.RawResponse() )
def ConvertCompletionDataToVimData( completion_data ):
# see :h complete-items for a description of the dictionary fields
vim_data = {
'word' : ToUtf8IfNeeded( completion_data[ 'insertion_text' ] ),
@ -89,6 +92,6 @@ def _ConvertCompletionDataToVimData( completion_data ):
return vim_data
def _ConvertCompletionResponseToVimDatas( response_data ):
return [ _ConvertCompletionDataToVimData( x )
for x in response_data[ 'completions' ] ]
def _ConvertCompletionDatasToVimDatas( response_data ):
return [ ConvertCompletionDataToVimData( x )
for x in response_data ]

View File

@ -29,7 +29,7 @@ class ConvertCompletionResponseToVimDatas_test:
completion_request._ConvertCompletionResponseToVimDatas method """
def _Check( self, completion_data, expected_vim_data ):
vim_data = completion_request._ConvertCompletionDataToVimData(
vim_data = completion_request.ConvertCompletionDataToVimData(
completion_data )
try:

View File

@ -0,0 +1,393 @@
#!/usr/bin/env python
#
# Copyright (C) 2015 YouCompleteMe contributors
#
# 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, DEFAULT )
from nose.tools import eq_
from hamcrest import assert_that, empty
from ycm import vimsupport
from ycm.youcompleteme import YouCompleteMe
def GetCompleteDoneHooks_ResultOnCsharp_test():
vimsupport.CurrentFiletypes = MagicMock( return_value = [ "cs" ] )
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
result = ycm_state.GetCompleteDoneHooks()
eq_( 1, len( list( result ) ) )
def GetCompleteDoneHooks_EmptyOnOtherFiletype_test():
vimsupport.CurrentFiletypes = MagicMock( return_value = [ "txt" ] )
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
result = ycm_state.GetCompleteDoneHooks()
eq_( 0, len( list( result ) ) )
def OnCompleteDone_WithActionCallsIt_test():
vimsupport.CurrentFiletypes = MagicMock( return_value = [ "txt" ] )
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
action = MagicMock()
ycm_state._complete_done_hooks[ "txt" ] = action
ycm_state.OnCompleteDone()
assert action.called
def OnCompleteDone_NoActionNoError_test():
vimsupport.CurrentFiletypes = MagicMock( return_value = [ "txt" ] )
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
ycm_state.OnCompleteDone()
def FilterToCompletedCompletions_NewVim_MatchIsReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Test" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state._FilterToMatchingCompletions( completions, False )
eq_( list( result ), completions )
def FilterToCompletedCompletions_NewVim_ShortTextDoesntRaise_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "A" )
completions = [ _BuildCompletion( "AAA" ) ]
ycm_state._FilterToMatchingCompletions( completions, False )
def FilterToCompletedCompletions_NewVim_ExactMatchIsReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Test" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state._FilterToMatchingCompletions( completions, False )
eq_( list( result ), completions )
def FilterToCompletedCompletions_NewVim_NonMatchIsntReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( " Quote" )
completions = [ _BuildCompletion( "A" ) ]
result = ycm_state._FilterToMatchingCompletions( completions, False )
assert_that( list( result ), empty() )
def FilterToCompletedCompletions_OldVim_MatchIsReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Test" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state._FilterToMatchingCompletions( completions, False )
eq_( list( result ), completions )
def FilterToCompletedCompletions_OldVim_ShortTextDoesntRaise_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = "X" )
completions = [ _BuildCompletion( "AAA" ) ]
ycm_state._FilterToMatchingCompletions( completions, False )
def FilterToCompletedCompletions_OldVim_ExactMatchIsReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = "Test" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state._FilterToMatchingCompletions( completions, False )
eq_( list( result ), completions )
def FilterToCompletedCompletions_OldVim_NonMatchIsntReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Quote" )
completions = [ _BuildCompletion( "A" ) ]
result = ycm_state._FilterToMatchingCompletions( completions, False )
assert_that( list( result ), empty() )
def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_MatchIsReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Te" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
eq_( result, True )
def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_ShortTextDoesntRaise_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = "X" )
completions = [ _BuildCompletion( "AAA" ) ]
ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_ExactMatchIsntReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = "Test" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
eq_( result, False )
def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_NonMatchIsntReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Quote" )
completions = [ _BuildCompletion( "A" ) ]
result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
eq_( result, False )
def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_MatchIsReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Te" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
eq_( result, True )
def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_ShortTextDoesntRaise_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "X" )
completions = [ _BuildCompletion( "AAA" ) ]
ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_ExactMatchIsntReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Test" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
eq_( result, False )
def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_NonMatchIsntReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( " Quote" )
completions = [ _BuildCompletion( "A" ) ]
result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
eq_( result, False )
def GetRequiredNamespaceImport_ReturnNoneForNoExtraData_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
eq_( None, 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 GetCompletionsUserMayHaveCompleted_ReturnEmptyIfNotDone_test():
ycm_state = _SetupForCsharpCompletionDone( [] )
ycm_state._latest_completion_request.Done = MagicMock( return_value = False )
eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() )
def GetCompletionsUserMayHaveCompleted_ReturnEmptyIfPendingMatches_NewVim_test():
completions = [ _BuildCompletion( None ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Te" )
eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() )
def GetCompletionsUserMayHaveCompleted_ReturnEmptyIfPendingMatches_OldVim_test():
completions = [ _BuildCompletion( None ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Te" )
eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() )
def GetCompletionsUserMayHaveCompleted_ReturnMatchIfExactMatches_NewVim_test():
info = [ "NS","Test", "Abbr", "Menu", "Info", "Kind" ]
completions = [ _BuildCompletion( *info ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( *info[ 1 : ] )
eq_( completions, ycm_state.GetCompletionsUserMayHaveCompleted() )
def GetCompletionsUserMayHaveCompleted_ReturnMatchIfExactMatchesEvenIfPartial_NewVim_test():
info = [ "NS", "Test", "Abbr", "Menu", "Info", "Kind" ]
completions = [ _BuildCompletion( *info ),
_BuildCompletion( insertion_text = "TestTest" ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( *info[ 1 : ] )
eq_( [ completions[ 0 ] ], ycm_state.GetCompletionsUserMayHaveCompleted() )
def GetCompletionsUserMayHaveCompleted_DontReturnMatchIfNontExactMatchesAndPartial_NewVim_test():
info = [ "NS", "Test", "Abbr", "Menu", "Info", "Kind" ]
completions = [ _BuildCompletion( insertion_text = info[ 0 ] ),
_BuildCompletion( insertion_text = "TestTest" ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( *info[ 1 : ] )
eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() )
def GetCompletionsUserMayHaveCompleted_ReturnMatchIfMatches_NewVim_test():
completions = [ _BuildCompletion( None ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.VimVersionAtLeast = MagicMock( return_value = True )
vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Test" )
eq_( completions, ycm_state.GetCompletionsUserMayHaveCompleted() )
def GetCompletionsUserMayHaveCompleted_ReturnMatchIfMatches_OldVim_test():
completions = [ _BuildCompletion( None ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.VimVersionAtLeast = MagicMock( return_value = False )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Test" )
eq_( completions, ycm_state.GetCompletionsUserMayHaveCompleted() )
def PostCompleteCsharp_EmptyDoesntInsertNamespace_test():
ycm_state = _SetupForCsharpCompletionDone( [] )
ycm_state._OnCompleteDone_Csharp()
assert not vimsupport.InsertNamespace.called
def PostCompleteCsharp_ExistingWithoutNamespaceDoesntInsertNamespace_test():
completions = [ _BuildCompletion( None ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
ycm_state._OnCompleteDone_Csharp()
assert not vimsupport.InsertNamespace.called
def PostCompleteCsharp_ValueDoesInsertNamespace_test():
namespace = "A_NAMESPACE"
completions = [ _BuildCompletion( namespace ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
ycm_state._OnCompleteDone_Csharp()
vimsupport.InsertNamespace.assert_called_once_with( namespace )
def PostCompleteCsharp_InsertSecondNamespaceIfSelected_test():
namespace = "A_NAMESPACE"
namespace2 = "ANOTHER_NAMESPACE"
completions = [
_BuildCompletion( namespace ),
_BuildCompletion( namespace2 ),
]
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.PresentDialog = MagicMock( return_value = 1 )
ycm_state._OnCompleteDone_Csharp()
vimsupport.InsertNamespace.assert_called_once_with( namespace2 )
def _SetupForCsharpCompletionDone( completions ):
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 = None, insertion_text = 'Test',
menu_text = None, extra_menu_info = None,
detailed_info = None, kind = None ):
return {
'extra_data': { 'required_namespace_import' : namespace },
'insertion_text': insertion_text,
'menu_text': menu_text,
'extra_menu_info': extra_menu_info,
'kind': kind,
'detailed_info': detailed_info,
}
def GetVariableValue_CompleteItemIs( word, abbr = None, menu = None,
info = None, kind = None ):
def Result( variable ):
if variable == 'v:completed_item':
return {
'word': word,
'abbr': abbr,
'menu': menu,
'info': info,
'kind': kind,
}
else:
return DEFAULT
return MagicMock( side_effect = Result )

View File

@ -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
@ -60,14 +61,20 @@ def TextAfterCursor():
return vim.current.line[ CurrentColumn(): ]
def TextBeforeCursor():
"""Returns the text before CurrentColumn."""
return vim.current.line[ :CurrentColumn() ]
# Expects version_string in 'MAJOR.MINOR.PATCH' format, e.g. '7.4.301'
def VimVersionAtLeast( version_string ):
major, minor, patch = [ int( x ) for x in version_string.split( '.' ) ]
# For Vim 7.4.301, v:version is '704'
actual_major_and_minor = GetIntValue( 'v:version' )
if actual_major_and_minor != major * 100 + minor:
return False
matching_major_and_minor = major * 100 + minor
if actual_major_and_minor != matching_major_and_minor:
return actual_major_and_minor > matching_major_and_minor
return GetBoolValue( 'has("patch{0}")'.format( patch ) )
@ -447,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 )
@ -534,3 +549,29 @@ 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_csharp_insert_namespace_expr' ):
expr = GetVariableValue( 'g:ycm_csharp_insert_namespace_expr' )
if expr:
SetVariableValue( "g:ycm_namespace_to_insert", namespace )
vim.eval( expr )
return
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 ]

View File

@ -21,6 +21,7 @@ import os
import vim
import tempfile
import json
import re
import signal
import base64
from subprocess import PIPE
@ -34,7 +35,8 @@ from ycm.client.ycmd_keepalive import YcmdKeepalive
from ycm.client.base_request import BaseRequest, BuildRequestData
from ycm.client.completer_available_request import SendCompleterAvailableRequest
from ycm.client.command_request import SendCommandRequest
from ycm.client.completion_request import CompletionRequest
from ycm.client.completion_request import ( CompletionRequest,
ConvertCompletionDataToVimData )
from ycm.client.omni_completion_request import OmniCompletionRequest
from ycm.client.event_notification import ( SendEventNotificationAsync,
EventNotification )
@ -96,6 +98,9 @@ class YouCompleteMe( object ):
self._ycmd_keepalive = YcmdKeepalive()
self._SetupServer()
self._ycmd_keepalive.Start()
self._complete_done_hooks = {
'cs': lambda( self ): self._OnCompleteDone_Csharp()
}
def _SetupServer( self ):
self._available_completers = {}
@ -294,6 +299,159 @@ class YouCompleteMe( object ):
SendEventNotificationAsync( 'CurrentIdentifierFinished' )
def OnCompleteDone( self ):
complete_done_actions = self.GetCompleteDoneHooks()
for action in complete_done_actions:
action(self)
def GetCompleteDoneHooks( self ):
filetypes = vimsupport.CurrentFiletypes()
for key, value in self._complete_done_hooks.iteritems():
if key in filetypes:
yield value
def GetCompletionsUserMayHaveCompleted( self ):
latest_completion_request = self.GetCurrentCompletionRequest()
if not latest_completion_request or not latest_completion_request.Done():
return []
completions = latest_completion_request.RawResponse()
result = self._FilterToMatchingCompletions( completions, True )
result = list( result )
if result:
return result
if self._HasCompletionsThatCouldBeCompletedWithMoreText( completions ):
# Since the way that YCM works leads to CompleteDone called on every
# character, return blank if the completion might not be done. This won't
# match if the completion is ended with typing a non-keyword character.
return []
result = self._FilterToMatchingCompletions( completions, False )
return list( result )
def _FilterToMatchingCompletions( self, completions, full_match_only ):
self._PatchBasedOnVimVersion()
return self._FilterToMatchingCompletions( completions, full_match_only)
def _HasCompletionsThatCouldBeCompletedWithMoreText( self, completions ):
self._PatchBasedOnVimVersion()
return self._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
def _PatchBasedOnVimVersion( self ):
if vimsupport.VimVersionAtLeast( "7.4.774" ):
self._HasCompletionsThatCouldBeCompletedWithMoreText = \
self._HasCompletionsThatCouldBeCompletedWithMoreText_NewerVim
self._FilterToMatchingCompletions = \
self._FilterToMatchingCompletions_NewerVim
else:
self._FilterToMatchingCompletions = \
self._FilterToMatchingCompletions_OlderVim
self._HasCompletionsThatCouldBeCompletedWithMoreText = \
self._HasCompletionsThatCouldBeCompletedWithMoreText_OlderVim
def _FilterToMatchingCompletions_NewerVim( self, completions,
full_match_only ):
""" Filter to completions matching the item Vim said was completed """
completed = vimsupport.GetVariableValue( 'v:completed_item' )
for completion in completions:
item = ConvertCompletionDataToVimData( completion )
match_keys = ( [ "word", "abbr", "menu", "info" ] if full_match_only
else [ 'word' ] )
matcher = lambda key: completed.get( key, "" ) == item.get( key, "" )
if all( [ matcher( i ) for i in match_keys ] ):
yield completion
def _FilterToMatchingCompletions_OlderVim( self, completions,
full_match_only ):
""" Filter to completions matching the buffer text """
if full_match_only:
return # Only supported in 7.4.774+
# No support for multiple line completions
text = vimsupport.TextBeforeCursor()
for completion in completions:
word = completion[ "insertion_text" ]
# Trim complete-ending character if needed
text = re.sub( r"[^a-zA-Z0-9_]$", "", text )
buffer_text = text[ -1 * len( word ) : ]
if buffer_text == word:
yield completion
def _HasCompletionsThatCouldBeCompletedWithMoreText_NewerVim( self,
completions ):
completed_item = vimsupport.GetVariableValue( 'v:completed_item' )
completed_word = completed_item[ 'word' ]
if not completed_word:
return False
# Sometime CompleteDone is called after the next character is inserted
# If so, use inserted character to filter possible completions further
text = vimsupport.TextBeforeCursor()
reject_exact_match = True
if text and text[ -1 ] != completed_word[ -1 ]:
reject_exact_match = False
completed_word += text[ -1 ]
for completion in completions:
word = ConvertCompletionDataToVimData( completion )[ 'word' ]
if reject_exact_match and word == completed_word:
continue
if word.startswith( completed_word ):
return True
return False
def _HasCompletionsThatCouldBeCompletedWithMoreText_OlderVim( self,
completions ):
# No support for multiple line completions
text = vimsupport.TextBeforeCursor()
for completion in completions:
word = ConvertCompletionDataToVimData( completion )[ 'word' ]
for i in range( 1, len( word ) - 1 ): # Excluding full word
if text[ -1 * i : ] == word[ : i ]:
return True
return False
def _OnCompleteDone_Csharp( self ):
completions = self.GetCompletionsUserMayHaveCompleted()
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 _GetRequiredNamespaceImport( self, completion ):
if ( "extra_data" not in completion
or "required_namespace_import" not in completion[ "extra_data" ] ):
return None
return completion[ "extra_data" ][ "required_namespace_import" ]
def DiagnosticsForCurrentFileReady( self ):
return bool( self._latest_file_parse_request and
self._latest_file_parse_request.Done() )
@ -418,5 +576,3 @@ def _AddUltiSnipsDataIfNeeded( extra_data ):
extra_data[ 'ultisnips_snippets' ] = [ { 'trigger': x.trigger,
'description': x.description
} for x in rawsnips ]