diff --git a/python/ycm/client/command_request.py b/python/ycm/client/command_request.py index 6e766e99..6311d308 100644 --- a/python/ycm/client/command_request.py +++ b/python/ycm/client/command_request.py @@ -22,6 +22,7 @@ from ycm.client.base_request import BaseRequest, BuildRequestData, ServerError from ycm import vimsupport from ycmd.utils import ToUtf8IfNeeded + def _EnsureBackwardsCompatibility( arguments ): if arguments and arguments[ 0 ] == 'GoToDefinitionElseDeclaration': arguments[ 0 ] = 'GoTo' @@ -49,7 +50,7 @@ class CommandRequest( BaseRequest ): } ) try: self._response = self.PostDataToHandler( request_data, - 'run_completer_command' ) + 'run_completer_command' ) except ServerError as e: vimsupport.PostMultiLineNotice( e ) @@ -57,6 +58,7 @@ class CommandRequest( BaseRequest ): def Response( self ): return self._response + def RunPostCommandActionsIfNeeded( self ): if not self.Done() or not self._response: return @@ -68,6 +70,7 @@ class CommandRequest( BaseRequest ): elif 'message' in self._response: self._HandleMessageResponse() + def _HandleGotoResponse( self ): if isinstance( self._response, list ): defs = [ _BuildQfListItem( x ) for x in self._response ] @@ -75,56 +78,27 @@ class CommandRequest( BaseRequest ): vim.eval( 'youcompleteme#OpenGoToList()' ) else: vimsupport.JumpToLocation( self._response[ 'filepath' ], - self._response[ 'line_num' ], - self._response[ 'column_num' ] ) + self._response[ 'line_num' ], + self._response[ 'column_num' ] ) + def _HandleFixitResponse( self ): if not len( self._response[ 'fixits' ] ): vimsupport.EchoText( "No fixits found for current line" ) else: - fixit = self._response[ 'fixits' ][ 0 ] + chunks = self._response[ 'fixits' ][ 0 ][ 'chunks' ] - # We need to track the difference in length, but ensuring we apply fixes - # in ascending order of insertion point. - fixit[ 'chunks' ].sort( key = lambda chunk: ( - str(chunk[ 'range' ][ 'start' ][ 'line_num' ]) - + ',' - + str(chunk[ 'range' ][ 'start' ][ 'column_num' ]) - )) + vimsupport.ReplaceChunksList( chunks ) - # Remember the line number we're processing. Negative line number means we - # haven't processed any lines yet (by nature of being not equal to any - # real line number). - last_line = -1 + vimsupport.EchoTextVimWidth( "FixIt applied " + + str( len( chunks ) ) + + " changes" ) - # Counter of changes applied, so the user has a mental picture of the - # undo history this change is creating. - num_fixed = 0 - line_delta = 0 - for chunk in fixit[ 'chunks' ]: - if chunk[ 'range' ][ 'start' ][ 'line_num' ] != last_line: - # If this chunk is on a different line than the previous chunk, - # then ignore previous deltas (as offsets won't have changed). - last_line = chunk[ 'range' ][ 'end' ][ 'line_num' ] - char_delta = 0 - - (new_line_delta, new_char_delta) = vimsupport.ReplaceChunk( - chunk[ 'range' ][ 'start' ], - chunk[ 'range' ][ 'end' ], - chunk[ 'replacement_text' ], - line_delta, char_delta ) - line_delta += new_line_delta - char_delta += new_char_delta - - num_fixed = num_fixed + 1 - - vimsupport.EchoTextVimWidth("FixIt applied " - + str(num_fixed) - + " changes") def _HandleMessageResponse( self ): vimsupport.EchoText( self._response[ 'message' ] ) + def SendCommandRequest( arguments, completer ): request = CommandRequest( arguments, completer ) # This is a blocking call. diff --git a/python/ycm/tests/vimsupport_test.py b/python/ycm/tests/vimsupport_test.py index c0c4d5eb..ff4e8e1c 100644 --- a/python/ycm/tests/vimsupport_test.py +++ b/python/ycm/tests/vimsupport_test.py @@ -20,6 +20,7 @@ from ycm import vimsupport from nose.tools import eq_ + def ReplaceChunk_SingleLine_Repl_1_test(): # Replace with longer range # 12345678901234567 @@ -38,7 +39,7 @@ def ReplaceChunk_SingleLine_Repl_1_test(): # and replace again, using delta start, end = _BuildLocations( 1, 10, 1, 11 ) - ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( + ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( start, end, ' piece of ', @@ -58,7 +59,7 @@ def ReplaceChunk_SingleLine_Repl_1_test(): # and once more, for luck start, end = _BuildLocations( 1, 11, 1, 17 ) - ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( + ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( start, end, 'pie', @@ -75,6 +76,7 @@ def ReplaceChunk_SingleLine_Repl_1_test(): eq_( line_offset, 0 ) eq_( char_offset, 10 ) + def ReplaceChunk_SingleLine_Repl_2_test(): # Replace with shorter range # 12345678901234567 @@ -91,6 +93,7 @@ def ReplaceChunk_SingleLine_Repl_2_test(): eq_( line_offset, 0 ) eq_( char_offset, -2 ) + def ReplaceChunk_SingleLine_Repl_3_test(): # Replace with equal range # 12345678901234567 @@ -107,6 +110,7 @@ def ReplaceChunk_SingleLine_Repl_3_test(): eq_( line_offset, 0 ) eq_( char_offset, 0 ) + def ReplaceChunk_SingleLine_Add_1_test(): # Insert at start result_buffer = [ "is a string" ] @@ -122,6 +126,7 @@ def ReplaceChunk_SingleLine_Add_1_test(): eq_( line_offset, 0 ) eq_( char_offset, 5 ) + def ReplaceChunk_SingleLine_Add_2_test(): # Insert at end result_buffer = [ "This is a " ] @@ -137,6 +142,7 @@ def ReplaceChunk_SingleLine_Add_2_test(): eq_( line_offset, 0 ) eq_( char_offset, 6 ) + def ReplaceChunk_SingleLine_Add_3_test(): # Insert in the middle result_buffer = [ "This is a string" ] @@ -152,6 +158,7 @@ def ReplaceChunk_SingleLine_Add_3_test(): eq_( line_offset, 0 ) eq_( char_offset, 4 ) + def ReplaceChunk_SingleLine_Del_1_test(): # Delete from start result_buffer = [ "This is a string" ] @@ -167,6 +174,7 @@ def ReplaceChunk_SingleLine_Del_1_test(): eq_( line_offset, 0 ) eq_( char_offset, -5 ) + def ReplaceChunk_SingleLine_Del_2_test(): # Delete from end result_buffer = [ "This is a string" ] @@ -182,6 +190,7 @@ def ReplaceChunk_SingleLine_Del_2_test(): eq_( line_offset, 0 ) eq_( char_offset, -8 ) + def ReplaceChunk_SingleLine_Del_3_test(): # Delete from middle result_buffer = [ "This is not a string" ] @@ -197,6 +206,7 @@ def ReplaceChunk_SingleLine_Del_3_test(): eq_( line_offset, 0 ) eq_( char_offset, -4 ) + def ReplaceChunk_RemoveSingleLine_test(): result_buffer = [ "aAa", "aBa", "aCa" ] start, end = _BuildLocations( 2, 1, 3, 1 ) @@ -209,13 +219,13 @@ def ReplaceChunk_RemoveSingleLine_test(): def ReplaceChunk_SingleToMultipleLines_test(): - result_buffer = [ "aAa", - "aBa", + result_buffer = [ "aAa", + "aBa", "aCa" ] start, end = _BuildLocations( 2, 2, 2, 2 ) ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'Eb\nbF', 0, 0, result_buffer ) - expected_buffer = [ "aAa", + expected_buffer = [ "aAa", "aEb", "bFBa", "aCa" ] @@ -245,12 +255,12 @@ def ReplaceChunk_SingleToMultipleLines2_test(): result_buffer = [ "aAa", "aBa", "aCa" ] start, end = _BuildLocations( 2, 2, 2, 2 ) ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, + end, 'Eb\nbFb\nG', - 0, - 0, + 0, + 0, result_buffer ) - expected_buffer = [ "aAa", "aEb" ,"bFb", "GBa", "aCa" ] + expected_buffer = [ "aAa", "aEb", "bFb", "GBa", "aCa" ] eq_( expected_buffer, result_buffer ) eq_( line_offset, 2 ) eq_( char_offset, 0 ) @@ -259,45 +269,47 @@ def ReplaceChunk_SingleToMultipleLines2_test(): def ReplaceChunk_SingleToMultipleLines3_test(): result_buffer = [ "aAa", "aBa", "aCa" ] start, end = _BuildLocations( 2, 2, 2, 2 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, + ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, + end, 'Eb\nbFb\nbGb', - 0, - 0, + 0, + 0, result_buffer ) - expected_buffer = [ "aAa", "aEb" ,"bFb", "bGbBa", "aCa" ] + expected_buffer = [ "aAa", "aEb", "bFb", "bGbBa", "aCa" ] eq_( expected_buffer, result_buffer ) eq_( line_offset, 2 ) eq_( char_offset, 2 ) + def ReplaceChunk_SingleToMultipleLinesReplace_test(): result_buffer = [ "aAa", "aBa", "aCa" ] start, end = _BuildLocations( 1, 2, 1, 4 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, + ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, + end, 'Eb\nbFb\nbGb', - 0, - 0, + 0, + 0, result_buffer ) expected_buffer = [ "aEb", "bFb", "bGb", "aBa", "aCa" ] eq_( expected_buffer, result_buffer ) eq_( line_offset, 2 ) eq_( char_offset, 0 ) + def ReplaceChunk_SingleToMultipleLinesReplace_2_test(): - result_buffer = [ "aAa", - "aBa", + result_buffer = [ "aAa", + "aBa", "aCa" ] start, end = _BuildLocations( 1, 2, 1, 4 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, + ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, + end, 'Eb\nbFb\nbGb', - 0, - 0, + 0, + 0, result_buffer ) - expected_buffer = [ "aEb", - "bFb", - "bGb", + expected_buffer = [ "aEb", + "bFb", + "bGb", "aBa", "aCa" ] eq_( expected_buffer, result_buffer ) @@ -306,7 +318,7 @@ def ReplaceChunk_SingleToMultipleLinesReplace_2_test(): # now do a subsequent change (insert at end of line "1") start, end = _BuildLocations( 1, 4, 1, 4 ) - ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( + ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( start, end, 'cccc', @@ -340,7 +352,7 @@ def ReplaceChunk_MultipleLinesToSingleLine_test(): # make another modification applying offsets start, end = _BuildLocations( 3, 3, 3, 4 ) - ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( + ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( start, end, 'cccc', @@ -357,7 +369,7 @@ def ReplaceChunk_MultipleLinesToSingleLine_test(): # and another, for luck start, end = _BuildLocations( 3, 4, 3, 5 ) - ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( + ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( start, end, 'dd\ndd', @@ -367,7 +379,7 @@ def ReplaceChunk_MultipleLinesToSingleLine_test(): line_offset += new_line_offset char_offset += new_char_offset - + eq_( [ "aAa", "aECccccdd", "ddaa" ], result_buffer ) eq_( line_offset, 0 ) eq_( char_offset, -2 ) @@ -387,11 +399,11 @@ def ReplaceChunk_MultipleLinesToSameMultipleLines_test(): def ReplaceChunk_MultipleLinesToMoreMultipleLines_test(): result_buffer = [ "aAa", "aBa", "aCa", "aDe" ] start, end = _BuildLocations( 2, 2, 3, 2 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, + ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, + end, 'Eb\nbFb\nbG', - 0, - 0, + 0, + 0, result_buffer ) expected_buffer = [ "aAa", "aEb", "bFb", "bGCa", "aDe" ] eq_( expected_buffer, result_buffer ) @@ -501,11 +513,11 @@ def ReplaceChunk_MultipleLinesToSingleLineOffsetWorks_test(): def ReplaceChunk_MultipleLineOffsetWorks_test(): result_buffer = [ "aAa", "aBa", "aCa" ] start, end = _BuildLocations( 3, 1, 4, 3 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, + ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, + end, 'bDb\nbEb\nbFb', - -1, - 1, + -1, + 1, result_buffer ) expected_buffer = [ "aAa", "abDb", "bEb", "bFba" ] eq_( expected_buffer, result_buffer ) @@ -514,10 +526,53 @@ def ReplaceChunk_MultipleLineOffsetWorks_test(): def _BuildLocations( start_line, start_column, end_line, end_column ): - return { - 'line_num' : start_line, + return { + 'line_num' : start_line, 'column_num': start_column, }, { - 'line_num' : end_line, + 'line_num' : end_line, 'column_num': end_column, } + + +def ReplaceChunksList_SortedChunks_test(): + chunks = [ + _BuildChunk( 1, 4, 1, 4, '('), + _BuildChunk( 1, 11, 1, 11, ')' ) + ] + + result_buffer = [ "CT<10 >> 2> ct" ] + vimsupport.ReplaceChunksList( chunks, result_buffer ) + + expected_buffer = [ "CT<(10 >> 2)> ct" ] + eq_( expected_buffer, result_buffer ) + + +def ReplaceChunksList_UnsortedChunks_test(): + chunks = [ + _BuildChunk( 1, 11, 1, 11, ')'), + _BuildChunk( 1, 4, 1, 4, '(' ) + ] + + result_buffer = [ "CT<10 >> 2> ct" ] + vimsupport.ReplaceChunksList( chunks, result_buffer ) + + expected_buffer = [ "CT<(10 >> 2)> ct" ] + eq_( expected_buffer, result_buffer ) + + +def _BuildChunk( start_line, start_column, end_line, end_column, + replacement_text ): + return { + 'range': { + 'start': { + 'line_num': start_line, + 'column_num': start_column, + }, + 'end': { + 'line_num': end_line, + 'column_num': end_column, + }, + }, + 'replacement_text': replacement_text + } diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index 62200003..d53eae21 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -459,6 +459,40 @@ def GetIntValue( variable ): return int( vim.eval( variable ) ) +def ReplaceChunksList( chunks, vim_buffer = None ): + if vim_buffer is None: + vim_buffer = vim.current.buffer + + # We need to track the difference in length, but ensuring we apply fixes + # in ascending order of insertion point. + chunks.sort( key = lambda chunk: ( + chunk[ 'range' ][ 'start' ][ 'line_num' ], + chunk[ 'range' ][ 'start' ][ 'column_num' ] + ) ) + + # Remember the line number we're processing. Negative line number means we + # haven't processed any lines yet (by nature of being not equal to any + # real line number). + last_line = -1 + + line_delta = 0 + for chunk in chunks: + if chunk[ 'range' ][ 'start' ][ 'line_num' ] != last_line: + # If this chunk is on a different line than the previous chunk, + # then ignore previous deltas (as offsets won't have changed). + last_line = chunk[ 'range' ][ 'end' ][ 'line_num' ] + char_delta = 0 + + ( new_line_delta, new_char_delta ) = ReplaceChunk( + chunk[ 'range' ][ 'start' ], + chunk[ 'range' ][ 'end' ], + chunk[ 'replacement_text' ], + line_delta, char_delta, + vim_buffer ) + line_delta += new_line_delta + char_delta += new_char_delta + + # Replace the chunk of text specified by a contiguous range with the supplied # text. # * start and end are objects with line_num and column_num properties @@ -469,10 +503,7 @@ def GetIntValue( variable ): # returns the delta (in lines and characters) that any position after the end # needs to be adjusted by. def ReplaceChunk( start, end, replacement_text, line_delta, char_delta, - vim_buffer = None ): - if vim_buffer is None: - vim_buffer = vim.current.buffer - + vim_buffer ): # ycmd's results are all 1-based, but vim's/python's are all 0-based # (so we do -1 on all of the values) start_line = start[ 'line_num' ] - 1 + line_delta