diff --git a/python/ycm/omni_completer.py b/python/ycm/omni_completer.py index 178728323d..6d605938b2 100644 --- a/python/ycm/omni_completer.py +++ b/python/ycm/omni_completer.py @@ -90,13 +90,22 @@ def ComputeCandidatesInner( self, request_data ): # because it affects the value returned by 'query' request_data[ 'start_column' ] = return_value + 1 + # Calling directly the omnifunc may move the cursor position. This is the + # case with the default Vim omnifunc for C-family languages + # (ccomplete#Complete) which calls searchdecl to find a declaration. This + # function is supposed to move the cursor to the found declaration but it + # doesn't when called through the omni completion mapping (CTRL-X CTRL-O). + # So, we restore the cursor position after calling the omnifunc. + line, column = vimsupport.CurrentLineAndColumn() + omnifunc_call = [ self._omnifunc, "(0,'", vimsupport.EscapeForVim( request_data[ 'query' ] ), "')" ] - items = vim.eval( ''.join( omnifunc_call ) ) + vimsupport.SetCurrentLineAndColumn( line, column ) + if isinstance( items, dict ) and 'words' in items: items = items[ 'words' ] diff --git a/python/ycm/tests/omni_completer_test.py b/python/ycm/tests/omni_completer_test.py index a892813a47..e8e02b5aa5 100644 --- a/python/ycm/tests/omni_completer_test.py +++ b/python/ycm/tests/omni_completer_test.py @@ -30,6 +30,7 @@ MockVimModule, ToBytesOnPY2, VimBuffer ) MockVimModule() +from ycm import vimsupport from ycm.tests import YouCompleteMeInstance @@ -621,3 +622,39 @@ def Omnifunc( findstart, base ): 'completion_start_column': 13 } ) ) + + +@YouCompleteMeInstance( { 'cache_omnifunc': 1 } ) +def OmniCompleter_GetCompletions_RestoreCursorPositionAfterOmnifuncCall_test( + ycm ): + + # This omnifunc moves the cursor to the test definition like + # ccomplete#Complete would. + def Omnifunc( findstart, base ): + if findstart: + return 5 + vimsupport.SetCurrentLineAndColumn( 0, 0 ) + return [ 'length' ] + + current_buffer = VimBuffer( 'buffer', + contents = [ 'String test', + '', + 'test.' ], + filetype = 'java', + omnifunc = Omnifunc ) + + with MockVimBuffers( [ current_buffer ], current_buffer, ( 3, 5 ) ): + # Make sure there is an omnifunc set up. + ycm.OnFileReadyToParse() + ycm.SendCompletionRequest() + assert_that( + vimsupport.CurrentLineAndColumn(), + contains( 2, 5 ) + ) + assert_that( + ycm.GetCompletionResponse(), + has_entries( { + 'completions': ToBytesOnPY2( [ 'length' ] ), + 'completion_start_column': 6 + } ) + ) diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index ac67fd1c54..02565bc7f1 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -56,6 +56,12 @@ def CurrentLineAndColumn(): return line, column +def SetCurrentLineAndColumn( line, column ): + """Sets the cursor position to the 0-based line and 0-based column.""" + # Line from vim.current.window.cursor is 1-based. + vim.current.window.cursor = ( line + 1, column ) + + def CurrentColumn(): """Returns the 0-based current column. Do NOT access the CurrentColumn in vim.current.line. It doesn't exist yet when the cursor is at the end of the