From 22be4e777d64cd6af1fc3ae3cc6d79501135bfb7 Mon Sep 17 00:00:00 2001 From: Davit Samvelyan Date: Mon, 5 Jun 2017 22:14:51 +0400 Subject: [PATCH] Refactored diag interface, removed dummy sign. --- python/ycm/buffer.py | 2 +- python/ycm/diagnostic_interface.py | 233 +++++++++---------------- python/ycm/tests/test_utils.py | 13 +- python/ycm/tests/youcompleteme_test.py | 27 ++- python/ycm/vimsupport.py | 25 +-- 5 files changed, 124 insertions(+), 176 deletions(-) diff --git a/python/ycm/buffer.py b/python/ycm/buffer.py index 281057fb..111a140a 100644 --- a/python/ycm/buffer.py +++ b/python/ycm/buffer.py @@ -38,7 +38,7 @@ class Buffer( object ): self._parse_tick = 0 self._handled_tick = 0 self._parse_request = None - self._diag_interface = DiagnosticInterface( user_options ) + self._diag_interface = DiagnosticInterface( bufnr, user_options ) def FileParseRequestReady( self, block = False ): diff --git a/python/ycm/diagnostic_interface.py b/python/ycm/diagnostic_interface.py index 9fc9541c..4593428c 100644 --- a/python/ycm/diagnostic_interface.py +++ b/python/ycm/diagnostic_interface.py @@ -30,17 +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 ): @@ -54,11 +54,11 @@ 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 ): @@ -71,38 +71,26 @@ class DiagnosticInterface( object ): def UpdateWithNewDiagnostics( self, diags ): self._diagnostics = [ _NormalizeDiagnostic( x ) for x in self._ApplyDiagnosticFilter( diags ) ] - self._buffer_number_to_line_to_diags = _ConvertDiagListToDict( - self._diagnostics ) + 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._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 @@ -119,15 +107,11 @@ 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 _UpdateLocationList( self ): @@ -135,133 +119,92 @@ class DiagnosticInterface( object ): 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 ] + def _UpdateSquiggles( self ): + if self._bufnr != vim.current.buffer.number: + return - 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 ) + vimsupport.ClearYcmSyntaxMatches() - 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 diags in itervalues( self._line_to_diags ): + for diag in diags: + location_extent = diag[ 'location_extent' ] + is_error = _DiagnosticIsError( diag ) - 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 ) + 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 + def _UpdateSigns( self ): + new_signs, obsolete_signs = self._GetNewAndObsoleteSigns() - if dummy_sign_needed: - vimsupport.PlaceDummySign( next_sign_id + 1, - vim.current.buffer.number, - new_signs[ 0 ].line ) + self._PlaceNewSigns( new_signs ) - # 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 + self._UnplaceObsoleteSigns( obsolete_signs ) -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: + def _GetNewAndObsoleteSigns( self ): + new_signs = [] + obsolete_signs = list( 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 ) - 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 + self._next_sign_id += 1 + return new_signs, obsolete_signs -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 _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( kept_signs, placed_signs ): - for sign in placed_signs: - if sign not in kept_signs: - vimsupport.UnplaceSignInBuffer( sign.buffer, sign.id ) + def _UnplaceObsoleteSigns( self, obsolete_signs ): + for sign in obsolete_signs: + self._placed_signs.remove( sign ) + vimsupport.UnplaceSignInBuffer( self._bufnr, 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 ) + 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 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 oven an existing one. diags.sort( key = lambda diag: ( diag[ 'kind' ], diag[ 'location' ][ 'column_num' ] ) ) - return buffer_to_line_to_diags _DiagnosticIsError = CompileLevel( 'error' ) @@ -278,12 +221,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 ) diff --git a/python/ycm/tests/test_utils.py b/python/ycm/tests/test_utils.py index 41adffcb..f6f8cf97 100644 --- a/python/ycm/tests/test_utils.py +++ b/python/ycm/tests/test_utils.py @@ -43,7 +43,7 @@ GETBUFVAR_REGEX = re.compile( '^getbufvar\((?P[0-9]+), "(?P