mirror of
https://github.com/prabirshrestha/vim-lsp.git
synced 2025-12-14 20:35:59 +01:00
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:
committed by
Prabir Shrestha
parent
6608aad006
commit
a79fb04d36
@@ -351,6 +351,7 @@ function! s:ensure_start(buf, server_name, cb) abort
|
|||||||
\ 'on_stderr': function('s:on_stderr', [a:server_name]),
|
\ 'on_stderr': function('s:on_stderr', [a:server_name]),
|
||||||
\ 'on_exit': function('s:on_exit', [a:server_name]),
|
\ 'on_exit': function('s:on_exit', [a:server_name]),
|
||||||
\ 'on_notification': function('s:on_notification', [a:server_name]),
|
\ 'on_notification': function('s:on_notification', [a:server_name]),
|
||||||
|
\ 'on_request': function('s:on_request', [a:server_name]),
|
||||||
\ })
|
\ })
|
||||||
|
|
||||||
if l:lsp_id > 0
|
if l:lsp_id > 0
|
||||||
@@ -562,6 +563,13 @@ function! s:send_notification(server_name, data) abort
|
|||||||
call lsp#client#send_notification(l:lsp_id, a:data)
|
call lsp#client#send_notification(l:lsp_id, a:data)
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! s:send_response(server_name, data) abort
|
||||||
|
let l:lsp_id = s:servers[a:server_name]['lsp_id']
|
||||||
|
let l:data = copy(a:data)
|
||||||
|
call lsp#log_verbose('--->', l:lsp_id, a:server_name, l:data)
|
||||||
|
call lsp#client#send_response(l:lsp_id, a:data)
|
||||||
|
endfunction
|
||||||
|
|
||||||
function! s:on_stderr(server_name, id, data, event) abort
|
function! s:on_stderr(server_name, id, data, event) abort
|
||||||
call lsp#log_verbose('<---(stderr)', a:id, a:server_name, a:data)
|
call lsp#log_verbose('<---(stderr)', a:id, a:server_name, a:data)
|
||||||
endfunction
|
endfunction
|
||||||
@@ -604,6 +612,17 @@ function! s:on_notification(server_name, id, data, event) abort
|
|||||||
endfor
|
endfor
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! s:on_request(server_name, id, request) abort
|
||||||
|
call lsp#log_verbose('<---', a:id, a:request)
|
||||||
|
if a:request['method'] ==# 'workspace/applyEdit'
|
||||||
|
call lsp#utils#workspace_edit#apply_workspace_edit(a:request['params']['edit'])
|
||||||
|
call s:send_response(a:server_name, { 'id': a:request['id'], 'result': { 'applied': v:true } })
|
||||||
|
else
|
||||||
|
" Error returned according to json-rpc specification.
|
||||||
|
call s:send_response(a:server_name, { 'id': a:request['id'], 'error': { 'code': -32601, 'message': 'Method not found' } })
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
function! s:handle_initialize(server_name, data) abort
|
function! s:handle_initialize(server_name, data) abort
|
||||||
let l:response = a:data['response']
|
let l:response = a:data['response']
|
||||||
let l:server = s:servers[a:server_name]
|
let l:server = s:servers[a:server_name]
|
||||||
|
|||||||
@@ -82,7 +82,13 @@ function! s:on_stdout(id, data, event) abort
|
|||||||
if exists('l:response')
|
if exists('l:response')
|
||||||
" call appropriate callbacks
|
" call appropriate callbacks
|
||||||
let l:on_notification_data = { 'response': l:response }
|
let l:on_notification_data = { 'response': l:response }
|
||||||
if has_key(l:response, 'id')
|
if has_key(l:response, 'method') && has_key(l:response, 'id')
|
||||||
|
" it is a request from a server
|
||||||
|
let l:request = l:response
|
||||||
|
if has_key(l:ctx['opts'], 'on_request')
|
||||||
|
call l:ctx['opts']['on_request'](a:id, l:request)
|
||||||
|
endif
|
||||||
|
elseif has_key(l:response, 'id')
|
||||||
" it is a request->response
|
" it is a request->response
|
||||||
if !(type(l:response['id']) == type(0) || type(l:response['id']) == type(''))
|
if !(type(l:response['id']) == type(0) || type(l:response['id']) == type(''))
|
||||||
" response['id'] can be number | string | null based on the spec
|
" response['id'] can be number | string | null based on the spec
|
||||||
@@ -199,13 +205,14 @@ endfunction
|
|||||||
|
|
||||||
let s:send_type_request = 1
|
let s:send_type_request = 1
|
||||||
let s:send_type_notification = 2
|
let s:send_type_notification = 2
|
||||||
function! s:lsp_send(id, opts, type) abort " opts = { method, params?, on_notification }
|
let s:send_type_response = 3
|
||||||
|
function! s:lsp_send(id, opts, type) abort " opts = { id?, method?, result?, params?, on_notification }
|
||||||
let l:ctx = get(s:clients, a:id, {})
|
let l:ctx = get(s:clients, a:id, {})
|
||||||
if empty(l:ctx)
|
if empty(l:ctx)
|
||||||
return -1
|
return -1
|
||||||
endif
|
endif
|
||||||
|
|
||||||
let l:request = { 'jsonrpc': '2.0', 'method': a:opts['method'] }
|
let l:request = { 'jsonrpc': '2.0' }
|
||||||
|
|
||||||
if (a:type == s:send_type_request)
|
if (a:type == s:send_type_request)
|
||||||
let l:ctx['request_sequence'] = l:ctx['request_sequence'] + 1
|
let l:ctx['request_sequence'] = l:ctx['request_sequence'] + 1
|
||||||
@@ -216,9 +223,21 @@ function! s:lsp_send(id, opts, type) abort " opts = { method, params?, on_notifi
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if has_key(a:opts, 'id')
|
||||||
|
let l:request['id'] = a:opts['id']
|
||||||
|
endif
|
||||||
|
if has_key(a:opts, 'method')
|
||||||
|
let l:request['method'] = a:opts['method']
|
||||||
|
endif
|
||||||
if has_key(a:opts, 'params')
|
if has_key(a:opts, 'params')
|
||||||
let l:request['params'] = a:opts['params']
|
let l:request['params'] = a:opts['params']
|
||||||
endif
|
endif
|
||||||
|
if has_key(a:opts, 'result')
|
||||||
|
let l:request['result'] = a:opts['result']
|
||||||
|
endif
|
||||||
|
if has_key(a:opts, 'error')
|
||||||
|
let l:request['error'] = a:opts['error']
|
||||||
|
endif
|
||||||
|
|
||||||
let l:json = json_encode(l:request)
|
let l:json = json_encode(l:request)
|
||||||
let l:payload = 'Content-Length: ' . len(l:json) . "\r\n\r\n" . l:json
|
let l:payload = 'Content-Length: ' . len(l:json) . "\r\n\r\n" . l:json
|
||||||
@@ -275,6 +294,10 @@ function! lsp#client#send_notification(client_id, opts) abort
|
|||||||
return s:lsp_send(a:client_id, a:opts, s:send_type_notification)
|
return s:lsp_send(a:client_id, a:opts, s:send_type_notification)
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! lsp#client#send_response(client_id, opts) abort
|
||||||
|
return s:lsp_send(a:client_id, a:opts, s:send_type_response)
|
||||||
|
endfunction
|
||||||
|
|
||||||
function! lsp#client#get_last_request_id(client_id) abort
|
function! lsp#client#get_last_request_id(client_id) abort
|
||||||
return s:lsp_get_last_request_id(a:client_id)
|
return s:lsp_get_last_request_id(a:client_id)
|
||||||
endfunction
|
endfunction
|
||||||
|
|||||||
@@ -331,20 +331,52 @@ function! lsp#ui#vim#document_symbol() abort
|
|||||||
echo 'Retrieving document symbols ...'
|
echo 'Retrieving document symbols ...'
|
||||||
endfunction
|
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
|
" https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction
|
||||||
function! lsp#ui#vim#code_action() abort
|
function! lsp#ui#vim#code_action() abort
|
||||||
let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_code_action_provider(v:val)')
|
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: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
|
if len(l:servers) == 0
|
||||||
call s:not_supported('Code action')
|
call s:not_supported('Code action')
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if len(s:diagnostics) == 0
|
let l:range = s:get_visual_selection_range()
|
||||||
echo 'No diagnostics found under the cursors'
|
if empty(l:range)
|
||||||
return
|
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
|
endif
|
||||||
|
|
||||||
for l:server in l:servers
|
for l:server in l:servers
|
||||||
@@ -352,9 +384,9 @@ function! lsp#ui#vim#code_action() abort
|
|||||||
\ 'method': 'textDocument/codeAction',
|
\ 'method': 'textDocument/codeAction',
|
||||||
\ 'params': {
|
\ 'params': {
|
||||||
\ 'textDocument': lsp#get_text_document_identifier(),
|
\ 'textDocument': lsp#get_text_document_identifier(),
|
||||||
\ 'range': s:diagnostics['range'],
|
\ 'range': l:range,
|
||||||
\ 'context': {
|
\ 'context': {
|
||||||
\ 'diagnostics' : [s:diagnostics],
|
\ 'diagnostics' : l:diagnostics,
|
||||||
\ },
|
\ },
|
||||||
\ },
|
\ },
|
||||||
\ 'on_notification': function('s:handle_code_action', [l:server, s:last_req_id, 'codeAction']),
|
\ '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
|
return
|
||||||
endif
|
endif
|
||||||
|
|
||||||
call s:apply_workspace_edits(a:data['response']['result'])
|
call lsp#utils#workspace_edit#apply_workspace_edit(a:data['response']['result'])
|
||||||
|
|
||||||
echo 'Renamed'
|
echo 'Renamed'
|
||||||
endfunction
|
endfunction
|
||||||
@@ -486,6 +518,7 @@ function! s:handle_code_action(server, last_req_id, type, data) abort
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
let l:codeActions = a:data['response']['result']
|
let l:codeActions = a:data['response']['result']
|
||||||
|
|
||||||
let l:index = 0
|
let l:index = 0
|
||||||
let l:choices = []
|
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)
|
let l:choice = inputlist(l:choices)
|
||||||
|
|
||||||
if l:choice > 0 && l:choice <= l:index
|
if l:choice > 0 && l:choice <= l:index
|
||||||
call lsp#log('s:handle_code_action', l:codeActions[l:choice - 1]['arguments'][0])
|
call s:execute_command_or_code_action(a:server, l:codeActions[l:choice - 1])
|
||||||
call s:apply_workspace_edits(l:codeActions[l:choice - 1]['arguments'][0])
|
|
||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
" @params
|
" @params
|
||||||
" workspace_edits - https://microsoft.github.io/language-server-protocol/specification#workspaceedit
|
" server - string
|
||||||
function! s:apply_workspace_edits(workspace_edits) abort
|
" comand_or_code_action - Command | CodeAction
|
||||||
if has_key(a:workspace_edits, 'changes')
|
function! s:execute_command_or_code_action(server, command_or_code_action) abort
|
||||||
let l:cur_buffer = bufnr('%')
|
if has_key(a:command_or_code_action, 'command') && type(a:command_or_code_action['command']) == type('')
|
||||||
let l:view = winsaveview()
|
let l:command = a:command_or_code_action
|
||||||
for [l:uri, l:text_edits] in items(a:workspace_edits['changes'])
|
call s:execute_command(a:server, l:command)
|
||||||
call lsp#utils#text_edit#apply_text_edits(l:uri, l:text_edits)
|
else
|
||||||
endfor
|
let l:code_action = a:command_or_code_action
|
||||||
if l:cur_buffer !=# bufnr('%')
|
if has_key(l:code_action, 'edit')
|
||||||
execute 'keepjumps keepalt b ' . l:cur_buffer
|
call lsp#utils#workspace_edit#apply_workspace_edit(a:command_or_code_action['edit'])
|
||||||
endif
|
endif
|
||||||
call winrestview(l:view)
|
if has_key(l:code_action, 'command')
|
||||||
endif
|
call s:execute_command(a:server, l:code_action['command'])
|
||||||
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
|
|
||||||
endif
|
endif
|
||||||
call winrestview(l:view)
|
|
||||||
endif
|
endif
|
||||||
endfunction
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
25
autoload/lsp/utils/workspace_edit.vim
Normal file
25
autoload/lsp/utils/workspace_edit.vim
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
" Applies WorkspaceEdit changes.
|
||||||
|
function! lsp#utils#workspace_edit#apply_workspace_edit(workspace_edit) abort
|
||||||
|
if has_key(a:workspace_edit, 'changes')
|
||||||
|
let l:cur_buffer = bufnr('%')
|
||||||
|
let l:view = winsaveview()
|
||||||
|
for [l:uri, l:text_edits] in items(a:workspace_edit['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
|
||||||
|
endif
|
||||||
|
call winrestview(l:view)
|
||||||
|
endif
|
||||||
|
if has_key(a:workspace_edit, 'documentChanges')
|
||||||
|
let l:cur_buffer = bufnr('%')
|
||||||
|
let l:view = winsaveview()
|
||||||
|
for l:text_document_edit in a:workspace_edit['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
|
||||||
|
endif
|
||||||
|
call winrestview(l:view)
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
@@ -31,7 +31,7 @@ if g:lsp_auto_enable
|
|||||||
augroup END
|
augroup END
|
||||||
endif
|
endif
|
||||||
|
|
||||||
command! LspCodeAction call lsp#ui#vim#code_action()
|
command! -range LspCodeAction call lsp#ui#vim#code_action()
|
||||||
command! LspDeclaration call lsp#ui#vim#declaration()
|
command! LspDeclaration call lsp#ui#vim#declaration()
|
||||||
command! LspDefinition call lsp#ui#vim#definition()
|
command! LspDefinition call lsp#ui#vim#definition()
|
||||||
command! LspDocumentSymbol call lsp#ui#vim#document_symbol()
|
command! LspDocumentSymbol call lsp#ui#vim#document_symbol()
|
||||||
|
|||||||
Reference in New Issue
Block a user