fixup! Add CompleteDone hook, with namespace insertion for C#

This commit is contained in:
Spencer G. Jones 2015-08-31 10:51:23 -06:00
parent dd27184970
commit 9f568be39a
7 changed files with 255 additions and 121 deletions

View File

@ -1365,6 +1365,18 @@ Default: `0`
let g:ycm_csharp_server_port = 0 let g:ycm_csharp_server_port = 0
### The `g:ycm_csharp_insert_namespace_expr` option
When YCM inserts a namespace, by default, it will insert it under the nearest
using statement. When this option is set, YCM will instead 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 expression is responsible
for inserting the namespace.
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,9 +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()
if pyeval( 'vimsupport.VimVersionAtLeast("7.3.598")' )
autocmd CompleteDone * call s:OnCompleteDone() autocmd CompleteDone * call s:OnCompleteDone()
endif
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

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|
@ -1581,6 +1582,19 @@ Default: '0'
let g:ycm_csharp_server_port = 0 let g:ycm_csharp_server_port = 0
< <
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
The *g:ycm_csharp_insert_namespace_expr* option
When YCM inserts a namespace, by default, it will insert it under the nearest
using statement. When this option is set, YCM will instead 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 expression is responsible
for inserting the namespace.
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

@ -1,6 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# #
# Copyright (C) 2013 Google Inc. # Copyright (C) 2015 YouCompleteMe contributors
# #
# This file is part of YouCompleteMe. # This file is part of YouCompleteMe.
# #
@ -23,22 +23,117 @@ from hamcrest import assert_that, empty
from ycm import vimsupport from ycm import vimsupport
from ycm.youcompleteme import YouCompleteMe from ycm.youcompleteme import YouCompleteMe
def HasPostCompletionAction_TrueOnCsharp_test(): def GetCompleteDoneHooks_ResultOnCsharp_test():
vimsupport.CurrentFiletypes = MagicMock( return_value = [ "cs" ] ) vimsupport.CurrentFiletypes = MagicMock( return_value = [ "cs" ] )
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
eq_( True, ycm_state.HasPostCompletionAction() ) result = ycm_state.GetCompleteDoneHooks()
eq_( 1, len( list( result ) ) )
def HasPostCompletionAction_FalseOnOtherFiletype_test(): def GetCompleteDoneHooks_EmptyOnOtherFiletype_test():
vimsupport.CurrentFiletypes = MagicMock( return_value = [ "txt" ] ) vimsupport.CurrentFiletypes = MagicMock( return_value = [ "txt" ] )
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
eq_( False, ycm_state.HasPostCompletionAction() ) result = ycm_state.GetCompleteDoneHooks()
eq_( 0, len( list( result ) ) )
def GetRequiredNamespaceImport_ReturnEmptyForNoExtraData_test(): 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 = YouCompleteMe( MagicMock( spec_set = dict ) )
eq_( "", ycm_state.GetRequiredNamespaceImport( {} ) ) ycm_state.OnCompleteDone()
def FilterToCompletionsMatchingOnCursor_MatchIsReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Test" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state.FilterToCompletionsMatchingOnCursor( completions )
eq_( list( result ), completions )
def FilterToCompletionsMatchingOnCursor_ShortTextDoesntRaise_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.TextBeforeCursor = MagicMock( return_value = "X" )
completions = [ _BuildCompletion( "AAA" ) ]
ycm_state.FilterToCompletionsMatchingOnCursor( completions )
def FilterToCompletionsMatchingOnCursor_ExactMatchIsReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.TextBeforeCursor = MagicMock( return_value = "Test" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state.FilterToCompletionsMatchingOnCursor( completions )
eq_( list( result ), completions )
def FilterToCompletionsMatchingOnCursor_NonMatchIsntReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Quote" )
completions = [ _BuildCompletion( "A" ) ]
result = ycm_state.FilterToCompletionsMatchingOnCursor( completions )
assert_that( list( result ), empty() )
def HasCompletionsThatCouldMatchOnCursorWithMoreText_MatchIsReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Te" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state.HasCompletionsThatCouldMatchOnCursorWithMoreText( completions )
eq_( result, True )
def HasCompletionsThatCouldMatchOnCursorWithMoreText_ShortTextDoesntRaise_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.TextBeforeCursor = MagicMock( return_value = "X" )
completions = [ _BuildCompletion( "AAA" ) ]
ycm_state.HasCompletionsThatCouldMatchOnCursorWithMoreText( completions )
def HasCompletionsThatCouldMatchOnCursorWithMoreText_ExactMatchIsntReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.TextBeforeCursor = MagicMock( return_value = "Test" )
completions = [ _BuildCompletion( "Test" ) ]
result = ycm_state.HasCompletionsThatCouldMatchOnCursorWithMoreText( completions )
eq_( result, False )
def HasCompletionsThatCouldMatchOnCursorWithMoreText_NonMatchIsntReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Quote" )
completions = [ _BuildCompletion( "A" ) ]
result = ycm_state.HasCompletionsThatCouldMatchOnCursorWithMoreText( completions )
eq_( result, False )
def GetRequiredNamespaceImport_ReturnNoneForNoExtraData_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
eq_( None, ycm_state.GetRequiredNamespaceImport( {} ) )
def GetRequiredNamespaceImport_ReturnNamespaceFromExtraData_test(): def GetRequiredNamespaceImport_ReturnNamespaceFromExtraData_test():
@ -50,86 +145,71 @@ def GetRequiredNamespaceImport_ReturnNamespaceFromExtraData_test():
)) ))
def FilterMatchingCompletions_MatchIsReturned_test(): def GetMatchingCompletionsOnCursor_ReturnEmptyIfNotDone_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) ycm_state = _SetupForCsharpCompletionDone( [] )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Test" ) ycm_state._latest_completion_request.Done = MagicMock( return_value = False )
completions = [ _BuildCompletion( "A" ) ]
result = ycm_state.FilterMatchingCompletions( completions ) eq_( [], ycm_state.GetMatchingCompletionsOnCursor() )
eq_( list( result ), completions )
def FilterMatchingCompletions_ShortTextDoesntRaise_test(): def GetMatchingCompletionsOnCursor_ReturnEmptyIfPendingMatches_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 ) ] completions = [ _BuildCompletion( None ) ]
ycm_state = _SetupForCompletionDone( completions ) ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Te" )
ycm_state.OnCompleteDone() eq_( [], ycm_state.GetMatchingCompletionsOnCursor() )
def GetMatchingCompletionsOnCursor_ReturnMatchIfMatches_test():
completions = [ _BuildCompletion( None ) ]
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Test" )
eq_( completions, ycm_state.GetMatchingCompletionsOnCursor() )
def PostCompleteCsharp_EmptyDoesntInsertNamespace_test():
ycm_state = _SetupForCsharpCompletionDone( [] )
ycm_state.OnCompleteDone_Csharp()
assert not vimsupport.InsertNamespace.called assert not vimsupport.InsertNamespace.called
def PostComplete_ValueDoesInsertNamespace_test(): 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" namespace = "A_NAMESPACE"
completions = [ _BuildCompletion( namespace ) ] completions = [ _BuildCompletion( namespace ) ]
ycm_state = _SetupForCompletionDone( completions ) ycm_state = _SetupForCsharpCompletionDone( completions )
ycm_state.OnCompleteDone() ycm_state.OnCompleteDone_Csharp()
vimsupport.InsertNamespace.assert_called_once_with( namespace ) vimsupport.InsertNamespace.assert_called_once_with( namespace )
def PostComplete_InsertSecondNamespaceIfSelected_test(): def PostCompleteCsharp_InsertSecondNamespaceIfSelected_test():
namespace = "A_NAMESPACE" namespace = "A_NAMESPACE"
namespace2 = "ANOTHER_NAMESPACE" namespace2 = "ANOTHER_NAMESPACE"
completions = [ completions = [
_BuildCompletion( namespace ), _BuildCompletion( namespace ),
_BuildCompletion( namespace2 ), _BuildCompletion( namespace2 ),
] ]
ycm_state = _SetupForCompletionDone( completions ) ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.PresentDialog = MagicMock( return_value = 1 ) vimsupport.PresentDialog = MagicMock( return_value = 1 )
ycm_state.OnCompleteDone() ycm_state.OnCompleteDone_Csharp()
vimsupport.InsertNamespace.assert_called_once_with( namespace2 ) vimsupport.InsertNamespace.assert_called_once_with( namespace2 )
def _SetupForCompletionDone( completions ): def _SetupForCsharpCompletionDone( completions ):
vimsupport.CurrentFiletypes = MagicMock( return_value = [ "cs" ] )
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
request = MagicMock(); request = MagicMock();
request.Done = MagicMock( return_value = True ) request.Done = MagicMock( return_value = True )

View File

@ -521,11 +521,13 @@ def ReplaceChunk( start, end, replacement_text, line_delta, char_delta,
def InsertNamespace( namespace ): def InsertNamespace( namespace ):
if VariableExists( 'g:ycm_cs_insert_namespace_function' ): if VariableExists( 'g:ycm_csharp_insert_namespace_expr' ):
function = GetVariableValue( 'g:ycm_cs_insert_namespace_function' ) expr = GetVariableValue( 'g:ycm_csharp_insert_namespace_expr' )
SetVariableValue( "g:ycm_namespace", namespace ) if expr:
vim.eval( function ) SetVariableValue( "g:ycm_namespace_to_insert", namespace )
else: vim.eval( expr )
return
pattern = '^\s*using\(\s\+[a-zA-Z0-9]\+\s\+=\)\?\s\+[a-zA-Z0-9.]\+\s*;\s*' pattern = '^\s*using\(\s\+[a-zA-Z0-9]\+\s\+=\)\?\s\+[a-zA-Z0-9.]\+\s*;\s*'
line = SearchInCurrentBuffer( pattern ) line = SearchInCurrentBuffer( pattern )
existing_line = LineTextInCurrentBuffer( line ) existing_line = LineTextInCurrentBuffer( 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
@ -96,6 +97,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 = {}
@ -293,18 +297,58 @@ class YouCompleteMe( object ):
def OnCompleteDone( self ): def OnCompleteDone( self ):
if not self.HasPostCompletionAction(): complete_done_actions = self.GetCompleteDoneHooks()
return 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 GetMatchingCompletionsOnCursor( self ):
latest_completion_request = self.GetCurrentCompletionRequest() latest_completion_request = self.GetCurrentCompletionRequest()
if not latest_completion_request.Done(): if not latest_completion_request.Done():
return return []
completions = latest_completion_request.RawResponse() completions = latest_completion_request.RawResponse()
completions = list( self.FilterMatchingCompletions( completions ) ) if self.HasCompletionsThatCouldMatchOnCursorWithMoreText( completions ):
if not completions: # Since the way that YCM works leads to CompleteDone called on every
return # 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.FilterToCompletionsMatchingOnCursor( completions )
return list( result )
def FilterToCompletionsMatchingOnCursor( self, completions ):
text = vimsupport.TextBeforeCursor() # No support for multiple line completions
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 HasCompletionsThatCouldMatchOnCursorWithMoreText( self, completions ):
text = vimsupport.TextBeforeCursor() # No support for multiple line completions
for completion in completions:
word = completion[ "insertion_text" ]
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.GetMatchingCompletionsOnCursor()
namespaces = [ self.GetRequiredNamespaceImport( c ) namespaces = [ self.GetRequiredNamespaceImport( c )
for c in completions ] for c in completions ]
namespaces = [ n for n in namespaces if n ] namespaces = [ n for n in namespaces if n ]
@ -312,10 +356,9 @@ class YouCompleteMe( object ):
return return
if len( namespaces ) > 1: if len( namespaces ) > 1:
choices = [ "{0}: {1}".format( i + 1, n ) choices = [ "{0} {1}".format( i + 1, n )
for i,n in enumerate( namespaces ) ] for i,n in enumerate( namespaces ) ]
choice = vimsupport.PresentDialog( choice = vimsupport.PresentDialog( "Insert which namespace:", choices )
"Insert which namespace:", choices )
if choice < 0: if choice < 0:
return return
namespace = namespaces[ choice ] namespace = namespaces[ choice ]
@ -325,25 +368,10 @@ class YouCompleteMe( object ):
vimsupport.InsertNamespace( namespace ) 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 ): def GetRequiredNamespaceImport( self, completion ):
if ( "extra_data" not in completion if ( "extra_data" not in completion
or "required_namespace_import" not in completion[ "extra_data" ] ): or "required_namespace_import" not in completion[ "extra_data" ] ):
return "" return None
return completion[ "extra_data" ][ "required_namespace_import" ] return completion[ "extra_data" ][ "required_namespace_import" ]