From 7e9333a9c270fbbb2683e2b98a55a92cef87b930 Mon Sep 17 00:00:00 2001 From: "Spencer G. Jones" Date: Fri, 28 Aug 2015 08:26:04 -0600 Subject: [PATCH 1/9] VimVersionAtLeast was not "at least" for minor/major version differences --- python/ycm/vimsupport.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index 62200003..27c634a6 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -60,14 +60,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 ) ) From dd27184970ea54d7e31f7ff2ed5d7b630c532341 Mon Sep 17 00:00:00 2001 From: "Spencer G. Jones" Date: Fri, 28 Aug 2015 08:26:18 -0600 Subject: [PATCH 2/9] Add CompleteDone hook, with namespace insertion for C# Add a new vim hook on CompleteDone. This hook is called when a completions is selected. When forcing semantic completion with the keybind, C# completions can return a list of importable types. These types are from namespaces which havn't been imported, and thus are not valid to use without also adding their namespace's import statement. This change makes YCM automatically insert the necessary using statement to import that namespace on completion completion. In the case there are multiple possible namespaces, it prompts you to choose one. --- autoload/youcompleteme.vim | 8 ++ python/ycm/client/completion_request.py | 13 ++- python/ycm/tests/postcomplete_tests.py | 147 ++++++++++++++++++++++++ python/ycm/vimsupport.py | 33 ++++++ python/ycm/youcompleteme.py | 55 +++++++++ 5 files changed, 251 insertions(+), 5 deletions(-) create mode 100644 python/ycm/tests/postcomplete_tests.py diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 37568dc0..d5d7b35e 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -84,6 +84,9 @@ function! youcompleteme#Enable() autocmd InsertLeave * call s:OnInsertLeave() autocmd InsertEnter * call s:OnInsertEnter() autocmd VimLeave * call s:OnVimLeave() + if pyeval( 'vimsupport.VimVersionAtLeast("7.3.598")' ) + autocmd CompleteDone * call s:OnCompleteDone() + endif augroup END " Calling these once solves the problem of BufReadPre/BufRead/BufEnter not @@ -359,6 +362,11 @@ function! s:OnVimLeave() endfunction +function! s:OnCompleteDone() + py ycm_state.OnCompleteDone() +endfunction + + function! s:OnBufferReadPre(filename) let threshold = g:ycm_disable_for_files_larger_than_kb * 1024 diff --git a/python/ycm/client/completion_request.py b/python/ycm/client/completion_request.py index c630fb22..fafb8198 100644 --- a/python/ycm/client/completion_request.py +++ b/python/ycm/client/completion_request.py @@ -40,7 +40,7 @@ class CompletionRequest( BaseRequest ): return self._response_future.done() - def Response( self ): + def RawResponse( self ): if not self._response_future: return [] try: @@ -50,13 +50,16 @@ class CompletionRequest( BaseRequest ): for e in errors: HandleServerException( MakeServerException( e ) ) - return _ConvertCompletionResponseToVimDatas( response ) + return JsonFromFuture( self._response_future )[ 'completions' ] except Exception as e: HandleServerException( e ) - return [] + def Response( self ): + return _ConvertCompletionDatasToVimDatas( self.RawResponse() ) + + def _ConvertCompletionDataToVimData( completion_data ): # see :h complete-items for a description of the dictionary fields vim_data = { @@ -77,6 +80,6 @@ def _ConvertCompletionDataToVimData( completion_data ): return vim_data -def _ConvertCompletionResponseToVimDatas( response_data ): +def _ConvertCompletionDatasToVimDatas( response_data ): return [ _ConvertCompletionDataToVimData( x ) - for x in response_data[ 'completions' ] ] + for x in response_data ] diff --git a/python/ycm/tests/postcomplete_tests.py b/python/ycm/tests/postcomplete_tests.py new file mode 100644 index 00000000..1c0d0bc6 --- /dev/null +++ b/python/ycm/tests/postcomplete_tests.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python +# +# Copyright (C) 2013 Google Inc. +# +# This file is part of YouCompleteMe. +# +# YouCompleteMe is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# YouCompleteMe is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with YouCompleteMe. If not, see . + +from mock import MagicMock +from nose.tools import eq_ +from hamcrest import assert_that, empty +from ycm import vimsupport +from ycm.youcompleteme import YouCompleteMe + +def HasPostCompletionAction_TrueOnCsharp_test(): + vimsupport.CurrentFiletypes = MagicMock( return_value = [ "cs" ] ) + ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) + eq_( True, ycm_state.HasPostCompletionAction() ) + + +def HasPostCompletionAction_FalseOnOtherFiletype_test(): + vimsupport.CurrentFiletypes = MagicMock( return_value = [ "txt" ] ) + ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) + eq_( False, ycm_state.HasPostCompletionAction() ) + + +def GetRequiredNamespaceImport_ReturnEmptyForNoExtraData_test(): + ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) + + eq_( "", ycm_state.GetRequiredNamespaceImport( {} ) ) + + +def GetRequiredNamespaceImport_ReturnNamespaceFromExtraData_test(): + namespace = "A_NAMESPACE" + ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) + + eq_( namespace, ycm_state.GetRequiredNamespaceImport( + _BuildCompletion( namespace ) + )) + + +def FilterMatchingCompletions_MatchIsReturned_test(): + ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) + vimsupport.TextBeforeCursor = MagicMock( return_value = " Test" ) + completions = [ _BuildCompletion( "A" ) ] + + result = ycm_state.FilterMatchingCompletions( completions ) + + eq_( list( result ), completions ) + + +def FilterMatchingCompletions_ShortTextDoesntRaise_test(): + ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) + vimsupport.TextBeforeCursor = MagicMock( return_value = "X" ) + completions = [ _BuildCompletion( "A" ) ] + + ycm_state.FilterMatchingCompletions( completions ) + + +def FilterMatchingCompletions_ExactMatchIsReturned_test(): + ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) + vimsupport.TextBeforeCursor = MagicMock( return_value = "Test" ) + completions = [ _BuildCompletion( "A" ) ] + + result = ycm_state.FilterMatchingCompletions( completions ) + + eq_( list( result ), completions ) + + +def FilterMatchingCompletions_NonMatchIsntReturned_test(): + ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) + vimsupport.TextBeforeCursor = MagicMock( return_value = " Quote" ) + completions = [ _BuildCompletion( "A" ) ] + + result = ycm_state.FilterMatchingCompletions( completions ) + + assert_that( list( result ), empty() ) + + +def PostComplete_EmptyDoesntInsertNamespace_test(): + ycm_state = _SetupForCompletionDone( [] ) + + ycm_state.OnCompleteDone() + + assert not vimsupport.InsertNamespace.called + +def PostComplete_ExistingWithoutNamespaceDoesntInsertNamespace_test(): + completions = [ _BuildCompletion( None ) ] + ycm_state = _SetupForCompletionDone( completions ) + + ycm_state.OnCompleteDone() + + assert not vimsupport.InsertNamespace.called + + +def PostComplete_ValueDoesInsertNamespace_test(): + namespace = "A_NAMESPACE" + completions = [ _BuildCompletion( namespace ) ] + ycm_state = _SetupForCompletionDone( completions ) + + ycm_state.OnCompleteDone() + + vimsupport.InsertNamespace.assert_called_once_with( namespace ) + +def PostComplete_InsertSecondNamespaceIfSelected_test(): + namespace = "A_NAMESPACE" + namespace2 = "ANOTHER_NAMESPACE" + completions = [ + _BuildCompletion( namespace ), + _BuildCompletion( namespace2 ), + ] + ycm_state = _SetupForCompletionDone( completions ) + vimsupport.PresentDialog = MagicMock( return_value = 1 ) + + ycm_state.OnCompleteDone() + + vimsupport.InsertNamespace.assert_called_once_with( namespace2 ) + + +def _SetupForCompletionDone( completions ): + vimsupport.CurrentFiletypes = MagicMock( return_value = [ "cs" ] ) + ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) + request = MagicMock(); + request.Done = MagicMock( return_value = True ) + request.RawResponse = MagicMock( return_value = completions ) + ycm_state._latest_completion_request = request + vimsupport.InsertNamespace = MagicMock() + vimsupport.TextBeforeCursor = MagicMock( return_value = " Test" ) + return ycm_state + + +def _BuildCompletion( namespace ): + return { + 'extra_data': { 'required_namespace_import' : namespace }, + 'insertion_text': 'Test' + } diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index 27c634a6..eb939822 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -21,6 +21,7 @@ import vim import os import tempfile import json +import re from ycmd.utils import ToUtf8IfNeeded from ycmd import user_options_store @@ -453,6 +454,14 @@ def FiletypesForBuffer( buffer_object ): return GetBufferOption( buffer_object, 'ft' ).split( '.' ) +def VariableExists( variable ): + return GetBoolValue( "exists( '{0}' )".format( EscapeForVim( variable ) ) ) + + +def SetVariableValue( variable, value ): + vim.command( "let {0} = '{1}'".format( variable, EscapeForVim( value ) ) ) + + def GetVariableValue( variable ): return vim.eval( variable ) @@ -509,3 +518,27 @@ def ReplaceChunk( start, end, replacement_text, line_delta, char_delta, new_line_delta = replacement_lines_count - source_lines_count return ( new_line_delta, new_char_delta ) + + +def InsertNamespace( namespace ): + if VariableExists( 'g:ycm_cs_insert_namespace_function' ): + function = GetVariableValue( 'g:ycm_cs_insert_namespace_function' ) + SetVariableValue( "g:ycm_namespace", namespace ) + vim.eval( function ) + else: + pattern = '^\s*using\(\s\+[a-zA-Z0-9]\+\s\+=\)\?\s\+[a-zA-Z0-9.]\+\s*;\s*' + line = SearchInCurrentBuffer( pattern ) + existing_line = LineTextInCurrentBuffer( line ) + existing_indent = re.sub( r"\S.*", "", existing_line ) + new_line = "{0}using {1};\n\n".format( existing_indent, namespace ) + replace_pos = { 'line_num': line + 1, 'column_num': 1 } + ReplaceChunk( replace_pos, replace_pos, new_line, 0, 0 ) + PostVimMessage( "Add namespace: {0}".format( namespace ) ) + + +def SearchInCurrentBuffer( pattern ): + return GetIntValue( "search('{0}', 'Wcnb')".format( EscapeForVim( pattern ))) + + +def LineTextInCurrentBuffer( line ): + return vim.current.buffer[ line ] diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index 895296dd..f1f735cb 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -292,6 +292,61 @@ class YouCompleteMe( object ): SendEventNotificationAsync( 'CurrentIdentifierFinished' ) + def OnCompleteDone( self ): + if not self.HasPostCompletionAction(): + return + + latest_completion_request = self.GetCurrentCompletionRequest() + if not latest_completion_request.Done(): + return + + completions = latest_completion_request.RawResponse() + completions = list( self.FilterMatchingCompletions( completions ) ) + if not completions: + return + + namespaces = [ self.GetRequiredNamespaceImport( c ) + for c in completions ] + namespaces = [ n for n in namespaces if n ] + if not namespaces: + return + + if len( namespaces ) > 1: + choices = [ "{0}: {1}".format( i + 1, n ) + for i,n in enumerate( namespaces ) ] + choice = vimsupport.PresentDialog( + "Insert which namespace:", choices ) + if choice < 0: + return + namespace = namespaces[ choice ] + else: + namespace = namespaces[ 0 ] + + vimsupport.InsertNamespace( namespace ) + + + def HasPostCompletionAction( self ): + filetype = vimsupport.CurrentFiletypes()[ 0 ] + return filetype == 'cs' + + + def FilterMatchingCompletions( self, completions ): + text = vimsupport.TextBeforeCursor() # No support for multiple line completions + for completion in completions: + word = completion[ "insertion_text" ] + for i in [ None, -1 ]: + if text[ -1 * len( word ) + ( i or 0 ) : i ] == word: + yield completion + break + + + def GetRequiredNamespaceImport( self, completion ): + if ( "extra_data" not in completion + or "required_namespace_import" not in completion[ "extra_data" ] ): + return "" + return completion[ "extra_data" ][ "required_namespace_import" ] + + def DiagnosticsForCurrentFileReady( self ): return bool( self._latest_file_parse_request and self._latest_file_parse_request.Done() ) From 9f568be39a36738f649f937e99f37f596a6ccb72 Mon Sep 17 00:00:00 2001 From: "Spencer G. Jones" Date: Mon, 31 Aug 2015 10:51:23 -0600 Subject: [PATCH 3/9] fixup! Add CompleteDone hook, with namespace insertion for C# --- README.md | 12 ++ autoload/youcompleteme.vim | 4 +- doc/youcompleteme.txt | 48 +++--- plugin/youcompleteme.vim | 4 +- python/ycm/tests/postcomplete_tests.py | 202 +++++++++++++++++-------- python/ycm/vimsupport.py | 28 ++-- python/ycm/youcompleteme.py | 78 +++++++--- 7 files changed, 255 insertions(+), 121 deletions(-) diff --git a/README.md b/README.md index e4619698..5a503ded 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index d5d7b35e..56a9162e 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -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 + autocmd CompleteDone * call s:OnCompleteDone() augroup END " Calling these once solves the problem of BufReadPre/BufRead/BufEnter not diff --git a/doc/youcompleteme.txt b/doc/youcompleteme.txt index f47138ae..d7a40446 100644 --- a/doc/youcompleteme.txt +++ b/doc/youcompleteme.txt @@ -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 diff --git a/plugin/youcompleteme.vim b/plugin/youcompleteme.vim index f1624cf5..e2fde723 100644 --- a/plugin/youcompleteme.vim +++ b/plugin/youcompleteme.vim @@ -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 diff --git a/python/ycm/tests/postcomplete_tests.py b/python/ycm/tests/postcomplete_tests.py index 1c0d0bc6..b9efcefb 100644 --- a/python/ycm/tests/postcomplete_tests.py +++ b/python/ycm/tests/postcomplete_tests.py @@ -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_( [], ycm_state.GetMatchingCompletionsOnCursor() ) + - eq_( list( result ), completions ) - - -def FilterMatchingCompletions_ShortTextDoesntRaise_test(): - ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) - vimsupport.TextBeforeCursor = MagicMock( return_value = "X" ) - completions = [ _BuildCompletion( "A" ) ] - - ycm_state.FilterMatchingCompletions( completions ) - - -def FilterMatchingCompletions_ExactMatchIsReturned_test(): - ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) - vimsupport.TextBeforeCursor = MagicMock( return_value = "Test" ) - completions = [ _BuildCompletion( "A" ) ] - - result = ycm_state.FilterMatchingCompletions( completions ) - - eq_( list( result ), completions ) - - -def FilterMatchingCompletions_NonMatchIsntReturned_test(): - ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) - vimsupport.TextBeforeCursor = MagicMock( return_value = " Quote" ) - completions = [ _BuildCompletion( "A" ) ] - - result = ycm_state.FilterMatchingCompletions( completions ) - - assert_that( list( result ), empty() ) - - -def PostComplete_EmptyDoesntInsertNamespace_test(): - ycm_state = _SetupForCompletionDone( [] ) - - ycm_state.OnCompleteDone() - - assert not vimsupport.InsertNamespace.called - -def PostComplete_ExistingWithoutNamespaceDoesntInsertNamespace_test(): +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 ) diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index eb939822..d80a2914 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -521,19 +521,21 @@ 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: - 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 ) ) + 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 ): diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index f1f735cb..4f9e9ac8 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -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" ] From 9f134c4dd0cfe2ea801978827a626b504a1ddf92 Mon Sep 17 00:00:00 2001 From: "Spencer G. Jones" Date: Wed, 9 Sep 2015 08:27:15 -0600 Subject: [PATCH 4/9] To determine which completion prompted CompleteDone, look at the new variable if vim is recent enough. --- python/ycm/client/completion_request.py | 4 +- python/ycm/youcompleteme.py | 85 +++++++++++++++++++------ 2 files changed, 66 insertions(+), 23 deletions(-) diff --git a/python/ycm/client/completion_request.py b/python/ycm/client/completion_request.py index fafb8198..49e6fe2d 100644 --- a/python/ycm/client/completion_request.py +++ b/python/ycm/client/completion_request.py @@ -60,7 +60,7 @@ class CompletionRequest( BaseRequest ): return _ConvertCompletionDatasToVimDatas( self.RawResponse() ) -def _ConvertCompletionDataToVimData( completion_data ): +def ConvertCompletionDataToVimData( completion_data ): # see :h complete-items for a description of the dictionary fields vim_data = { 'word' : ToUtf8IfNeeded( completion_data[ 'insertion_text' ] ), @@ -81,5 +81,5 @@ def _ConvertCompletionDataToVimData( completion_data ): def _ConvertCompletionDatasToVimDatas( response_data ): - return [ _ConvertCompletionDataToVimData( x ) + return [ ConvertCompletionDataToVimData( x ) for x in response_data ] diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index 4f9e9ac8..4b0d77c1 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -35,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 ) @@ -309,46 +310,88 @@ class YouCompleteMe( object ): yield value - def GetMatchingCompletionsOnCursor( self ): + def GetCompletedCompletions( self ): latest_completion_request = self.GetCurrentCompletionRequest() - if not latest_completion_request.Done(): + if not latest_completion_request or not latest_completion_request.Done(): return [] completions = latest_completion_request.RawResponse() - if self.HasCompletionsThatCouldMatchOnCursorWithMoreText( completions ): + + result = self.FilterToCompletedCompletions( completions, + full_match_only = 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.FilterToCompletionsMatchingOnCursor( completions ) + result = self.FilterToCompletedCompletions( completions, + full_match_only = False ) 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 FilterToCompletedCompletions( self, completions, full_match_only ): + if vimsupport.VimVersionAtLeast( "7.4.774" ): + 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 + else: + if full_match_only: + return + # 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 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 ]: + def HasCompletionsThatCouldBeCompletedWithMoreText( self, completions ): + if vimsupport.VimVersionAtLeast( "7.4.774" ): + 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 + else: + text = vimsupport.TextBeforeCursor() # No support for multiple line completions + 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.GetMatchingCompletionsOnCursor() + completions = self.GetCompletedCompletions() namespaces = [ self.GetRequiredNamespaceImport( c ) for c in completions ] namespaces = [ n for n in namespaces if n ] From af4aaa1d1792edd6fa4a66cdfcbd34c47aa68070 Mon Sep 17 00:00:00 2001 From: "Spencer G. Jones" Date: Wed, 9 Sep 2015 09:08:41 -0600 Subject: [PATCH 5/9] fixup! To determine which completion prompted CompleteDone, look at the new variable if vim is recent enough. --- python/ycm/tests/postcomplete_tests.py | 225 +++++++++++++++++++++---- 1 file changed, 195 insertions(+), 30 deletions(-) diff --git a/python/ycm/tests/postcomplete_tests.py b/python/ycm/tests/postcomplete_tests.py index b9efcefb..6d18b3fb 100644 --- a/python/ycm/tests/postcomplete_tests.py +++ b/python/ycm/tests/postcomplete_tests.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with YouCompleteMe. If not, see . -from mock import MagicMock +from mock import ( MagicMock, DEFAULT ) from nose.tools import eq_ from hamcrest import assert_that, empty from ycm import vimsupport @@ -54,78 +54,170 @@ def OnCompleteDone_NoActionNoError_test(): ycm_state.OnCompleteDone() -def FilterToCompletionsMatchingOnCursor_MatchIsReturned_test(): +def FilterToCompletedCompletions_NewVim_MatchIsReturned_test(): ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) - vimsupport.TextBeforeCursor = MagicMock( return_value = " Test" ) + vimsupport.VimVersionAtLeast = MagicMock( return_value = True ) + vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Test" ) completions = [ _BuildCompletion( "Test" ) ] - result = ycm_state.FilterToCompletionsMatchingOnCursor( completions ) + result = ycm_state.FilterToCompletedCompletions( completions, False ) eq_( list( result ), completions ) -def FilterToCompletionsMatchingOnCursor_ShortTextDoesntRaise_test(): +def FilterToCompletedCompletions_NewVim_ShortTextDoesntRaise_test(): ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) - vimsupport.TextBeforeCursor = MagicMock( return_value = "X" ) + vimsupport.VimVersionAtLeast = MagicMock( return_value = True ) + vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "A" ) completions = [ _BuildCompletion( "AAA" ) ] - ycm_state.FilterToCompletionsMatchingOnCursor( completions ) + ycm_state.FilterToCompletedCompletions( completions, False ) -def FilterToCompletionsMatchingOnCursor_ExactMatchIsReturned_test(): +def FilterToCompletedCompletions_NewVim_ExactMatchIsReturned_test(): ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) - vimsupport.TextBeforeCursor = MagicMock( return_value = "Test" ) + vimsupport.VimVersionAtLeast = MagicMock( return_value = True ) + vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Test" ) completions = [ _BuildCompletion( "Test" ) ] - result = ycm_state.FilterToCompletionsMatchingOnCursor( completions ) + result = ycm_state.FilterToCompletedCompletions( completions, False ) eq_( list( result ), completions ) -def FilterToCompletionsMatchingOnCursor_NonMatchIsntReturned_test(): +def FilterToCompletedCompletions_NewVim_NonMatchIsntReturned_test(): ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) - vimsupport.TextBeforeCursor = MagicMock( return_value = " Quote" ) + vimsupport.VimVersionAtLeast = MagicMock( return_value = True ) + vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( " Quote" ) completions = [ _BuildCompletion( "A" ) ] - result = ycm_state.FilterToCompletionsMatchingOnCursor( completions ) + result = ycm_state.FilterToCompletedCompletions( completions, False ) assert_that( list( result ), empty() ) -def HasCompletionsThatCouldMatchOnCursorWithMoreText_MatchIsReturned_test(): +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.FilterToCompletedCompletions( 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.FilterToCompletedCompletions( 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.FilterToCompletedCompletions( 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.FilterToCompletedCompletions( 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.HasCompletionsThatCouldMatchOnCursorWithMoreText( completions ) + result = ycm_state.HasCompletionsThatCouldBeCompletedWithMoreText( completions ) eq_( result, True ) -def HasCompletionsThatCouldMatchOnCursorWithMoreText_ShortTextDoesntRaise_test(): +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.HasCompletionsThatCouldMatchOnCursorWithMoreText( completions ) + ycm_state.HasCompletionsThatCouldBeCompletedWithMoreText( completions ) -def HasCompletionsThatCouldMatchOnCursorWithMoreText_ExactMatchIsntReturned_test(): +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.HasCompletionsThatCouldMatchOnCursorWithMoreText( completions ) + result = ycm_state.HasCompletionsThatCouldBeCompletedWithMoreText( completions ) eq_( result, False ) -def HasCompletionsThatCouldMatchOnCursorWithMoreText_NonMatchIsntReturned_test(): +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.HasCompletionsThatCouldMatchOnCursorWithMoreText( completions ) + 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 ) @@ -145,27 +237,79 @@ def GetRequiredNamespaceImport_ReturnNamespaceFromExtraData_test(): )) -def GetMatchingCompletionsOnCursor_ReturnEmptyIfNotDone_test(): +def GetCompletedCompletions_ReturnEmptyIfNotDone_test(): ycm_state = _SetupForCsharpCompletionDone( [] ) ycm_state._latest_completion_request.Done = MagicMock( return_value = False ) - eq_( [], ycm_state.GetMatchingCompletionsOnCursor() ) - + eq_( [], ycm_state.GetCompletedCompletions() ) -def GetMatchingCompletionsOnCursor_ReturnEmptyIfPendingMatches_test(): + +def GetCompletedCompletions_ReturnEmptyIfPendingMatches_NewVim_test(): completions = [ _BuildCompletion( None ) ] ycm_state = _SetupForCsharpCompletionDone( completions ) + vimsupport.VimVersionAtLeast = MagicMock( return_value = True ) + vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Te" ) + + eq_( [], ycm_state.GetCompletedCompletions() ) + + +def GetCompletedCompletions_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.GetMatchingCompletionsOnCursor() ) + eq_( [], ycm_state.GetCompletedCompletions() ) -def GetMatchingCompletionsOnCursor_ReturnMatchIfMatches_test(): +def GetCompletedCompletions_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.GetCompletedCompletions() ) + + +def GetCompletedCompletions_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.GetCompletedCompletions() ) + + +def GetCompletedCompletions_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.GetCompletedCompletions() ) + + +def GetCompletedCompletions_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.GetCompletedCompletions() ) + + +def GetCompletedCompletions_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.GetMatchingCompletionsOnCursor() ) + eq_( completions, ycm_state.GetCompletedCompletions() ) def PostCompleteCsharp_EmptyDoesntInsertNamespace_test(): @@ -220,8 +364,29 @@ def _SetupForCsharpCompletionDone( completions ): return ycm_state -def _BuildCompletion( namespace ): +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': 'Test' + '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 ) From 28e318c0bb890e874090b81574e8b96d7a68c247 Mon Sep 17 00:00:00 2001 From: "Spencer G. Jones" Date: Thu, 10 Sep 2015 09:18:48 -0600 Subject: [PATCH 6/9] fixup! To determine which completion prompted CompleteDone, look at the new variable if vim is recent enough. --- README.md | 11 +- doc/youcompleteme.txt | 11 +- python/ycm/tests/postcomplete_tests.py | 77 ++++++------- python/ycm/youcompleteme.py | 148 +++++++++++++++---------- 4 files changed, 140 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index 5a503ded..aff18ed9 100644 --- a/README.md +++ b/README.md @@ -1367,11 +1367,12 @@ Default: `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. +If you desired to control where YCM inserts namespaces, set this option. When +YCM inserts a namespace, by default, it will insert the namespace 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: `''` diff --git a/doc/youcompleteme.txt b/doc/youcompleteme.txt index d7a40446..87abfac0 100644 --- a/doc/youcompleteme.txt +++ b/doc/youcompleteme.txt @@ -1584,11 +1584,12 @@ Default: '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. +If you desired to control where YCM inserts namespaces, set this option. When +YCM inserts a namespace, by default, it will insert the namespace 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: "''" > diff --git a/python/ycm/tests/postcomplete_tests.py b/python/ycm/tests/postcomplete_tests.py index 6d18b3fb..caa6de1f 100644 --- a/python/ycm/tests/postcomplete_tests.py +++ b/python/ycm/tests/postcomplete_tests.py @@ -60,7 +60,7 @@ def FilterToCompletedCompletions_NewVim_MatchIsReturned_test(): vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Test" ) completions = [ _BuildCompletion( "Test" ) ] - result = ycm_state.FilterToCompletedCompletions( completions, False ) + result = ycm_state._FilterToMatchingCompletions( completions, False ) eq_( list( result ), completions ) @@ -71,7 +71,7 @@ def FilterToCompletedCompletions_NewVim_ShortTextDoesntRaise_test(): vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "A" ) completions = [ _BuildCompletion( "AAA" ) ] - ycm_state.FilterToCompletedCompletions( completions, False ) + ycm_state._FilterToMatchingCompletions( completions, False ) def FilterToCompletedCompletions_NewVim_ExactMatchIsReturned_test(): @@ -80,7 +80,7 @@ def FilterToCompletedCompletions_NewVim_ExactMatchIsReturned_test(): vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Test" ) completions = [ _BuildCompletion( "Test" ) ] - result = ycm_state.FilterToCompletedCompletions( completions, False ) + result = ycm_state._FilterToMatchingCompletions( completions, False ) eq_( list( result ), completions ) @@ -91,7 +91,7 @@ def FilterToCompletedCompletions_NewVim_NonMatchIsntReturned_test(): vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( " Quote" ) completions = [ _BuildCompletion( "A" ) ] - result = ycm_state.FilterToCompletedCompletions( completions, False ) + result = ycm_state._FilterToMatchingCompletions( completions, False ) assert_that( list( result ), empty() ) @@ -102,7 +102,7 @@ def FilterToCompletedCompletions_OldVim_MatchIsReturned_test(): vimsupport.TextBeforeCursor = MagicMock( return_value = " Test" ) completions = [ _BuildCompletion( "Test" ) ] - result = ycm_state.FilterToCompletedCompletions( completions, False ) + result = ycm_state._FilterToMatchingCompletions( completions, False ) eq_( list( result ), completions ) @@ -113,7 +113,7 @@ def FilterToCompletedCompletions_OldVim_ShortTextDoesntRaise_test(): vimsupport.TextBeforeCursor = MagicMock( return_value = "X" ) completions = [ _BuildCompletion( "AAA" ) ] - ycm_state.FilterToCompletedCompletions( completions, False ) + ycm_state._FilterToMatchingCompletions( completions, False ) def FilterToCompletedCompletions_OldVim_ExactMatchIsReturned_test(): @@ -122,7 +122,7 @@ def FilterToCompletedCompletions_OldVim_ExactMatchIsReturned_test(): vimsupport.TextBeforeCursor = MagicMock( return_value = "Test" ) completions = [ _BuildCompletion( "Test" ) ] - result = ycm_state.FilterToCompletedCompletions( completions, False ) + result = ycm_state._FilterToMatchingCompletions( completions, False ) eq_( list( result ), completions ) @@ -133,7 +133,7 @@ def FilterToCompletedCompletions_OldVim_NonMatchIsntReturned_test(): vimsupport.TextBeforeCursor = MagicMock( return_value = " Quote" ) completions = [ _BuildCompletion( "A" ) ] - result = ycm_state.FilterToCompletedCompletions( completions, False ) + result = ycm_state._FilterToMatchingCompletions( completions, False ) assert_that( list( result ), empty() ) @@ -144,7 +144,7 @@ def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_MatchIsReturned_test() vimsupport.TextBeforeCursor = MagicMock( return_value = " Te" ) completions = [ _BuildCompletion( "Test" ) ] - result = ycm_state.HasCompletionsThatCouldBeCompletedWithMoreText( completions ) + result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions ) eq_( result, True ) @@ -155,7 +155,7 @@ def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_ShortTextDoesntRaise_t vimsupport.TextBeforeCursor = MagicMock( return_value = "X" ) completions = [ _BuildCompletion( "AAA" ) ] - ycm_state.HasCompletionsThatCouldBeCompletedWithMoreText( completions ) + ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions ) def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_ExactMatchIsntReturned_test(): @@ -164,7 +164,7 @@ def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_ExactMatchIsntReturned vimsupport.TextBeforeCursor = MagicMock( return_value = "Test" ) completions = [ _BuildCompletion( "Test" ) ] - result = ycm_state.HasCompletionsThatCouldBeCompletedWithMoreText( completions ) + result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions ) eq_( result, False ) @@ -175,7 +175,7 @@ def HasCompletionsThatCouldBeCompletedWithMoreText_OldVim_NonMatchIsntReturned_t vimsupport.TextBeforeCursor = MagicMock( return_value = " Quote" ) completions = [ _BuildCompletion( "A" ) ] - result = ycm_state.HasCompletionsThatCouldBeCompletedWithMoreText( completions ) + result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions ) eq_( result, False ) @@ -186,7 +186,7 @@ def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_MatchIsReturned_test() vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Te" ) completions = [ _BuildCompletion( "Test" ) ] - result = ycm_state.HasCompletionsThatCouldBeCompletedWithMoreText( completions ) + result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions ) eq_( result, True ) @@ -197,7 +197,7 @@ def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_ShortTextDoesntRaise_t vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "X" ) completions = [ _BuildCompletion( "AAA" ) ] - ycm_state.HasCompletionsThatCouldBeCompletedWithMoreText( completions ) + ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions ) def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_ExactMatchIsntReturned_test(): @@ -206,7 +206,7 @@ def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_ExactMatchIsntReturned vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( "Test" ) completions = [ _BuildCompletion( "Test" ) ] - result = ycm_state.HasCompletionsThatCouldBeCompletedWithMoreText( completions ) + result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions ) eq_( result, False ) @@ -217,7 +217,7 @@ def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_NonMatchIsntReturned_t vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( " Quote" ) completions = [ _BuildCompletion( "A" ) ] - result = ycm_state.HasCompletionsThatCouldBeCompletedWithMoreText( completions ) + result = ycm_state._HasCompletionsThatCouldBeCompletedWithMoreText( completions ) eq_( result, False ) @@ -225,54 +225,54 @@ def HasCompletionsThatCouldBeCompletedWithMoreText_NewVim_NonMatchIsntReturned_t def GetRequiredNamespaceImport_ReturnNoneForNoExtraData_test(): ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) - eq_( None, ycm_state.GetRequiredNamespaceImport( {} ) ) + eq_( None, ycm_state._GetRequiredNamespaceImport( {} ) ) def GetRequiredNamespaceImport_ReturnNamespaceFromExtraData_test(): namespace = "A_NAMESPACE" ycm_state = YouCompleteMe( MagicMock( spec_set = dict ) ) - eq_( namespace, ycm_state.GetRequiredNamespaceImport( + eq_( namespace, ycm_state._GetRequiredNamespaceImport( _BuildCompletion( namespace ) )) -def GetCompletedCompletions_ReturnEmptyIfNotDone_test(): +def GetCompletionsUserMayHaveCompleted_ReturnEmptyIfNotDone_test(): ycm_state = _SetupForCsharpCompletionDone( [] ) ycm_state._latest_completion_request.Done = MagicMock( return_value = False ) - eq_( [], ycm_state.GetCompletedCompletions() ) + eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() ) -def GetCompletedCompletions_ReturnEmptyIfPendingMatches_NewVim_test(): +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.GetCompletedCompletions() ) + eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() ) -def GetCompletedCompletions_ReturnEmptyIfPendingMatches_OldVim_test(): +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.GetCompletedCompletions() ) + eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() ) -def GetCompletedCompletions_ReturnMatchIfExactMatches_NewVim_test(): +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.GetCompletedCompletions() ) + eq_( completions, ycm_state.GetCompletionsUserMayHaveCompleted() ) -def GetCompletedCompletions_ReturnMatchIfExactMatchesEvenIfPartial_NewVim_test(): +def GetCompletionsUserMayHaveCompleted_ReturnMatchIfExactMatchesEvenIfPartial_NewVim_test(): info = [ "NS", "Test", "Abbr", "Menu", "Info", "Kind" ] completions = [ _BuildCompletion( *info ), _BuildCompletion( insertion_text = "TestTest" ) ] @@ -280,10 +280,10 @@ def GetCompletedCompletions_ReturnMatchIfExactMatchesEvenIfPartial_NewVim_test() vimsupport.VimVersionAtLeast = MagicMock( return_value = True ) vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( *info[ 1 : ] ) - eq_( [ completions[ 0 ] ], ycm_state.GetCompletedCompletions() ) + eq_( [ completions[ 0 ] ], ycm_state.GetCompletionsUserMayHaveCompleted() ) -def GetCompletedCompletions_DontReturnMatchIfNontExactMatchesAndPartial_NewVim_test(): +def GetCompletionsUserMayHaveCompleted_DontReturnMatchIfNontExactMatchesAndPartial_NewVim_test(): info = [ "NS", "Test", "Abbr", "Menu", "Info", "Kind" ] completions = [ _BuildCompletion( insertion_text = info[ 0 ] ), _BuildCompletion( insertion_text = "TestTest" ) ] @@ -291,31 +291,31 @@ def GetCompletedCompletions_DontReturnMatchIfNontExactMatchesAndPartial_NewVim_t vimsupport.VimVersionAtLeast = MagicMock( return_value = True ) vimsupport.GetVariableValue = GetVariableValue_CompleteItemIs( *info[ 1 : ] ) - eq_( [], ycm_state.GetCompletedCompletions() ) + eq_( [], ycm_state.GetCompletionsUserMayHaveCompleted() ) -def GetCompletedCompletions_ReturnMatchIfMatches_NewVim_test(): +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.GetCompletedCompletions() ) + eq_( completions, ycm_state.GetCompletionsUserMayHaveCompleted() ) -def GetCompletedCompletions_ReturnMatchIfMatches_OldVim_test(): +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.GetCompletedCompletions() ) + eq_( completions, ycm_state.GetCompletionsUserMayHaveCompleted() ) def PostCompleteCsharp_EmptyDoesntInsertNamespace_test(): ycm_state = _SetupForCsharpCompletionDone( [] ) - ycm_state.OnCompleteDone_Csharp() + ycm_state._OnCompleteDone_Csharp() assert not vimsupport.InsertNamespace.called @@ -324,7 +324,7 @@ def PostCompleteCsharp_ExistingWithoutNamespaceDoesntInsertNamespace_test(): completions = [ _BuildCompletion( None ) ] ycm_state = _SetupForCsharpCompletionDone( completions ) - ycm_state.OnCompleteDone_Csharp() + ycm_state._OnCompleteDone_Csharp() assert not vimsupport.InsertNamespace.called @@ -334,7 +334,7 @@ def PostCompleteCsharp_ValueDoesInsertNamespace_test(): completions = [ _BuildCompletion( namespace ) ] ycm_state = _SetupForCsharpCompletionDone( completions ) - ycm_state.OnCompleteDone_Csharp() + ycm_state._OnCompleteDone_Csharp() vimsupport.InsertNamespace.assert_called_once_with( namespace ) @@ -348,7 +348,7 @@ def PostCompleteCsharp_InsertSecondNamespaceIfSelected_test(): ycm_state = _SetupForCsharpCompletionDone( completions ) vimsupport.PresentDialog = MagicMock( return_value = 1 ) - ycm_state.OnCompleteDone_Csharp() + ycm_state._OnCompleteDone_Csharp() vimsupport.InsertNamespace.assert_called_once_with( namespace2 ) @@ -376,6 +376,7 @@ def _BuildCompletion( namespace = None, insertion_text = 'Test', 'detailed_info': detailed_info, } + def GetVariableValue_CompleteItemIs( word, abbr = None, menu = None, info = None, kind = None ): def Result( variable ): diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index 4b0d77c1..a0eb96b9 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -99,7 +99,7 @@ class YouCompleteMe( object ): self._SetupServer() self._ycmd_keepalive.Start() self._complete_done_hooks = { - 'cs': lambda( self ): self.OnCompleteDone_Csharp() + 'cs': lambda( self ): self._OnCompleteDone_Csharp() } def _SetupServer( self ): @@ -310,89 +310,121 @@ class YouCompleteMe( object ): yield value - def GetCompletedCompletions( self ): + 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.FilterToCompletedCompletions( completions, - full_match_only = True ) + result = self._FilterToMatchingCompletions( completions, True ) result = list( result ) if result: return result - if self.HasCompletionsThatCouldBeCompletedWithMoreText( completions ): + 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.FilterToCompletedCompletions( completions, - full_match_only = False ) + result = self._FilterToMatchingCompletions( completions, False ) return list( result ) - def FilterToCompletedCompletions( self, completions, full_match_only ): + 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" ): - 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 + self._HasCompletionsThatCouldBeCompletedWithMoreText = \ + self._HasCompletionsThatCouldBeCompletedWithMoreText_NewerVim + self._FilterToMatchingCompletions = \ + self._FilterToMatchingCompletions_NewerVim else: - if full_match_only: - return - # 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 + self._FilterToMatchingCompletions = \ + self._FilterToMatchingCompletions_OlderVim + self._HasCompletionsThatCouldBeCompletedWithMoreText = \ + self._HasCompletionsThatCouldBeCompletedWithMoreText_OlderVim - def HasCompletionsThatCouldBeCompletedWithMoreText( self, completions ): - if vimsupport.VimVersionAtLeast( "7.4.774" ): - completed_item = vimsupport.GetVariableValue( 'v:completed_item' ) - completed_word = completed_item[ 'word' ] - if not completed_word: - return False + 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 - # 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 - else: - text = vimsupport.TextBeforeCursor() # No support for multiple line completions - 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 + 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 OnCompleteDone_Csharp( self ): - completions = self.GetCompletedCompletions() - namespaces = [ self.GetRequiredNamespaceImport( c ) + 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: @@ -411,7 +443,7 @@ class YouCompleteMe( object ): vimsupport.InsertNamespace( namespace ) - def GetRequiredNamespaceImport( self, completion ): + def _GetRequiredNamespaceImport( self, completion ): if ( "extra_data" not in completion or "required_namespace_import" not in completion[ "extra_data" ] ): return None @@ -542,5 +574,3 @@ def _AddUltiSnipsDataIfNeeded( extra_data ): extra_data[ 'ultisnips_snippets' ] = [ { 'trigger': x.trigger, 'description': x.description } for x in rawsnips ] - - From 391b3df3b7ed0ab2a74d8b218ef095251bf5182b Mon Sep 17 00:00:00 2001 From: "Spencer G. Jones" Date: Thu, 10 Sep 2015 10:12:59 -0600 Subject: [PATCH 7/9] Update mimimum version in documention --- README.md | 10 +++++----- doc/youcompleteme.txt | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index aff18ed9..21086d41 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,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). @@ -220,7 +220,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`. @@ -278,19 +278,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. diff --git a/doc/youcompleteme.txt b/doc/youcompleteme.txt index 87abfac0..567ec079 100644 --- a/doc/youcompleteme.txt +++ b/doc/youcompleteme.txt @@ -313,7 +313,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). @@ -370,7 +370,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'. @@ -431,19 +431,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. From df73ca6b663dbf09ce66fcea0dd0b150b9931afd Mon Sep 17 00:00:00 2001 From: "Spencer G. Jones" Date: Fri, 11 Sep 2015 17:14:09 -0600 Subject: [PATCH 8/9] Update g:ycm_csharp_insert_namespace_expr documention --- README.md | 18 +++++++++++------- doc/youcompleteme.txt | 18 +++++++++++------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 21086d41..ff52c7c8 100644 --- a/README.md +++ b/README.md @@ -1367,14 +1367,18 @@ Default: `0` ### The `g:ycm_csharp_insert_namespace_expr` option -If you desired to control where YCM inserts namespaces, set this option. When -YCM inserts a namespace, by default, it will insert the namespace 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. +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. -Default: `''` +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 = '' diff --git a/doc/youcompleteme.txt b/doc/youcompleteme.txt index 567ec079..60541976 100644 --- a/doc/youcompleteme.txt +++ b/doc/youcompleteme.txt @@ -1584,14 +1584,18 @@ Default: '0' ------------------------------------------------------------------------------- The *g:ycm_csharp_insert_namespace_expr* option -If you desired to control where YCM inserts namespaces, set this option. When -YCM inserts a namespace, by default, it will insert the namespace 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. +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. -Default: "''" +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 = '' < From bb2115079820bffe3515711387b8c160ed30cdc6 Mon Sep 17 00:00:00 2001 From: "Spencer G. Jones" Date: Sun, 13 Sep 2015 08:57:09 -0600 Subject: [PATCH 9/9] Fix merge test failure --- python/ycm/client/tests/completion_request_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ycm/client/tests/completion_request_test.py b/python/ycm/client/tests/completion_request_test.py index 89e536f6..1ca0a0c6 100644 --- a/python/ycm/client/tests/completion_request_test.py +++ b/python/ycm/client/tests/completion_request_test.py @@ -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: