Skip to content

Commit

Permalink
Support for LSP/tsserver Code Actions (dense-analysis#3437)
Browse files Browse the repository at this point in the history
* Added tsserver and LSP code action support.
* tsserver refactors support added.
* Handling special case when new text is added after new line symbol.
* ale#code_action#ApplyChanges simplified.
* Initial attempt on LSP Code Actions.
* workspace/executeCommand added.
* Some null checks added.
* Add last column to LSP Code Action message.
* Pass diagnostics to LSP code action.

Previously ApplyChanges code was applied from top-to-bottom that required 
extra parameters to track progress and there was bug. I have changed code
to bottom-to-top approach as that does not require those extra parameters
and solved the bug.

Tested with typescript-language-server and it is working.
  • Loading branch information
daliusd authored and ivorpeles committed Nov 22, 2020
1 parent 53ea4c4 commit 5beb9b0
Show file tree
Hide file tree
Showing 15 changed files with 1,258 additions and 84 deletions.
34 changes: 34 additions & 0 deletions ale_linters/python/jedils.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
" Author: Dalius Dobravolskas <dalius.dobravolskas@gmail.com>
" Description: https://github.com/pappasam/jedi-language-server

call ale#Set('python_jedils_executable', 'jedi-language-server')
call ale#Set('python_jedils_use_global', get(g:, 'ale_use_global_executables', 0))
call ale#Set('python_jedils_auto_pipenv', 0)

function! ale_linters#python#jedils#GetExecutable(buffer) abort
if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_jedils_auto_pipenv'))
\ && ale#python#PipenvPresent(a:buffer)
return 'pipenv'
endif

return ale#python#FindExecutable(a:buffer, 'python_jedils', ['jedi-language-server'])
endfunction

function! ale_linters#python#jedils#GetCommand(buffer) abort
let l:executable = ale_linters#python#jedils#GetExecutable(a:buffer)

let l:exec_args = l:executable =~? 'pipenv$'
\ ? ' run jedi-language-server'
\ : ''

return ale#Escape(l:executable) . l:exec_args
endfunction

call ale#linter#Define('python', {
\ 'name': 'jedils',
\ 'lsp': 'stdio',
\ 'executable': function('ale_linters#python#jedils#GetExecutable'),
\ 'command': function('ale_linters#python#jedils#GetCommand'),
\ 'project_root': function('ale#python#FindProjectRoot'),
\ 'completion_filter': 'ale#completion#python#CompletionItemFilter',
\})
106 changes: 77 additions & 29 deletions autoload/ale/code_action.vim
Original file line number Diff line number Diff line change
Expand Up @@ -33,35 +33,35 @@ endfunction

function! s:ChangeCmp(left, right) abort
if a:left.start.line < a:right.start.line
return -1
return 1
endif

if a:left.start.line > a:right.start.line
return 1
return -1
endif

if a:left.start.offset < a:right.start.offset
return -1
return 1
endif

if a:left.start.offset > a:right.start.offset
return 1
return -1
endif

if a:left.end.line < a:right.end.line
return -1
return 1
endif

if a:left.end.line > a:right.end.line
return 1
return -1
endif

if a:left.end.offset < a:right.end.offset
return -1
return 1
endif

if a:left.end.offset > a:right.end.offset
return 1
return -1
endif

return 0
Expand All @@ -85,29 +85,14 @@ function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
let l:pos = [1, 1]
endif

" We have to keep track of how many lines we have added, and offset
" changes accordingly.
let l:line_offset = 0
let l:column_offset = 0
let l:last_end_line = 0

" Changes have to be sorted so we apply them from top-to-bottom.
" Changes have to be sorted so we apply them from bottom-to-top
for l:code_edit in sort(copy(a:changes), function('s:ChangeCmp'))
if l:code_edit.start.line isnot l:last_end_line
let l:column_offset = 0
endif

let l:line = l:code_edit.start.line + l:line_offset
let l:column = l:code_edit.start.offset + l:column_offset
let l:end_line = l:code_edit.end.line + l:line_offset
let l:end_column = l:code_edit.end.offset + l:column_offset
let l:line = l:code_edit.start.line
let l:column = l:code_edit.start.offset
let l:end_line = l:code_edit.end.line
let l:end_column = l:code_edit.end.offset
let l:text = l:code_edit.newText

let l:cur_line = l:pos[0]
let l:cur_column = l:pos[1]

let l:last_end_line = l:end_line

" Adjust the ends according to previous edits.
if l:end_line > len(l:lines)
let l:end_line_len = 0
Expand All @@ -125,6 +110,12 @@ function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
let l:start = l:lines[: l:line - 2]
endif

" Special case when text must be added after new line
if l:column > len(l:lines[l:line - 1])
call extend(l:start, [l:lines[l:line - 1]])
let l:column = 1
endif

if l:column is 1
" We need to handle column 1 specially, because we can't slice an
" empty string ending on index 0.
Expand All @@ -140,7 +131,6 @@ function! ale#code_action#ApplyChanges(filename, changes, should_save) abort
let l:lines = l:start + l:middle + l:lines[l:end_line :]

let l:current_line_offset = len(l:lines) - l:lines_before_change
let l:line_offset += l:current_line_offset
let l:column_offset = len(l:middle[-1]) - l:end_line_len

let l:pos = s:UpdateCursor(l:pos,
Expand Down Expand Up @@ -215,3 +205,61 @@ function! s:UpdateCursor(cursor, start, end, offset) abort

return [l:cur_line, l:cur_column]
endfunction

function! ale#code_action#GetChanges(workspace_edit) abort
let l:changes = {}

if has_key(a:workspace_edit, 'changes') && !empty(a:workspace_edit.changes)
return a:workspace_edit.changes
elseif has_key(a:workspace_edit, 'documentChanges')
let l:document_changes = []

if type(a:workspace_edit.documentChanges) is v:t_dict
\ && has_key(a:workspace_edit.documentChanges, 'edits')
call add(l:document_changes, a:workspace_edit.documentChanges)
elseif type(a:workspace_edit.documentChanges) is v:t_list
let l:document_changes = a:workspace_edit.documentChanges
endif

for l:text_document_edit in l:document_changes
let l:filename = l:text_document_edit.textDocument.uri
let l:edits = l:text_document_edit.edits
let l:changes[l:filename] = l:edits
endfor
endif

return l:changes
endfunction

function! ale#code_action#BuildChangesList(changes_map) abort
let l:changes = []

for l:file_name in keys(a:changes_map)
let l:text_edits = a:changes_map[l:file_name]
let l:text_changes = []

for l:edit in l:text_edits
let l:range = l:edit.range
let l:new_text = l:edit.newText

call add(l:text_changes, {
\ 'start': {
\ 'line': l:range.start.line + 1,
\ 'offset': l:range.start.character + 1,
\ },
\ 'end': {
\ 'line': l:range.end.line + 1,
\ 'offset': l:range.end.character + 1,
\ },
\ 'newText': l:new_text,
\})
endfor

call add(l:changes, {
\ 'fileName': ale#path#FromURI(l:file_name),
\ 'textChanges': l:text_changes,
\})
endfor

return l:changes
endfunction
Loading

0 comments on commit 5beb9b0

Please sign in to comment.