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
### 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
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 InsertEnter * call s:OnInsertEnter()
autocmd VimLeave * call s:OnVimLeave()
if pyeval( 'vimsupport.VimVersionAtLeast("7.3.598")' )
autocmd CompleteDone * call s:OnCompleteDone()
endif
augroup END
" Calling these once solves the problem of BufReadPre/BufRead/BufEnter not

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|
@ -1581,6 +1582,19 @@ Default: '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
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

@ -1,6 +1,6 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Google Inc.
# Copyright (C) 2015 YouCompleteMe contributors
#
# This file is part of YouCompleteMe.
#
@ -23,22 +23,117 @@ from hamcrest import assert_that, empty
from ycm import vimsupport
from ycm.youcompleteme import YouCompleteMe
def HasPostCompletionAction_TrueOnCsharp_test():
def GetCompleteDoneHooks_ResultOnCsharp_test():
vimsupport.CurrentFiletypes = MagicMock( return_value = [ "cs" ] )
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" ] )
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 ) )
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():
@ -50,86 +145,71 @@ def GetRequiredNamespaceImport_ReturnNamespaceFromExtraData_test():
))
def FilterMatchingCompletions_MatchIsReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Test" )
completions = [ _BuildCompletion( "A" ) ]
def GetMatchingCompletionsOnCursor_ReturnEmptyIfNotDone_test():
ycm_state = _SetupForCsharpCompletionDone( [] )
ycm_state._latest_completion_request.Done = MagicMock( return_value = False )
result = ycm_state.FilterMatchingCompletions( completions )
eq_( list( result ), completions )
eq_( [], ycm_state.GetMatchingCompletionsOnCursor() )
def FilterMatchingCompletions_ShortTextDoesntRaise_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.TextBeforeCursor = MagicMock( return_value = "X" )
completions = [ _BuildCompletion( "A" ) ]
ycm_state.FilterMatchingCompletions( completions )
def FilterMatchingCompletions_ExactMatchIsReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.TextBeforeCursor = MagicMock( return_value = "Test" )
completions = [ _BuildCompletion( "A" ) ]
result = ycm_state.FilterMatchingCompletions( completions )
eq_( list( result ), completions )
def FilterMatchingCompletions_NonMatchIsntReturned_test():
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
vimsupport.TextBeforeCursor = MagicMock( return_value = " Quote" )
completions = [ _BuildCompletion( "A" ) ]
result = ycm_state.FilterMatchingCompletions( completions )
assert_that( list( result ), empty() )
def PostComplete_EmptyDoesntInsertNamespace_test():
ycm_state = _SetupForCompletionDone( [] )
ycm_state.OnCompleteDone()
assert not vimsupport.InsertNamespace.called
def PostComplete_ExistingWithoutNamespaceDoesntInsertNamespace_test():
def GetMatchingCompletionsOnCursor_ReturnEmptyIfPendingMatches_test():
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
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"
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 )
def PostComplete_InsertSecondNamespaceIfSelected_test():
def PostCompleteCsharp_InsertSecondNamespaceIfSelected_test():
namespace = "A_NAMESPACE"
namespace2 = "ANOTHER_NAMESPACE"
completions = [
_BuildCompletion( namespace ),
_BuildCompletion( namespace2 ),
]
ycm_state = _SetupForCompletionDone( completions )
ycm_state = _SetupForCsharpCompletionDone( completions )
vimsupport.PresentDialog = MagicMock( return_value = 1 )
ycm_state.OnCompleteDone()
ycm_state.OnCompleteDone_Csharp()
vimsupport.InsertNamespace.assert_called_once_with( namespace2 )
def _SetupForCompletionDone( completions ):
vimsupport.CurrentFiletypes = MagicMock( return_value = [ "cs" ] )
def _SetupForCsharpCompletionDone( completions ):
ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) )
request = MagicMock();
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 ):
if VariableExists( 'g:ycm_cs_insert_namespace_function' ):
function = GetVariableValue( 'g:ycm_cs_insert_namespace_function' )
SetVariableValue( "g:ycm_namespace", namespace )
vim.eval( function )
else:
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 )

View File

@ -21,6 +21,7 @@ import os
import vim
import tempfile
import json
import re
import signal
import base64
from subprocess import PIPE
@ -96,6 +97,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 = {}
@ -293,18 +297,58 @@ class YouCompleteMe( object ):
def OnCompleteDone( self ):
if not self.HasPostCompletionAction():
return
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 GetMatchingCompletionsOnCursor( self ):
latest_completion_request = self.GetCurrentCompletionRequest()
if not latest_completion_request.Done():
return
return []
completions = latest_completion_request.RawResponse()
completions = list( self.FilterMatchingCompletions( completions ) )
if not completions:
return
if self.HasCompletionsThatCouldMatchOnCursorWithMoreText( 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.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 )
for c in completions ]
namespaces = [ n for n in namespaces if n ]
@ -312,10 +356,9 @@ class YouCompleteMe( object ):
return
if len( namespaces ) > 1:
choices = [ "{0}: {1}".format( i + 1, n )
choices = [ "{0} {1}".format( i + 1, n )
for i,n in enumerate( namespaces ) ]
choice = vimsupport.PresentDialog(
"Insert which namespace:", choices )
choice = vimsupport.PresentDialog( "Insert which namespace:", choices )
if choice < 0:
return
namespace = namespaces[ choice ]
@ -325,25 +368,10 @@ class YouCompleteMe( object ):
vimsupport.InsertNamespace( namespace )
def HasPostCompletionAction( self ):
filetype = vimsupport.CurrentFiletypes()[ 0 ]
return filetype == 'cs'
def FilterMatchingCompletions( self, completions ):
text = vimsupport.TextBeforeCursor() # No support for multiple line completions
for completion in completions:
word = completion[ "insertion_text" ]
for i in [ None, -1 ]:
if text[ -1 * len( word ) + ( i or 0 ) : i ] == word:
yield completion
break
def GetRequiredNamespaceImport( self, completion ):
if ( "extra_data" not in completion
or "required_namespace_import" not in completion[ "extra_data" ] ):
return ""
return None
return completion[ "extra_data" ][ "required_namespace_import" ]