Auto merge of #2898 - micbou:line-overlapping-chunks, r=micbou

[READY] Apply chunks from bottom to top

Without the proposed fix, the test included in that PR fails as follows
```
FAIL: ycm.tests.vimsupport_test.ReplaceChunksInBuffer_LineOverlappingChunks_test
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Python36\lib\site-packages\nose\case.py", line 198, in runTest
    self.test(*self.arg)
  File "C:\Users\micbou\projects\YouCompleteMe\python\ycm\tests\vimsupport_test.py", line 768, in ReplaceChunksInBuffer_LineOverlappingChunks_test
    AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer )
  File "C:\Users\micbou\projects\YouCompleteMe\python\ycm\tests\vimsupport_test.py", line 86, in AssertBuffersAreEqualAsBytes
    eq_( ToBytes( result_line ), ToBytes( expected_line ) )
AssertionError: b'    third line' != b'    third '
```
Found the issue while trying to add code formatting support to the language server completer. The test is based on a real-world scenario when formatting with jdt.ls (for some reason, jdt.ls is including the newline of the previous line when fixing indentation).

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/valloric/youcompleteme/2898)
<!-- Reviewable:end -->
This commit is contained in:
zzbot 2018-02-10 15:20:24 -08:00 committed by GitHub
commit 39fe6d1f86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 328 additions and 538 deletions

View File

@ -183,628 +183,364 @@ def AssertBuffersAreEqualAsBytes( result_buffer, expected_buffer ):
def ReplaceChunk_SingleLine_Repl_1_test(): def ReplaceChunk_SingleLine_Repl_1_test():
# Replace with longer range # Replace with longer range
result_buffer = [ 'This is a string' ] result_buffer = VimBuffer( 'buffer', contents = [ 'This is a string' ] )
start, end = _BuildLocations( 1, 1, 1, 5 ) start, end = _BuildLocations( 1, 11, 1, 17 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, vimsupport.ReplaceChunk( start, end, 'pie', result_buffer )
end,
'How long',
0,
0,
result_buffer )
AssertBuffersAreEqualAsBytes( [ 'How long is a string' ], result_buffer ) AssertBuffersAreEqualAsBytes( [ 'This is a pie' ], result_buffer )
eq_( line_offset, 0 )
eq_( char_offset, 4 )
# and replace again, using delta # and replace again
start, end = _BuildLocations( 1, 10, 1, 11 ) start, end = _BuildLocations( 1, 10, 1, 11 )
( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( vimsupport.ReplaceChunk( start, end, ' piece of ', result_buffer )
start,
end,
' piece of ',
line_offset,
char_offset,
result_buffer )
line_offset += new_line_offset AssertBuffersAreEqualAsBytes( [ 'This is a piece of pie' ], result_buffer )
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 )
# and once more, for luck # and once more, for luck
start, end = _BuildLocations( 1, 11, 1, 17 ) start, end = _BuildLocations( 1, 1, 1, 5 )
vimsupport.ReplaceChunk( start, end, 'How long', result_buffer )
( 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
AssertBuffersAreEqualAsBytes( [ 'How long is a piece of pie' ], AssertBuffersAreEqualAsBytes( [ 'How long is a piece of pie' ],
result_buffer ) result_buffer )
eq_( new_line_offset, 0 )
eq_( new_char_offset, -3 )
eq_( line_offset, 0 )
eq_( char_offset, 10 )
def ReplaceChunk_SingleLine_Repl_2_test(): def ReplaceChunk_SingleLine_Repl_2_test():
# Replace with shorter range # 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 ) start, end = _BuildLocations( 1, 11, 1, 17 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, vimsupport.ReplaceChunk( start, end, 'test', result_buffer )
end,
'test',
0,
0,
result_buffer )
AssertBuffersAreEqualAsBytes( [ 'This is a test' ], result_buffer ) AssertBuffersAreEqualAsBytes( [ 'This is a test' ], result_buffer )
eq_( line_offset, 0 )
eq_( char_offset, -2 )
def ReplaceChunk_SingleLine_Repl_3_test(): def ReplaceChunk_SingleLine_Repl_3_test():
# Replace with equal range # 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 ) start, end = _BuildLocations( 1, 6, 1, 8 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, vimsupport.ReplaceChunk( start, end, 'be', result_buffer )
end,
'be',
0,
0,
result_buffer )
AssertBuffersAreEqualAsBytes( [ 'This be a string' ], result_buffer ) AssertBuffersAreEqualAsBytes( [ 'This be a string' ], result_buffer )
eq_( line_offset, 0 )
eq_( char_offset, 0 )
def ReplaceChunk_SingleLine_Add_1_test(): def ReplaceChunk_SingleLine_Add_1_test():
# Insert at start # Insert at start
result_buffer = [ 'is a string' ] result_buffer = VimBuffer( 'buffer', contents = [ 'is a string' ] )
start, end = _BuildLocations( 1, 1, 1, 1 ) start, end = _BuildLocations( 1, 1, 1, 1 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, vimsupport.ReplaceChunk( start, end, 'This ', result_buffer )
end,
'This ',
0,
0,
result_buffer )
AssertBuffersAreEqualAsBytes( [ 'This is a string' ], result_buffer ) AssertBuffersAreEqualAsBytes( [ 'This is a string' ], result_buffer )
eq_( line_offset, 0 )
eq_( char_offset, 5 )
def ReplaceChunk_SingleLine_Add_2_test(): def ReplaceChunk_SingleLine_Add_2_test():
# Insert at end # Insert at end
result_buffer = [ 'This is a ' ] result_buffer = VimBuffer( 'buffer', contents = [ 'This is a ' ] )
start, end = _BuildLocations( 1, 11, 1, 11 ) start, end = _BuildLocations( 1, 11, 1, 11 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, vimsupport.ReplaceChunk( start, end, 'string', result_buffer )
end,
'string',
0,
0,
result_buffer )
AssertBuffersAreEqualAsBytes( [ 'This is a string' ], result_buffer ) AssertBuffersAreEqualAsBytes( [ 'This is a string' ], result_buffer )
eq_( line_offset, 0 )
eq_( char_offset, 6 )
def ReplaceChunk_SingleLine_Add_3_test(): def ReplaceChunk_SingleLine_Add_3_test():
# Insert in the middle # 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 ) start, end = _BuildLocations( 1, 8, 1, 8 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, vimsupport.ReplaceChunk( start, end, ' not', result_buffer )
end,
' not',
0,
0,
result_buffer )
AssertBuffersAreEqualAsBytes( [ 'This is not a string' ], result_buffer ) AssertBuffersAreEqualAsBytes( [ 'This is not a string' ], result_buffer )
eq_( line_offset, 0 )
eq_( char_offset, 4 )
def ReplaceChunk_SingleLine_Del_1_test(): def ReplaceChunk_SingleLine_Del_1_test():
# Delete from start # 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 ) start, end = _BuildLocations( 1, 1, 1, 6 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, vimsupport.ReplaceChunk( start, end, '', result_buffer )
end,
'',
0,
0,
result_buffer )
AssertBuffersAreEqualAsBytes( [ 'is a string' ], result_buffer ) AssertBuffersAreEqualAsBytes( [ 'is a string' ], result_buffer )
eq_( line_offset, 0 )
eq_( char_offset, -5 )
def ReplaceChunk_SingleLine_Del_2_test(): def ReplaceChunk_SingleLine_Del_2_test():
# Delete from end # 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 ) start, end = _BuildLocations( 1, 10, 1, 18 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, vimsupport.ReplaceChunk( start, end, '', result_buffer )
end,
'',
0,
0,
result_buffer )
AssertBuffersAreEqualAsBytes( [ 'This is a' ], result_buffer ) AssertBuffersAreEqualAsBytes( [ 'This is a' ], result_buffer )
eq_( line_offset, 0 )
eq_( char_offset, -8 )
def ReplaceChunk_SingleLine_Del_3_test(): def ReplaceChunk_SingleLine_Del_3_test():
# Delete from middle # 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 ) start, end = _BuildLocations( 1, 9, 1, 13 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, vimsupport.ReplaceChunk( start, end, '', result_buffer )
end,
'',
0,
0,
result_buffer )
AssertBuffersAreEqualAsBytes( [ 'This is a string' ], result_buffer ) AssertBuffersAreEqualAsBytes( [ 'This is a string' ], result_buffer )
eq_( line_offset, 0 )
eq_( char_offset, -4 )
def ReplaceChunk_SingleLine_Unicode_ReplaceUnicodeChars_test(): def ReplaceChunk_SingleLine_Unicode_ReplaceUnicodeChars_test():
# Replace Unicode characters. # 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 ) start, end = _BuildLocations( 1, 6, 1, 20 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, vimsupport.ReplaceChunk( start, end, 'Unicode ', result_buffer )
end,
'Unicode ',
0,
0,
result_buffer )
AssertBuffersAreEqualAsBytes( [ 'This Unicode string is in the middle' ], AssertBuffersAreEqualAsBytes( [ 'This Unicode string is in the middle' ],
result_buffer ) result_buffer )
eq_( line_offset, 0 )
eq_( char_offset, -6 )
def ReplaceChunk_SingleLine_Unicode_ReplaceAfterUnicode_test(): def ReplaceChunk_SingleLine_Unicode_ReplaceAfterUnicode_test():
# Replace ASCII characters after Unicode characters in the line. # 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 ) start, end = _BuildLocations( 1, 30, 1, 43 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, vimsupport.ReplaceChunk( start, end, 'fåke', result_buffer )
end,
'fåke',
0,
0,
result_buffer )
AssertBuffersAreEqualAsBytes( [ 'This Uniçø∂‰ string is fåke' ], AssertBuffersAreEqualAsBytes( [ 'This Uniçø∂‰ string is fåke' ],
result_buffer ) result_buffer )
eq_( line_offset, 0 )
eq_( char_offset, -8 )
def ReplaceChunk_SingleLine_Unicode_Grown_test(): def ReplaceChunk_SingleLine_Unicode_Grown_test():
# Replace ASCII characters after Unicode characters in the line. # 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 ) start, end = _BuildLocations( 1, 1, 1, 2 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, vimsupport.ReplaceChunk( start, end, 'å', result_buffer )
end,
'å',
0,
0,
result_buffer )
AssertBuffersAreEqualAsBytes( [ 'å' ], 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(): def ReplaceChunk_RemoveSingleLine_test():
result_buffer = [ 'aAa', result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
'aBa', 'aBa',
'aCa' ] 'aCa' ] )
start, end = _BuildLocations( 2, 1, 3, 1 ) start, end = _BuildLocations( 2, 1, 3, 1 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, '', vimsupport.ReplaceChunk( start, end, '', result_buffer )
0, 0, result_buffer )
# First line is not affected. # First line is not affected.
expected_buffer = [ 'aAa', AssertBuffersAreEqualAsBytes( [ 'aAa',
'aCa' ] 'aCa' ], result_buffer )
AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer )
eq_( line_offset, -1 )
eq_( char_offset, 0 )
def ReplaceChunk_SingleToMultipleLines_test(): def ReplaceChunk_SingleToMultipleLines_test():
result_buffer = [ 'aAa', result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
'aBa', 'aBa',
'aCa' ] '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
start, end = _BuildLocations( 2, 3, 2, 4 ) start, end = _BuildLocations( 2, 3, 2, 4 )
( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( vimsupport.ReplaceChunk( start, end, 'cccc', result_buffer )
start,
end,
'cccc',
line_offset,
char_offset,
result_buffer )
line_offset += new_line_offset
char_offset += new_char_offset
AssertBuffersAreEqualAsBytes( [ 'aAa', AssertBuffersAreEqualAsBytes( [ 'aAa',
'aEb', 'aBcccc',
'bFBcccc', 'aCa' ], result_buffer )
'aCa' ], result_buffer )
eq_( line_offset, 1 ) # now make another change to the second line
eq_( char_offset, 4 ) 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(): def ReplaceChunk_SingleToMultipleLines2_test():
result_buffer = [ 'aAa', 'aBa', 'aCa' ] result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
'aBa',
'aCa' ] )
start, end = _BuildLocations( 2, 2, 2, 2 ) start, end = _BuildLocations( 2, 2, 2, 2 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, vimsupport.ReplaceChunk( start, end, 'Eb\nbFb\nG', result_buffer )
end,
'Eb\nbFb\nG', AssertBuffersAreEqualAsBytes( [ 'aAa',
0, 'aEb',
0, 'bFb',
result_buffer ) 'GBa',
expected_buffer = [ 'aAa', 'aCa' ], result_buffer )
'aEb',
'bFb',
'GBa',
'aCa' ]
AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer )
eq_( line_offset, 2 )
eq_( char_offset, 0 )
def ReplaceChunk_SingleToMultipleLines3_test(): def ReplaceChunk_SingleToMultipleLines3_test():
result_buffer = [ 'aAa', 'aBa', 'aCa' ] result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
'aBa',
'aCa' ] )
start, end = _BuildLocations( 2, 2, 2, 2 ) start, end = _BuildLocations( 2, 2, 2, 2 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, vimsupport.ReplaceChunk( start, end, 'Eb\nbFb\nbGb', result_buffer )
end,
'Eb\nbFb\nbGb', AssertBuffersAreEqualAsBytes( [ 'aAa',
0, 'aEb',
0, 'bFb',
result_buffer ) 'bGbBa',
expected_buffer = [ 'aAa', 'aCa' ], result_buffer )
'aEb',
'bFb',
'bGbBa',
'aCa' ]
AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer )
eq_( line_offset, 2 )
eq_( char_offset, 2 )
def ReplaceChunk_SingleToMultipleLinesReplace_test(): def ReplaceChunk_SingleToMultipleLinesReplace_test():
result_buffer = [ 'aAa', 'aBa', 'aCa' ] result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
'aBa',
'aCa' ] )
start, end = _BuildLocations( 1, 2, 1, 4 ) start, end = _BuildLocations( 1, 2, 1, 4 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, vimsupport.ReplaceChunk( start, end, 'Eb\nbFb\nbGb', result_buffer )
end,
'Eb\nbFb\nbGb', AssertBuffersAreEqualAsBytes( [ 'aEb',
0, 'bFb',
0, 'bGb',
result_buffer ) 'aBa',
expected_buffer = [ 'aEb', 'aCa' ], result_buffer )
'bFb',
'bGb',
'aBa',
'aCa' ]
AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer )
eq_( line_offset, 2 )
eq_( char_offset, 0 )
def ReplaceChunk_SingleToMultipleLinesReplace_2_test(): def ReplaceChunk_SingleToMultipleLinesReplace_2_test():
result_buffer = [ 'aAa', result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
'aBa', 'aBa',
'aCa' ] '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")
start, end = _BuildLocations( 1, 4, 1, 4 ) start, end = _BuildLocations( 1, 4, 1, 4 )
( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( vimsupport.ReplaceChunk( start, end, 'cccc', result_buffer )
start,
end,
'cccc',
line_offset,
char_offset,
result_buffer )
line_offset += new_line_offset AssertBuffersAreEqualAsBytes( [ 'aAacccc',
char_offset += new_char_offset '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', AssertBuffersAreEqualAsBytes( [ 'aEb',
'bFb', 'bFb',
'bGbcccc', 'bGbcccc',
'aBa', 'aBa',
'aCa' ], result_buffer ) 'aCa' ], result_buffer )
eq_( line_offset, 2 )
eq_( char_offset, 4 )
def ReplaceChunk_MultipleLinesToSingleLine_test(): def ReplaceChunk_MultipleLinesToSingleLine_test():
result_buffer = [ 'aAa', 'aBa', 'aCaaaa' ] result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
start, end = _BuildLocations( 2, 2, 3, 2 ) 'aBa',
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'E', 'aCaaaa' ] )
0, 0, result_buffer ) start, end = _BuildLocations( 3, 4, 3, 5 )
expected_buffer = [ 'aAa', 'aECaaaa' ] vimsupport.ReplaceChunk( start, end, 'dd\ndd', result_buffer )
AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer )
eq_( line_offset, -1 ) AssertBuffersAreEqualAsBytes( [ 'aAa',
eq_( char_offset, 1 ) 'aBa',
'aCadd',
'ddaa' ], result_buffer )
# make another modification applying offsets # make another modification applying offsets
start, end = _BuildLocations( 3, 3, 3, 4 ) start, end = _BuildLocations( 3, 3, 3, 4 )
( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( vimsupport.ReplaceChunk( start, end, 'cccc', result_buffer )
start,
end,
'cccc',
line_offset,
char_offset,
result_buffer )
line_offset += new_line_offset
char_offset += new_char_offset
AssertBuffersAreEqualAsBytes( [ 'aAa', AssertBuffersAreEqualAsBytes( [ 'aAa',
'aECccccaaa' ], result_buffer ) 'aBa',
eq_( line_offset, -1 ) 'aCccccdd',
eq_( char_offset, 4 ) 'ddaa' ], result_buffer )
# and another, for luck # and another, for luck
start, end = _BuildLocations( 3, 4, 3, 5 ) start, end = _BuildLocations( 2, 2, 3, 2 )
( new_line_offset, new_char_offset ) = vimsupport.ReplaceChunk( vimsupport.ReplaceChunk( start, end, 'E', result_buffer )
start,
end,
'dd\ndd',
line_offset,
char_offset,
result_buffer )
line_offset += new_line_offset
char_offset += new_char_offset
AssertBuffersAreEqualAsBytes( [ 'aAa', AssertBuffersAreEqualAsBytes( [ 'aAa',
'aECccccdd', 'aECccccdd',
'ddaa' ], result_buffer ) 'ddaa' ], result_buffer )
eq_( line_offset, 0 )
eq_( char_offset, -2 )
def ReplaceChunk_MultipleLinesToSameMultipleLines_test(): def ReplaceChunk_MultipleLinesToSameMultipleLines_test():
result_buffer = [ 'aAa', result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
'aBa', 'aBa',
'aCa', 'aCa',
'aDe' ] 'aDe' ] )
start, end = _BuildLocations( 2, 2, 3, 2 ) start, end = _BuildLocations( 2, 2, 3, 2 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'Eb\nbF', vimsupport.ReplaceChunk( start, end, 'Eb\nbF', result_buffer )
0, 0, result_buffer )
expected_buffer = [ 'aAa', AssertBuffersAreEqualAsBytes( [ 'aAa',
'aEb', 'aEb',
'bFCa', 'bFCa',
'aDe' ] 'aDe' ], result_buffer )
AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer )
eq_( line_offset, 0 )
eq_( char_offset, 1 )
def ReplaceChunk_MultipleLinesToMoreMultipleLines_test(): def ReplaceChunk_MultipleLinesToMoreMultipleLines_test():
result_buffer = [ 'aAa', result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
'aBa', 'aBa',
'aCa', 'aCa',
'aDe' ] 'aDe' ] )
start, end = _BuildLocations( 2, 2, 3, 2 ) start, end = _BuildLocations( 2, 2, 3, 2 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, vimsupport.ReplaceChunk( start, end, 'Eb\nbFb\nbG', result_buffer )
end,
'Eb\nbFb\nbG', AssertBuffersAreEqualAsBytes( [ 'aAa',
0, 'aEb',
0, 'bFb',
result_buffer ) 'bGCa',
expected_buffer = [ 'aAa', 'aDe' ], result_buffer )
'aEb',
'bFb',
'bGCa',
'aDe' ]
AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer )
eq_( line_offset, 1 )
eq_( char_offset, 1 )
def ReplaceChunk_MultipleLinesToLessMultipleLines_test(): def ReplaceChunk_MultipleLinesToLessMultipleLines_test():
result_buffer = [ 'aAa', result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
'aBa', 'aBa',
'aCa', 'aCa',
'aDe' ] 'aDe' ] )
start, end = _BuildLocations( 1, 2, 3, 2 ) start, end = _BuildLocations( 1, 2, 3, 2 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'Eb\nbF', vimsupport.ReplaceChunk( start, end, 'Eb\nbF', result_buffer )
0, 0, result_buffer )
expected_buffer = [ 'aEb', 'bFCa', 'aDe' ] AssertBuffersAreEqualAsBytes( [ 'aEb',
AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) 'bFCa',
eq_( line_offset, -1 ) 'aDe' ], result_buffer )
eq_( char_offset, 1 )
def ReplaceChunk_MultipleLinesToEvenLessMultipleLines_test(): def ReplaceChunk_MultipleLinesToEvenLessMultipleLines_test():
result_buffer = [ 'aAa', result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
'aBa', 'aBa',
'aCa', 'aCa',
'aDe' ] 'aDe' ] )
start, end = _BuildLocations( 1, 2, 4, 2 ) start, end = _BuildLocations( 1, 2, 4, 2 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'Eb\nbF', vimsupport.ReplaceChunk( start, end, 'Eb\nbF', result_buffer )
0, 0, result_buffer )
expected_buffer = [ 'aEb', 'bFDe' ] AssertBuffersAreEqualAsBytes( [ 'aEb',
AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer ) 'bFDe' ], result_buffer )
eq_( line_offset, -2 )
eq_( char_offset, 1 )
def ReplaceChunk_SpanBufferEdge_test(): def ReplaceChunk_SpanBufferEdge_test():
result_buffer = [ 'aAa', result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
'aBa', 'aBa',
'aCa' ] 'aCa' ] )
start, end = _BuildLocations( 1, 1, 1, 3 ) start, end = _BuildLocations( 1, 1, 1, 3 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'bDb', vimsupport.ReplaceChunk( start, end, 'bDb', result_buffer )
0, 0, result_buffer )
expected_buffer = [ 'bDba', AssertBuffersAreEqualAsBytes( [ 'bDba',
'aBa', 'aBa',
'aCa' ] 'aCa' ], result_buffer )
AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer )
eq_( line_offset, 0 )
eq_( char_offset, 1 )
def ReplaceChunk_DeleteTextInLine_test(): def ReplaceChunk_DeleteTextInLine_test():
result_buffer = [ 'aAa', result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
'aBa', 'aBa',
'aCa' ] 'aCa' ] )
start, end = _BuildLocations( 2, 2, 2, 3 ) start, end = _BuildLocations( 2, 2, 2, 3 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, '', vimsupport.ReplaceChunk( start, end, '', result_buffer )
0, 0, result_buffer ) AssertBuffersAreEqualAsBytes( [ 'aAa',
expected_buffer = [ 'aAa', 'aa',
'aa', 'aCa' ], result_buffer )
'aCa' ]
AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer )
eq_( line_offset, 0 )
eq_( char_offset, -1 )
def ReplaceChunk_AddTextInLine_test(): def ReplaceChunk_AddTextInLine_test():
result_buffer = [ 'aAa', result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
'aBa', 'aBa',
'aCa' ] 'aCa' ] )
start, end = _BuildLocations( 2, 2, 2, 2 ) start, end = _BuildLocations( 2, 2, 2, 2 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'bDb', vimsupport.ReplaceChunk( start, end, 'bDb', result_buffer )
0, 0, result_buffer )
expected_buffer = [ 'aAa', AssertBuffersAreEqualAsBytes( [ 'aAa',
'abDbBa', 'abDbBa',
'aCa' ] 'aCa' ], result_buffer )
AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer )
eq_( line_offset, 0 )
eq_( char_offset, 3 )
def ReplaceChunk_ReplaceTextInLine_test(): def ReplaceChunk_ReplaceTextInLine_test():
result_buffer = [ 'aAa', result_buffer = VimBuffer( 'buffer', contents = [ 'aAa',
'aBa', 'aBa',
'aCa' ] 'aCa' ] )
start, end = _BuildLocations( 2, 2, 2, 3 ) start, end = _BuildLocations( 2, 2, 2, 3 )
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, 'bDb', vimsupport.ReplaceChunk( start, end, 'bDb', result_buffer )
0, 0, result_buffer )
expected_buffer = [ 'aAa', AssertBuffersAreEqualAsBytes( [ 'aAa',
'abDba', 'abDba',
'aCa' ] 'aCa' ], result_buffer )
AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer )
eq_( line_offset, 0 )
eq_( char_offset, 2 )
def ReplaceChunk_SingleLineOffsetWorks_test(): def ReplaceChunk_NewlineChunk_test():
result_buffer = [ 'aAa', result_buffer = VimBuffer( 'buffer', contents = [ 'first line',
'aBa', 'second line' ] )
'aCa' ] start, end = _BuildLocations( 1, 11, 2, 1 )
start, end = _BuildLocations( 1, 1, 1, 2 ) vimsupport.ReplaceChunk( start, end, '\n', result_buffer )
( 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 )
AssertBuffersAreEqualAsBytes( [ 'first line',
def ReplaceChunk_SingleLineToMultipleLinesOffsetWorks_test(): 'second line' ], result_buffer )
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 )
def _BuildLocations( start_line, start_column, end_line, end_column ): def _BuildLocations( start_line, start_column, end_line, end_column ):
@ -823,11 +559,10 @@ def ReplaceChunksInBuffer_SortedChunks_test():
_BuildChunk( 1, 11, 1, 11, ')' ) _BuildChunk( 1, 11, 1, 11, ')' )
] ]
result_buffer = [ 'CT<10 >> 2> ct' ] result_buffer = VimBuffer( 'buffer', contents = [ 'CT<10 >> 2> ct' ] )
vimsupport.ReplaceChunksInBuffer( chunks, result_buffer, None ) vimsupport.ReplaceChunksInBuffer( chunks, result_buffer )
expected_buffer = [ 'CT<(10 >> 2)> ct' ] AssertBuffersAreEqualAsBytes( [ 'CT<(10 >> 2)> ct' ], result_buffer )
AssertBuffersAreEqualAsBytes( expected_buffer, result_buffer )
def ReplaceChunksInBuffer_UnsortedChunks_test(): def ReplaceChunksInBuffer_UnsortedChunks_test():
@ -836,11 +571,78 @@ def ReplaceChunksInBuffer_UnsortedChunks_test():
_BuildChunk( 1, 4, 1, 4, '(' ) _BuildChunk( 1, 4, 1, 4, '(' )
] ]
result_buffer = [ 'CT<10 >> 2> ct' ] result_buffer = VimBuffer( 'buffer', contents = [ 'CT<10 >> 2> ct' ] )
vimsupport.ReplaceChunksInBuffer( chunks, result_buffer, None ) vimsupport.ReplaceChunksInBuffer( chunks, result_buffer )
expected_buffer = [ 'CT<(10 >> 2)> ct' ] AssertBuffersAreEqualAsBytes( [ 'CT<(10 >> 2)> ct' ], result_buffer )
AssertBuffersAreEqualAsBytes( expected_buffer, 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 ) @patch( 'ycm.vimsupport.VariableExists', return_value = False )

View File

@ -749,15 +749,14 @@ def ReplaceChunks( chunks ):
If for some reason a file could not be opened or changed, raises RuntimeError. If for some reason a file could not be opened or changed, raises RuntimeError.
Otherwise, returns no meaningful value.""" Otherwise, returns no meaningful value."""
# We apply the edits file-wise for efficiency, and because we must track the # We apply the edits file-wise for efficiency.
# file-wise offset deltas (caused by the modifications to the text).
chunks_by_file = _SortChunksByFile( chunks ) 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 ) ) sorted_file_list = sorted( iterkeys( chunks_by_file ) )
# Make sure the user is prepared to have her screen mutilated by the new # Make sure the user is prepared to have her screen mutilated by the new
# buffers # buffers.
num_files_to_open = _GetNumNonVisibleFiles( sorted_file_list ) num_files_to_open = _GetNumNonVisibleFiles( sorted_file_list )
if num_files_to_open > 0: if num_files_to_open > 0:
@ -770,11 +769,10 @@ def ReplaceChunks( chunks ):
locations = [] locations = []
for filepath in sorted_file_list: for filepath in sorted_file_list:
( buffer_num, close_window ) = _OpenFileInSplitIfNeeded( filepath ) buffer_num, close_window = _OpenFileInSplitIfNeeded( filepath )
ReplaceChunksInBuffer( chunks_by_file[ filepath ], locations.extend( ReplaceChunksInBuffer( chunks_by_file[ filepath ],
vim.buffers[ buffer_num ], vim.buffers[ buffer_num ] ) )
locations )
# When opening tons of files, we don't want to have a split for each new # 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 # file, as this simply does not scale, so we open the window, make the
@ -798,101 +796,91 @@ def ReplaceChunks( chunks ):
warning = False ) warning = False )
def ReplaceChunksInBuffer( chunks, vim_buffer, locations ): def ReplaceChunksInBuffer( chunks, vim_buffer ):
"""Apply changes in |chunks| to the buffer-like object |buffer|. Append each """Apply changes in |chunks| to the buffer-like object |buffer| and return the
chunk's start to the list |locations|""" locations for that buffer."""
# We need to track the difference in length, but ensuring we apply fixes # We apply the chunks from the bottom to the top of the buffer so that we
# in ascending order of insertion point. # 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: ( chunks.sort( key = lambda chunk: (
chunk[ 'range' ][ 'start' ][ 'line_num' ], chunk[ 'range' ][ 'start' ][ 'line_num' ],
chunk[ 'range' ][ 'start' ][ 'column_num' ] chunk[ 'range' ][ 'start' ][ 'column_num' ]
) ) ), reverse = True )
# Remember the line number we're processing. Negative line number means we # However, we still want to display the locations from the top of the buffer
# haven't processed any lines yet (by nature of being not equal to any # to its bottom.
# real line number). return reversed( [ ReplaceChunk( chunk[ 'range' ][ 'start' ],
last_line = -1 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( def SplitLines( contents ):
chunk[ 'range' ][ 'start' ], """Return a list of each of the lines in the byte string |contents|.
chunk[ 'range' ][ 'end' ], Behavior is equivalent to str.splitlines with the following exceptions:
chunk[ 'replacement_text' ], - empty strings are returned as [ '' ];
line_delta, char_delta, - a trailing newline is not ignored (i.e. SplitLines( '\n' )
vim_buffer, returns [ '', '' ], not [ '' ] )."""
locations ) if contents == b'':
line_delta += new_line_delta return [ b'' ]
char_delta += new_char_delta
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 # 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 # * start and end are objects with line_num and column_num properties
# * the range is inclusive # * the range is inclusive
# * indices are all 1-based # * 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 # NOTE: Works exclusively with bytes() instances and byte offsets as returned
# by ycmd and used within the Vim buffers # by ycmd and used within the Vim buffers
def ReplaceChunk( start, end, replacement_text, line_delta, char_delta, def ReplaceChunk( start, end, replacement_text, vim_buffer ):
vim_buffer, locations = None ):
# ycmd's results are all 1-based, but vim's/python's are all 0-based # 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) # (so we do -1 on all of the values)
start_line = start[ 'line_num' ] - 1 + line_delta start_line = start[ 'line_num' ] - 1
end_line = end[ 'line_num' ] - 1 + line_delta end_line = end[ 'line_num' ] - 1
source_lines_count = end_line - start_line + 1 start_column = start[ 'column_num' ] - 1
start_column = start[ 'column_num' ] - 1 + char_delta
end_column = end[ '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, # NOTE: replacement_text is unicode, but all our offsets are byte offsets,
# so we convert to bytes # so we convert to bytes
replacement_lines = ToBytes( replacement_text ).splitlines( False ) replacement_lines = SplitLines( ToBytes( replacement_text ) )
if not replacement_lines:
replacement_lines = [ bytes( b'' ) ]
replacement_lines_count = len( replacement_lines )
# NOTE: Vim buffers are a list of byte objects on Python 2 but unicode # NOTE: Vim buffers are a list of byte objects on Python 2 but unicode
# objects on Python 3. # objects on Python 3.
end_existing_text = ToBytes( vim_buffer[ end_line ] )[ end_column : ]
start_existing_text = ToBytes( vim_buffer[ start_line ] )[ : start_column ] start_existing_text = ToBytes( vim_buffer[ start_line ] )[ : start_column ]
end_existing_text = ToBytes( vim_buffer[ end_line ] )[ end_column : ]
new_char_delta = ( len( replacement_lines[ -1 ] )
- ( end_column - start_column ) )
if replacement_lines_count > 1:
new_char_delta -= start_column
replacement_lines[ 0 ] = start_existing_text + replacement_lines[ 0 ] replacement_lines[ 0 ] = start_existing_text + replacement_lines[ 0 ]
replacement_lines[ -1 ] = replacement_lines[ -1 ] + end_existing_text replacement_lines[ -1 ] = replacement_lines[ -1 ] + end_existing_text
vim_buffer[ start_line : end_line + 1 ] = replacement_lines[:] vim_buffer[ start_line : end_line + 1 ] = replacement_lines[:]
if locations is not None: return {
locations.append( { 'bufnr': vim_buffer.number,
'bufnr': vim_buffer.number, 'filename': vim_buffer.name,
'filename': vim_buffer.name, # line and column numbers are 1-based in qflist
# line and column numbers are 1-based in qflist 'lnum': start_line + 1,
'lnum': start_line + 1, 'col': start_column + 1,
'col': start_column + 1, 'text': replacement_text,
'text': replacement_text, 'type': 'F',
'type': 'F', }
} )
new_line_delta = replacement_lines_count - source_lines_count
return ( new_line_delta, new_char_delta )
def InsertNamespace( namespace ): def InsertNamespace( namespace ):
@ -909,9 +897,9 @@ def InsertNamespace( namespace ):
if line: if line:
existing_line = LineTextInCurrentBuffer( line ) existing_line = LineTextInCurrentBuffer( line )
existing_indent = re.sub( r"\S.*", "", existing_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 } 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 ) PostVimMessage( 'Add namespace: {0}'.format( namespace ), warning = False )