Skip to content

Commit

Permalink
Initial attempt on LSP Code Actions.
Browse files Browse the repository at this point in the history
  • Loading branch information
daliusd committed Nov 9, 2020
1 parent a762e32 commit f09e7d5
Show file tree
Hide file tree
Showing 6 changed files with 193 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',
\})
58 changes: 58 additions & 0 deletions autoload/ale/code_action.vim
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,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
106 changes: 76 additions & 30 deletions autoload/ale/codefix.vim
Original file line number Diff line number Diff line change
Expand Up @@ -144,41 +144,79 @@ function! ale#codefix#HandleTSServerResponse(conn_id, response) abort
endif
endfunction

function! s:OnReady(line, column, end_line, end_column, linter, lsp_details) abort
let l:id = a:lsp_details.connection_id
function! ale#codefix#HandleLSPResponse(conn_id, response) abort
if has_key(a:response, 'id')
\&& has_key(s:codefix_map, a:response.id)
let l:options = remove(s:codefix_map, a:response.id)

if a:linter.lsp isnot# 'tsserver'
call s:message('ALECodeAction currently only works with tsserver')
if !has_key(a:response, 'result') || type(a:response.result) != v:t_list
call s:message('No code actions received from server')

return
return
endif

let l:codeaction_no = 1
let l:codeactionstring = "Code Fixes:\n"

for l:codeaction in a:response.result
let l:codeactionstring .= l:codeaction_no . ') ' . l:codeaction.title . "\n"
let l:codeaction_no += 1
endfor

let l:codeactionstring .= 'Type number and <Enter> (empty cancels): '

let l:codeaction_to_apply = ale#util#Input(l:codeactionstring, '')
let l:codeaction_to_apply = str2nr(l:codeaction_to_apply)

if l:codeaction_to_apply == 0
return
endif

let l:changes_map = ale#code_action#GetChanges(a:response.result[l:codeaction_to_apply - 1].edit)

if empty(l:changes_map)
return
endif

let l:changes = ale#code_action#BuildChangesList(l:changes_map)

call ale#code_action#HandleCodeAction({
\ 'description': 'codeaction',
\ 'changes': l:changes,
\}, {})
endif
endfunction


function! s:OnReady(line, column, end_line, end_column, linter, lsp_details) abort
let l:id = a:lsp_details.connection_id

if !ale#lsp#HasCapability(l:id, 'code_actions')
return
endif

let l:buffer = a:lsp_details.buffer

if a:line == a:end_line && a:column == a:end_column
if !has_key(g:ale_buffer_info, l:buffer)
return
endif
if a:linter.lsp is# 'tsserver'
if a:line == a:end_line && a:column == a:end_column
if !has_key(g:ale_buffer_info, l:buffer)
return
endif

let l:nearest_error = v:null
let l:nearest_error_diff = -1
let l:nearest_error = v:null
let l:nearest_error_diff = -1

for l:error in get(g:ale_buffer_info[l:buffer], 'loclist', [])
if has_key(l:error, 'code') && l:error.lnum == a:line
let l:diff = abs(l:error.col - a:column)
for l:error in get(g:ale_buffer_info[l:buffer], 'loclist', [])
if has_key(l:error, 'code') && l:error.lnum == a:line
let l:diff = abs(l:error.col - a:column)

if l:nearest_error_diff == -1 || l:diff < l:nearest_error_diff
let l:nearest_error_diff = l:diff
let l:nearest_error = l:error.code
if l:nearest_error_diff == -1 || l:diff < l:nearest_error_diff
let l:nearest_error_diff = l:diff
let l:nearest_error = l:error.code
endif
endif
endif
endfor
endfor

if a:linter.lsp is# 'tsserver'
let l:message = ale#lsp#tsserver_message#GetCodeFixes(
\ l:buffer,
\ a:line,
Expand All @@ -187,9 +225,7 @@ function! s:OnReady(line, column, end_line, end_column, linter, lsp_details) abo
\ a:column,
\ [l:nearest_error],
\)
endif
else
if a:linter.lsp is# 'tsserver'
else
let l:message = ale#lsp#tsserver_message#GetApplicableRefactors(
\ l:buffer,
\ a:line,
Expand All @@ -198,9 +234,23 @@ function! s:OnReady(line, column, end_line, end_column, linter, lsp_details) abo
\ a:end_column,
\)
endif
else
" Send a message saying the buffer has changed first, otherwise
" completions won't know what text is nearby.
call ale#lsp#NotifyForChanges(l:id, l:buffer)

let l:message = ale#lsp#message#CodeAction(
\ l:buffer,
\ a:line,
\ a:column,
\ a:end_line,
\ a:end_column,
\)
endif

let l:Callback = function('ale#codefix#HandleTSServerResponse')
let l:Callback = a:linter.lsp is# 'tsserver'
\ ? function('ale#codefix#HandleTSServerResponse')
\ : function('ale#codefix#HandleLSPResponse')

call ale#lsp#RegisterCallback(l:id, l:Callback)

Expand All @@ -226,14 +276,10 @@ function! s:ExecuteGetCodeFix(linter, range) abort
else
let [l:line, l:column] = getpos("'<")[1:2]
let [l:end_line, l:end_column] = getpos("'>")[1:2]

let l:column = min([l:column, len(getline(l:line))])
let l:end_column = min([l:end_column, len(getline(l:end_line))])
endif

if a:linter.lsp isnot# 'tsserver'
let l:column = min([l:column, len(getline(l:line))])
endif
let l:column = min([l:column, len(getline(l:line))])
let l:end_column = min([l:end_column, len(getline(l:end_line))])

let l:Callback = function(
\ 's:OnReady', [l:line, l:column, l:end_line, l:end_column])
Expand Down
8 changes: 8 additions & 0 deletions autoload/ale/lsp.vim
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,14 @@ function! s:UpdateCapabilities(conn, capabilities) abort
let a:conn.capabilities.rename = 1
endif

if get(a:capabilities, 'codeActionProvider') is v:true
let a:conn.capabilities.code_actions = 1
endif

if type(get(a:capabilities, 'codeActionProvider')) is v:t_dict
let a:conn.capabilities.code_actions = 1
endif

if !empty(get(a:capabilities, 'completionProvider'))
let a:conn.capabilities.completion = 1
endif
Expand Down
15 changes: 15 additions & 0 deletions autoload/ale/lsp/message.vim
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,18 @@ function! ale#lsp#message#Rename(buffer, line, column, new_name) abort
\ 'newName': a:new_name,
\}]
endfunction

function! ale#lsp#message#CodeAction(buffer, line, column, end_line, end_column) abort
return [0, 'textDocument/codeAction', {
\ 'textDocument': {
\ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')),
\ },
\ 'range': {
\ 'start': {'line': a:line - 1, 'character': a:column - 1},
\ 'end': {'line': a:end_line - 1, 'character': a:end_column - 1},
\ },
\ 'context': {
\ 'diagnostics': []
\ },
\}]
endfunction
56 changes: 2 additions & 54 deletions autoload/ale/rename.vim
Original file line number Diff line number Diff line change
Expand Up @@ -90,31 +90,6 @@ function! ale#rename#HandleTSServerResponse(conn_id, response) abort
\)
endfunction

function! s: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#rename#HandleLSPResponse(conn_id, response) abort
if has_key(a:response, 'id')
\&& has_key(s:rename_map, a:response.id)
Expand All @@ -126,42 +101,15 @@ function! ale#rename#HandleLSPResponse(conn_id, response) abort
return
endif

let l:changes_map = s:getChanges(a:response.result)
let l:changes_map = ale#code_action#GetChanges(a:response.result)

if empty(l:changes_map)
call s:message('No changes received from server')

return
endif

let l:changes = []

for l:file_name in keys(l:changes_map)
let l:text_edits = l: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
let l:changes = ale#code_action#BuildChangesList(l:changes_map)

call ale#code_action#HandleCodeAction(
\ {
Expand Down

0 comments on commit f09e7d5

Please sign in to comment.