diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index c813d386..386e4edb 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -1,4 +1,4 @@ -" Copyright (C) 2011, 2012 Google Inc. +" Copyright (C) 2011-2018 YouCompleteMe contributors " " This file is part of YouCompleteMe. " @@ -475,13 +475,12 @@ endfunction function! s:OnBufferUnload() " Expanding returns the unloaded buffer number as a string but we want " it as a true number for the getbufvar function. - if !s:AllowedToCompleteInBuffer( str2nr( expand( '' ) ) ) + let buffer_number = str2nr( expand( '' ) ) + if !s:AllowedToCompleteInBuffer( buffer_number ) return endif - let deleted_buffer_file = expand( ':p' ) - exec s:python_command "ycm_state.OnBufferUnload(" . - \ "vim.eval( 'deleted_buffer_file' ) )" + exec s:python_command "ycm_state.OnBufferUnload( " . buffer_number . " )" endfunction diff --git a/python/ycm/client/base_request.py b/python/ycm/client/base_request.py index 36a763a6..03546db2 100644 --- a/python/ycm/client/base_request.py +++ b/python/ycm/client/base_request.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013 Google Inc. +# Copyright (C) 2013-2018 YouCompleteMe contributors # # This file is part of YouCompleteMe. # @@ -25,6 +25,7 @@ from builtins import * # noqa import contextlib import logging import json +import vim from future.utils import native from base64 import b64decode, b64encode from ycm import vimsupport @@ -152,22 +153,26 @@ class BaseRequest( object ): hmac_secret = '' -def BuildRequestData( filepath = None ): - """Build request for the current buffer or the buffer corresponding to - |filepath| if specified.""" - current_filepath = vimsupport.GetCurrentBufferFilepath() +def BuildRequestData( buffer_number = None ): + """Build request for the current buffer or the buffer with number + |buffer_number| if specified.""" working_dir = GetCurrentDirectory() + current_buffer = vim.current.buffer - if filepath and current_filepath != filepath: + if buffer_number and current_buffer.number != buffer_number: # Cursor position is irrelevant when filepath is not the current buffer. + buffer_object = vim.buffers[ buffer_number ] + filepath = vimsupport.GetBufferFilepath( buffer_object ) return { 'filepath': filepath, 'line_num': 1, 'column_num': 1, 'working_dir': working_dir, - 'file_data': vimsupport.GetUnsavedAndSpecifiedBufferData( filepath ) + 'file_data': vimsupport.GetUnsavedAndSpecifiedBufferData( buffer_object, + filepath ) } + current_filepath = vimsupport.GetBufferFilepath( current_buffer ) line, column = vimsupport.CurrentLineAndColumn() return { @@ -175,7 +180,8 @@ def BuildRequestData( filepath = None ): 'line_num': line + 1, 'column_num': column + 1, 'working_dir': working_dir, - 'file_data': vimsupport.GetUnsavedAndSpecifiedBufferData( current_filepath ) + 'file_data': vimsupport.GetUnsavedAndSpecifiedBufferData( current_buffer, + current_filepath ) } diff --git a/python/ycm/client/event_notification.py b/python/ycm/client/event_notification.py index b46dd5e6..9ccafc23 100644 --- a/python/ycm/client/event_notification.py +++ b/python/ycm/client/event_notification.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013 Google Inc. +# Copyright (C) 2013-2018 YouCompleteMe contributors # # This file is part of YouCompleteMe. # @@ -27,17 +27,17 @@ from ycm.client.base_request import ( BaseRequest, BuildRequestData, class EventNotification( BaseRequest ): - def __init__( self, event_name, filepath = None, extra_data = None ): + def __init__( self, event_name, buffer_number = None, extra_data = None ): super( EventNotification, self ).__init__() self._event_name = event_name - self._filepath = filepath + self._buffer_number = buffer_number self._extra_data = extra_data self._response_future = None self._cached_response = None def Start( self ): - request_data = BuildRequestData( self._filepath ) + request_data = BuildRequestData( self._buffer_number ) if self._extra_data: request_data.update( self._extra_data ) request_data[ 'event_name' ] = self._event_name @@ -64,7 +64,7 @@ class EventNotification( BaseRequest ): def SendEventNotificationAsync( event_name, - filepath = None, + buffer_number = None, extra_data = None ): - event = EventNotification( event_name, filepath, extra_data ) + event = EventNotification( event_name, buffer_number, extra_data ) event.Start() diff --git a/python/ycm/tests/client/base_request_test.py b/python/ycm/tests/client/base_request_test.py index a0fa89d5..66dd5ef5 100644 --- a/python/ycm/tests/client/base_request_test.py +++ b/python/ycm/tests/client/base_request_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017 YouCompleteMe Contributors +# Copyright (C) 2017-2018 YouCompleteMe Contributors # # This file is part of YouCompleteMe. # @@ -22,7 +22,7 @@ from __future__ import absolute_import # Not installing aliases from python-future; it's unreliable and slow. from builtins import * # noqa -from ycm.tests.test_utils import MockVimModule +from ycm.tests.test_utils import MockVimBuffers, MockVimModule, VimBuffer MockVimModule() from hamcrest import assert_that, has_entry @@ -32,14 +32,16 @@ from ycm.client.base_request import BuildRequestData @patch( 'ycm.client.base_request.GetCurrentDirectory', return_value = '/some/dir' ) -@patch( 'ycm.vimsupport.CurrentLineAndColumn', return_value = ( 1, 1 ) ) def BuildRequestData_AddWorkingDir_test( *args ): - assert_that( BuildRequestData(), has_entry( 'working_dir', '/some/dir' ) ) + current_buffer = VimBuffer( 'foo' ) + with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 1 ) ): + assert_that( BuildRequestData(), has_entry( 'working_dir', '/some/dir' ) ) @patch( 'ycm.client.base_request.GetCurrentDirectory', return_value = '/some/dir' ) -@patch( 'ycm.vimsupport.CurrentLineAndColumn', return_value = ( 1, 1 ) ) def BuildRequestData_AddWorkingDirWithFileName_test( *args ): - assert_that( BuildRequestData( 'foo' ), - has_entry( 'working_dir', '/some/dir' ) ) + current_buffer = VimBuffer( 'foo' ) + with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 1 ) ): + assert_that( BuildRequestData( current_buffer.number ), + has_entry( 'working_dir', '/some/dir' ) ) diff --git a/python/ycm/tests/event_notification_test.py b/python/ycm/tests/event_notification_test.py index ca65f22b..5d890a38 100644 --- a/python/ycm/tests/event_notification_test.py +++ b/python/ycm/tests/event_notification_test.py @@ -1,6 +1,6 @@ # coding: utf-8 # -# Copyright (C) 2015-2016 YouCompleteMe contributors +# Copyright (C) 2015-2018 YouCompleteMe contributors # # This file is part of YouCompleteMe. # @@ -25,8 +25,7 @@ from __future__ import absolute_import from builtins import * # noqa from ycm.tests.test_utils import ( CurrentWorkingDirectory, ExtendedMock, - MockVimBuffers, MockVimModule, VimBuffer, - ToBytesOnPY2 ) + MockVimBuffers, MockVimModule, VimBuffer ) MockVimModule() import contextlib @@ -398,7 +397,6 @@ def EventNotification_BufferVisit_BuildRequestForCurrentAndUnsavedBuffers_test( contents = [ 'current_buffer_contents' ], filetype = 'some_filetype', modified = False ) - modified_buffer_file = os.path.realpath( 'modified_buffer' ) modified_buffer = VimBuffer( name = modified_buffer_file, number = 2, @@ -465,7 +463,7 @@ def EventNotification_BufferUnload_BuildRequestForDeletedAndUnsavedBuffers_test( with patch( 'ycm.client.event_notification.EventNotification.' 'PostDataToHandlerAsync' ) as post_data_to_handler_async: with MockVimBuffers( [ current_buffer, deleted_buffer ], current_buffer ): - ycm.OnBufferUnload( ToBytesOnPY2( deleted_buffer_file ) ) + ycm.OnBufferUnload( deleted_buffer.number ) assert_that( # Positional arguments passed to PostDataToHandlerAsync. diff --git a/python/ycm/tests/test_utils.py b/python/ycm/tests/test_utils.py index b1593281..797d69df 100644 --- a/python/ycm/tests/test_utils.py +++ b/python/ycm/tests/test_utils.py @@ -1,5 +1,4 @@ -# Copyright (C) 2011-2012 Google Inc. -# 2016 YouCompleteMe contributors +# Copyright (C) 2011-2018 YouCompleteMe contributors # # This file is part of YouCompleteMe. # @@ -272,6 +271,10 @@ class VimBuffer( object ): self.omnifunc = omnifunc self.omnifunc_name = omnifunc.__name__ if omnifunc else '' self.changedtick = 1 + self.options = { + 'mod': modified, + 'bh': bufhidden + } def __getitem__( self, index ): @@ -292,6 +295,27 @@ class VimBuffer( object ): return [ ToUnicode( x ) for x in self.contents ] +class VimBuffers( object ): + """An object that looks like a vim.buffers object.""" + + def __init__( self, *buffers ): + """Arguments are VimBuffer objects.""" + self._buffers = buffers + + + def __getitem__( self, number ): + """Emulates vim.buffers[ number ]""" + for buffer_object in self._buffers: + if number == buffer_object.number: + return buffer_object + raise KeyError( number ) + + + def __iter__( self ): + """Emulates for loop on vim.buffers""" + return iter( self._buffers ) + + class VimMatch( object ): def __init__( self, group, pattern ): @@ -326,7 +350,7 @@ def MockVimBuffers( buffers, current_buffer, cursor_position = ( 1, 1 ) ): line = current_buffer.contents[ cursor_position[ 0 ] - 1 ] - with patch( 'vim.buffers', buffers ): + with patch( 'vim.buffers', VimBuffers( *buffers ) ): with patch( 'vim.current.buffer', current_buffer ): with patch( 'vim.current.window.cursor', cursor_position ): with patch( 'vim.current.line', line ): diff --git a/python/ycm/tests/vimsupport_test.py b/python/ycm/tests/vimsupport_test.py index f8ebcda3..7c36c6a0 100644 --- a/python/ycm/tests/vimsupport_test.py +++ b/python/ycm/tests/vimsupport_test.py @@ -1,6 +1,6 @@ # coding: utf-8 # -# Copyright (C) 2015-2016 YouCompleteMe contributors +# Copyright (C) 2015-2018 YouCompleteMe contributors # # This file is part of YouCompleteMe. # @@ -1434,7 +1434,8 @@ def GetUnsavedAndSpecifiedBufferData_EncodedUnicodeCharsInBuffers_test(): vim_buffer = VimBuffer( filepath, contents = contents ) with patch( 'vim.buffers', [ vim_buffer ] ): - assert_that( vimsupport.GetUnsavedAndSpecifiedBufferData( filepath ), + assert_that( vimsupport.GetUnsavedAndSpecifiedBufferData( vim_buffer, + filepath ), has_entry( filepath, has_entry( u'contents', u'abc\nfДa\n' ) ) ) diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index fe3832e7..ffac5e5e 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -1,5 +1,4 @@ -# Copyright (C) 2011-2012 Google Inc. -# 2016 YouCompleteMe contributors +# Copyright (C) 2011-2018 YouCompleteMe contributors # # This file is part of YouCompleteMe. # @@ -100,41 +99,33 @@ def TextBeforeCursor(): return ToUnicode( vim.current.line[ :CurrentColumn() ] ) -# Note the difference between buffer OPTIONS and VARIABLES; the two are not -# the same. -def GetBufferOption( buffer_object, option ): - # NOTE: We used to check for the 'options' property on the buffer_object which - # is available in recent versions of Vim and would then use: - # - # buffer_object.options[ option ] - # - # to read the value, BUT this caused annoying flickering when the - # buffer_object was a hidden buffer (with option = 'ft'). This was all due to - # a Vim bug. Until this is fixed, we won't use it. - - to_eval = 'getbufvar({0}, "&{1}")'.format( buffer_object.number, option ) - return GetVariableValue( to_eval ) - - def BufferModified( buffer_object ): - return bool( int( GetBufferOption( buffer_object, 'mod' ) ) ) + return buffer_object.options[ 'mod' ] -def GetUnsavedAndSpecifiedBufferData( including_filepath ): +def GetBufferData( buffer_object ): + return { + # Add a newline to match what gets saved to disk. See #1455 for details. + 'contents': JoinLinesAsUnicode( buffer_object ) + '\n', + 'filetypes': FiletypesForBuffer( buffer_object ) + } + + +def GetUnsavedAndSpecifiedBufferData( included_buffer, included_filepath ): """Build part of the request containing the contents and filetypes of all - dirty buffers as well as the buffer with filepath |including_filepath|.""" - buffers_data = {} + dirty buffers as well as the buffer |included_buffer| with its filepath + |included_filepath|.""" + buffers_data = { included_filepath: GetBufferData( included_buffer ) } + for buffer_object in vim.buffers: - buffer_filepath = GetBufferFilepath( buffer_object ) - if not ( BufferModified( buffer_object ) or - buffer_filepath == including_filepath ): + if not BufferModified( buffer_object ): continue - buffers_data[ buffer_filepath ] = { - # Add a newline to match what gets saved to disk. See #1455 for details. - 'contents': JoinLinesAsUnicode( buffer_object ) + '\n', - 'filetypes': FiletypesForBuffer( buffer_object ) - } + filepath = GetBufferFilepath( buffer_object ) + if filepath in buffers_data: + continue + + buffers_data[ filepath ] = GetBufferData( buffer_object ) return buffers_data @@ -362,7 +353,7 @@ def VimExpressionToPythonType( vim_expression ): def HiddenEnabled( buffer_object ): - if GetBufferOption( buffer_object, 'bh' ) == "hide": + if buffer_object.options[ 'bh' ] == "hide": return True return GetBoolValue( '&hidden' ) @@ -601,7 +592,14 @@ def GetBufferFiletypes( bufnr ): def FiletypesForBuffer( buffer_object ): # NOTE: Getting &ft for other buffers only works when the buffer has been # visited by the user at least once, which is true for modified buffers - return ToUnicode( GetBufferOption( buffer_object, 'ft' ) ).split( '.' ) + + # We don't use + # + # buffer_object.options[ 'ft' ] + # + # to get the filetypes because this causes annoying flickering when the buffer + # is hidden. + return GetBufferFiletypes( buffer_object.number ) def VariableExists( variable ): diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py index f7d3850b..1a7ac2cb 100644 --- a/python/ycm/youcompleteme.py +++ b/python/ycm/youcompleteme.py @@ -1,5 +1,4 @@ -# Copyright (C) 2011-2012 Google Inc. -# 2016-2017 YouCompleteMe contributors +# Copyright (C) 2011-2018 YouCompleteMe contributors # # This file is part of YouCompleteMe. # @@ -381,10 +380,8 @@ class YouCompleteMe( object ): self.CurrentBuffer().SendParseRequest( extra_data ) - def OnBufferUnload( self, deleted_buffer_file ): - SendEventNotificationAsync( - 'BufferUnload', - filepath = utils.ToUnicode( deleted_buffer_file ) ) + def OnBufferUnload( self, deleted_buffer_number ): + SendEventNotificationAsync( 'BufferUnload', deleted_buffer_number ) def OnBufferVisit( self ):