Latest upstream changes with buffer emulation.

Contains diagnostic interface improvents as well.
This commit is contained in:
Davit Samvelyan 2017-05-21 18:26:50 +04:00
parent 263bd88bd5
commit 0846673aa4
8 changed files with 327 additions and 216 deletions

View File

@ -479,15 +479,13 @@ function! s:OnFileReadyToParse( ... )
" We only want to send a new FileReadyToParse event notification if the buffer
" has changed since the last time we sent one, or if forced.
if force_parsing || b:changedtick != get( b:, 'ycm_changedtick', -1 )
if force_parsing || s:Pyeval( "ycm_state.NeedsReparse()" )
exec s:python_command "ycm_state.OnFileReadyToParse()"
call timer_stop( s:pollers.file_parse_response.id )
let s:pollers.file_parse_response.id = timer_start(
\ s:pollers.file_parse_response.wait_milliseconds,
\ function( 's:PollFileParseResponse' ) )
let b:ycm_changedtick = b:changedtick
endif
endfunction

105
python/ycm/buffer.py Normal file
View File

@ -0,0 +1,105 @@
# Copyright (C) 2016, Davit Samvelyan
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
from __future__ import unicode_literals
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from future import standard_library
standard_library.install_aliases()
from builtins import * # noqa
from ycm import vimsupport
from ycm.client.event_notification import EventNotification
from ycm.diagnostic_interface import DiagnosticInterface
class Buffer( object ):
def __init__( self, bufnr, user_options ):
self.number = bufnr
self._parse_tick = 0
self._handled_tick = 0
self._parse_request = None
self._diag_interface = DiagnosticInterface( bufnr, user_options )
def FileParseRequestReady( self, block = False ):
return bool( self._parse_request and
( block or self._parse_request.Done() ) )
def SendParseRequest( self, extra_data ):
self._parse_request = EventNotification( 'FileReadyToParse',
extra_data = extra_data )
self._parse_request.Start()
# Decrement handled tick to ensure correct handling when we are forcing
# reparse on buffer visit and changed tick remains the same.
self._handled_tick -= 1
self._parse_tick = self._ChangedTick()
def NeedsReparse( self ):
return self._parse_tick != self._ChangedTick()
def UpdateDiagnostics( self ):
self._diag_interface.UpdateWithNewDiagnostics(
self._parse_request.Response() )
def PopulateLocationList( self ):
return self._diag_interface.PopulateLocationList()
def GetResponse( self ):
return self._parse_request.Response()
def IsResponseHandled( self ):
return self._handled_tick == self._parse_tick
def MarkResponseHandled( self ):
self._handled_tick = self._parse_tick
def OnCursorMoved( self ):
self._diag_interface.OnCursorMoved()
def GetErrorCount( self ):
return self._diag_interface.GetErrorCount()
def GetWarningCount( self ):
return self._diag_interface.GetWarningCount()
def _ChangedTick( self ):
return vimsupport.GetBufferChangedTick(self.number)
class BufferDict( dict ):
def __init__( self, user_options ):
self._user_options = user_options
def __missing__( self, key ):
value = self[ key ] = Buffer( key, self._user_options )
return value

View File

@ -30,16 +30,17 @@ import vim
class DiagnosticInterface( object ):
def __init__( self, user_options ):
def __init__( self, bufnr, user_options ):
self._bufnr = bufnr
self._user_options = user_options
self._diagnostics = []
self._diag_filter = DiagnosticFilter.CreateFromOptions( user_options )
# Line and column numbers are 1-based
self._buffer_number_to_line_to_diags = defaultdict(
lambda: defaultdict( list ) )
self._line_to_diags = defaultdict( list )
self._placed_signs = []
self._next_sign_id = 1
self._previous_line_number = -1
self._diag_message_needs_clearing = False
self._placed_signs = []
def OnCursorMoved( self ):
@ -53,54 +54,43 @@ class DiagnosticInterface( object ):
def GetErrorCount( self ):
return len( self._FilterDiagnostics( _DiagnosticIsError ) )
return self._DiagnosticsCount( _DiagnosticIsError )
def GetWarningCount( self ):
return len( self._FilterDiagnostics( _DiagnosticIsWarning ) )
return self._DiagnosticsCount( _DiagnosticIsWarning )
def PopulateLocationList( self, diags ):
vimsupport.SetLocationList(
vimsupport.ConvertDiagnosticsToQfList(
self._ApplyDiagnosticFilter( diags ) ) )
def PopulateLocationList( self ):
# Do nothing if loc list is already populated by diag_interface
if not self._user_options[ 'always_populate_location_list' ]:
self._UpdateLocationList()
return bool( self._diagnostics )
def UpdateWithNewDiagnostics( self, diags ):
normalized_diags = [ _NormalizeDiagnostic( x ) for x in
self._ApplyDiagnosticFilter( diags ) ]
self._buffer_number_to_line_to_diags = _ConvertDiagListToDict(
normalized_diags )
self._diagnostics = [ _NormalizeDiagnostic( x ) for x in
self._ApplyDiagnosticFilter( diags ) ]
self._ConvertDiagListToDict()
if self._user_options[ 'enable_diagnostic_signs' ]:
self._placed_signs, self._next_sign_id = _UpdateSigns(
self._placed_signs,
self._buffer_number_to_line_to_diags,
self._next_sign_id )
self._UpdateSigns()
if self._user_options[ 'enable_diagnostic_highlighting' ]:
_UpdateSquiggles( self._buffer_number_to_line_to_diags )
self._UpdateSquiggles()
if self._user_options[ 'always_populate_location_list' ]:
self.PopulateLocationList( normalized_diags )
self._UpdateLocationList()
def _ApplyDiagnosticFilter( self, diags, extra_predicate = None ):
filetypes = vimsupport.CurrentFiletypes()
def _ApplyDiagnosticFilter( self, diags ):
filetypes = vimsupport.GetBufferFiletypes( self._bufnr )
diag_filter = self._diag_filter.SubsetForTypes( filetypes )
predicate = diag_filter.IsAllowed
if extra_predicate is not None:
def Filter( diag ):
return extra_predicate( diag ) and diag_filter.IsAllowed( diag )
predicate = Filter
return filter( predicate, diags )
return filter( diag_filter.IsAllowed, diags )
def _EchoDiagnosticForLine( self, line_num ):
buffer_num = vim.current.buffer.number
diags = self._buffer_number_to_line_to_diags[ buffer_num ][ line_num ]
diags = self._line_to_diags[ line_num ]
if not diags:
if self._diag_message_needs_clearing:
# Clear any previous diag echo
@ -117,144 +107,105 @@ class DiagnosticInterface( object ):
self._diag_message_needs_clearing = True
def _FilterDiagnostics( self, predicate ):
matched_diags = []
line_to_diags = self._buffer_number_to_line_to_diags[
vim.current.buffer.number ]
for diags in itervalues( line_to_diags ):
matched_diags.extend( list(
self._ApplyDiagnosticFilter( diags, predicate ) ) )
return matched_diags
def _DiagnosticsCount( self, predicate ):
count = 0
for diags in itervalues( self._line_to_diags ):
count += sum( 1 for d in diags if predicate( d ) )
return count
def _UpdateSquiggles( buffer_number_to_line_to_diags ):
vimsupport.ClearYcmSyntaxMatches()
line_to_diags = buffer_number_to_line_to_diags[ vim.current.buffer.number ]
def _ConvertDiagListToDict( self ):
self._line_to_diags = defaultdict( list )
for diag in self._diagnostics:
location = diag[ 'location' ]
bufnr = vimsupport.GetBufferNumberForFilename( location[ 'filepath' ] )
if bufnr != self._bufnr:
continue
line_number = location[ 'line_num' ]
self._line_to_diags[ line_number ].append( diag )
for diags in itervalues( line_to_diags ):
# Insert squiggles in reverse order so that errors overlap warnings.
for diag in reversed( diags ):
location_extent = diag[ 'location_extent' ]
is_error = _DiagnosticIsError( diag )
if location_extent[ 'start' ][ 'line_num' ] <= 0:
location = diag[ 'location' ]
vimsupport.AddDiagnosticSyntaxMatch(
location[ 'line_num' ],
location[ 'column_num' ],
is_error = is_error )
else:
vimsupport.AddDiagnosticSyntaxMatch(
location_extent[ 'start' ][ 'line_num' ],
location_extent[ 'start' ][ 'column_num' ],
location_extent[ 'end' ][ 'line_num' ],
location_extent[ 'end' ][ 'column_num' ],
is_error = is_error )
for diag_range in diag[ 'ranges' ]:
vimsupport.AddDiagnosticSyntaxMatch(
diag_range[ 'start' ][ 'line_num' ],
diag_range[ 'start' ][ 'column_num' ],
diag_range[ 'end' ][ 'line_num' ],
diag_range[ 'end' ][ 'column_num' ],
is_error = is_error )
def _UpdateSigns( placed_signs, buffer_number_to_line_to_diags, next_sign_id ):
new_signs, kept_signs, next_sign_id = _GetKeptAndNewSigns(
placed_signs, buffer_number_to_line_to_diags, next_sign_id
)
# Dummy sign used to prevent "flickering" in Vim when last mark gets
# deleted from buffer. Dummy sign prevents Vim to collapsing the sign column
# in that case.
# There's also a vim bug which causes the whole window to redraw in some
# conditions (vim redraw logic is very complex). But, somehow, if we place a
# dummy sign before placing other "real" signs, it will not redraw the
# buffer (patch to vim pending).
dummy_sign_needed = not kept_signs and new_signs
if dummy_sign_needed:
vimsupport.PlaceDummySign( next_sign_id + 1,
vim.current.buffer.number,
new_signs[ 0 ].line )
# We place only those signs that haven't been placed yet.
new_placed_signs = _PlaceNewSigns( kept_signs, new_signs )
# We use incremental placement, so signs that already placed on the correct
# lines will not be deleted and placed again, which should improve performance
# in case of many diags. Signs which don't exist in the current diag should be
# deleted.
_UnplaceObsoleteSigns( kept_signs, placed_signs )
if dummy_sign_needed:
vimsupport.UnPlaceDummySign( next_sign_id + 1, vim.current.buffer.number )
return new_placed_signs, next_sign_id
def _GetKeptAndNewSigns( placed_signs, buffer_number_to_line_to_diags,
next_sign_id ):
new_signs = []
kept_signs = []
for buffer_number, line_to_diags in iteritems(
buffer_number_to_line_to_diags ):
if not vimsupport.BufferIsVisible( buffer_number ):
continue
for line, diags in iteritems( line_to_diags ):
# Only one sign is visible by line.
first_diag = diags[ 0 ]
sign = _DiagSignPlacement( next_sign_id,
line,
buffer_number,
_DiagnosticIsError( first_diag ) )
if sign not in placed_signs:
new_signs.append( sign )
next_sign_id += 1
else:
# We use .index here because `sign` contains a new id, but
# we need the sign with the old id to unplace it later on.
# We won't be placing the new sign.
kept_signs.append( placed_signs[ placed_signs.index( sign ) ] )
return new_signs, kept_signs, next_sign_id
def _PlaceNewSigns( kept_signs, new_signs ):
placed_signs = kept_signs[:]
for sign in new_signs:
# Do not set two signs on the same line, it will screw up storing sign
# locations.
if sign in placed_signs:
continue
vimsupport.PlaceSign( sign.id, sign.line, sign.buffer, sign.is_error )
placed_signs.append( sign )
return placed_signs
def _UnplaceObsoleteSigns( kept_signs, placed_signs ):
for sign in placed_signs:
if sign not in kept_signs:
vimsupport.UnplaceSignInBuffer( sign.buffer, sign.id )
def _ConvertDiagListToDict( diag_list ):
buffer_to_line_to_diags = defaultdict( lambda: defaultdict( list ) )
for diag in diag_list:
location = diag[ 'location' ]
buffer_number = vimsupport.GetBufferNumberForFilename(
location[ 'filepath' ] )
line_number = location[ 'line_num' ]
buffer_to_line_to_diags[ buffer_number ][ line_number ].append( diag )
for line_to_diags in itervalues( buffer_to_line_to_diags ):
for diags in itervalues( line_to_diags ):
# We want errors to be listed before warnings so that errors aren't hidden
# by the warnings.
for diags in itervalues( self._line_to_diags ):
# We also want errors to be listed before warnings so that errors aren't
# hidden by the warnings; Vim won't place a sign over an existing one.
diags.sort( key = lambda diag: ( diag[ 'kind' ],
diag[ 'location' ][ 'column_num' ] ) )
return buffer_to_line_to_diags
def _UpdateSigns( self ):
new_signs, obsolete_signs = self._GetNewAndObsoleteSigns()
self._PlaceNewSigns( new_signs )
self._UnplaceObsoleteSigns( obsolete_signs )
def _GetNewAndObsoleteSigns( self ):
new_signs = []
obsolete_signs = self._placed_signs[:]
for line, diags in iteritems( self._line_to_diags ):
# We always go for the first diagnostic on line,
# because it is sorted giving priority to the Errors.
diag = diags[ 0 ]
sign = _DiagSignPlacement( self._next_sign_id,
line, _DiagnosticIsError( diag ) )
try:
obsolete_signs.remove( sign )
except ValueError:
new_signs.append( sign )
self._next_sign_id += 1
return new_signs, obsolete_signs
def _PlaceNewSigns( self, new_signs ):
for sign in new_signs:
vimsupport.PlaceSign( sign.id, sign.line, self._bufnr, sign.is_error )
self._placed_signs.append( sign )
def _UnplaceObsoleteSigns( self, obsolete_signs ):
for sign in obsolete_signs:
self._placed_signs.remove( sign )
vimsupport.UnplaceSignInBuffer( self._bufnr, sign.id )
def _UpdateSquiggles( self ):
if self._bufnr != vim.current.buffer.number:
return
vimsupport.ClearYcmSyntaxMatches()
for diags in itervalues( self._line_to_diags ):
for diag in reversed( diags ):
location_extent = diag[ 'location_extent' ]
is_error = _DiagnosticIsError( diag )
if location_extent[ 'start' ][ 'line_num' ] <= 0:
location = diag[ 'location' ]
vimsupport.AddDiagnosticSyntaxMatch(
location[ 'line_num' ],
location[ 'column_num' ],
is_error = is_error )
else:
vimsupport.AddDiagnosticSyntaxMatch(
location_extent[ 'start' ][ 'line_num' ],
location_extent[ 'start' ][ 'column_num' ],
location_extent[ 'end' ][ 'line_num' ],
location_extent[ 'end' ][ 'column_num' ],
is_error = is_error )
for diag_range in diag[ 'ranges' ]:
vimsupport.AddDiagnosticSyntaxMatch(
diag_range[ 'start' ][ 'line_num' ],
diag_range[ 'start' ][ 'column_num' ],
diag_range[ 'end' ][ 'line_num' ],
diag_range[ 'end' ][ 'column_num' ],
is_error = is_error )
def _UpdateLocationList( self ):
vimsupport.SetLocationList(
vimsupport.ConvertDiagnosticsToQfList( self._diagnostics ) )
_DiagnosticIsError = CompileLevel( 'error' )
@ -271,12 +222,10 @@ def _NormalizeDiagnostic( diag ):
return diag
class _DiagSignPlacement(
namedtuple( "_DiagSignPlacement",
[ 'id', 'line', 'buffer', 'is_error' ] ) ):
class _DiagSignPlacement( namedtuple( "_DiagSignPlacement",
[ 'id', 'line', 'is_error' ] ) ):
# We want two signs that have different ids but the same location to compare
# equal. ID doesn't matter.
def __eq__( self, other ):
return ( self.line == other.line and
self.buffer == other.buffer and
self.is_error == other.is_error )

View File

@ -105,7 +105,10 @@ def MockEventNotification( response_method, native_filetype_completer = True ):
'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
return_value = native_filetype_completer ):
yield
with patch( 'ycm.youcompleteme.YouCompleteMe.IsServerReadyWithCache',
return_value = True ):
yield
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
@ -293,7 +296,6 @@ def _Check_FileReadyToParse_Diagnostic_Error( ycm, vim_command ):
# Consequent calls to HandleFileParseRequest shouldn't mess with
# existing diagnostics, when there is no new parse request.
vim_command.reset_mock()
ok_( not ycm.FileParseRequestReady() )
ycm.HandleFileParseRequest()
vim_command.assert_not_called()
eq_( ycm.GetErrorCount(), 1 )
@ -327,7 +329,6 @@ def _Check_FileReadyToParse_Diagnostic_Warning( ycm, vim_command ):
# Consequent calls to HandleFileParseRequest shouldn't mess with
# existing diagnostics, when there is no new parse request.
vim_command.reset_mock()
ok_( not ycm.FileParseRequestReady() )
ycm.HandleFileParseRequest()
vim_command.assert_not_called()
eq_( ycm.GetErrorCount(), 0 )
@ -351,6 +352,8 @@ def _Check_FileReadyToParse_Diagnostic_Clean( ycm, vim_command ):
@patch( 'ycm.youcompleteme.YouCompleteMe._AddUltiSnipsDataIfNeeded' )
@patch( 'ycm.youcompleteme.YouCompleteMe.IsServerReadyWithCache',
return_value = True )
@YouCompleteMeInstance( { 'collect_identifiers_from_tags_files': 1 } )
def EventNotification_FileReadyToParse_TagFiles_UnicodeWorkingDirectory_test(
ycm, *args ):
@ -495,6 +498,8 @@ def EventNotification_BufferUnload_BuildRequestForDeletedAndUnsavedBuffers_test(
@patch( 'ycm.syntax_parse.SyntaxKeywordsForCurrentBuffer',
return_value = [ 'foo', 'bar' ] )
@patch( 'ycm.youcompleteme.YouCompleteMe.IsServerReadyWithCache',
return_value = True )
@YouCompleteMeInstance( { 'seed_identifiers_with_syntax': 1 } )
def EventNotification_FileReadyToParse_SyntaxKeywords_SeedWithCache_test(
ycm, *args ):
@ -529,6 +534,8 @@ def EventNotification_FileReadyToParse_SyntaxKeywords_SeedWithCache_test(
@patch( 'ycm.syntax_parse.SyntaxKeywordsForCurrentBuffer',
return_value = [ 'foo', 'bar' ] )
@patch( 'ycm.youcompleteme.YouCompleteMe.IsServerReadyWithCache',
return_value = True )
@YouCompleteMeInstance( { 'seed_identifiers_with_syntax': 1 } )
def EventNotification_FileReadyToParse_SyntaxKeywords_ClearCacheIfRestart_test(
ycm, *args ):

View File

@ -40,7 +40,7 @@ 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>.+)"\)$' )
'^getbufvar\((?P<buffer_number>[0-9]+), "(?P<option>.+)"\)$' )
MATCHADD_REGEX = re.compile(
'^matchadd\(\'(?P<group>.+)\', \'(?P<pattern>.+)\'\)$' )
MATCHDELETE_REGEX = re.compile( '^matchdelete\((?P<id>)\)$' )
@ -85,10 +85,12 @@ def _MockGetBufferWindowNumber( buffer_number ):
def _MockGetBufferVariable( buffer_number, option ):
for vim_buffer in VIM_MOCK.buffers:
if vim_buffer.number == buffer_number:
if option == 'mod':
if option == '&mod':
return vim_buffer.modified
if option == 'ft':
if option == '&ft':
return vim_buffer.filetype
if option == 'changedtick':
return vim_buffer.changedtick
return ''
return ''
@ -230,6 +232,7 @@ class VimBuffer( object ):
self.modified = modified
self.window = window
self.omnifunc = omnifunc
self.changedtick = 1
def __getitem__( self, index ):
@ -250,6 +253,10 @@ class VimBuffer( object ):
return [ ToUnicode( x ) for x in self.contents ]
def EmulateCurrentBufferChange():
VIM_MOCK.current.buffer.changedtick += 1
class VimMatch( object ):
def __init__( self, group, pattern ):

View File

@ -349,6 +349,8 @@ def YouCompleteMe_ShowDiagnostics_NoDiagnosticsDetected_test(
'open_loclist_on_ycm_diags': 0 } )
@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
return_value = True )
@patch( 'ycm.youcompleteme.YouCompleteMe.IsServerReadyWithCache',
return_value = True )
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
@patch( 'ycm.vimsupport.SetLocationList', new_callable = ExtendedMock )
def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_DoNotOpenLocationList_test(
@ -388,6 +390,8 @@ def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_DoNotOpenLocationList_test(
@YouCompleteMeInstance( { 'open_loclist_on_ycm_diags': 1 } )
@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
return_value = True )
@patch( 'ycm.youcompleteme.YouCompleteMe.IsServerReadyWithCache',
return_value = True )
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
@patch( 'ycm.vimsupport.SetLocationList', new_callable = ExtendedMock )
@patch( 'ycm.vimsupport.OpenLocationList', new_callable = ExtendedMock )
@ -431,6 +435,8 @@ def YouCompleteMe_ShowDiagnostics_DiagnosticsFound_OpenLocationList_test(
'enable_diagnostic_highlighting': 1 } )
@patch( 'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
return_value = True )
@patch( 'ycm.youcompleteme.YouCompleteMe.IsServerReadyWithCache',
return_value = True )
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
@patch( 'vim.command', new_callable = ExtendedMock )
def YouCompleteMe_UpdateDiagnosticInterface_PrioritizeErrorsOverWarnings_test(
@ -526,11 +532,7 @@ def YouCompleteMe_UpdateDiagnosticInterface_PrioritizeErrorsOverWarnings_test(
# Only the error sign is placed.
vim_command.assert_has_exact_calls( [
call( 'sign define ycm_dummy_sign' ),
call( 'sign place 3 name=ycm_dummy_sign line=3 buffer=5' ),
call( 'sign place 1 name=YcmError line=3 buffer=5' ),
call( 'sign undefine ycm_dummy_sign' ),
call( 'sign unplace 3 buffer=5' )
] )
# When moving the cursor on the diagnostics, the error is displayed to the
@ -540,3 +542,29 @@ def YouCompleteMe_UpdateDiagnosticInterface_PrioritizeErrorsOverWarnings_test(
call( "expected ';' after expression (FixIt)",
truncate = True, warning = False )
] )
# Test the case when error is fixed, but warning remains.
with MockVimBuffers( [ current_buffer ], current_buffer, ( 3, 1 ) ):
with patch( 'ycm.client.event_notification.EventNotification.Response',
return_value = diagnostics[ 1 : 2 ] ):
with patch( 'ycm.vimsupport.ClearYcmSyntaxMatches' ) as clear_matches:
test_utils.VIM_MATCHES = []
vim_command.reset_mock()
ycm.OnFileReadyToParse()
ycm.HandleFileParseRequest( block = True )
clear_matches.assert_has_calls( [ call() ] )
# Error match is added after warning matches.
assert_that(
test_utils.VIM_MATCHES,
contains(
VimMatch( 'YcmWarningSection', '\%3l\%5c\_.\{-}\%3l\%7c' ),
VimMatch( 'YcmWarningSection', '\%3l\%3c\_.\{-}\%3l\%9c' ),
)
)
# Only the error sign is placed.
vim_command.assert_has_exact_calls( [
call( 'sign place 2 name=YcmWarning line=3 buffer=5' ),
call( 'try | exec "sign unplace 1 buffer=5" | catch /E158/ | endtry' ),
] )

View File

@ -147,6 +147,14 @@ def GetBufferFilepath( buffer_object ):
return os.path.join( GetCurrentDirectory(), str( buffer_object.number ) )
def GetCurrentBufferNumber():
return vim.current.buffer.number
def GetBufferChangedTick( bufnr ):
return GetIntValue( 'getbufvar({0}, "changedtick")'.format( bufnr ) )
def UnplaceSignInBuffer( buffer_number, sign_id ):
if buffer_number < 0:
return
@ -584,6 +592,11 @@ def CurrentFiletypes():
return VimExpressionToPythonType( "&filetype" ).split( '.' )
def GetBufferFiletypes( bufnr ):
command = 'getbufvar({0}, "&ft")'.format( bufnr )
return VimExpressionToPythonType( command ).split( '.' )
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

View File

@ -33,10 +33,10 @@ import vim
from subprocess import PIPE
from tempfile import NamedTemporaryFile
from ycm import base, paths, vimsupport
from ycm.buffer import BufferDict
from ycmd import utils
from ycmd import server_utils
from ycmd.request_wrap import RequestWrap
from ycm.diagnostic_interface import DiagnosticInterface
from ycm.omni_completer import OmniCompleter
from ycm import syntax_parse
from ycm.client.ycmd_keepalive import YcmdKeepalive
@ -49,8 +49,7 @@ from ycm.client.completion_request import ( CompletionRequest,
from ycm.client.debug_info_request import ( SendDebugInfoRequest,
FormatDebugInfoResponse )
from ycm.client.omni_completion_request import OmniCompletionRequest
from ycm.client.event_notification import ( SendEventNotificationAsync,
EventNotification )
from ycm.client.event_notification import SendEventNotificationAsync
from ycm.client.shutdown_request import SendShutdownRequest
@ -114,11 +113,9 @@ class YouCompleteMe( object ):
self._available_completers = {}
self._user_options = user_options
self._user_notified_about_crash = False
self._diag_interface = DiagnosticInterface( user_options )
self._omnicomp = OmniCompleter( user_options )
self._latest_file_parse_request = None
self._buffers = BufferDict( user_options )
self._latest_completion_request = None
self._latest_diagnostics = []
self._logger = logging.getLogger( 'ycm' )
self._client_logfile = None
self._server_stdout = None
@ -134,6 +131,11 @@ class YouCompleteMe( object ):
'cs': lambda self: self._OnCompleteDone_Csharp()
}
def _GetCurrentBuffer( self ):
return self._buffers[ vimsupport.GetCurrentBufferNumber() ]
def _SetupServer( self ):
self._available_completers = {}
self._user_notified_about_crash = False
@ -230,6 +232,10 @@ class YouCompleteMe( object ):
return self._server_is_ready_with_cache
def IsServerReadyWithCache( self ):
return self._server_is_ready_with_cache
def _NotifyUserIfServerCrashed( self ):
if self._user_notified_about_crash or self.IsServerAlive():
return
@ -353,11 +359,18 @@ class YouCompleteMe( object ):
self.NativeFiletypeCompletionAvailable() )
def NeedsReparse( self ):
return self._GetCurrentBuffer().NeedsReparse()
def OnFileReadyToParse( self ):
if not self.IsServerAlive():
self._NotifyUserIfServerCrashed()
return
if not self.IsServerReadyWithCache():
return
self._omnicomp.OnFileReadyToParse( None )
extra_data = {}
@ -365,9 +378,7 @@ class YouCompleteMe( object ):
self._AddSyntaxDataIfNeeded( extra_data )
self._AddExtraConfDataIfNeeded( extra_data )
self._latest_file_parse_request = EventNotification(
'FileReadyToParse', extra_data = extra_data )
self._latest_file_parse_request.Start()
self._GetCurrentBuffer().SendParseRequest( extra_data )
def OnBufferUnload( self, deleted_buffer_file ):
@ -385,7 +396,7 @@ class YouCompleteMe( object ):
def OnCursorMoved( self ):
self._diag_interface.OnCursorMoved()
self._GetCurrentBuffer().OnCursorMoved()
def _CleanLogfile( self ):
@ -512,11 +523,11 @@ class YouCompleteMe( object ):
def GetErrorCount( self ):
return self._diag_interface.GetErrorCount()
return self._GetCurrentBuffer().GetErrorCount()
def GetWarningCount( self ):
return self._diag_interface.GetWarningCount()
return self._GetCurrentBuffer().GetWarningCount()
def DiagnosticUiSupportedForCurrentFiletype( self ):
@ -530,31 +541,33 @@ class YouCompleteMe( object ):
def _PopulateLocationListWithLatestDiagnostics( self ):
# Do nothing if loc list is already populated by diag_interface
if not self._user_options[ 'always_populate_location_list' ]:
self._diag_interface.PopulateLocationList( self._latest_diagnostics )
return bool( self._latest_diagnostics )
return self._GetCurrentBuffer().PopulateLocationList()
def UpdateDiagnosticInterface( self ):
self._diag_interface.UpdateWithNewDiagnostics( self._latest_diagnostics )
def FileParseRequestReady( self, block = False ):
return bool( self._latest_file_parse_request and
( block or self._latest_file_parse_request.Done() ) )
def FileParseRequestReady( self ):
# Return True if server is not ready yet, to stop repeating check timer.
return ( not self.IsServerReadyWithCache() or
self._GetCurrentBuffer().FileParseRequestReady() )
def HandleFileParseRequest( self, block = False ):
if not self.IsServerReadyWithCache():
return
current_buffer = self._GetCurrentBuffer()
# Order is important here:
# FileParseRequestReady has a low cost, while
# NativeFiletypeCompletionUsable is a blocking server request
if ( self.FileParseRequestReady( block ) and
if ( not current_buffer.IsResponseHandled() and
current_buffer.FileParseRequestReady( block ) and
self.NativeFiletypeCompletionUsable() ):
if self.ShouldDisplayDiagnostics():
self._latest_diagnostics = self._latest_file_parse_request.Response()
self.UpdateDiagnosticInterface()
current_buffer.UpdateDiagnostics()
else:
# YCM client has a hard-coded list of filetypes which are known
# to support diagnostics, self.DiagnosticUiSupportedForCurrentFiletype()
@ -563,18 +576,9 @@ class YouCompleteMe( object ):
# the _latest_file_parse_request for any exception or UnknownExtraConf
# response, to allow the server to raise configuration warnings, etc.
# to the user. We ignore any other supplied data.
self._latest_file_parse_request.Response()
current_buffer.GetResponse()
# We set the file parse request to None because we want to prevent
# repeated issuing of the same warnings/errors/prompts. Setting this to
# None makes FileParseRequestReady return False until the next
# request is created.
#
# Note: it is the server's responsibility to determine the frequency of
# error/warning/prompts when receiving a FileReadyToParse event, but
# it our responsibility to ensure that we only apply the
# warning/error/prompt received once (for each event).
self._latest_file_parse_request = None
current_buffer.MarkResponseHandled()
def DebugInfo( self ):
@ -709,7 +713,7 @@ class YouCompleteMe( object ):
if filetype in self._filetypes_with_keywords_loaded:
return
if self.IsServerReady():
if self.IsServerReadyWithCache():
self._filetypes_with_keywords_loaded.add( filetype )
extra_data[ 'syntax_keywords' ] = list(
syntax_parse.SyntaxKeywordsForCurrentBuffer() )