Fix #3312 - Fix a false positive for auto imports

ALE was incorrectly detecting completion results from servers such as
rust-analyzer as wanting to add import lines when additionalTextEdits
was present, but empty.

Now ALE only filters out completion results if the autoimport setting is
off, and one of the additionalTextEdits starts on some line other than
the current line. If any additionalTextEdits happen to be identical to
the change from completion anyway, ALE will skip them.
This commit is contained in:
w0rp 2020-08-27 08:44:43 +01:00
parent 3e2abe3f25
commit 396fba7cca
No known key found for this signature in database
GPG Key ID: 0FC1ECAA8C81CD83
2 changed files with 119 additions and 17 deletions

View File

@ -496,6 +496,18 @@ function! ale#completion#NullFilter(buffer, item) abort
return 1 return 1
endfunction endfunction
" Check if additional text edits make changes starting on lines other than the
" one you're asking for completions on.
function! s:TextEditsChangeOtherLines(line, text_edit_list) abort
for l:edit in a:text_edit_list
if l:edit.range.start.line + 1 isnot a:line
return 1
endif
endfor
return 0
endfunction
function! ale#completion#ParseLSPCompletions(response) abort function! ale#completion#ParseLSPCompletions(response) abort
let l:buffer = bufnr('') let l:buffer = bufnr('')
let l:info = get(b:, 'ale_completion_info', {}) let l:info = get(b:, 'ale_completion_info', {})
@ -540,7 +552,9 @@ function! ale#completion#ParseLSPCompletions(response) abort
" Don't use LSP items with additional text edits when autoimport for " Don't use LSP items with additional text edits when autoimport for
" completions is turned off. " completions is turned off.
if has_key(l:item, 'additionalTextEdits') && !g:ale_completion_autoimport if has_key(l:item, 'additionalTextEdits')
\&& !g:ale_completion_autoimport
\&& s:TextEditsChangeOtherLines(l:info.line, l:item.additionalTextEdits)
continue continue
endif endif
@ -562,31 +576,41 @@ function! ale#completion#ParseLSPCompletions(response) abort
let l:text_changes = [] let l:text_changes = []
for l:edit in l:item.additionalTextEdits for l:edit in l:item.additionalTextEdits
let l:range = l:edit.range " Don't apply additional text edits that are identical to the
" word we're going to insert anyway.
if l:edit.newText is# l:word
\&& l:edit.range.start.line + 1 is l:info.line
\&& l:edit.range.end.line + 1 is l:info.line
\&& l:edit.range.start.character is l:edit.range.end.character
continue
endif
call add(l:text_changes, { call add(l:text_changes, {
\ 'start': { \ 'start': {
\ 'line': l:range.start.line + 1, \ 'line': l:edit.range.start.line + 1,
\ 'offset': l:range.start.character + 1, \ 'offset': l:edit.range.start.character + 1,
\ }, \ },
\ 'end': { \ 'end': {
\ 'line': l:range.end.line + 1, \ 'line': l:edit.range.end.line + 1,
\ 'offset': l:range.end.character + 1, \ 'offset': l:edit.range.end.character + 1,
\ }, \ },
\ 'newText': l:edit.newText, \ 'newText': l:edit.newText,
\}) \})
endfor endfor
let l:changes = [{ if !empty(l:text_changes)
\ 'fileName': expand('#' . l:buffer . ':p'), let l:result.user_data = json_encode({
\ 'textChanges': l:text_changes, \ 'codeActions': [{
\}] \ 'description': 'completion',
\ \ 'changes': [
let l:result.user_data = json_encode({ \ {
\ 'codeActions': [{ \ 'fileName': expand('#' . l:buffer . ':p'),
\ 'description': 'completion', \ 'textChanges': l:text_changes,
\ 'changes': l:changes, \ }
\ }], \ ],
\ }) \ }],
\})
endif
endif endif
call add(l:results, l:result) call add(l:results, l:result)
@ -900,6 +924,8 @@ function! ale#completion#Done() abort
endfunction endfunction
augroup ALECompletionActions augroup ALECompletionActions
autocmd!
autocmd CompleteDone * call ale#completion#HandleUserData(v:completed_item) autocmd CompleteDone * call ale#completion#HandleUserData(v:completed_item)
augroup END augroup END

View File

@ -537,6 +537,7 @@ Execute(Should handle completion messages with the deprecated insertText attribu
Execute(Should handle completion messages with additionalTextEdits when ale_completion_autoimport is turned on): Execute(Should handle completion messages with additionalTextEdits when ale_completion_autoimport is turned on):
let g:ale_completion_autoimport = 1 let g:ale_completion_autoimport = 1
let b:ale_completion_info = {'line': 30}
AssertEqual AssertEqual
\ [ \ [
@ -591,6 +592,19 @@ Execute(Should handle completion messages with additionalTextEdits when ale_comp
\ { \ {
\ 'range': { \ 'range': {
\ 'start': { \ 'start': {
\ 'line': 29,
\ 'character': 10,
\ },
\ 'end': {
\ 'line': 29,
\ 'character': 10,
\ },
\ },
\ 'newText': 'next_callback',
\ },
\ {
\ 'range': {
\ 'start': {
\ 'line': 10, \ 'line': 10,
\ 'character': 1, \ 'character': 1,
\ }, \ },
@ -609,6 +623,7 @@ Execute(Should handle completion messages with additionalTextEdits when ale_comp
Execute(Should not handle completion messages with additionalTextEdits when ale_completion_autoimport is turned off): Execute(Should not handle completion messages with additionalTextEdits when ale_completion_autoimport is turned off):
let g:ale_completion_autoimport = 0 let g:ale_completion_autoimport = 0
let b:ale_completion_info = {'line': 30}
AssertEqual AssertEqual
\ [], \ [],
@ -630,6 +645,19 @@ Execute(Should not handle completion messages with additionalTextEdits when ale_
\ { \ {
\ 'range': { \ 'range': {
\ 'start': { \ 'start': {
\ 'line': 29,
\ 'character': 10,
\ },
\ 'end': {
\ 'line': 29,
\ 'character': 10,
\ },
\ },
\ 'newText': 'next_callback',
\ },
\ {
\ 'range': {
\ 'start': {
\ 'line': 10, \ 'line': 10,
\ 'character': 1, \ 'character': 1,
\ }, \ },
@ -645,3 +673,51 @@ Execute(Should not handle completion messages with additionalTextEdits when ale_
\ ], \ ],
\ }, \ },
\ }) \ })
Execute(Should still handle completion messages with additionalTextEdits with ale_completion_autoimport turned off, if edits all start on the line):
let g:ale_completion_autoimport = 0
let b:ale_completion_info = {'line': 30}
AssertEqual
\ [
\ {
\ 'word': 'next_callback',
\ 'menu': 'PlayTimeCallback',
\ 'info': '',
\ 'kind': 'v',
\ 'icase': 1,
\ }
\ ],
\ ale#completion#ParseLSPCompletions({
\ 'id': 226,
\ 'jsonrpc': '2.0',
\ 'result': {
\ 'isIncomplete': v:false,
\ 'items': [
\ {
\ 'detail': 'PlayTimeCallback',
\ 'filterText': 'next_callback',
\ 'insertText': 'next_callback',
\ 'insertTextFormat': 1,
\ 'kind': 6,
\ 'label': ' next_callback',
\ 'sortText': '3ee19999next_callback',
\ 'additionalTextEdits': [
\ {
\ 'range': {
\ 'start': {
\ 'line': 29,
\ 'character': 10,
\ },
\ 'end': {
\ 'line': 29,
\ 'character': 10,
\ },
\ },
\ 'newText': 'next_callback',
\ },
\ ],
\ },
\ ],
\ },
\ })