From c692b48bc61e64c43d2cf715ee4535430c878bf7 Mon Sep 17 00:00:00 2001 From: micbou Date: Sat, 10 Feb 2018 14:10:44 +0100 Subject: [PATCH] Apply chunks from bottom to top --- python/ycm/tests/vimsupport_test.py | 734 ++++++++++------------------ python/ycm/vimsupport.py | 132 +++-- 2 files changed, 328 insertions(+), 538 deletions(-) diff --git a/python/ycm/tests/vimsupport_test.py b/python/ycm/tests/vimsupport_test.py index 2e9a1a4d..c61fcecc 100644 --- a/python/ycm/tests/vimsupport_test.py +++ b/python/ycm/tests/vimsupport_test.py @@ -88,628 +88,364 @@ def AssertBuffersAreEqualAsBytes( result_buffer, expected_buffer ): def ReplaceChunk_SingleLine_Repl_1_test(): # Replace with longer range - result_buffer = [ 'This is a string' ] - start, end = _BuildLocations( 1, 1, 1, 5 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - 'How long', - 0, - 0, - result_buffer ) + result_buffer = VimBuffer( 'buffer', contents = [ 'This is a string' ] ) + start, end = _BuildLocations( 1, 11, 1, 17 ) + vimsupport.ReplaceChunk( start, end, 'pie', result_buffer ) - AssertBuffersAreEqualAsBytes( [ 'How long is a string' ], result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, 4 ) + AssertBuffersAreEqualAsBytes( [ 'This is a pie' ], result_buffer ) - # and replace again, using delta + # and replace again start, end = _BuildLocations( 1, 10, 1, 11 ) - ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( - start, - end, - ' piece of ', - line_offset, - char_offset, - result_buffer ) + vimsupport.ReplaceChunk( start, end, ' piece of ', result_buffer ) - line_offset += new_line_offset - char_offset += new_char_offset - - AssertBuffersAreEqualAsBytes( [ 'How long is a piece of string' ], - result_buffer ) - eq_( new_line_offset, 0 ) - eq_( new_char_offset, 9 ) - eq_( line_offset, 0 ) - eq_( char_offset, 13 ) + AssertBuffersAreEqualAsBytes( [ 'This is a piece of pie' ], result_buffer ) # and once more, for luck - start, end = _BuildLocations( 1, 11, 1, 17 ) - - ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( - start, - end, - 'pie', - line_offset, - char_offset, - result_buffer ) - - line_offset += new_line_offset - char_offset += new_char_offset + start, end = _BuildLocations( 1, 1, 1, 5 ) + vimsupport.ReplaceChunk( start, end, 'How long', result_buffer ) AssertBuffersAreEqualAsBytes( [ 'How long is a piece of pie' ], - result_buffer ) - eq_( new_line_offset, 0 ) - eq_( new_char_offset, -3 ) - eq_( line_offset, 0 ) - eq_( char_offset, 10 ) + result_buffer ) def ReplaceChunk_SingleLine_Repl_2_test(): # Replace with shorter range - result_buffer = [ 'This is a string' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'This is a string' ] ) start, end = _BuildLocations( 1, 11, 1, 17 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - 'test', - 0, - 0, - result_buffer ) + vimsupport.ReplaceChunk( start, end, 'test', result_buffer ) AssertBuffersAreEqualAsBytes( [ 'This is a test' ], result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, -2 ) def ReplaceChunk_SingleLine_Repl_3_test(): # Replace with equal range - result_buffer = [ 'This is a string' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'This is a string' ] ) start, end = _BuildLocations( 1, 6, 1, 8 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - 'be', - 0, - 0, - result_buffer ) + vimsupport.ReplaceChunk( start, end, 'be', result_buffer ) AssertBuffersAreEqualAsBytes( [ 'This be a string' ], result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, 0 ) def ReplaceChunk_SingleLine_Add_1_test(): # Insert at start - result_buffer = [ 'is a string' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'is a string' ] ) start, end = _BuildLocations( 1, 1, 1, 1 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - 'This ', - 0, - 0, - result_buffer ) + vimsupport.ReplaceChunk( start, end, 'This ', result_buffer ) AssertBuffersAreEqualAsBytes( [ 'This is a string' ], result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, 5 ) def ReplaceChunk_SingleLine_Add_2_test(): # Insert at end - result_buffer = [ 'This is a ' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'This is a ' ] ) start, end = _BuildLocations( 1, 11, 1, 11 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - 'string', - 0, - 0, - result_buffer ) + vimsupport.ReplaceChunk( start, end, 'string', result_buffer ) AssertBuffersAreEqualAsBytes( [ 'This is a string' ], result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, 6 ) def ReplaceChunk_SingleLine_Add_3_test(): # Insert in the middle - result_buffer = [ 'This is a string' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'This is a string' ] ) start, end = _BuildLocations( 1, 8, 1, 8 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - ' not', - 0, - 0, - result_buffer ) + vimsupport.ReplaceChunk( start, end, ' not', result_buffer ) AssertBuffersAreEqualAsBytes( [ 'This is not a string' ], result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, 4 ) def ReplaceChunk_SingleLine_Del_1_test(): # Delete from start - result_buffer = [ 'This is a string' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'This is a string' ] ) start, end = _BuildLocations( 1, 1, 1, 6 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - '', - 0, - 0, - result_buffer ) + vimsupport.ReplaceChunk( start, end, '', result_buffer ) AssertBuffersAreEqualAsBytes( [ 'is a string' ], result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, -5 ) def ReplaceChunk_SingleLine_Del_2_test(): # Delete from end - result_buffer = [ 'This is a string' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'This is a string' ] ) start, end = _BuildLocations( 1, 10, 1, 18 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - '', - 0, - 0, - result_buffer ) + vimsupport.ReplaceChunk( start, end, '', result_buffer ) AssertBuffersAreEqualAsBytes( [ 'This is a' ], result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, -8 ) def ReplaceChunk_SingleLine_Del_3_test(): # Delete from middle - result_buffer = [ 'This is not a string' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'This is not a string' ] ) start, end = _BuildLocations( 1, 9, 1, 13 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - '', - 0, - 0, - result_buffer ) + vimsupport.ReplaceChunk( start, end, '', result_buffer ) AssertBuffersAreEqualAsBytes( [ 'This is a string' ], result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, -4 ) def ReplaceChunk_SingleLine_Unicode_ReplaceUnicodeChars_test(): # Replace Unicode characters. - result_buffer = [ 'This Uniçø∂‰ string is in the middle' ] + result_buffer = VimBuffer( + 'buffer', contents = [ 'This Uniçø∂‰ string is in the middle' ] ) start, end = _BuildLocations( 1, 6, 1, 20 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - 'Unicode ', - 0, - 0, - result_buffer ) + vimsupport.ReplaceChunk( start, end, 'Unicode ', result_buffer ) AssertBuffersAreEqualAsBytes( [ 'This Unicode string is in the middle' ], result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, -6 ) def ReplaceChunk_SingleLine_Unicode_ReplaceAfterUnicode_test(): # Replace ASCII characters after Unicode characters in the line. - result_buffer = [ 'This Uniçø∂‰ string is in the middle' ] + result_buffer = VimBuffer( + 'buffer', contents = [ 'This Uniçø∂‰ string is in the middle' ] ) start, end = _BuildLocations( 1, 30, 1, 43 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - 'fåke', - 0, - 0, - result_buffer ) + vimsupport.ReplaceChunk( start, end, 'fåke', result_buffer ) AssertBuffersAreEqualAsBytes( [ 'This Uniçø∂‰ string is fåke' ], result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, -8 ) def ReplaceChunk_SingleLine_Unicode_Grown_test(): # Replace ASCII characters after Unicode characters in the line. - result_buffer = [ 'a' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'a' ] ) start, end = _BuildLocations( 1, 1, 1, 2 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - 'å', - 0, - 0, - result_buffer ) + vimsupport.ReplaceChunk( start, end, 'å', result_buffer ) AssertBuffersAreEqualAsBytes( [ 'å' ], result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, 1 ) # Note: byte difference (a = 1 byte, å = 2 bytes) def ReplaceChunk_RemoveSingleLine_test(): - result_buffer = [ 'aAa', - 'aBa', - 'aCa' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'aAa', + 'aBa', + 'aCa' ] ) start, end = _BuildLocations( 2, 1, 3, 1 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, '', - 0, 0, result_buffer ) + vimsupport.ReplaceChunk( start, end, '', result_buffer ) # First line is not affected. - expected_buffer = [ 'aAa', - 'aCa' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, -1 ) - eq_( char_offset, 0 ) + AssertBuffersAreEqualAsBytes( [ 'aAa', + 'aCa' ], result_buffer ) def ReplaceChunk_SingleToMultipleLines_test(): - 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', - 'aEb', - 'bFBa', - 'aCa' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, 1 ) - eq_( char_offset, 1 ) - - # now make another change to the "2nd" line + result_buffer = VimBuffer( 'buffer', contents = [ 'aAa', + 'aBa', + 'aCa' ] ) start, end = _BuildLocations( 2, 3, 2, 4 ) - ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( - start, - end, - 'cccc', - line_offset, - char_offset, - result_buffer ) - - line_offset += new_line_offset - char_offset += new_char_offset + vimsupport.ReplaceChunk( start, end, 'cccc', result_buffer ) AssertBuffersAreEqualAsBytes( [ 'aAa', - 'aEb', - 'bFBcccc', - 'aCa' ], result_buffer ) - eq_( line_offset, 1 ) - eq_( char_offset, 4 ) + 'aBcccc', + 'aCa' ], result_buffer ) + + # now make another change to the second line + start, end = _BuildLocations( 2, 2, 2, 2 ) + vimsupport.ReplaceChunk( start, end, 'Eb\nbF', result_buffer ) + + AssertBuffersAreEqualAsBytes( [ 'aAa', + 'aEb', + 'bFBcccc', + 'aCa' ], result_buffer ) def ReplaceChunk_SingleToMultipleLines2_test(): - result_buffer = [ 'aAa', 'aBa', 'aCa' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'aAa', + 'aBa', + 'aCa' ] ) start, end = _BuildLocations( 2, 2, 2, 2 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - 'Eb\nbFb\nG', - 0, - 0, - result_buffer ) - expected_buffer = [ 'aAa', - 'aEb', - 'bFb', - 'GBa', - 'aCa' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, 2 ) - eq_( char_offset, 0 ) + vimsupport.ReplaceChunk( start, end, 'Eb\nbFb\nG', result_buffer ) + + AssertBuffersAreEqualAsBytes( [ 'aAa', + 'aEb', + 'bFb', + 'GBa', + 'aCa' ], result_buffer ) def ReplaceChunk_SingleToMultipleLines3_test(): - result_buffer = [ 'aAa', 'aBa', 'aCa' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'aAa', + 'aBa', + 'aCa' ] ) start, end = _BuildLocations( 2, 2, 2, 2 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - 'Eb\nbFb\nbGb', - 0, - 0, - result_buffer ) - expected_buffer = [ 'aAa', - 'aEb', - 'bFb', - 'bGbBa', - 'aCa' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, 2 ) - eq_( char_offset, 2 ) + vimsupport.ReplaceChunk( start, end, 'Eb\nbFb\nbGb', result_buffer ) + + AssertBuffersAreEqualAsBytes( [ 'aAa', + 'aEb', + 'bFb', + 'bGbBa', + 'aCa' ], result_buffer ) def ReplaceChunk_SingleToMultipleLinesReplace_test(): - result_buffer = [ 'aAa', 'aBa', 'aCa' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'aAa', + 'aBa', + 'aCa' ] ) start, end = _BuildLocations( 1, 2, 1, 4 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - 'Eb\nbFb\nbGb', - 0, - 0, - result_buffer ) - expected_buffer = [ 'aEb', - 'bFb', - 'bGb', - 'aBa', - 'aCa' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, 2 ) - eq_( char_offset, 0 ) + vimsupport.ReplaceChunk( start, end, 'Eb\nbFb\nbGb', result_buffer ) + + AssertBuffersAreEqualAsBytes( [ 'aEb', + 'bFb', + 'bGb', + 'aBa', + 'aCa' ], result_buffer ) def ReplaceChunk_SingleToMultipleLinesReplace_2_test(): - result_buffer = [ 'aAa', - 'aBa', - 'aCa' ] - start, end = _BuildLocations( 1, 2, 1, 4 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - 'Eb\nbFb\nbGb', - 0, - 0, - result_buffer ) - expected_buffer = [ 'aEb', - 'bFb', - 'bGb', - 'aBa', - 'aCa' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, 2 ) - eq_( char_offset, 0 ) - - # now do a subsequent change (insert at end of line "1") + result_buffer = VimBuffer( 'buffer', contents = [ 'aAa', + 'aBa', + 'aCa' ] ) start, end = _BuildLocations( 1, 4, 1, 4 ) - ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( - start, - end, - 'cccc', - line_offset, - char_offset, - result_buffer ) + vimsupport.ReplaceChunk( start, end, 'cccc', result_buffer ) - line_offset += new_line_offset - char_offset += new_char_offset + AssertBuffersAreEqualAsBytes( [ 'aAacccc', + 'aBa', + 'aCa', ], result_buffer ) + + # now do a subsequent change (insert in the middle of the first line) + start, end = _BuildLocations( 1, 2, 1, 4 ) + vimsupport.ReplaceChunk( start, end, 'Eb\nbFb\nbGb', result_buffer ) AssertBuffersAreEqualAsBytes( [ 'aEb', - 'bFb', - 'bGbcccc', - 'aBa', - 'aCa' ], result_buffer ) - - eq_( line_offset, 2 ) - eq_( char_offset, 4 ) + 'bFb', + 'bGbcccc', + 'aBa', + 'aCa' ], result_buffer ) def ReplaceChunk_MultipleLinesToSingleLine_test(): - result_buffer = [ 'aAa', 'aBa', 'aCaaaa' ] - start, end = _BuildLocations( 2, 2, 3, 2 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'E', - 0, 0, result_buffer ) - expected_buffer = [ 'aAa', 'aECaaaa' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, -1 ) - eq_( char_offset, 1 ) + result_buffer = VimBuffer( 'buffer', contents = [ 'aAa', + 'aBa', + 'aCaaaa' ] ) + start, end = _BuildLocations( 3, 4, 3, 5 ) + vimsupport.ReplaceChunk( start, end, 'dd\ndd', result_buffer ) + + AssertBuffersAreEqualAsBytes( [ 'aAa', + 'aBa', + 'aCadd', + 'ddaa' ], result_buffer ) # make another modification applying offsets start, end = _BuildLocations( 3, 3, 3, 4 ) - ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( - start, - end, - 'cccc', - line_offset, - char_offset, - result_buffer ) - - line_offset += new_line_offset - char_offset += new_char_offset + vimsupport.ReplaceChunk( start, end, 'cccc', result_buffer ) AssertBuffersAreEqualAsBytes( [ 'aAa', - 'aECccccaaa' ], result_buffer ) - eq_( line_offset, -1 ) - eq_( char_offset, 4 ) + 'aBa', + 'aCccccdd', + 'ddaa' ], result_buffer ) # and another, for luck - start, end = _BuildLocations( 3, 4, 3, 5 ) - ( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( - start, - end, - 'dd\ndd', - line_offset, - char_offset, - result_buffer ) - - line_offset += new_line_offset - char_offset += new_char_offset + start, end = _BuildLocations( 2, 2, 3, 2 ) + vimsupport.ReplaceChunk( start, end, 'E', result_buffer ) AssertBuffersAreEqualAsBytes( [ 'aAa', - 'aECccccdd', - 'ddaa' ], result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, -2 ) + 'aECccccdd', + 'ddaa' ], result_buffer ) def ReplaceChunk_MultipleLinesToSameMultipleLines_test(): - result_buffer = [ 'aAa', - 'aBa', - 'aCa', - 'aDe' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'aAa', + 'aBa', + 'aCa', + 'aDe' ] ) start, end = _BuildLocations( 2, 2, 3, 2 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'Eb\nbF', - 0, 0, result_buffer ) - expected_buffer = [ 'aAa', - 'aEb', - 'bFCa', - 'aDe' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, 1 ) + vimsupport.ReplaceChunk( start, end, 'Eb\nbF', result_buffer ) + + AssertBuffersAreEqualAsBytes( [ 'aAa', + 'aEb', + 'bFCa', + 'aDe' ], result_buffer ) def ReplaceChunk_MultipleLinesToMoreMultipleLines_test(): - result_buffer = [ 'aAa', - 'aBa', - 'aCa', - 'aDe' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'aAa', + 'aBa', + 'aCa', + 'aDe' ] ) start, end = _BuildLocations( 2, 2, 3, 2 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - 'Eb\nbFb\nbG', - 0, - 0, - result_buffer ) - expected_buffer = [ 'aAa', - 'aEb', - 'bFb', - 'bGCa', - 'aDe' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, 1 ) - eq_( char_offset, 1 ) + vimsupport.ReplaceChunk( start, end, 'Eb\nbFb\nbG', result_buffer ) + + AssertBuffersAreEqualAsBytes( [ 'aAa', + 'aEb', + 'bFb', + 'bGCa', + 'aDe' ], result_buffer ) def ReplaceChunk_MultipleLinesToLessMultipleLines_test(): - result_buffer = [ 'aAa', - 'aBa', - 'aCa', - 'aDe' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'aAa', + 'aBa', + 'aCa', + 'aDe' ] ) start, end = _BuildLocations( 1, 2, 3, 2 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'Eb\nbF', - 0, 0, result_buffer ) - expected_buffer = [ 'aEb', 'bFCa', 'aDe' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, -1 ) - eq_( char_offset, 1 ) + vimsupport.ReplaceChunk( start, end, 'Eb\nbF', result_buffer ) + + AssertBuffersAreEqualAsBytes( [ 'aEb', + 'bFCa', + 'aDe' ], result_buffer ) def ReplaceChunk_MultipleLinesToEvenLessMultipleLines_test(): - result_buffer = [ 'aAa', - 'aBa', - 'aCa', - 'aDe' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'aAa', + 'aBa', + 'aCa', + 'aDe' ] ) start, end = _BuildLocations( 1, 2, 4, 2 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'Eb\nbF', - 0, 0, result_buffer ) - expected_buffer = [ 'aEb', 'bFDe' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, -2 ) - eq_( char_offset, 1 ) + vimsupport.ReplaceChunk( start, end, 'Eb\nbF', result_buffer ) + + AssertBuffersAreEqualAsBytes( [ 'aEb', + 'bFDe' ], result_buffer ) def ReplaceChunk_SpanBufferEdge_test(): - result_buffer = [ 'aAa', - 'aBa', - 'aCa' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'aAa', + 'aBa', + 'aCa' ] ) start, end = _BuildLocations( 1, 1, 1, 3 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'bDb', - 0, 0, result_buffer ) - expected_buffer = [ 'bDba', - 'aBa', - 'aCa' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, 1 ) + vimsupport.ReplaceChunk( start, end, 'bDb', result_buffer ) + + AssertBuffersAreEqualAsBytes( [ 'bDba', + 'aBa', + 'aCa' ], result_buffer ) def ReplaceChunk_DeleteTextInLine_test(): - result_buffer = [ 'aAa', - 'aBa', - 'aCa' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'aAa', + 'aBa', + 'aCa' ] ) start, end = _BuildLocations( 2, 2, 2, 3 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, '', - 0, 0, result_buffer ) - expected_buffer = [ 'aAa', - 'aa', - 'aCa' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, -1 ) + vimsupport.ReplaceChunk( start, end, '', result_buffer ) + AssertBuffersAreEqualAsBytes( [ 'aAa', + 'aa', + 'aCa' ], result_buffer ) def ReplaceChunk_AddTextInLine_test(): - result_buffer = [ 'aAa', - 'aBa', - 'aCa' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'aAa', + 'aBa', + 'aCa' ] ) start, end = _BuildLocations( 2, 2, 2, 2 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'bDb', - 0, 0, result_buffer ) - expected_buffer = [ 'aAa', - 'abDbBa', - 'aCa' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, 3 ) + vimsupport.ReplaceChunk( start, end, 'bDb', result_buffer ) + + AssertBuffersAreEqualAsBytes( [ 'aAa', + 'abDbBa', + 'aCa' ], result_buffer ) def ReplaceChunk_ReplaceTextInLine_test(): - result_buffer = [ 'aAa', - 'aBa', - 'aCa' ] + result_buffer = VimBuffer( 'buffer', contents = [ 'aAa', + 'aBa', + 'aCa' ] ) start, end = _BuildLocations( 2, 2, 2, 3 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'bDb', - 0, 0, result_buffer ) - expected_buffer = [ 'aAa', - 'abDba', - 'aCa' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, 2 ) + vimsupport.ReplaceChunk( start, end, 'bDb', result_buffer ) + + AssertBuffersAreEqualAsBytes( [ 'aAa', + 'abDba', + 'aCa' ], result_buffer ) -def ReplaceChunk_SingleLineOffsetWorks_test(): - result_buffer = [ 'aAa', - 'aBa', - 'aCa' ] - start, end = _BuildLocations( 1, 1, 1, 2 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'bDb', - 1, 1, result_buffer ) - expected_buffer = [ 'aAa', - 'abDba', - 'aCa' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, 0 ) - eq_( char_offset, 2 ) +def ReplaceChunk_NewlineChunk_test(): + result_buffer = VimBuffer( 'buffer', contents = [ 'first line', + 'second line' ] ) + start, end = _BuildLocations( 1, 11, 2, 1 ) + vimsupport.ReplaceChunk( start, end, '\n', result_buffer ) - -def ReplaceChunk_SingleLineToMultipleLinesOffsetWorks_test(): - result_buffer = [ 'aAa', - 'aBa', - 'aCa' ] - start, end = _BuildLocations( 1, 1, 1, 2 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'Db\nE', - 1, 1, result_buffer ) - expected_buffer = [ 'aAa', - 'aDb', - 'Ea', - 'aCa' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, 1 ) - eq_( char_offset, -1 ) - - -def ReplaceChunk_MultipleLinesToSingleLineOffsetWorks_test(): - result_buffer = [ 'aAa', - 'aBa', - 'aCa' ] - start, end = _BuildLocations( 1, 1, 2, 2 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'bDb', - 1, 1, result_buffer ) - expected_buffer = [ 'aAa', - 'abDbCa' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, -1 ) - eq_( char_offset, 3 ) - - -def ReplaceChunk_MultipleLineOffsetWorks_test(): - result_buffer = [ 'aAa', - 'aBa', - 'aCa' ] - start, end = _BuildLocations( 3, 1, 4, 3 ) - ( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, - end, - 'bDb\nbEb\nbFb', - -1, - 1, - result_buffer ) - expected_buffer = [ 'aAa', - 'abDb', - 'bEb', - 'bFba' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) - eq_( line_offset, 1 ) - eq_( char_offset, 1 ) + AssertBuffersAreEqualAsBytes( [ 'first line', + 'second line' ], result_buffer ) def _BuildLocations( start_line, start_column, end_line, end_column ): @@ -728,11 +464,10 @@ def ReplaceChunksInBuffer_SortedChunks_test(): _BuildChunk( 1, 11, 1, 11, ')' ) ] - result_buffer = [ 'CT<10 >> 2> ct' ] - vimsupport.ReplaceChunksInBuffer( chunks, result_buffer, None ) + result_buffer = VimBuffer( 'buffer', contents = [ 'CT<10 >> 2> ct' ] ) + vimsupport.ReplaceChunksInBuffer( chunks, result_buffer ) - expected_buffer = [ 'CT<(10 >> 2)> ct' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) + AssertBuffersAreEqualAsBytes( [ 'CT<(10 >> 2)> ct' ], result_buffer ) def ReplaceChunksInBuffer_UnsortedChunks_test(): @@ -741,11 +476,78 @@ def ReplaceChunksInBuffer_UnsortedChunks_test(): _BuildChunk( 1, 4, 1, 4, '(' ) ] - result_buffer = [ 'CT<10 >> 2> ct' ] - vimsupport.ReplaceChunksInBuffer( chunks, result_buffer, None ) + result_buffer = VimBuffer( 'buffer', contents = [ 'CT<10 >> 2> ct' ] ) + vimsupport.ReplaceChunksInBuffer( chunks, result_buffer ) - expected_buffer = [ 'CT<(10 >> 2)> ct' ] - AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) + AssertBuffersAreEqualAsBytes( [ 'CT<(10 >> 2)> ct' ], result_buffer ) + + +def ReplaceChunksInBuffer_LineOverlappingChunks_test(): + chunks = [ + _BuildChunk( 1, 11, 2, 1, '\n ' ), + _BuildChunk( 2, 12, 3, 1, '\n ' ), + _BuildChunk( 3, 11, 4, 1, '\n ' ) + ] + + result_buffer = VimBuffer( 'buffer', contents = [ 'first line', + 'second line', + 'third line', + 'fourth line' ] ) + vimsupport.ReplaceChunksInBuffer( chunks, result_buffer ) + + AssertBuffersAreEqualAsBytes( [ 'first line', + ' second line', + ' third line', + ' fourth line' ], result_buffer ) + + +def ReplaceChunksInBuffer_OutdentChunks_test(): + chunks = [ + _BuildChunk( 1, 1, 1, 5, ' ' ), + _BuildChunk( 1, 15, 2, 9, '\n ' ), + _BuildChunk( 2, 20, 3, 3, '\n' ) + ] + + result_buffer = VimBuffer( 'buffer', contents = [ ' first line', + ' second line', + ' third line' ] ) + vimsupport.ReplaceChunksInBuffer( chunks, result_buffer ) + + AssertBuffersAreEqualAsBytes( [ ' first line', + ' second line', + ' third line' ], result_buffer ) + + +def ReplaceChunksInBuffer_OneLineIndentingChunks_test(): + chunks = [ + _BuildChunk( 1, 8, 2, 1, '\n ' ), + _BuildChunk( 2, 9, 2, 10, '\n ' ), + _BuildChunk( 2, 19, 2, 20, '\n ' ) + ] + + result_buffer = VimBuffer( 'buffer', contents = [ 'class {', + 'method { statement }', + '}' ] ) + vimsupport.ReplaceChunksInBuffer( chunks, result_buffer ) + + AssertBuffersAreEqualAsBytes( [ 'class {', + ' method {', + ' statement', + ' }', + '}' ], result_buffer ) + + +def ReplaceChunksInBuffer_SameLocation_test(): + chunks = [ + _BuildChunk( 1, 1, 1, 1, 'this ' ), + _BuildChunk( 1, 1, 1, 1, 'is ' ), + _BuildChunk( 1, 1, 1, 1, 'pure ' ) + ] + + result_buffer = VimBuffer( 'buffer', contents = [ 'folly' ] ) + vimsupport.ReplaceChunksInBuffer( chunks, result_buffer ) + + AssertBuffersAreEqualAsBytes( [ 'this is pure folly' ], result_buffer ) @patch( 'ycm.vimsupport.VariableExists', return_value = False ) diff --git a/python/ycm/vimsupport.py b/python/ycm/vimsupport.py index 7070d55f..510fca6c 100644 --- a/python/ycm/vimsupport.py +++ b/python/ycm/vimsupport.py @@ -711,15 +711,14 @@ def ReplaceChunks( chunks ): If for some reason a file could not be opened or changed, raises RuntimeError. Otherwise, returns no meaningful value.""" - # We apply the edits file-wise for efficiency, and because we must track the - # file-wise offset deltas (caused by the modifications to the text). + # We apply the edits file-wise for efficiency. chunks_by_file = _SortChunksByFile( chunks ) - # We sort the file list simply to enable repeatable testing + # We sort the file list simply to enable repeatable testing. sorted_file_list = sorted( iterkeys( chunks_by_file ) ) # Make sure the user is prepared to have her screen mutilated by the new - # buffers + # buffers. num_files_to_open = _GetNumNonVisibleFiles( sorted_file_list ) if num_files_to_open > 0: @@ -732,11 +731,10 @@ def ReplaceChunks( chunks ): locations = [] for filepath in sorted_file_list: - ( buffer_num, close_window ) = _OpenFileInSplitIfNeeded( filepath ) + buffer_num, close_window = _OpenFileInSplitIfNeeded( filepath ) - ReplaceChunksInBuffer( chunks_by_file[ filepath ], - vim.buffers[ buffer_num ], - locations ) + locations.extend( ReplaceChunksInBuffer( chunks_by_file[ filepath ], + vim.buffers[ buffer_num ] ) ) # When opening tons of files, we don't want to have a split for each new # file, as this simply does not scale, so we open the window, make the @@ -760,101 +758,91 @@ def ReplaceChunks( chunks ): warning = False ) -def ReplaceChunksInBuffer( chunks, vim_buffer, locations ): - """Apply changes in |chunks| to the buffer-like object |buffer|. Append each - chunk's start to the list |locations|""" +def ReplaceChunksInBuffer( chunks, vim_buffer ): + """Apply changes in |chunks| to the buffer-like object |buffer| and return the + locations for that buffer.""" - # We need to track the difference in length, but ensuring we apply fixes - # in ascending order of insertion point. + # We apply the chunks from the bottom to the top of the buffer so that we + # don't need to adjust the position of the remaining chunks due to text + # changes. This assumes that chunks are not overlapping. However, we still + # allow multiple chunks to share the same starting position (because of the + # language server protocol specs). These chunks must be applied in their order + # of appareance. Since Python sorting is stable, if we sort the whole list in + # reverse order of location, these chunks will be reversed. Therefore, we + # need to fully reverse the list then sort it on the starting position in + # reverse order. + chunks.reverse() chunks.sort( key = lambda chunk: ( chunk[ 'range' ][ 'start' ][ 'line_num' ], chunk[ 'range' ][ 'start' ][ 'column_num' ] - ) ) + ), reverse = True ) - # 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 + # However, we still want to display the locations from the top of the buffer + # to its bottom. + return reversed( [ ReplaceChunk( chunk[ 'range' ][ 'start' ], + chunk[ 'range' ][ 'end' ], + chunk[ 'replacement_text' ], + vim_buffer ) + for chunk in chunks ] ) - 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, - locations ) - line_delta += new_line_delta - char_delta += new_char_delta +def SplitLines( contents ): + """Return a list of each of the lines in the byte string |contents|. + Behavior is equivalent to str.splitlines with the following exceptions: + - empty strings are returned as [ '' ]; + - a trailing newline is not ignored (i.e. SplitLines( '\n' ) + returns [ '', '' ], not [ '' ] ).""" + if contents == b'': + return [ b'' ] + + lines = contents.splitlines() + + if contents.endswith( b'\r' ) or contents.endswith( b'\n' ): + lines.append( b'' ) + + return lines # Replace the chunk of text specified by a contiguous range with the supplied -# text. +# text and return the location. # * start and end are objects with line_num and column_num properties # * the range is inclusive # * indices are all 1-based -# * the returned character delta is the delta for the last line -# -# returns the delta (in lines and characters) that any position after the end -# needs to be adjusted by. # # NOTE: Works exclusively with bytes() instances and byte offsets as returned # by ycmd and used within the Vim buffers -def ReplaceChunk( start, end, replacement_text, line_delta, char_delta, - vim_buffer, locations = None ): +def ReplaceChunk( start, end, replacement_text, 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 - end_line = end[ 'line_num' ] - 1 + line_delta + start_line = start[ 'line_num' ] - 1 + end_line = end[ 'line_num' ] - 1 - source_lines_count = end_line - start_line + 1 - start_column = start[ 'column_num' ] - 1 + char_delta + start_column = start[ 'column_num' ] - 1 end_column = end[ 'column_num' ] - 1 - if source_lines_count == 1: - end_column += char_delta # NOTE: replacement_text is unicode, but all our offsets are byte offsets, # so we convert to bytes - replacement_lines = ToBytes( replacement_text ).splitlines( False ) - if not replacement_lines: - replacement_lines = [ bytes( b'' ) ] - replacement_lines_count = len( replacement_lines ) + replacement_lines = SplitLines( ToBytes( replacement_text ) ) # NOTE: Vim buffers are a list of byte objects on Python 2 but unicode # objects on Python 3. - end_existing_text = ToBytes( vim_buffer[ end_line ] )[ end_column : ] start_existing_text = ToBytes( vim_buffer[ start_line ] )[ : start_column ] - - new_char_delta = ( len( replacement_lines[ -1 ] ) - - ( end_column - start_column ) ) - if replacement_lines_count > 1: - new_char_delta -= start_column + end_existing_text = ToBytes( vim_buffer[ end_line ] )[ end_column : ] replacement_lines[ 0 ] = start_existing_text + replacement_lines[ 0 ] replacement_lines[ -1 ] = replacement_lines[ -1 ] + end_existing_text vim_buffer[ start_line : end_line + 1 ] = replacement_lines[:] - if locations is not None: - locations.append( { - 'bufnr': vim_buffer.number, - 'filename': vim_buffer.name, - # line and column numbers are 1-based in qflist - 'lnum': start_line + 1, - 'col': start_column + 1, - 'text': replacement_text, - 'type': 'F', - } ) - - new_line_delta = replacement_lines_count - source_lines_count - return ( new_line_delta, new_char_delta ) + return { + 'bufnr': vim_buffer.number, + 'filename': vim_buffer.name, + # line and column numbers are 1-based in qflist + 'lnum': start_line + 1, + 'col': start_column + 1, + 'text': replacement_text, + 'type': 'F', + } def InsertNamespace( namespace ): @@ -871,9 +859,9 @@ def InsertNamespace( namespace ): if line: existing_line = LineTextInCurrentBuffer( line ) existing_indent = re.sub( r"\S.*", "", existing_line ) - new_line = "{0}using {1};\n\n".format( existing_indent, namespace ) + new_line = "{0}using {1};\n".format( existing_indent, namespace ) replace_pos = { 'line_num': line + 1, 'column_num': 1 } - ReplaceChunk( replace_pos, replace_pos, new_line, 0, 0, vim.current.buffer ) + ReplaceChunk( replace_pos, replace_pos, new_line, vim.current.buffer ) PostVimMessage( 'Add namespace: {0}'.format( namespace ), warning = False )