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:
Homu 2016-10-11 07:15:16 +09:00
commit 768aea435b
8 changed files with 366 additions and 180 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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