Преглед на файлове

Optimize request building

Reduce the time spent to build the request when there are a lot of buffers by:
 - using the options property on the buffer object to get the mod variable
instead of evaluating getbufvar;
 - not computing the buffer filepath if the buffer is not modified;
 - passing the number of the unloaded buffer instead of its filepath on the
   BufferUnload event. Getting the Python buffer object from its number is
   easier than from its filepath.
micbou преди 7 години
родител
ревизия
43ebd5252d

+ 4 - 5
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 <abuf> 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( '<abuf>' ) ) )
+  let buffer_number = str2nr( expand( '<abuf>' ) )
+  if !s:AllowedToCompleteInBuffer( buffer_number )
     return
   endif
 
-  let deleted_buffer_file = expand( '<afile>:p' )
-  exec s:python_command "ycm_state.OnBufferUnload(" .
-        \ "vim.eval( 'deleted_buffer_file' ) )"
+  exec s:python_command "ycm_state.OnBufferUnload( " . buffer_number . " )"
 endfunction
 
 

+ 14 - 8
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 )
   }
 
 

+ 6 - 6
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()

+ 9 - 7
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' ) )

+ 3 - 5
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.

+ 27 - 3
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 ):

+ 3 - 2
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' ) ) )
 

+ 29 - 31
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 buffer_object.options[ 'mod' ]
 
 
-def BufferModified( buffer_object ):
-  return bool( int( GetBufferOption( buffer_object, 'mod' ) ) )
+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( including_filepath ):
+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 ):

+ 3 - 6
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 ):