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 Please refer to the full Installation Guide below; the following commands are
provided on a best-effort basis and may not work for you. 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 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 `vim --version`. If the version is too old, you may need to [compile Vim
from source][vim-build] (don't worry, it's easy). 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 provided on a best-effort basis and may not work for you. OpenBSD / FreeBSD are
not officially supported platforms by YCM. 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 OpenBSD 5.5 and later have a Vim that's recent enough. You can see the version of
Vim installed by running `vim --version`. Vim installed by running `vim --version`.
@ -277,19 +277,19 @@ process.
**Please follow the instructions carefully. Read EVERY WORD.** **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**. support for python2 scripting**.
Inside Vim, type `:version`. Look at the first two to three lines of output; 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 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 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 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 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). 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 Vim: `:echo has('python')`. The output should be 1. If it's 0, then get a
version of Vim with Python support. version of Vim with Python support.
@ -1364,6 +1364,23 @@ Default: `0`
let g:ycm_csharp_server_port = 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 ### The `g:ycm_add_preview_to_completeopt` option
When this option is set to `1`, YCM will add the `preview` string to Vim's 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 InsertLeave * call s:OnInsertLeave()
autocmd InsertEnter * call s:OnInsertEnter() autocmd InsertEnter * call s:OnInsertEnter()
autocmd VimLeave * call s:OnVimLeave() autocmd VimLeave * call s:OnVimLeave()
autocmd CompleteDone * call s:OnCompleteDone()
augroup END augroup END
" Calling these once solves the problem of BufReadPre/BufRead/BufEnter not " Calling these once solves the problem of BufReadPre/BufRead/BufEnter not
@ -359,6 +360,11 @@ function! s:OnVimLeave()
endfunction endfunction
function! s:OnCompleteDone()
py ycm_state.OnCompleteDone()
endfunction
function! s:OnBufferReadPre(filename) function! s:OnBufferReadPre(filename)
let threshold = g:ycm_disable_for_files_larger_than_kb * 1024 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 26. The |g:ycm_auto_start_csharp_server| option
27. The |g:ycm_auto_stop_csharp_server| option 27. The |g:ycm_auto_stop_csharp_server| option
28. The |g:ycm_csharp_server_port| option 28. The |g:ycm_csharp_server_port| option
29. The |g:ycm_add_preview_to_completeopt| option 29. The |g:ycm_csharp_insert_namespace_expr| option
30. The |g:ycm_autoclose_preview_window_after_completion| option 30. The |g:ycm_add_preview_to_completeopt| option
31. The |g:ycm_autoclose_preview_window_after_insertion| option 31. The |g:ycm_autoclose_preview_window_after_completion| option
32. The |g:ycm_max_diagnostics_to_display| option 32. The |g:ycm_autoclose_preview_window_after_insertion| option
33. The |g:ycm_key_list_select_completion| option 33. The |g:ycm_max_diagnostics_to_display| option
34. The |g:ycm_key_list_previous_completion| option 34. The |g:ycm_key_list_select_completion| option
35. The |g:ycm_key_invoke_completion| option 35. The |g:ycm_key_list_previous_completion| option
36. The |g:ycm_key_detailed_diagnostics| option 36. The |g:ycm_key_invoke_completion| option
37. The |g:ycm_global_ycm_extra_conf| option 37. The |g:ycm_key_detailed_diagnostics| option
38. The |g:ycm_confirm_extra_conf| option 38. The |g:ycm_global_ycm_extra_conf| option
39. The |g:ycm_extra_conf_globlist| option 39. The |g:ycm_confirm_extra_conf| option
40. The |g:ycm_filepath_completion_use_working_dir| option 40. The |g:ycm_extra_conf_globlist| option
41. The |g:ycm_semantic_triggers| option 41. The |g:ycm_filepath_completion_use_working_dir| option
42. The |g:ycm_cache_omnifunc| option 42. The |g:ycm_semantic_triggers| option
43. The |g:ycm_use_ultisnips_completer| option 43. The |g:ycm_cache_omnifunc| option
44. The |g:ycm_goto_buffer_command| option 44. The |g:ycm_use_ultisnips_completer| option
45. The |g:ycm_disable_for_files_larger_than_kb| option 45. The |g:ycm_goto_buffer_command| option
46. The |g:ycm_disable_for_files_larger_than_kb| option
8. FAQ |youcompleteme-faq| 8. FAQ |youcompleteme-faq|
1. I used to be able to 'import vim' in '.ycm_extra_conf.py', but now can't |import-vim| 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| 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 Please refer to the full Installation Guide below; the following commands are
provided on a best-effort basis and may not work for you. 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 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 running 'vim --version'. If the version is too old, you may need to compile Vim
from source [16] (don't worry, it's easy). 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 provided on a best-effort basis and may not work for you. OpenBSD / FreeBSD are
not officially supported platforms by YCM. 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 OpenBSD 5.5 and later have a Vim that's recent enough. You can see the version
of Vim installed by running 'vim --version'. of Vim installed by running 'vim --version'.
@ -429,19 +430,19 @@ process.
**Please follow the instructions carefully. Read EVERY WORD.** **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**. support for python2 scripting**.
Inside Vim, type ':version'. Look at the first two to three lines of 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 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 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: 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 If your version of Vim is not recent enough, you may need to compile Vim
from source [16] (don't worry, it's easy). 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 in Vim: ":echo has('python')". The output should be 1. If it's 0, then
get a version of Vim with Python support. get a version of Vim with Python support.
@ -1580,6 +1581,24 @@ Default: '0'
let g:ycm_csharp_server_port = 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 The *g:ycm_add_preview_to_completeopt* option
When this option is set to '1', YCM will add the 'preview' string to Vim's 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" ) if exists( "g:loaded_youcompleteme" )
call s:restore_cpo() call s:restore_cpo()
finish finish
elseif v:version < 703 || (v:version == 703 && !has('patch584')) elseif v:version < 703 || (v:version == 703 && !has('patch598'))
echohl WarningMsg | echohl WarningMsg |
\ echomsg "YouCompleteMe unavailable: requires Vim 7.3.584+" | \ echomsg "YouCompleteMe unavailable: requires Vim 7.3.598+" |
\ echohl None \ echohl None
call s:restore_cpo() call s:restore_cpo()
finish finish

View File

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

View File

@ -29,7 +29,7 @@ class ConvertCompletionResponseToVimDatas_test:
completion_request._ConvertCompletionResponseToVimDatas method """ completion_request._ConvertCompletionResponseToVimDatas method """
def _Check( self, completion_data, expected_vim_data ): def _Check( self, completion_data, expected_vim_data ):
vim_data = completion_request._ConvertCompletionDataToVimData( vim_data = completion_request.ConvertCompletionDataToVimData(
completion_data ) completion_data )
try: 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 os
import tempfile import tempfile
import json import json
import re
from ycmd.utils import ToUtf8IfNeeded from ycmd.utils import ToUtf8IfNeeded
from ycmd import user_options_store from ycmd import user_options_store
@ -60,14 +61,20 @@ def TextAfterCursor():
return vim.current.line[ CurrentColumn(): ] 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' # Expects version_string in 'MAJOR.MINOR.PATCH' format, e.g. '7.4.301'
def VimVersionAtLeast( version_string ): def VimVersionAtLeast( version_string ):
major, minor, patch = [ int( x ) for x in version_string.split( '.' ) ] major, minor, patch = [ int( x ) for x in version_string.split( '.' ) ]
# For Vim 7.4.301, v:version is '704' # For Vim 7.4.301, v:version is '704'
actual_major_and_minor = GetIntValue( 'v:version' ) actual_major_and_minor = GetIntValue( 'v:version' )
if actual_major_and_minor != major * 100 + minor: matching_major_and_minor = major * 100 + minor
return False 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 ) ) return GetBoolValue( 'has("patch{0}")'.format( patch ) )
@ -447,6 +454,14 @@ def FiletypesForBuffer( buffer_object ):
return GetBufferOption( buffer_object, 'ft' ).split( '.' ) 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 ): def GetVariableValue( variable ):
return vim.eval( 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 new_line_delta = replacement_lines_count - source_lines_count
return ( new_line_delta, new_char_delta ) 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 vim
import tempfile import tempfile
import json import json
import re
import signal import signal
import base64 import base64
from subprocess import PIPE 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.base_request import BaseRequest, BuildRequestData
from ycm.client.completer_available_request import SendCompleterAvailableRequest from ycm.client.completer_available_request import SendCompleterAvailableRequest
from ycm.client.command_request import SendCommandRequest 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.omni_completion_request import OmniCompletionRequest
from ycm.client.event_notification import ( SendEventNotificationAsync, from ycm.client.event_notification import ( SendEventNotificationAsync,
EventNotification ) EventNotification )
@ -96,6 +98,9 @@ class YouCompleteMe( object ):
self._ycmd_keepalive = YcmdKeepalive() self._ycmd_keepalive = YcmdKeepalive()
self._SetupServer() self._SetupServer()
self._ycmd_keepalive.Start() self._ycmd_keepalive.Start()
self._complete_done_hooks = {
'cs': lambda( self ): self._OnCompleteDone_Csharp()
}
def _SetupServer( self ): def _SetupServer( self ):
self._available_completers = {} self._available_completers = {}
@ -294,6 +299,159 @@ class YouCompleteMe( object ):
SendEventNotificationAsync( 'CurrentIdentifierFinished' ) 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 ): def DiagnosticsForCurrentFileReady( self ):
return bool( self._latest_file_parse_request and return bool( self._latest_file_parse_request and
self._latest_file_parse_request.Done() ) self._latest_file_parse_request.Done() )
@ -418,5 +576,3 @@ def _AddUltiSnipsDataIfNeeded( extra_data ):
extra_data[ 'ultisnips_snippets' ] = [ { 'trigger': x.trigger, extra_data[ 'ultisnips_snippets' ] = [ { 'trigger': x.trigger,
'description': x.description 'description': x.description
} for x in rawsnips ] } for x in rawsnips ]