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.
This commit is contained in:
micbou 2018-01-06 13:16:30 +01:00
parent ad41dfb8af
commit 43ebd5252d
No known key found for this signature in database
GPG Key ID: C7E8FD1F3BDA1E05
9 changed files with 99 additions and 74 deletions

View File

@ -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

View File

@ -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 )
}

View File

@ -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()

View File

@ -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 ):
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' ),
current_buffer = VimBuffer( 'foo' )
with MockVimBuffers( [ current_buffer ], current_buffer, ( 1, 1 ) ):
assert_that( BuildRequestData( current_buffer.number ),
has_entry( 'working_dir', '/some/dir' ) )

View File

@ -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.

View File

@ -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 ):

View File

@ -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' ) ) )

View File

@ -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,42 +99,34 @@ 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 ):
"""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 = {}
for buffer_object in vim.buffers:
buffer_filepath = GetBufferFilepath( buffer_object )
if not ( BufferModified( buffer_object ) or
buffer_filepath == including_filepath ):
continue
buffers_data[ buffer_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 |included_buffer| with its filepath
|included_filepath|."""
buffers_data = { included_filepath: GetBufferData( included_buffer ) }
for buffer_object in vim.buffers:
if not BufferModified( buffer_object ):
continue
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 ):

View File

@ -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 ):