From 979f14acfd061091dd92455a195feb16efba99fc Mon Sep 17 00:00:00 2001 From: Davit Samvelyan Date: Mon, 1 May 2017 01:17:20 +0400 Subject: [PATCH] Moved change tracking to python. Per buffer diags --- python/ycm/buffer.py | 100 +++++++++++++++++ python/ycm/diagnostic_interface.py | 23 ++-- python/ycm/tests/event_notification_test.py | 112 +++++++++++--------- python/ycm/tests/test_utils.py | 13 ++- python/ycm/tests/youcompleteme_test.py | 6 +- python/ycm/vimsupport.py | 8 ++ python/ycm/youcompleteme.py | 94 +++++++--------- 7 files changed, 236 insertions(+), 120 deletions(-) create mode 100644 python/ycm/buffer.py 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