diff --git a/python/ycm/buffer.py b/python/ycm/buffer.py
new file mode 100644
index 00000000..8d931a7d
--- /dev/null
+++ b/python/ycm/buffer.py
@@ -0,0 +1,100 @@
+# 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 .
+
+from __future__ import unicode_literals
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+# Not installing aliases from python-future; it's unreliable and slow.
+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( user_options )
+
+
+ def FileParseRequestReady( self, block = False ):
+ return self._parse_tick == 0 or block or self._parse_request.Done()
+
+
+ def SendParseRequest( self, extra_data ):
+ self._parse_request = EventNotification( 'FileReadyToParse',
+ extra_data = extra_data )
+ self._parse_request.Start()
+ self._parse_tick = self._ChangedTick()
+
+
+ def NeedsReparse( self ):
+ return self._parse_tick != self._ChangedTick()
+
+
+ def UpdateDiagnostics( self ):
+ diagnostics = self._parse_request.Response()
+ self._diag_interface.UpdateWithNewDiagnostics( diagnostics )
+
+
+ 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
diff --git a/python/ycm/diagnostic_interface.py b/python/ycm/diagnostic_interface.py
index 89051635..9fc9541c 100644
--- a/python/ycm/diagnostic_interface.py
+++ b/python/ycm/diagnostic_interface.py
@@ -32,6 +32,7 @@ import vim
class DiagnosticInterface( object ):
def __init__( self, user_options ):
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(
@@ -60,17 +61,18 @@ class DiagnosticInterface( object ):
return len( self._FilterDiagnostics( _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._diagnostics = [ _NormalizeDiagnostic( x ) for x in
+ self._ApplyDiagnosticFilter( diags ) ]
self._buffer_number_to_line_to_diags = _ConvertDiagListToDict(
- normalized_diags )
+ self._diagnostics )
if self._user_options[ 'enable_diagnostic_signs' ]:
self._placed_signs, self._next_sign_id = _UpdateSigns(
@@ -82,7 +84,7 @@ class DiagnosticInterface( object ):
_UpdateSquiggles( self._buffer_number_to_line_to_diags )
if self._user_options[ 'always_populate_location_list' ]:
- self.PopulateLocationList( normalized_diags )
+ self._UpdateLocationList()
def _ApplyDiagnosticFilter( self, diags, extra_predicate = None ):
@@ -128,6 +130,11 @@ class DiagnosticInterface( object ):
return matched_diags
+ def _UpdateLocationList( self ):
+ vimsupport.SetLocationList(
+ vimsupport.ConvertDiagnosticsToQfList( self._diagnostics ) )
+
+
def _UpdateSquiggles( buffer_number_to_line_to_diags ):
vimsupport.ClearYcmSyntaxMatches()
line_to_diags = buffer_number_to_line_to_diags[ vim.current.buffer.number ]
diff --git a/python/ycm/tests/event_notification_test.py b/python/ycm/tests/event_notification_test.py
index fa4e082a..463cd060 100644
--- a/python/ycm/tests/event_notification_test.py
+++ b/python/ycm/tests/event_notification_test.py
@@ -25,6 +25,7 @@ from __future__ import absolute_import
from builtins import * # noqa
from ycm.tests.test_utils import ( CurrentWorkingDirectory, ExtendedMock,
+ EmulateCurrentBufferChange,
MockVimBuffers, MockVimModule, VimBuffer )
MockVimModule()
@@ -105,7 +106,10 @@ def MockEventNotification( response_method, native_filetype_completer = True ):
'ycm.youcompleteme.YouCompleteMe.FiletypeCompleterExistsForFiletype',
return_value = native_filetype_completer ):
- yield
+ with patch( 'ycm.youcompleteme.YouCompleteMe.IsServerReady',
+ return_value = True ):
+
+ yield
@patch( 'ycm.vimsupport.PostVimMessage', new_callable = ExtendedMock )
@@ -138,6 +142,7 @@ def EventNotification_FileReadyToParse_NonDiagnostic_Error_test(
call( ERROR_TEXT, truncate = True )
] )
+ EmulateCurrentBufferChange()
# But it does if a subsequent event raises again
ycm.OnFileReadyToParse()
ok_( ycm.FileParseRequestReady() )
@@ -207,6 +212,7 @@ def EventNotification_FileReadyToParse_NonDiagnostic_ConfirmExtraConf_test(
call( FILE_NAME ),
] )
+ EmulateCurrentBufferChange()
# But it does if a subsequent event raises again
ycm.OnFileReadyToParse()
ok_( ycm.FileParseRequestReady() )
@@ -221,6 +227,7 @@ def EventNotification_FileReadyToParse_NonDiagnostic_ConfirmExtraConf_test(
call( FILE_NAME ),
] )
+ EmulateCurrentBufferChange()
# When the user rejects the extra conf, we reject it
with patch( 'ycm.vimsupport.PresentDialog',
return_value = 1,
@@ -246,6 +253,7 @@ def EventNotification_FileReadyToParse_NonDiagnostic_ConfirmExtraConf_test(
call( FILE_NAME ),
] )
+ EmulateCurrentBufferChange()
# But it does if a subsequent event raises again
ycm.OnFileReadyToParse()
ok_( ycm.FileParseRequestReady() )
@@ -263,9 +271,12 @@ def EventNotification_FileReadyToParse_NonDiagnostic_ConfirmExtraConf_test(
@YouCompleteMeInstance()
def EventNotification_FileReadyToParse_Diagnostic_Error_Native_test( ycm ):
- _Check_FileReadyToParse_Diagnostic_Error( ycm )
- _Check_FileReadyToParse_Diagnostic_Warning( ycm )
- _Check_FileReadyToParse_Diagnostic_Clean( ycm )
+ with MockArbitraryBuffer( 'cpp' ):
+ _Check_FileReadyToParse_Diagnostic_Error( ycm )
+ EmulateCurrentBufferChange()
+ _Check_FileReadyToParse_Diagnostic_Warning( ycm )
+ EmulateCurrentBufferChange()
+ _Check_FileReadyToParse_Diagnostic_Clean( ycm )
@patch( 'vim.command' )
@@ -279,25 +290,24 @@ def _Check_FileReadyToParse_Diagnostic_Error( ycm, vim_command ):
diagnostic = Diagnostic( [], start, extent, 'expected ;', 'ERROR' )
return [ BuildDiagnosticData( diagnostic ) ]
- with MockArbitraryBuffer( 'cpp' ):
- with MockEventNotification( DiagnosticResponse ):
- ycm.OnFileReadyToParse()
- ok_( ycm.FileParseRequestReady() )
- ycm.HandleFileParseRequest()
- vim_command.assert_has_calls( [
- PlaceSign_Call( 1, 1, 1, True )
- ] )
- eq_( ycm.GetErrorCount(), 1 )
- eq_( ycm.GetWarningCount(), 0 )
+ with MockEventNotification( DiagnosticResponse ):
+ ycm.OnFileReadyToParse()
+ ok_( ycm.FileParseRequestReady() )
+ ycm.HandleFileParseRequest()
+ vim_command.assert_has_calls( [
+ PlaceSign_Call( 1, 1, 1, True )
+ ] )
+ eq_( ycm.GetErrorCount(), 1 )
+ eq_( ycm.GetWarningCount(), 0 )
- # 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 )
- eq_( ycm.GetWarningCount(), 0 )
+ # Consequent calls to HandleFileParseRequest shouldn't mess with
+ # existing diagnostics, when there is no new parse request.
+ vim_command.reset_mock()
+ ok_( ycm.FileParseRequestReady() )
+ ycm.HandleFileParseRequest()
+ vim_command.assert_not_called()
+ eq_( ycm.GetErrorCount(), 1 )
+ eq_( ycm.GetWarningCount(), 0 )
@patch( 'vim.command' )
@@ -312,26 +322,25 @@ def _Check_FileReadyToParse_Diagnostic_Warning( ycm, vim_command ):
diagnostic = Diagnostic( [], start, extent, 'cast', 'WARNING' )
return [ BuildDiagnosticData( diagnostic ) ]
- with MockArbitraryBuffer( 'cpp' ):
- with MockEventNotification( DiagnosticResponse ):
- ycm.OnFileReadyToParse()
- ok_( ycm.FileParseRequestReady() )
- ycm.HandleFileParseRequest()
- vim_command.assert_has_calls( [
- PlaceSign_Call( 2, 2, 1, False ),
- UnplaceSign_Call( 1, 1 )
- ] )
- eq_( ycm.GetErrorCount(), 0 )
- eq_( ycm.GetWarningCount(), 1 )
+ with MockEventNotification( DiagnosticResponse ):
+ ycm.OnFileReadyToParse()
+ ok_( ycm.FileParseRequestReady() )
+ ycm.HandleFileParseRequest()
+ vim_command.assert_has_calls( [
+ PlaceSign_Call( 2, 2, 1, False ),
+ UnplaceSign_Call( 1, 1 )
+ ] )
+ eq_( ycm.GetErrorCount(), 0 )
+ eq_( ycm.GetWarningCount(), 1 )
- # 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 )
- eq_( ycm.GetWarningCount(), 1 )
+ # Consequent calls to HandleFileParseRequest shouldn't mess with
+ # existing diagnostics, when there is no new parse request.
+ vim_command.reset_mock()
+ ok_( ycm.FileParseRequestReady() )
+ ycm.HandleFileParseRequest()
+ vim_command.assert_not_called()
+ eq_( ycm.GetErrorCount(), 0 )
+ eq_( ycm.GetWarningCount(), 1 )
@patch( 'vim.command' )
@@ -339,15 +348,14 @@ def _Check_FileReadyToParse_Diagnostic_Clean( ycm, vim_command ):
# Tests Vim sign unplacement and error/warning count python API
# when there are no errors/warnings left.
# Should be called after _Check_FileReadyToParse_Diagnostic_Warning
- with MockArbitraryBuffer( 'cpp' ):
- with MockEventNotification( MagicMock( return_value = [] ) ):
- ycm.OnFileReadyToParse()
- ycm.HandleFileParseRequest()
- vim_command.assert_has_calls( [
- UnplaceSign_Call( 2, 1 )
- ] )
- eq_( ycm.GetErrorCount(), 0 )
- eq_( ycm.GetWarningCount(), 0 )
+ with MockEventNotification( MagicMock( return_value = [] ) ):
+ ycm.OnFileReadyToParse()
+ ycm.HandleFileParseRequest()
+ vim_command.assert_has_calls( [
+ UnplaceSign_Call( 2, 1 )
+ ] )
+ eq_( ycm.GetErrorCount(), 0 )
+ eq_( ycm.GetWarningCount(), 0 )
@patch( 'ycm.youcompleteme.YouCompleteMe._AddUltiSnipsDataIfNeeded' )
@@ -364,7 +372,9 @@ def EventNotification_FileReadyToParse_TagFiles_UnicodeWorkingDirectory_test(
'PostDataToHandlerAsync' ) as post_data_to_handler_async:
with CurrentWorkingDirectory( unicode_dir ):
with MockVimBuffers( [ current_buffer ], current_buffer, ( 6, 5 ) ):
- ycm.OnFileReadyToParse()
+ with patch( 'ycm.youcompleteme.YouCompleteMe.IsServerReady',
+ return_value = True ):
+ ycm.OnFileReadyToParse()
assert_that(
# Positional arguments passed to PostDataToHandlerAsync.
diff --git a/python/ycm/tests/test_utils.py b/python/ycm/tests/test_utils.py
index 36ce2478..30740188 100644
--- a/python/ycm/tests/test_utils.py
+++ b/python/ycm/tests/test_utils.py
@@ -40,7 +40,7 @@ BUFWINNR_REGEX = re.compile( '^bufwinnr\((?P[0-9]+)\)$' )
BWIPEOUT_REGEX = re.compile(
'^(?:silent! )bwipeout!? (?P[0-9]+)$' )
GETBUFVAR_REGEX = re.compile(
- '^getbufvar\((?P[0-9]+), "&(?P