Support refactorings through code actions. (#359)

* Support refactorings through code actions.

- make LspCodeAction work on a selected range, to support extract method
or extract variable refactorings
- properly execute commands returned by code actions
- support workspace/applyEdit requests from server

* Fixes to match the Google VimScript Style Guide

* Refactor handling of requests from server.

Introduce an on_request option in lsp#client#start, that will be called
every time a request is received from a server.

* Use robust operator ==#

* Move apply_workspace_edit to separate file.
This commit is contained in:
Tomasz Zurkowski
2019-04-06 12:26:04 -04:00
committed by Prabir Shrestha
parent 6608aad006
commit a79fb04d36
5 changed files with 140 additions and 34 deletions

View File

@@ -331,20 +331,52 @@ function! lsp#ui#vim#document_symbol() abort
echo 'Retrieving document symbols ...'
endfunction
" Returns currently selected range. If nothing is selected, returns empty
" dictionary.
"
" @returns
" Range - https://microsoft.github.io/language-server-protocol/specification#range
function! s:get_visual_selection_range() abort
" TODO: unify this method with s:get_visual_selection_pos()
let [l:line_start, l:column_start] = getpos("'<")[1:2]
let [l:line_end, l:column_end] = getpos("'>")[1:2]
call lsp#log([l:line_start, l:column_start, l:line_end, l:column_end])
if l:line_start == 0
return {}
endif
" For line selection, column_end is a very large number, so trim it to
" number of characters in this line.
if l:column_end - 1 > len(getline(l:line_end))
let l:column_end = len(getline(l:line_end)) + 1
endif
return {
\ 'start': { 'line': l:line_start - 1, 'character': l:column_start - 1 },
\ 'end': { 'line': l:line_end - 1, 'character': l:column_end - 1 },
\}
endfunction
" https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction
function! lsp#ui#vim#code_action() abort
let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_code_action_provider(v:val)')
let s:last_req_id = s:last_req_id + 1
let s:diagnostics = lsp#ui#vim#diagnostics#get_diagnostics_under_cursor()
let l:diagnostic = lsp#ui#vim#diagnostics#get_diagnostics_under_cursor()
if len(l:servers) == 0
call s:not_supported('Code action')
return
endif
if len(s:diagnostics) == 0
echo 'No diagnostics found under the cursors'
return
let l:range = s:get_visual_selection_range()
if empty(l:range)
if empty(l:diagnostic)
echo 'No diagnostics found under the cursors'
return
else
let l:range = l:diagnostic['range']
let l:diagnostics = [l:diagnostic]
end
else
let l:diagnostics = []
endif
for l:server in l:servers
@@ -352,9 +384,9 @@ function! lsp#ui#vim#code_action() abort
\ 'method': 'textDocument/codeAction',
\ 'params': {
\ 'textDocument': lsp#get_text_document_identifier(),
\ 'range': s:diagnostics['range'],
\ 'range': l:range,
\ 'context': {
\ 'diagnostics' : [s:diagnostics],
\ 'diagnostics' : l:diagnostics,
\ },
\ },
\ 'on_notification': function('s:handle_code_action', [l:server, s:last_req_id, 'codeAction']),
@@ -459,7 +491,7 @@ function! s:handle_workspace_edit(server, last_req_id, type, data) abort
return
endif
call s:apply_workspace_edits(a:data['response']['result'])
call lsp#utils#workspace_edit#apply_workspace_edit(a:data['response']['result'])
echo 'Renamed'
endfunction
@@ -486,6 +518,7 @@ function! s:handle_code_action(server, last_req_id, type, data) abort
endif
let l:codeActions = a:data['response']['result']
let l:index = 0
let l:choices = []
@@ -505,35 +538,41 @@ function! s:handle_code_action(server, last_req_id, type, data) abort
let l:choice = inputlist(l:choices)
if l:choice > 0 && l:choice <= l:index
call lsp#log('s:handle_code_action', l:codeActions[l:choice - 1]['arguments'][0])
call s:apply_workspace_edits(l:codeActions[l:choice - 1]['arguments'][0])
call s:execute_command_or_code_action(a:server, l:codeActions[l:choice - 1])
endif
endfunction
" @params
" workspace_edits - https://microsoft.github.io/language-server-protocol/specification#workspaceedit
function! s:apply_workspace_edits(workspace_edits) abort
if has_key(a:workspace_edits, 'changes')
let l:cur_buffer = bufnr('%')
let l:view = winsaveview()
for [l:uri, l:text_edits] in items(a:workspace_edits['changes'])
call lsp#utils#text_edit#apply_text_edits(l:uri, l:text_edits)
endfor
if l:cur_buffer !=# bufnr('%')
execute 'keepjumps keepalt b ' . l:cur_buffer
" server - string
" comand_or_code_action - Command | CodeAction
function! s:execute_command_or_code_action(server, command_or_code_action) abort
if has_key(a:command_or_code_action, 'command') && type(a:command_or_code_action['command']) == type('')
let l:command = a:command_or_code_action
call s:execute_command(a:server, l:command)
else
let l:code_action = a:command_or_code_action
if has_key(l:code_action, 'edit')
call lsp#utils#workspace_edit#apply_workspace_edit(a:command_or_code_action['edit'])
endif
call winrestview(l:view)
endif
if has_key(a:workspace_edits, 'documentChanges')
let l:cur_buffer = bufnr('%')
let l:view = winsaveview()
for l:text_document_edit in a:workspace_edits['documentChanges']
call lsp#utils#text_edit#apply_text_edits(l:text_document_edit['textDocument']['uri'], l:text_document_edit['edits'])
endfor
if l:cur_buffer !=# bufnr('%')
execute 'keepjumps keepalt b ' . l:cur_buffer
if has_key(l:code_action, 'command')
call s:execute_command(a:server, l:code_action['command'])
endif
call winrestview(l:view)
endif
endfunction
" Sends workspace/executeCommand with given command.
" @params
" server - string
" command - https://microsoft.github.io/language-server-protocol/specification#command
function! s:execute_command(server, command) abort
let l:params = {'command': a:command['command']}
if has_key(a:command, 'arguments')
let l:params['arguments'] = a:command['arguments']
endif
call lsp#send_request(a:server, {
\ 'method': 'workspace/executeCommand',
\ 'params': l:params,
\ })
endfunction