Auto merge of #1555 - puremourning:fixit, r=Valloric
[RFC] YCM Client support for FixIt commands This change provides the client-side support in YouCompleteMe for vim providing a new :YcmFixIt command which applies changes to the vim buffer to implement trivial fixes to common coding errors where the completer provides instructions on how to fix a diagnostic. A note is added "(FixIt)" to the diagnostic text echo'd in vim when a FixIt is available. While this may seem out of the scope of ycmd/YouCompleteMe (as a completion engine), I think it is a really useful feature, and provided straight out of libclang. so I'm sending this slightly incomplete PR (along with the associated ycmd change) to see what the appetite is within the user and maintainer community, and to get feedback on the approach. I know that I need to write some tests, and perhaps tidy up some TODOs, but I would really appreciate any feedback on the approach. Also, I should confess that I haven't yet run the existing tests as I'm having trouble getting them to run on my mac. Hoping that travis will help me out :) I know I also need to update the readme. Testing has been performed manually by going through the list of .c, .cpp and .m files in clang's tests/FixIt folder and ensuring that by hitting :YcmFixIt (actually "@:" a lot!) on each diagnostic, the diagnostic went away (assuming a FixIt was available). I also mangled a number of the clang test such that the fix was spread across multiple lines, etc. I also haven't tested this with completers other than clang completer (due to having trouble one with diagnostics getting them to work) , but of course I will prior to this being ready to merge. I also understand that adding the vim modelines might be divisive, but the standard in this project doesn't match my usual setting :) Happy to remove them if people react angrily. Once again thanks for the time and a great (and improving!) product. I have signed the CLA Demo: ![ycmfixit](https://cloud.githubusercontent.com/assets/10584846/8396802/8fce516c-1dad-11e5-91e7-a26253e489fd.gif)
This commit is contained in:
commit
c946aa5fd4
47
README.md
47
README.md
@ -655,7 +655,15 @@ You may want to map this command to a key; try putting `nnoremap <F5>
|
||||
### The `:YcmDiags` command
|
||||
|
||||
Calling this command will fill Vim's `locationlist` with errors or warnings if
|
||||
any were detected in your file and then open it.
|
||||
any were detected in your file and then open it. If a given error or warning can
|
||||
be fixed by a call to `:YcmCompleter FixIt`, then ` (FixIt available)` is
|
||||
appended to the error or warning text. See the `FixIt` completer subcommand for
|
||||
more information.
|
||||
|
||||
NOTE: The absense of ` (FixIt available)` does not strictly imply a fix-it is
|
||||
not available as not all completers are able to provide this indication. For
|
||||
example, the c-sharp completer provides many fix-its but does not add this
|
||||
additional indication.
|
||||
|
||||
The `g:ycm_open_loclist_on_ycm_diags` option can be used to prevent the location
|
||||
list from opening, but still have it filled with new diagnostic data. See the
|
||||
@ -817,6 +825,40 @@ NOTE: Causes reparsing of the current translation unit.
|
||||
|
||||
Supported in filetypes: `c, cpp, objc, objcpp`
|
||||
|
||||
### The `FixIt` subcommand
|
||||
|
||||
Where available, attempts to make changes to the buffer to correct the
|
||||
diagnostic closest to the cursor position.
|
||||
|
||||
Completers which provide diagnostics may also provide trivial modifications to
|
||||
the source in order to correct the diagnostic. Examples include syntax errors
|
||||
such as missing trailing semi-colons, spurious characters, or other errors which
|
||||
the semantic engine can deterministically suggest corrections.
|
||||
|
||||
If no fix-it is available for the current line, or there is no diagnostic on the
|
||||
current line, this command has no effect on the current buffer. If any
|
||||
modifications are made, the number of changes made to the buffer is echo'd and
|
||||
the user may use the editor's undo command to revert.
|
||||
|
||||
When a diagnostic is available, and `g:ycm_echo_current_diagnostic` is set to 1,
|
||||
then the text ` (FixIt)` is appended to the echo'd diagnostic when the
|
||||
completer is able to add this indication. The text ` (FixIt available)` is
|
||||
also appended to the diagnostic text in the output of the `:YcmDiags` command
|
||||
for any diagnostics with available fix-its (where the completer can provide this
|
||||
indication).
|
||||
|
||||
NOTE: Causes re-parsing of the current translation unit.
|
||||
|
||||
NOTE: After applying a fix-it, the diagnostics UI is not immediately updated.
|
||||
This is due to a technical restriction in vim, and moving the cursor, or issuing
|
||||
the the `:YcmForceCompileAndDiagnostics` command will refresh the diagnostics.
|
||||
Repeated invocations of the `FixIt` command on a given line, however, _do_ apply
|
||||
all diagnostics as expected without requiring refreshing of the diagnostics UI.
|
||||
This is particularly useful where there are multiple diagnostics on one line, or
|
||||
where after fixing one diagnostic, another fix-it is available.
|
||||
|
||||
Supported in filetypes: `c, cpp, objc, objcpp, cs`
|
||||
|
||||
### The `StartServer` subcommand
|
||||
|
||||
Starts the semantic-engine-as-localhost-server for those semantic engines that
|
||||
@ -1075,7 +1117,8 @@ Default: `1`
|
||||
### The `g:ycm_echo_current_diagnostic` option
|
||||
|
||||
When this option is set, YCM will echo the text of the diagnostic present on the
|
||||
current line when you move your cursor to that line.
|
||||
current line when you move your cursor to that line. If a `FixIt` is available
|
||||
for the current diagnostic, then ` (FixIt)` is appended.
|
||||
|
||||
This option is part of the Syntastic compatibility layer; if the option is not
|
||||
set, YCM will fall back to the value of the `g:syntastic_echo_current_error`
|
||||
|
@ -36,6 +36,8 @@ class CommandRequest( BaseRequest ):
|
||||
else 'filetype_default' )
|
||||
self._is_goto_command = (
|
||||
self._arguments and self._arguments[ 0 ].startswith( 'GoTo' ) )
|
||||
self._is_fixit_command = (
|
||||
self._arguments and self._arguments[ 0 ].startswith( 'FixIt' ) )
|
||||
self._response = None
|
||||
|
||||
|
||||
@ -55,23 +57,73 @@ class CommandRequest( BaseRequest ):
|
||||
def Response( self ):
|
||||
return self._response
|
||||
|
||||
|
||||
def RunPostCommandActionsIfNeeded( self ):
|
||||
if not self.Done() or not self._response:
|
||||
return
|
||||
|
||||
if self._is_goto_command:
|
||||
if isinstance( self._response, list ):
|
||||
defs = [ _BuildQfListItem( x ) for x in self._response ]
|
||||
vim.eval( 'setqflist( %s )' % repr( defs ) )
|
||||
vim.eval( 'youcompleteme#OpenGoToList()' )
|
||||
else:
|
||||
vimsupport.JumpToLocation( self._response[ 'filepath' ],
|
||||
self._response[ 'line_num' ],
|
||||
self._response[ 'column_num' ] )
|
||||
self._HandleGotoResponse()
|
||||
elif self._is_fixit_command:
|
||||
self._HandleFixitResponse()
|
||||
elif 'message' in self._response:
|
||||
vimsupport.EchoText( self._response['message'] )
|
||||
self._HandleMessageResponse()
|
||||
|
||||
def _HandleGotoResponse( self ):
|
||||
if isinstance( self._response, list ):
|
||||
defs = [ _BuildQfListItem( x ) for x in self._response ]
|
||||
vim.eval( 'setqflist( %s )' % repr( defs ) )
|
||||
vim.eval( 'youcompleteme#OpenGoToList()' )
|
||||
else:
|
||||
vimsupport.JumpToLocation( self._response[ 'filepath' ],
|
||||
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 ]
|
||||
|
||||
# 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' ])
|
||||
))
|
||||
|
||||
# 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
|
||||
|
||||
# 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 )
|
||||
|
@ -43,7 +43,6 @@ class DiagnosticInterface( object ):
|
||||
if self._user_options[ 'echo_current_diagnostic' ]:
|
||||
self._EchoDiagnosticForLine( line )
|
||||
|
||||
|
||||
def UpdateWithNewDiagnostics( self, diags ):
|
||||
normalized_diags = [ _NormalizeDiagnostic( x ) for x in diags ]
|
||||
self._buffer_number_to_line_to_diags = _ConvertDiagListToDict(
|
||||
@ -62,7 +61,6 @@ class DiagnosticInterface( object ):
|
||||
vimsupport.SetLocationList(
|
||||
vimsupport.ConvertDiagnosticsToQfList( normalized_diags ) )
|
||||
|
||||
|
||||
def _EchoDiagnosticForLine( self, line_num ):
|
||||
buffer_num = vim.current.buffer.number
|
||||
diags = self._buffer_number_to_line_to_diags[ buffer_num ][ line_num ]
|
||||
@ -72,7 +70,12 @@ class DiagnosticInterface( object ):
|
||||
vimsupport.EchoText( '', False )
|
||||
self._diag_message_needs_clearing = False
|
||||
return
|
||||
vimsupport.EchoTextVimWidth( diags[ 0 ][ 'text' ] )
|
||||
|
||||
text = diags[ 0 ][ 'text' ]
|
||||
if diags[ 0 ].get( 'fixit_available', False ):
|
||||
text += ' (FixIt)'
|
||||
|
||||
vimsupport.EchoTextVimWidth( text )
|
||||
self._diag_message_needs_clearing = True
|
||||
|
||||
|
||||
|
523
python/ycm/tests/vimsupport_test.py
Normal file
523
python/ycm/tests/vimsupport_test.py
Normal file
@ -0,0 +1,523 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2015 YouCompleteMe contributors
|
||||
#
|
||||
# This file is part of YouCompleteMe.
|
||||
#
|
||||
# YouCompleteMe is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# YouCompleteMe is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ycm import vimsupport
|
||||
from nose.tools import eq_
|
||||
|
||||
def ReplaceChunk_SingleLine_Repl_1_test():
|
||||
# Replace with longer range
|
||||
# 12345678901234567
|
||||
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 )
|
||||
|
||||
eq_( [ "How long is a string" ], result_buffer )
|
||||
eq_( line_offset, 0 )
|
||||
eq_( char_offset, 4 )
|
||||
|
||||
# and replace again, using delta
|
||||
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 )
|
||||
|
||||
line_offset += new_line_offset
|
||||
char_offset += new_char_offset
|
||||
|
||||
eq_( [ '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
|
||||
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
|
||||
|
||||
eq_( ['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 )
|
||||
|
||||
def ReplaceChunk_SingleLine_Repl_2_test():
|
||||
# Replace with shorter range
|
||||
# 12345678901234567
|
||||
result_buffer = [ "This is a string" ]
|
||||
start, end = _BuildLocations( 1, 11, 1, 17 )
|
||||
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start,
|
||||
end,
|
||||
'test',
|
||||
0,
|
||||
0,
|
||||
result_buffer )
|
||||
|
||||
eq_( [ "This is a test" ], result_buffer )
|
||||
eq_( line_offset, 0 )
|
||||
eq_( char_offset, -2 )
|
||||
|
||||
def ReplaceChunk_SingleLine_Repl_3_test():
|
||||
# Replace with equal range
|
||||
# 12345678901234567
|
||||
result_buffer = [ "This is a string" ]
|
||||
start, end = _BuildLocations( 1, 6, 1, 8 )
|
||||
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start,
|
||||
end,
|
||||
'be',
|
||||
0,
|
||||
0,
|
||||
result_buffer )
|
||||
|
||||
eq_( [ "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" ]
|
||||
start, end = _BuildLocations( 1, 1, 1, 1 )
|
||||
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start,
|
||||
end,
|
||||
'This ',
|
||||
0,
|
||||
0,
|
||||
result_buffer )
|
||||
|
||||
eq_( [ "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 " ]
|
||||
start, end = _BuildLocations( 1, 11, 1, 11 )
|
||||
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start,
|
||||
end,
|
||||
'string',
|
||||
0,
|
||||
0,
|
||||
result_buffer )
|
||||
|
||||
eq_( [ "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" ]
|
||||
start, end = _BuildLocations( 1, 8, 1, 8 )
|
||||
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start,
|
||||
end,
|
||||
' not',
|
||||
0,
|
||||
0,
|
||||
result_buffer )
|
||||
|
||||
eq_( [ "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" ]
|
||||
start, end = _BuildLocations( 1, 1, 1, 6 )
|
||||
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start,
|
||||
end,
|
||||
'',
|
||||
0,
|
||||
0,
|
||||
result_buffer )
|
||||
|
||||
eq_( [ "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" ]
|
||||
start, end = _BuildLocations( 1, 10, 1, 18 )
|
||||
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start,
|
||||
end,
|
||||
'',
|
||||
0,
|
||||
0,
|
||||
result_buffer )
|
||||
|
||||
eq_( [ "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" ]
|
||||
start, end = _BuildLocations( 1, 9, 1, 13 )
|
||||
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start,
|
||||
end,
|
||||
'',
|
||||
0,
|
||||
0,
|
||||
result_buffer )
|
||||
|
||||
eq_( [ "This is a string" ], result_buffer )
|
||||
eq_( line_offset, 0 )
|
||||
eq_( char_offset, -4 )
|
||||
|
||||
def ReplaceChunk_RemoveSingleLine_test():
|
||||
result_buffer = [ "aAa", "aBa", "aCa" ]
|
||||
start, end = _BuildLocations( 2, 1, 3, 1 )
|
||||
( line_offset, char_offset ) = vimsupport.ReplaceChunk( start, end, '',
|
||||
0, 0, result_buffer )
|
||||
expected_buffer = [ "aAa", "aCa" ]
|
||||
eq_( expected_buffer, result_buffer )
|
||||
eq_( line_offset, -1 )
|
||||
eq_( char_offset, 0 )
|
||||
|
||||
|
||||
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" ]
|
||||
eq_( 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 )
|
||||
( 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
|
||||
|
||||
eq_( [ "aAa", "aEb", "bFBcccc", "aCa" ], result_buffer )
|
||||
eq_( line_offset, 1 )
|
||||
eq_( char_offset, 4 )
|
||||
|
||||
|
||||
def ReplaceChunk_SingleToMultipleLines2_test():
|
||||
result_buffer = [ "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" ]
|
||||
eq_( expected_buffer, result_buffer )
|
||||
eq_( line_offset, 2 )
|
||||
eq_( char_offset, 0 )
|
||||
|
||||
|
||||
def ReplaceChunk_SingleToMultipleLines3_test():
|
||||
result_buffer = [ "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" ]
|
||||
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,
|
||||
'Eb\nbFb\nbGb',
|
||||
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",
|
||||
"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" ]
|
||||
eq_( 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 )
|
||||
( 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
|
||||
|
||||
eq_( [ "aEb",
|
||||
"bFb",
|
||||
"bGbcccc",
|
||||
"aBa",
|
||||
"aCa" ], result_buffer )
|
||||
|
||||
eq_( line_offset, 2 )
|
||||
eq_( char_offset, 4 )
|
||||
|
||||
|
||||
|
||||
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" ]
|
||||
eq_( expected_buffer, result_buffer )
|
||||
eq_( line_offset, -1 )
|
||||
eq_( char_offset, 1 )
|
||||
|
||||
# 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
|
||||
|
||||
eq_( [ "aAa", "aECccccaaa" ], result_buffer )
|
||||
eq_( line_offset, -1 )
|
||||
eq_( char_offset, 4 )
|
||||
|
||||
# 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
|
||||
|
||||
eq_( [ "aAa", "aECccccdd", "ddaa" ], result_buffer )
|
||||
eq_( line_offset, 0 )
|
||||
eq_( char_offset, -2 )
|
||||
|
||||
|
||||
def ReplaceChunk_MultipleLinesToSameMultipleLines_test():
|
||||
result_buffer = [ "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" ]
|
||||
eq_( expected_buffer, result_buffer )
|
||||
eq_( line_offset, 0 )
|
||||
eq_( char_offset, 1 )
|
||||
|
||||
|
||||
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,
|
||||
'Eb\nbFb\nbG',
|
||||
0,
|
||||
0,
|
||||
result_buffer )
|
||||
expected_buffer = [ "aAa", "aEb", "bFb", "bGCa", "aDe" ]
|
||||
eq_( expected_buffer, result_buffer )
|
||||
eq_( line_offset, 1 )
|
||||
eq_( char_offset, 1 )
|
||||
|
||||
|
||||
def ReplaceChunk_MultipleLinesToLessMultipleLines_test():
|
||||
result_buffer = [ "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" ]
|
||||
eq_( expected_buffer, result_buffer )
|
||||
eq_( line_offset, -1 )
|
||||
eq_( char_offset, 1 )
|
||||
|
||||
|
||||
def ReplaceChunk_MultipleLinesToEvenLessMultipleLines_test():
|
||||
result_buffer = [ "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" ]
|
||||
eq_( expected_buffer, result_buffer )
|
||||
eq_( line_offset, -2 )
|
||||
eq_( char_offset, 1 )
|
||||
|
||||
|
||||
def ReplaceChunk_SpanBufferEdge_test():
|
||||
result_buffer = [ "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" ]
|
||||
eq_( expected_buffer, result_buffer )
|
||||
eq_( line_offset, 0 )
|
||||
eq_( char_offset, 1 )
|
||||
|
||||
|
||||
def ReplaceChunk_DeleteTextInLine_test():
|
||||
result_buffer = [ "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" ]
|
||||
eq_( expected_buffer, result_buffer )
|
||||
eq_( line_offset, 0 )
|
||||
eq_( char_offset, -1 )
|
||||
|
||||
|
||||
def ReplaceChunk_AddTextInLine_test():
|
||||
result_buffer = [ "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" ]
|
||||
eq_( expected_buffer, result_buffer )
|
||||
eq_( line_offset, 0 )
|
||||
eq_( char_offset, 3 )
|
||||
|
||||
|
||||
def ReplaceChunk_ReplaceTextInLine_test():
|
||||
result_buffer = [ "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" ]
|
||||
eq_( expected_buffer, result_buffer )
|
||||
eq_( line_offset, 0 )
|
||||
eq_( char_offset, 2 )
|
||||
|
||||
|
||||
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" ]
|
||||
eq_( expected_buffer, result_buffer )
|
||||
eq_( line_offset, 0 )
|
||||
eq_( char_offset, 2 )
|
||||
|
||||
|
||||
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" ]
|
||||
eq_( 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" ]
|
||||
eq_( 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" ]
|
||||
eq_( expected_buffer, result_buffer )
|
||||
eq_( line_offset, 1 )
|
||||
eq_( char_offset, 1 )
|
||||
|
||||
|
||||
def _BuildLocations( start_line, start_column, end_line, end_column ):
|
||||
return {
|
||||
'line_num' : start_line,
|
||||
'column_num': start_column,
|
||||
}, {
|
||||
'line_num' : end_line,
|
||||
'column_num': end_column,
|
||||
}
|
@ -245,11 +245,15 @@ def ConvertDiagnosticsToQfList( diagnostics ):
|
||||
if line_num < 1:
|
||||
line_num = 1
|
||||
|
||||
text = diagnostic[ 'text' ]
|
||||
if diagnostic.get( 'fixit_available', False ):
|
||||
text += ' (FixIt available)'
|
||||
|
||||
return {
|
||||
'bufnr' : GetBufferNumberForFilename( location[ 'filepath' ] ),
|
||||
'lnum' : line_num,
|
||||
'col' : location[ 'column_num' ],
|
||||
'text' : ToUtf8IfNeeded( diagnostic[ 'text' ] ),
|
||||
'text' : ToUtf8IfNeeded( text ),
|
||||
'type' : diagnostic[ 'kind' ][ 0 ],
|
||||
'valid' : 1
|
||||
}
|
||||
@ -454,3 +458,48 @@ def GetBoolValue( variable ):
|
||||
def GetIntValue( variable ):
|
||||
return int( vim.eval( variable ) )
|
||||
|
||||
|
||||
# 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
|
||||
# * 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.
|
||||
def ReplaceChunk( start, end, replacement_text, line_delta, char_delta,
|
||||
vim_buffer = None ):
|
||||
if vim_buffer is None:
|
||||
vim_buffer = vim.current.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
|
||||
source_lines_count = end_line - start_line + 1
|
||||
start_column = start[ 'column_num' ] - 1 + char_delta
|
||||
end_column = end[ 'column_num' ] - 1
|
||||
if source_lines_count == 1:
|
||||
end_column += char_delta
|
||||
|
||||
replacement_lines = replacement_text.splitlines( False )
|
||||
if not replacement_lines:
|
||||
replacement_lines = [ '' ]
|
||||
replacement_lines_count = len( replacement_lines )
|
||||
|
||||
end_existing_text = vim_buffer[ end_line ][ end_column : ]
|
||||
start_existing_text = 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
|
||||
|
||||
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[:]
|
||||
|
||||
new_line_delta = replacement_lines_count - source_lines_count
|
||||
return ( new_line_delta, new_char_delta )
|
||||
|
Loading…
Reference in New Issue
Block a user