Auto merge of #2367 - micbou:on-buffer-unload, r=Valloric
[READY] Use deleted buffer instead of current buffer when sending BufferUnload event notification When deleting a buffer, we use the current buffer to send the `BufferUnload` event notification request to ycmd instead of the buffer being deleted. This is an issue when the current buffer is not of the same filetype as the deleted one. In this case, three different scenarios may occur: - the current buffer filetype is not allowed: no request is sent to the ycmd server. The `OnBufferUnload` method from the completer corresponding to the filetype of the deleted buffer is not called. If the filetype is a C-family language, the translation unit is not destroyed. If it is TypeScript, we don't tell TSServer to close the file (not really an issue unless the file is modified elsewhere); - the current buffer filetype has no semantic support in ycmd: the request is sent to the ycmd server but no semantic completer is used. Same result; - the current buffer filetype has semantic support in ycmd: the `OnBufferUnload` method from the wrong completer is called in ycmd. LibClang and TSServer are able to cope with that and ignore the file. Same result in definitive. The solution is obvious: build the request for the deleted buffer in this case. However, this involves more changes than expected because the code was written with the assumption that requests are always for the current buffer. The `include_buffer_data` parameter from `BuildRequestData` was removed as it is always used with its default value. This fix may alleviate issue https://github.com/Valloric/YouCompleteMe/issues/2232 in the sense that it now gives a reliable way to limit memory usage by deleting buffers in the case of multiple TUs. Next step is to update PR https://github.com/Valloric/ycmd/pull/542 by removing the `unloaded_buffer` parameter from ycmd API then remove it from the `BufferUnload` request in YCM. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/valloric/youcompleteme/2367) <!-- Reviewable:end -->
This commit is contained in:
commit
768aea435b
@ -91,7 +91,7 @@ function! youcompleteme#Enable()
|
||||
autocmd BufReadPre * call s:OnBufferReadPre( expand( '<afile>:p' ) )
|
||||
autocmd BufRead,FileType * call s:OnBufferRead()
|
||||
autocmd BufEnter * call s:OnBufferEnter()
|
||||
autocmd BufUnload * call s:OnBufferUnload( expand( '<afile>:p' ) )
|
||||
autocmd BufUnload * call s:OnBufferUnload()
|
||||
autocmd CursorHold,CursorHoldI * call s:OnCursorHold()
|
||||
autocmd InsertLeave * call s:OnInsertLeave()
|
||||
autocmd InsertEnter * call s:OnInsertEnter()
|
||||
@ -323,10 +323,12 @@ function! s:TurnOffSyntasticForCFamily()
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:AllowedToCompleteInCurrentBuffer()
|
||||
if empty( &filetype ) ||
|
||||
\ getbufvar( winbufnr( winnr() ), "&buftype" ) ==# 'nofile' ||
|
||||
\ &filetype ==# 'qf'
|
||||
function! s:AllowedToCompleteInBuffer( buffer )
|
||||
let buffer_filetype = getbufvar( a:buffer, '&filetype' )
|
||||
|
||||
if empty( buffer_filetype ) ||
|
||||
\ getbufvar( a:buffer, '&buftype' ) ==# 'nofile' ||
|
||||
\ buffer_filetype ==# 'qf'
|
||||
return 0
|
||||
endif
|
||||
|
||||
@ -335,13 +337,18 @@ function! s:AllowedToCompleteInCurrentBuffer()
|
||||
endif
|
||||
|
||||
let whitelist_allows = has_key( g:ycm_filetype_whitelist, '*' ) ||
|
||||
\ has_key( g:ycm_filetype_whitelist, &filetype )
|
||||
let blacklist_allows = !has_key( g:ycm_filetype_blacklist, &filetype )
|
||||
\ has_key( g:ycm_filetype_whitelist, buffer_filetype )
|
||||
let blacklist_allows = !has_key( g:ycm_filetype_blacklist, buffer_filetype )
|
||||
|
||||
return whitelist_allows && blacklist_allows
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:AllowedToCompleteInCurrentBuffer()
|
||||
return s:AllowedToCompleteInBuffer( '%' )
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:VisitedBufferRequiresReparse()
|
||||
if !s:AllowedToCompleteInCurrentBuffer()
|
||||
return 0
|
||||
@ -471,13 +478,16 @@ function! s:OnBufferEnter()
|
||||
endfunction
|
||||
|
||||
|
||||
function! s:OnBufferUnload( deleted_buffer_file )
|
||||
if !s:AllowedToCompleteInCurrentBuffer() || empty( a:deleted_buffer_file )
|
||||
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>' ) ) )
|
||||
return
|
||||
endif
|
||||
|
||||
let deleted_buffer_file = expand( '<afile>:p' )
|
||||
exec s:python_command "ycm_state.OnBufferUnload("
|
||||
\ "vim.eval( 'a:deleted_buffer_file' ) )"
|
||||
\ "vim.eval( 'deleted_buffer_file' ) )"
|
||||
endfunction
|
||||
|
||||
|
||||
|
@ -154,19 +154,28 @@ class BaseRequest( object ):
|
||||
hmac_secret = ''
|
||||
|
||||
|
||||
def BuildRequestData( include_buffer_data = True ):
|
||||
line, column = vimsupport.CurrentLineAndColumn()
|
||||
filepath = vimsupport.GetCurrentBufferFilepath()
|
||||
request_data = {
|
||||
'line_num': line + 1,
|
||||
'column_num': column + 1,
|
||||
'filepath': filepath
|
||||
def BuildRequestData( filepath = None ):
|
||||
"""Build request for the current buffer or the buffer corresponding to
|
||||
|filepath| if specified."""
|
||||
current_filepath = vimsupport.GetCurrentBufferFilepath()
|
||||
|
||||
if filepath and current_filepath != filepath:
|
||||
# Cursor position is irrelevant when filepath is not the current buffer.
|
||||
return {
|
||||
'filepath': filepath,
|
||||
'line_num': 1,
|
||||
'column_num': 1,
|
||||
'file_data': vimsupport.GetUnsavedAndSpecifiedBufferData( filepath )
|
||||
}
|
||||
|
||||
if include_buffer_data:
|
||||
request_data[ 'file_data' ] = vimsupport.GetUnsavedAndCurrentBufferData()
|
||||
line, column = vimsupport.CurrentLineAndColumn()
|
||||
|
||||
return request_data
|
||||
return {
|
||||
'filepath': current_filepath,
|
||||
'line_num': line + 1,
|
||||
'column_num': column + 1,
|
||||
'file_data': vimsupport.GetUnsavedAndSpecifiedBufferData( current_filepath )
|
||||
}
|
||||
|
||||
|
||||
def JsonFromFuture( future ):
|
||||
|
@ -32,15 +32,16 @@ from ycm.client.base_request import ( BaseRequest, BuildRequestData,
|
||||
|
||||
|
||||
class EventNotification( BaseRequest ):
|
||||
def __init__( self, event_name, extra_data = None ):
|
||||
def __init__( self, event_name, filepath = None, extra_data = None ):
|
||||
super( EventNotification, self ).__init__()
|
||||
self._event_name = event_name
|
||||
self._filepath = filepath
|
||||
self._extra_data = extra_data
|
||||
self._cached_response = None
|
||||
|
||||
|
||||
def Start( self ):
|
||||
request_data = BuildRequestData()
|
||||
request_data = BuildRequestData( self._filepath )
|
||||
if self._extra_data:
|
||||
request_data.update( self._extra_data )
|
||||
request_data[ 'event_name' ] = self._event_name
|
||||
@ -74,8 +75,10 @@ class EventNotification( BaseRequest ):
|
||||
return self._cached_response if self._cached_response else []
|
||||
|
||||
|
||||
def SendEventNotificationAsync( event_name, extra_data = None ):
|
||||
event = EventNotification( event_name, extra_data )
|
||||
def SendEventNotificationAsync( event_name,
|
||||
filepath = None,
|
||||
extra_data = None ):
|
||||
event = EventNotification( event_name, filepath, extra_data )
|
||||
event.Start()
|
||||
|
||||
|
||||
|
@ -33,9 +33,12 @@ import functools
|
||||
from ycmd.utils import ToUnicode
|
||||
|
||||
|
||||
BUFNR_REGEX = re.compile( r"^bufnr\('(.+)', ([0-9]+)\)$" )
|
||||
BUFWINNR_REGEX = re.compile( r"^bufwinnr\(([0-9]+)\)$" )
|
||||
BWIPEOUT_REGEX = re.compile( r"^(?:silent! )bwipeout!? ([0-9]+)$" )
|
||||
BUFNR_REGEX = re.compile( '^bufnr\(\'(?P<buffer_filename>.+)\', ([01])\)$' )
|
||||
BUFWINNR_REGEX = re.compile( '^bufwinnr\((?P<buffer_number>[0-9]+)\)$' )
|
||||
BWIPEOUT_REGEX = re.compile(
|
||||
'^(?:silent! )bwipeout!? (?P<buffer_number>[0-9]+)$' )
|
||||
GETBUFVAR_REGEX = re.compile(
|
||||
'^getbufvar\((?P<buffer_number>[0-9]+), "&(?P<option>.+)"\)$' )
|
||||
|
||||
# One-and only instance of mocked Vim object. The first 'import vim' that is
|
||||
# executed binds the vim module to the instance of MagicMock that is created,
|
||||
@ -49,46 +52,74 @@ VIM_MOCK = MagicMock()
|
||||
|
||||
|
||||
def MockGetBufferNumber( buffer_filename ):
|
||||
for buffer in VIM_MOCK.buffers:
|
||||
if buffer[ 'filename' ] == buffer_filename:
|
||||
return buffer[ 'number' ]
|
||||
for vim_buffer in VIM_MOCK.buffers:
|
||||
if vim_buffer.name == buffer_filename:
|
||||
return vim_buffer.number
|
||||
return -1
|
||||
|
||||
|
||||
def MockGetBufferWindowNumber( buffer_number ):
|
||||
for buffer in VIM_MOCK.buffers:
|
||||
if buffer[ 'number' ] == buffer_number and 'window' in buffer:
|
||||
return buffer[ 'window' ]
|
||||
for vim_buffer in VIM_MOCK.buffers:
|
||||
if vim_buffer.number == buffer_number and vim_buffer.window:
|
||||
return vim_buffer.window
|
||||
return -1
|
||||
|
||||
|
||||
def MockVimEval( value ):
|
||||
if value == "g:ycm_min_num_of_chars_for_completion":
|
||||
return 0
|
||||
if value == "g:ycm_server_python_interpreter":
|
||||
def MockGetBufferVariable( buffer_number, option ):
|
||||
for vim_buffer in VIM_MOCK.buffers:
|
||||
if vim_buffer.number == buffer_number:
|
||||
if option == 'mod':
|
||||
return vim_buffer.modified
|
||||
if option == 'ft':
|
||||
return vim_buffer.filetype
|
||||
return ''
|
||||
if value == "tempname()":
|
||||
return ''
|
||||
|
||||
|
||||
def MockVimEval( value ):
|
||||
if value == 'g:ycm_min_num_of_chars_for_completion':
|
||||
return 0
|
||||
|
||||
if value == 'g:ycm_server_python_interpreter':
|
||||
return ''
|
||||
|
||||
if value == 'tempname()':
|
||||
return '_TEMP_FILE_'
|
||||
if value == "&previewheight":
|
||||
|
||||
if value == '&previewheight':
|
||||
# Default value from Vim
|
||||
return 12
|
||||
|
||||
if value == '&omnifunc':
|
||||
return VIM_MOCK.current.buffer.omnifunc
|
||||
|
||||
if value == '&filetype':
|
||||
return VIM_MOCK.current.buffer.filetype
|
||||
|
||||
match = BUFNR_REGEX.search( value )
|
||||
if match:
|
||||
return MockGetBufferNumber( match.group( 1 ) )
|
||||
buffer_filename = match.group( 'buffer_filename' )
|
||||
return MockGetBufferNumber( buffer_filename )
|
||||
|
||||
match = BUFWINNR_REGEX.search( value )
|
||||
if match:
|
||||
return MockGetBufferWindowNumber( int( match.group( 1 ) ) )
|
||||
buffer_number = int( match.group( 'buffer_number' ) )
|
||||
return MockGetBufferWindowNumber( buffer_number )
|
||||
|
||||
raise ValueError( 'Unexpected evaluation: ' + value )
|
||||
match = GETBUFVAR_REGEX.search( value )
|
||||
if match:
|
||||
buffer_number = int( match.group( 'buffer_number' ) )
|
||||
option = match.group( 'option' )
|
||||
return MockGetBufferVariable( buffer_number, option )
|
||||
|
||||
raise ValueError( 'Unexpected evaluation: {0}'.format( value ) )
|
||||
|
||||
|
||||
def MockWipeoutBuffer( buffer_number ):
|
||||
buffers = VIM_MOCK.buffers
|
||||
|
||||
for index, buffer in enumerate( buffers ):
|
||||
if buffer[ 'number' ] == buffer_number:
|
||||
if buffer.number == buffer_number:
|
||||
return buffers.pop( index )
|
||||
|
||||
|
||||
@ -100,6 +131,50 @@ def MockVimCommand( command ):
|
||||
raise RuntimeError( 'Unexpected command: ' + command )
|
||||
|
||||
|
||||
class VimBuffer( object ):
|
||||
"""An object that looks like a vim.buffer object:
|
||||
- |name| : full path of the buffer;
|
||||
- |number| : buffer number;
|
||||
- |contents|: list of lines representing the buffer contents;
|
||||
- |filetype|: buffer filetype. Empty string if no filetype is set;
|
||||
- |modified|: True if the buffer has unsaved changes, False otherwise;
|
||||
- |window| : number of the buffer window. None if the buffer is hidden;
|
||||
- |omnifunc|: omni completion function used by the buffer."""
|
||||
|
||||
def __init__( self, name,
|
||||
number = 1,
|
||||
contents = [],
|
||||
filetype = '',
|
||||
modified = True,
|
||||
window = None,
|
||||
omnifunc = '' ):
|
||||
self.name = name
|
||||
self.number = number
|
||||
self.contents = contents
|
||||
self.filetype = filetype
|
||||
self.modified = modified
|
||||
self.window = window
|
||||
self.omnifunc = omnifunc
|
||||
|
||||
|
||||
def __getitem__( self, index ):
|
||||
"""Return the bytes for a given line at index |index|."""
|
||||
return self.contents[ index ]
|
||||
|
||||
|
||||
def __len__( self ):
|
||||
return len( self.contents )
|
||||
|
||||
|
||||
def __setitem__( self, key, value ):
|
||||
return self.contents.__setitem__( key, value )
|
||||
|
||||
|
||||
def GetLines( self ):
|
||||
"""Return the contents of the buffer as a list of unicode strings."""
|
||||
return [ ToUnicode( x ) for x in self.contents ]
|
||||
|
||||
|
||||
def MockVimModule():
|
||||
"""The 'vim' module is something that is only present when running inside the
|
||||
Vim Python interpreter, so we replace it with a MagicMock for tests. If you
|
||||
|
@ -23,7 +23,7 @@ from future import standard_library
|
||||
standard_library.install_aliases()
|
||||
from builtins import * # noqa
|
||||
|
||||
from ycm.test_utils import MockVimModule, ExtendedMock
|
||||
from ycm.test_utils import ExtendedMock, MockVimModule, VimBuffer
|
||||
MockVimModule()
|
||||
|
||||
import contextlib
|
||||
@ -33,6 +33,7 @@ from ycm.tests.server_test import Server_test
|
||||
from ycmd.responses import ( BuildDiagnosticData, Diagnostic, Location, Range,
|
||||
UnknownExtraConf, ServerError )
|
||||
|
||||
from hamcrest import assert_that, contains, has_entries
|
||||
from mock import call, MagicMock, patch
|
||||
from nose.tools import eq_, ok_
|
||||
|
||||
@ -59,45 +60,18 @@ def MockArbitraryBuffer( filetype, native_available = True ):
|
||||
"""Used via the with statement, set up mocked versions of the vim module such
|
||||
that a single buffer is open with an arbitrary name and arbirary contents. Its
|
||||
filetype is set to the supplied filetype"""
|
||||
with patch( 'vim.current' ) as vim_current:
|
||||
def VimEval( value ):
|
||||
"""Local mock of the vim.eval() function, used to ensure we get the
|
||||
correct behvaiour"""
|
||||
|
||||
if value == '&omnifunc':
|
||||
# The omnicompleter is not required here
|
||||
return ''
|
||||
|
||||
if value == 'getbufvar(0, "&mod")':
|
||||
# Ensure that we actually send the even to the server
|
||||
return 1
|
||||
|
||||
if value == 'getbufvar(0, "&ft")' or value == '&filetype':
|
||||
return filetype
|
||||
|
||||
if value.startswith( 'bufnr(' ):
|
||||
return 0
|
||||
|
||||
if value.startswith( 'bufwinnr(' ):
|
||||
return 0
|
||||
|
||||
raise ValueError( 'Unexpected evaluation' )
|
||||
|
||||
# Arbitrary, but valid, cursor position
|
||||
vim_current.window.cursor = ( 1, 2 )
|
||||
|
||||
# Arbitrary, but valid, single buffer open
|
||||
current_buffer = MagicMock()
|
||||
current_buffer.number = 0
|
||||
current_buffer.filename = os.path.realpath( 'TEST_BUFFER' )
|
||||
current_buffer.name = 'TEST_BUFFER'
|
||||
current_buffer.window = 0
|
||||
# Arbitrary, but valid, single buffer open.
|
||||
current_buffer = VimBuffer( os.path.realpath( 'TEST_BUFFER' ),
|
||||
window = 1,
|
||||
filetype = filetype )
|
||||
|
||||
# The rest just mock up the Vim module so that our single arbitrary buffer
|
||||
# makes sense to vimsupport module.
|
||||
with patch( 'vim.buffers', [ current_buffer ] ):
|
||||
with patch( 'vim.current.buffer', current_buffer ):
|
||||
with patch( 'vim.eval', side_effect=VimEval ):
|
||||
# Arbitrary but valid cursor position.
|
||||
with patch( 'vim.current.window.cursor', ( 1, 2 ) ):
|
||||
yield
|
||||
|
||||
|
||||
@ -310,7 +284,7 @@ class EventNotification_test( Server_test ):
|
||||
ok_( self._server_state.FileParseRequestReady() )
|
||||
self._server_state.HandleFileParseRequest()
|
||||
vim_command.assert_has_calls( [
|
||||
PlaceSign_Call( 1, 1, 0, True )
|
||||
PlaceSign_Call( 1, 1, 1, True )
|
||||
] )
|
||||
eq_( self._server_state.GetErrorCount(), 1 )
|
||||
eq_( self._server_state.GetWarningCount(), 0 )
|
||||
@ -343,8 +317,8 @@ class EventNotification_test( Server_test ):
|
||||
ok_( self._server_state.FileParseRequestReady() )
|
||||
self._server_state.HandleFileParseRequest()
|
||||
vim_command.assert_has_calls( [
|
||||
PlaceSign_Call( 2, 2, 0, False ),
|
||||
UnplaceSign_Call( 1, 0 )
|
||||
PlaceSign_Call( 2, 2, 1, False ),
|
||||
UnplaceSign_Call( 1, 1 )
|
||||
] )
|
||||
eq_( self._server_state.GetErrorCount(), 0 )
|
||||
eq_( self._server_state.GetWarningCount(), 1 )
|
||||
@ -369,7 +343,114 @@ class EventNotification_test( Server_test ):
|
||||
self._server_state.OnFileReadyToParse()
|
||||
self._server_state.HandleFileParseRequest()
|
||||
vim_command.assert_has_calls( [
|
||||
UnplaceSign_Call( 2, 0 )
|
||||
UnplaceSign_Call( 2, 1 )
|
||||
] )
|
||||
eq_( self._server_state.GetErrorCount(), 0 )
|
||||
eq_( self._server_state.GetWarningCount(), 0 )
|
||||
|
||||
|
||||
@patch( 'ycm.youcompleteme.YouCompleteMe._AddUltiSnipsDataIfNeeded' )
|
||||
@patch( 'ycm.client.base_request.BaseRequest.PostDataToHandlerAsync',
|
||||
new_callable = ExtendedMock )
|
||||
def BufferVisit_BuildRequestForCurrentAndUnsavedBuffers_test(
|
||||
self, post_data_to_handler_async, *args ):
|
||||
|
||||
current_buffer_file = os.path.realpath( 'current_buffer' )
|
||||
current_buffer = VimBuffer( name = current_buffer_file,
|
||||
number = 1,
|
||||
contents = [ 'current_buffer_content' ],
|
||||
filetype = 'some_filetype',
|
||||
modified = False )
|
||||
|
||||
modified_buffer_file = os.path.realpath( 'modified_buffer' )
|
||||
modified_buffer = VimBuffer( name = modified_buffer_file,
|
||||
number = 2,
|
||||
contents = [ 'modified_buffer_content' ],
|
||||
filetype = 'some_filetype',
|
||||
modified = True )
|
||||
|
||||
unmodified_buffer_file = os.path.realpath( 'unmodified_buffer' )
|
||||
unmodified_buffer = VimBuffer( name = unmodified_buffer_file,
|
||||
number = 3,
|
||||
contents = [ 'unmodified_buffer_content' ],
|
||||
filetype = 'some_filetype',
|
||||
modified = False )
|
||||
|
||||
with patch( 'vim.buffers', [ current_buffer,
|
||||
modified_buffer,
|
||||
unmodified_buffer ] ):
|
||||
with patch( 'vim.current.buffer', current_buffer ):
|
||||
with patch( 'vim.current.window.cursor', ( 3, 5 ) ):
|
||||
self._server_state.OnBufferVisit()
|
||||
|
||||
assert_that(
|
||||
# Positional arguments passed to PostDataToHandlerAsync.
|
||||
post_data_to_handler_async.call_args[ 0 ],
|
||||
contains(
|
||||
has_entries( {
|
||||
'filepath': current_buffer_file,
|
||||
'line_num': 3,
|
||||
'column_num': 6,
|
||||
'file_data': has_entries( {
|
||||
current_buffer_file: has_entries( {
|
||||
'contents': 'current_buffer_content\n',
|
||||
'filetypes': [ 'some_filetype' ]
|
||||
} ),
|
||||
modified_buffer_file: has_entries( {
|
||||
'contents': 'modified_buffer_content\n',
|
||||
'filetypes': [ 'some_filetype' ]
|
||||
} )
|
||||
} ),
|
||||
'event_name': 'BufferVisit'
|
||||
} ),
|
||||
'event_notification'
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@patch( 'ycm.client.base_request.BaseRequest.PostDataToHandlerAsync',
|
||||
new_callable = ExtendedMock )
|
||||
def BufferUnload_BuildRequestForDeletedAndUnsavedBuffers_test(
|
||||
self, post_data_to_handler_async ):
|
||||
|
||||
current_buffer_file = os.path.realpath( 'current_buffer' )
|
||||
current_buffer = VimBuffer( name = current_buffer_file,
|
||||
number = 1,
|
||||
contents = [ 'current_buffer_content' ],
|
||||
filetype = 'some_filetype',
|
||||
modified = True )
|
||||
|
||||
deleted_buffer_file = os.path.realpath( 'deleted_buffer' )
|
||||
deleted_buffer = VimBuffer( name = deleted_buffer_file,
|
||||
number = 2,
|
||||
contents = [ 'deleted_buffer_content' ],
|
||||
filetype = 'some_filetype',
|
||||
modified = False )
|
||||
|
||||
with patch( 'vim.buffers', [ current_buffer, deleted_buffer ] ):
|
||||
with patch( 'vim.current.buffer', current_buffer ):
|
||||
self._server_state.OnBufferUnload( deleted_buffer_file )
|
||||
|
||||
assert_that(
|
||||
# Positional arguments passed to PostDataToHandlerAsync.
|
||||
post_data_to_handler_async.call_args[ 0 ],
|
||||
contains(
|
||||
has_entries( {
|
||||
'filepath': deleted_buffer_file,
|
||||
'line_num': 1,
|
||||
'column_num': 1,
|
||||
'file_data': has_entries( {
|
||||
current_buffer_file: has_entries( {
|
||||
'contents': 'current_buffer_content\n',
|
||||
'filetypes': [ 'some_filetype' ]
|
||||
} ),
|
||||
deleted_buffer_file: has_entries( {
|
||||
'contents': 'deleted_buffer_content\n',
|
||||
'filetypes': [ 'some_filetype' ]
|
||||
} )
|
||||
} ),
|
||||
'event_name': 'BufferUnload'
|
||||
} ),
|
||||
'event_notification'
|
||||
)
|
||||
)
|
||||
|
@ -25,14 +25,15 @@ from future import standard_library
|
||||
standard_library.install_aliases()
|
||||
from builtins import * # noqa
|
||||
|
||||
from ycm.test_utils import ExtendedMock, MockVimModule, MockVimCommand
|
||||
from ycm.test_utils import ( ExtendedMock, MockVimCommand, VimBuffer,
|
||||
MockVimModule )
|
||||
MockVimModule()
|
||||
|
||||
from ycm import vimsupport
|
||||
from nose.tools import eq_
|
||||
from hamcrest import assert_that, calling, raises, none, has_entry
|
||||
from mock import MagicMock, call, patch
|
||||
from ycmd.utils import ToBytes, ToUnicode
|
||||
from ycmd.utils import ToBytes
|
||||
import os
|
||||
import json
|
||||
|
||||
@ -705,34 +706,6 @@ def ReplaceChunksInBuffer_UnsortedChunks_test():
|
||||
AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer )
|
||||
|
||||
|
||||
class MockBuffer( object ):
|
||||
"""An object that looks like a vim.buffer object, enough for ReplaceChunk to
|
||||
generate a location list"""
|
||||
|
||||
def __init__( self, lines, name, number ):
|
||||
self.lines = lines
|
||||
self.name = name
|
||||
self.number = number
|
||||
|
||||
|
||||
def __getitem__( self, index ):
|
||||
""" Return the bytes for a given line at index |index| """
|
||||
return self.lines[ index ]
|
||||
|
||||
|
||||
def __len__( self ):
|
||||
return len( self.lines )
|
||||
|
||||
|
||||
def __setitem__( self, key, value ):
|
||||
return self.lines.__setitem__( key, value )
|
||||
|
||||
|
||||
def GetLines( self ):
|
||||
""" Return the contents of the buffer as a list of unicode strings"""
|
||||
return [ ToUnicode( x ) for x in self.lines ]
|
||||
|
||||
|
||||
@patch( 'ycm.vimsupport.VariableExists', return_value = False )
|
||||
@patch( 'ycm.vimsupport.SetFittingHeightForCurrentWindow' )
|
||||
@patch( 'ycm.vimsupport.GetBufferNumberForFilename',
|
||||
@ -758,11 +731,14 @@ def ReplaceChunks_SingleFile_Open_test( vim_command,
|
||||
_BuildChunk( 1, 1, 2, 1, 'replacement', 'single_file' )
|
||||
]
|
||||
|
||||
result_buffer = MockBuffer( [
|
||||
result_buffer = VimBuffer(
|
||||
'single_file',
|
||||
contents = [
|
||||
'line1',
|
||||
'line2',
|
||||
'line3',
|
||||
], 'single_file', 1 )
|
||||
'line3'
|
||||
]
|
||||
)
|
||||
|
||||
with patch( 'vim.buffers', [ None, result_buffer, None ] ):
|
||||
vimsupport.ReplaceChunks( chunks )
|
||||
@ -845,11 +821,14 @@ def ReplaceChunks_SingleFile_NotOpen_test( vim_command,
|
||||
_BuildChunk( 1, 1, 2, 1, 'replacement', 'single_file' )
|
||||
]
|
||||
|
||||
result_buffer = MockBuffer( [
|
||||
result_buffer = VimBuffer(
|
||||
'single_file',
|
||||
contents = [
|
||||
'line1',
|
||||
'line2',
|
||||
'line3',
|
||||
], 'single_file', 1 )
|
||||
'line3'
|
||||
]
|
||||
)
|
||||
|
||||
with patch( 'vim.buffers', [ None, result_buffer, None ] ):
|
||||
vimsupport.ReplaceChunks( chunks )
|
||||
@ -954,11 +933,14 @@ def ReplaceChunks_User_Declines_To_Open_File_test(
|
||||
_BuildChunk( 1, 1, 2, 1, 'replacement', 'single_file' )
|
||||
]
|
||||
|
||||
result_buffer = MockBuffer( [
|
||||
result_buffer = VimBuffer(
|
||||
'single_file',
|
||||
contents = [
|
||||
'line1',
|
||||
'line2',
|
||||
'line3',
|
||||
], 'single_file', 1 )
|
||||
'line3'
|
||||
]
|
||||
)
|
||||
|
||||
with patch( 'vim.buffers', [ None, result_buffer, None ] ):
|
||||
vimsupport.ReplaceChunks( chunks )
|
||||
@ -1031,11 +1013,14 @@ def ReplaceChunks_User_Aborts_Opening_File_test(
|
||||
_BuildChunk( 1, 1, 2, 1, 'replacement', 'single_file' )
|
||||
]
|
||||
|
||||
result_buffer = MockBuffer( [
|
||||
result_buffer = VimBuffer(
|
||||
'single_file',
|
||||
contents = [
|
||||
'line1',
|
||||
'line2',
|
||||
'line3',
|
||||
], 'single_file', 1 )
|
||||
'line3'
|
||||
]
|
||||
)
|
||||
|
||||
with patch( 'vim.buffers', [ None, result_buffer, None ] ):
|
||||
assert_that( calling( vimsupport.ReplaceChunks ).with_args( chunks ),
|
||||
@ -1114,15 +1099,23 @@ def ReplaceChunks_MultiFile_Open_test( vim_command,
|
||||
_BuildChunk( 2, 1, 2, 1, 'second_file_replacement ', '2_another_file' ),
|
||||
]
|
||||
|
||||
first_file = MockBuffer( [
|
||||
first_file = VimBuffer(
|
||||
'1_first_file',
|
||||
number = 22,
|
||||
contents = [
|
||||
'line1',
|
||||
'line2',
|
||||
'line3',
|
||||
], '1_first_file', 22 )
|
||||
another_file = MockBuffer( [
|
||||
]
|
||||
)
|
||||
another_file = VimBuffer(
|
||||
'2_another_file',
|
||||
number = 19,
|
||||
contents = [
|
||||
'another line1',
|
||||
'ACME line2',
|
||||
], '2_another_file', 19 )
|
||||
]
|
||||
)
|
||||
|
||||
vim_buffers = [ None ] * 23
|
||||
vim_buffers[ 22 ] = first_file
|
||||
@ -1222,9 +1215,10 @@ def _BuildChunk( start_line,
|
||||
|
||||
@patch( 'vim.eval', new_callable = ExtendedMock )
|
||||
def AddDiagnosticSyntaxMatch_ErrorInMiddleOfLine_test( vim_eval ):
|
||||
current_buffer = MockBuffer( [
|
||||
'Highlight this error please'
|
||||
], 'some_file', 1 )
|
||||
current_buffer = VimBuffer(
|
||||
'some_file',
|
||||
contents = [ 'Highlight this error please' ]
|
||||
)
|
||||
|
||||
with patch( 'vim.current.buffer', current_buffer ):
|
||||
vimsupport.AddDiagnosticSyntaxMatch( 1, 16, 1, 21 )
|
||||
@ -1235,9 +1229,10 @@ def AddDiagnosticSyntaxMatch_ErrorInMiddleOfLine_test( vim_eval ):
|
||||
|
||||
@patch( 'vim.eval', new_callable = ExtendedMock )
|
||||
def AddDiagnosticSyntaxMatch_WarningAtEndOfLine_test( vim_eval ):
|
||||
current_buffer = MockBuffer( [
|
||||
'Highlight this warning'
|
||||
], 'some_file', 1 )
|
||||
current_buffer = VimBuffer(
|
||||
'some_file',
|
||||
contents = [ 'Highlight this warning' ]
|
||||
)
|
||||
|
||||
with patch( 'vim.current.buffer', current_buffer ):
|
||||
vimsupport.AddDiagnosticSyntaxMatch( 1, 16, 1, 23, is_error = False )
|
||||
@ -1339,30 +1334,41 @@ def CheckFilename_test():
|
||||
|
||||
|
||||
def BufferIsVisibleForFilename_test():
|
||||
buffers = [
|
||||
{
|
||||
'number': 1,
|
||||
'filename': os.path.realpath( 'visible_filename' ),
|
||||
'window': 1
|
||||
},
|
||||
{
|
||||
'number': 2,
|
||||
'filename': os.path.realpath( 'hidden_filename' ),
|
||||
}
|
||||
vim_buffers = [
|
||||
VimBuffer(
|
||||
os.path.realpath( 'visible_filename' ),
|
||||
number = 1,
|
||||
window = 1
|
||||
),
|
||||
VimBuffer(
|
||||
os.path.realpath( 'hidden_filename' ),
|
||||
number = 2,
|
||||
window = None
|
||||
)
|
||||
]
|
||||
|
||||
with patch( 'vim.buffers', buffers ):
|
||||
with patch( 'vim.buffers', vim_buffers ):
|
||||
eq_( vimsupport.BufferIsVisibleForFilename( 'visible_filename' ), True )
|
||||
eq_( vimsupport.BufferIsVisibleForFilename( 'hidden_filename' ), False )
|
||||
eq_( vimsupport.BufferIsVisibleForFilename( 'another_filename' ), False )
|
||||
|
||||
|
||||
@patch( 'ycm.vimsupport.GetBufferNumberForFilename',
|
||||
side_effect = [ 2, 5, -1 ] )
|
||||
@patch( 'vim.command',
|
||||
side_effect = MockVimCommand,
|
||||
new_callable = ExtendedMock )
|
||||
def CloseBuffersForFilename_test( vim_command, *args ):
|
||||
vim_buffers = [
|
||||
VimBuffer(
|
||||
os.path.realpath( 'some_filename' ),
|
||||
number = 2
|
||||
),
|
||||
VimBuffer(
|
||||
os.path.realpath( 'some_filename' ),
|
||||
number = 5
|
||||
)
|
||||
]
|
||||
|
||||
with patch( 'vim.buffers', vim_buffers ):
|
||||
vimsupport.CloseBuffersForFilename( 'some_filename' )
|
||||
|
||||
vim_command.assert_has_exact_calls( [
|
||||
@ -1401,17 +1407,14 @@ def OpenFilename_test( vim_current, vim_command ):
|
||||
] )
|
||||
|
||||
|
||||
@patch( 'ycm.vimsupport.BufferModified', side_effect = [ True ] )
|
||||
@patch( 'ycm.vimsupport.FiletypesForBuffer', side_effect = [ [ 'cpp' ] ] )
|
||||
def GetUnsavedAndCurrentBufferData_EncodedUnicodeCharsInBuffers_test( *args ):
|
||||
mock_buffer = MagicMock()
|
||||
mock_buffer.name = os.path.realpath( 'filename' )
|
||||
mock_buffer.number = 1
|
||||
mock_buffer.__iter__.return_value = [ ToBytes ( u'abc' ), ToBytes( u'fДa' ) ]
|
||||
def GetUnsavedAndSpecifiedBufferData_EncodedUnicodeCharsInBuffers_test():
|
||||
filepath = os.path.realpath( 'filename' )
|
||||
contents = [ ToBytes( u'abc' ), ToBytes( u'fДa' ) ]
|
||||
vim_buffer = VimBuffer( filepath, contents = contents )
|
||||
|
||||
with patch( 'vim.buffers', [ mock_buffer ] ):
|
||||
assert_that( vimsupport.GetUnsavedAndCurrentBufferData(),
|
||||
has_entry( mock_buffer.name,
|
||||
with patch( 'vim.buffers', [ vim_buffer ] ):
|
||||
assert_that( vimsupport.GetUnsavedAndSpecifiedBufferData( filepath ),
|
||||
has_entry( filepath,
|
||||
has_entry( u'contents', u'abc\nfДa\n' ) ) )
|
||||
|
||||
|
||||
|
@ -116,14 +116,17 @@ def BufferModified( buffer_object ):
|
||||
return bool( int( GetBufferOption( buffer_object, 'mod' ) ) )
|
||||
|
||||
|
||||
def GetUnsavedAndCurrentBufferData():
|
||||
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_object == vim.current.buffer ):
|
||||
buffer_filepath == including_filepath ):
|
||||
continue
|
||||
|
||||
buffers_data[ GetBufferFilepath( buffer_object ) ] = {
|
||||
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 )
|
||||
|
@ -311,16 +311,18 @@ class YouCompleteMe( object ):
|
||||
self._AddSyntaxDataIfNeeded( extra_data )
|
||||
self._AddExtraConfDataIfNeeded( extra_data )
|
||||
|
||||
self._latest_file_parse_request = EventNotification( 'FileReadyToParse',
|
||||
extra_data )
|
||||
self._latest_file_parse_request = EventNotification(
|
||||
'FileReadyToParse', extra_data = extra_data )
|
||||
self._latest_file_parse_request.Start()
|
||||
|
||||
|
||||
def OnBufferUnload( self, deleted_buffer_file ):
|
||||
if not self.IsServerAlive():
|
||||
return
|
||||
SendEventNotificationAsync( 'BufferUnload',
|
||||
{ 'unloaded_buffer': deleted_buffer_file } )
|
||||
SendEventNotificationAsync(
|
||||
'BufferUnload',
|
||||
filepath = deleted_buffer_file,
|
||||
extra_data = { 'unloaded_buffer': deleted_buffer_file } )
|
||||
|
||||
|
||||
def OnBufferVisit( self ):
|
||||
@ -328,7 +330,7 @@ class YouCompleteMe( object ):
|
||||
return
|
||||
extra_data = {}
|
||||
self._AddUltiSnipsDataIfNeeded( extra_data )
|
||||
SendEventNotificationAsync( 'BufferVisit', extra_data )
|
||||
SendEventNotificationAsync( 'BufferVisit', extra_data = extra_data )
|
||||
|
||||
|
||||
def OnInsertLeave( self ):
|
||||
|
Loading…
Reference in New Issue
Block a user