Files
vim-lsp-mirror/autoload/lsp/ui/vim.vim
hrsh7th 70234feca4 Improve code action (#663)
* Improve code action

* Add LspCodeActionSync

* Fix miss argument

* Fix for the review

* Add utils and tests

* Remove unused function
2020-01-09 08:35:39 -08:00

583 lines
20 KiB
VimL

function! s:not_supported(what) abort
return lsp#utils#error(a:what.' not supported for '.&filetype)
endfunction
function! lsp#ui#vim#implementation(in_preview) abort
let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_implementation_provider(v:val)')
let l:command_id = lsp#_new_command()
call setqflist([])
if len(l:servers) == 0
call s:not_supported('Retrieving implementation')
return
endif
let l:ctx = { 'counter': len(l:servers), 'list':[], 'last_command_id': l:command_id, 'jump_if_one': 1, 'in_preview': a:in_preview }
for l:server in l:servers
call lsp#send_request(l:server, {
\ 'method': 'textDocument/implementation',
\ 'params': {
\ 'textDocument': lsp#get_text_document_identifier(),
\ 'position': lsp#get_position(),
\ },
\ 'on_notification': function('s:handle_location', [l:ctx, l:server, 'implementation']),
\ })
endfor
echo 'Retrieving implementation ...'
endfunction
function! lsp#ui#vim#type_definition(in_preview) abort
let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_type_definition_provider(v:val)')
let l:command_id = lsp#_new_command()
call setqflist([])
if len(l:servers) == 0
call s:not_supported('Retrieving type definition')
return
endif
let l:ctx = { 'counter': len(l:servers), 'list':[], 'last_command_id': l:command_id, 'jump_if_one': 1, 'in_preview': a:in_preview }
for l:server in l:servers
call lsp#send_request(l:server, {
\ 'method': 'textDocument/typeDefinition',
\ 'params': {
\ 'textDocument': lsp#get_text_document_identifier(),
\ 'position': lsp#get_position(),
\ },
\ 'on_notification': function('s:handle_location', [l:ctx, l:server, 'type definition']),
\ })
endfor
echo 'Retrieving type definition ...'
endfunction
function! lsp#ui#vim#type_hierarchy() abort
let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_type_hierarchy_provider(v:val)')
let l:command_id = lsp#_new_command()
if len(l:servers) == 0
call s:not_supported('Retrieving type hierarchy')
return
endif
let l:ctx = { 'counter': len(l:servers), 'list':[], 'last_command_id': l:command_id }
" direction 0 children, 1 parent, 2 both
for l:server in l:servers
call lsp#send_request(l:server, {
\ 'method': 'textDocument/typeHierarchy',
\ 'params': {
\ 'textDocument': lsp#get_text_document_identifier(),
\ 'position': lsp#get_position(),
\ 'direction': 2,
\ 'resolve': 1,
\ },
\ 'on_notification': function('s:handle_type_hierarchy', [l:ctx, l:server, 'type hierarchy']),
\ })
endfor
echo 'Retrieving type hierarchy ...'
endfunction
function! lsp#ui#vim#declaration(in_preview) abort
let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_declaration_provider(v:val)')
let l:command_id = lsp#_new_command()
call setqflist([])
if len(l:servers) == 0
call s:not_supported('Retrieving declaration')
return
endif
let l:ctx = { 'counter': len(l:servers), 'list':[], 'last_command_id': l:command_id, 'jump_if_one': 1, 'in_preview': a:in_preview }
for l:server in l:servers
call lsp#send_request(l:server, {
\ 'method': 'textDocument/declaration',
\ 'params': {
\ 'textDocument': lsp#get_text_document_identifier(),
\ 'position': lsp#get_position(),
\ },
\ 'on_notification': function('s:handle_location', [l:ctx, l:server, 'declaration']),
\ })
endfor
echo 'Retrieving declaration ...'
endfunction
function! lsp#ui#vim#definition(in_preview) abort
let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_definition_provider(v:val)')
let l:command_id = lsp#_new_command()
call setqflist([])
if len(l:servers) == 0
call s:not_supported('Retrieving definition')
return
endif
let l:ctx = { 'counter': len(l:servers), 'list':[], 'last_command_id': l:command_id, 'jump_if_one': 1, 'in_preview': a:in_preview }
for l:server in l:servers
call lsp#send_request(l:server, {
\ 'method': 'textDocument/definition',
\ 'params': {
\ 'textDocument': lsp#get_text_document_identifier(),
\ 'position': lsp#get_position(),
\ },
\ 'on_notification': function('s:handle_location', [l:ctx, l:server, 'definition']),
\ })
endfor
echo 'Retrieving definition ...'
endfunction
function! lsp#ui#vim#references() abort
let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_references_provider(v:val)')
let l:command_id = lsp#_new_command()
call setqflist([])
let l:ctx = { 'counter': len(l:servers), 'list':[], 'last_command_id': l:command_id, 'jump_if_one': 0, 'in_preview': 0 }
if len(l:servers) == 0
call s:not_supported('Retrieving references')
return
endif
for l:server in l:servers
call lsp#send_request(l:server, {
\ 'method': 'textDocument/references',
\ 'params': {
\ 'textDocument': lsp#get_text_document_identifier(),
\ 'position': lsp#get_position(),
\ 'context': {'includeDeclaration': v:false},
\ },
\ 'on_notification': function('s:handle_location', [l:ctx, l:server, 'references']),
\ })
endfor
echo 'Retrieving references ...'
endfunction
function! s:rename(server, new_name, pos) abort
if empty(a:new_name)
echo '... Renaming aborted ...'
return
endif
" needs to flush existing open buffers
call lsp#send_request(a:server, {
\ 'method': 'textDocument/rename',
\ 'params': {
\ 'textDocument': lsp#get_text_document_identifier(),
\ 'position': a:pos,
\ 'newName': a:new_name,
\ },
\ 'on_notification': function('s:handle_workspace_edit', [a:server, lsp#_last_command(), 'rename']),
\ })
echo ' ... Renaming ...'
endfunction
function! lsp#ui#vim#rename() abort
let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_rename_prepare_provider(v:val)')
let l:prepare_support = 1
if len(l:servers) == 0
let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_rename_provider(v:val)')
let l:prepare_support = 0
endif
let l:command_id = lsp#_new_command()
if len(l:servers) == 0
call s:not_supported('Renaming')
return
endif
" TODO: ask the user which server it should use to rename if there are multiple
let l:server = l:servers[0]
if l:prepare_support
call lsp#send_request(l:server, {
\ 'method': 'textDocument/prepareRename',
\ 'params': {
\ 'textDocument': lsp#get_text_document_identifier(),
\ 'position': lsp#get_position(),
\ },
\ 'on_notification': function('s:handle_rename_prepare', [l:server, l:command_id, 'rename_prepare']),
\ })
return
endif
call s:rename(l:server, input('new name: ', expand('<cword>')), lsp#get_position())
endfunction
function! s:document_format(sync) abort
let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_document_formatting_provider(v:val)')
let l:command_id = lsp#_new_command()
if len(l:servers) == 0
call s:not_supported('Document formatting')
return
endif
" TODO: ask user to select server for formatting
let l:server = l:servers[0]
redraw | echo 'Formatting document ...'
call lsp#send_request(l:server, {
\ 'method': 'textDocument/formatting',
\ 'params': {
\ 'textDocument': lsp#get_text_document_identifier(),
\ 'options': {
\ 'tabSize': getbufvar(bufnr('%'), '&tabstop'),
\ 'insertSpaces': getbufvar(bufnr('%'), '&expandtab') ? v:true : v:false,
\ },
\ },
\ 'sync': a:sync,
\ 'on_notification': function('s:handle_text_edit', [l:server, l:command_id, 'document format']),
\ })
endfunction
function! lsp#ui#vim#document_format_sync() abort
let l:mode = mode()
if l:mode =~# '[vV]' || l:mode ==# "\<C-V>"
return s:document_format_range(1)
endif
return s:document_format(1)
endfunction
function! lsp#ui#vim#document_format() abort
let l:mode = mode()
if l:mode =~# '[vV]' || l:mode ==# "\<C-V>"
return s:document_format_range(0)
endif
return s:document_format(0)
endfunction
function! lsp#ui#vim#stop_server(...) abort
let l:name = get(a:000, 0, '')
for l:server in lsp#get_whitelisted_servers()
if !empty(l:name) && l:server != l:name
continue
endif
echo 'Stopping' l:server 'server ...'
call lsp#stop_server(server)
endfor
endfunction
function! s:get_selection_pos(type) abort
if a:type ==? 'v'
let l:start_pos = getpos("'<")[1:2]
let l:end_pos = getpos("'>")[1:2]
" fix end_pos column (see :h getpos() and :h 'selection')
let l:end_line = getline(l:end_pos[0])
let l:offset = (&selection ==# 'inclusive' ? 1 : 2)
let l:end_pos[1] = len(l:end_line[:l:end_pos[1]-l:offset])
" edge case: single character selected with selection=exclusive
if l:start_pos[0] == l:end_pos[0] && l:start_pos[1] > l:end_pos[1]
let l:end_pos[1] = l:start_pos[1]
endif
elseif a:type ==? 'line'
let l:start_pos = [line("'["), 1]
let l:end_lnum = line("']")
let l:end_pos = [line("']"), len(getline(l:end_lnum))]
elseif a:type ==? 'char'
let l:start_pos = getpos("'[")[1:2]
let l:end_pos = getpos("']")[1:2]
else
let l:start_pos = [0, 0]
let l:end_pos = [0, 0]
endif
return l:start_pos + l:end_pos
endfunction
function! s:document_format_range(sync, type) abort
let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_document_range_formatting_provider(v:val)')
let l:command_id = lsp#_new_command()
if len(l:servers) == 0
call s:not_supported('Document range formatting')
return
endif
" TODO: ask user to select server for formatting
let l:server = l:servers[0]
let [l:start_lnum, l:start_col, l:end_lnum, l:end_col] = s:get_selection_pos(a:type)
let l:start_char = lsp#utils#to_char('%', l:start_lnum, l:start_col)
let l:end_char = lsp#utils#to_char('%', l:end_lnum, l:end_col)
redraw | echo 'Formatting document range ...'
call lsp#send_request(l:server, {
\ 'method': 'textDocument/rangeFormatting',
\ 'params': {
\ 'textDocument': lsp#get_text_document_identifier(),
\ 'range': {
\ 'start': { 'line': l:start_lnum - 1, 'character': l:start_char },
\ 'end': { 'line': l:end_lnum - 1, 'character': l:end_char },
\ },
\ 'options': {
\ 'tabSize': getbufvar(bufnr('%'), '&shiftwidth'),
\ 'insertSpaces': getbufvar(bufnr('%'), '&expandtab') ? v:true : v:false,
\ },
\ },
\ 'sync': a:sync,
\ 'on_notification': function('s:handle_text_edit', [l:server, l:command_id, 'range format']),
\ })
endfunction
function! lsp#ui#vim#document_range_format_sync() abort
return s:document_format_range(1, visualmode())
endfunction
function! lsp#ui#vim#document_range_format() abort
return s:document_format_range(0, visualmode())
endfunction
function! lsp#ui#vim#document_range_format_opfunc(type) abort
return s:document_format_range(1, a:type)
endfunction
function! lsp#ui#vim#workspace_symbol() abort
let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_workspace_symbol_provider(v:val)')
let l:command_id = lsp#_new_command()
call setqflist([])
if len(l:servers) == 0
call s:not_supported('Retrieving workspace symbols')
return
endif
let l:query = input('query>')
for l:server in l:servers
call lsp#send_request(l:server, {
\ 'method': 'workspace/symbol',
\ 'params': {
\ 'query': l:query,
\ },
\ 'on_notification': function('s:handle_symbol', [l:server, l:command_id, 'workspaceSymbol']),
\ })
endfor
echo 'Retrieving workspace symbols ...'
endfunction
function! lsp#ui#vim#document_symbol() abort
let l:servers = filter(lsp#get_whitelisted_servers(), 'lsp#capabilities#has_document_symbol_provider(v:val)')
let l:command_id = lsp#_new_command()
call setqflist([])
if len(l:servers) == 0
call s:not_supported('Retrieving symbols')
return
endif
for l:server in l:servers
call lsp#send_request(l:server, {
\ 'method': 'textDocument/documentSymbol',
\ 'params': {
\ 'textDocument': lsp#get_text_document_identifier(),
\ },
\ 'on_notification': function('s:handle_symbol', [l:server, l:command_id, 'documentSymbol']),
\ })
endfor
echo 'Retrieving document symbols ...'
endfunction
function! s:handle_symbol(server, last_command_id, type, data) abort
if a:last_command_id != lsp#_last_command()
return
endif
if lsp#client#is_error(a:data['response'])
call lsp#utils#error('Failed to retrieve '. a:type . ' for ' . a:server . ': ' . lsp#client#error_message(a:data['response']))
return
endif
let l:list = lsp#ui#vim#utils#symbols_to_loc_list(a:server, a:data)
call setqflist(l:list)
if empty(l:list)
call lsp#utils#error('No ' . a:type .' found')
else
echo 'Retrieved ' . a:type
botright copen
endif
endfunction
function! s:handle_location(ctx, server, type, data) abort "ctx = {counter, list, jump_if_one, last_command_id, in_preview}
if a:ctx['last_command_id'] != lsp#_last_command()
return
endif
let a:ctx['counter'] = a:ctx['counter'] - 1
if lsp#client#is_error(a:data['response']) || !has_key(a:data['response'], 'result')
call lsp#utils#error('Failed to retrieve '. a:type . ' for ' . a:server . ': ' . lsp#client#error_message(a:data['response']))
else
let a:ctx['list'] = a:ctx['list'] + lsp#utils#location#_lsp_to_vim_list(a:data['response']['result'])
endif
if a:ctx['counter'] == 0
if empty(a:ctx['list'])
call lsp#utils#error('No ' . a:type .' found')
else
call lsp#utils#tagstack#_update()
let l:loc = a:ctx['list'][0]
if len(a:ctx['list']) == 1 && a:ctx['jump_if_one'] && !a:ctx['in_preview']
call lsp#utils#location#_open_vim_list_item(l:loc)
echo 'Retrieved ' . a:type
redraw
elseif !a:ctx['in_preview']
call setqflist(a:ctx['list'])
echo 'Retrieved ' . a:type
botright copen
else
let l:lines = readfile(fnameescape(l:loc['filename']))
if has_key(l:loc,'viewstart') " showing a locationLink
let l:view = l:lines[l:loc['viewstart'] : l:loc['viewend']]
call lsp#ui#vim#output#preview(a:server, l:view, {
\ 'statusline': ' LSP Peek ' . a:type,
\ 'filetype': &filetype
\ })
else " showing a location
call lsp#ui#vim#output#preview(a:server, l:lines, {
\ 'statusline': ' LSP Peek ' . a:type,
\ 'cursor': { 'line': l:loc['lnum'], 'col': l:loc['col'], 'align': g:lsp_peek_alignment },
\ 'filetype': &filetype
\ })
endif
endif
endif
endif
endfunction
function! s:handle_rename_prepare(server, last_command_id, type, data) abort
if a:last_command_id != lsp#_last_command()
return
endif
if lsp#client#is_error(a:data['response'])
call lsp#utils#error('Failed to retrieve '. a:type . ' for ' . a:server . ': ' . lsp#client#error_message(a:data['response']))
return
endif
let l:range = a:data['response']['result']
let l:lines = getline(1, '$')
let [l:start_line, l:start_col] = lsp#utils#position#_lsp_to_vim('%', l:range['start'])
let [l:end_line, l:end_col] = lsp#utils#position#_lsp_to_vim('%', l:range['end'])
if l:start_line ==# l:end_line
let l:name = l:lines[l:start_line - 1][l:start_col - 1 : l:end_col - 2]
else
let l:name = l:lines[l:start_line - 1][l:start_col - 1 :]
for l:i in range(l:start_line, l:end_line - 2)
let l:name .= "\n" . l:lines[l:i]
endfor
if l:end_col - 2 < 0
let l:name .= "\n"
else
let l:name .= l:lines[l:end_line - 1][: l:end_col - 2]
endif
endif
call timer_start(1, {x->s:rename(a:server, input('new name: ', l:name), l:range['start'])})
endfunction
function! s:handle_workspace_edit(server, last_command_id, type, data) abort
if a:last_command_id != lsp#_last_command()
return
endif
if lsp#client#is_error(a:data['response'])
call lsp#utils#error('Failed to retrieve '. a:type . ' for ' . a:server . ': ' . lsp#client#error_message(a:data['response']))
return
endif
call lsp#utils#workspace_edit#apply_workspace_edit(a:data['response']['result'])
echo 'Renamed'
endfunction
function! s:handle_text_edit(server, last_command_id, type, data) abort
if a:last_command_id != lsp#_last_command()
return
endif
if lsp#client#is_error(a:data['response'])
call lsp#utils#error('Failed to '. a:type . ' for ' . a:server . ': ' . lsp#client#error_message(a:data['response']))
return
endif
call lsp#utils#text_edit#apply_text_edits(a:data['request']['params']['textDocument']['uri'], a:data['response']['result'])
redraw | echo 'Document formatted'
endfunction
function! s:handle_type_hierarchy(ctx, server, type, data) abort "ctx = {counter, list, last_command_id}
if a:ctx['last_command_id'] != lsp#_last_command()
return
endif
if lsp#client#is_error(a:data['response'])
call lsp#utils#error('Failed to '. a:type . ' for ' . a:server . ': ' . lsp#client#error_message(a:data['response']))
return
endif
if empty(a:data['response']['result'])
echo 'No type hierarchy found'
return
endif
" Create new buffer in a split
let l:position = 'topleft'
let l:orientation = 'new'
exec l:position . ' ' . 10 . l:orientation
let l:provider = {
\ 'root': a:data['response']['result'],
\ 'root_state': 'expanded',
\ 'bufnr': bufnr('%'),
\ 'getChildren': function('s:get_children_for_tree_hierarchy'),
\ 'getParent': function('s:get_parent_for_tree_hierarchy'),
\ 'getTreeItem': function('s:get_treeitem_for_tree_hierarchy'),
\ }
call lsp#utils#tree#new(l:provider)
echo 'Retrieved type hierarchy'
endfunction
function! s:hierarchyitem_to_treeitem(hierarchyitem) abort
return {
\ 'id': a:hierarchyitem,
\ 'label': a:hierarchyitem['name'],
\ 'command': function('s:hierarchy_treeitem_command', [a:hierarchyitem]),
\ 'collapsibleState': has_key(a:hierarchyitem, 'parents') && !empty(a:hierarchyitem['parents']) ? 'expanded' : 'none',
\ }
endfunction
function! s:hierarchy_treeitem_command(hierarchyitem) abort
bwipeout
call lsp#utils#tagstack#_update()
call lsp#utils#location#_open_lsp_location(a:hierarchyitem)
endfunction
function! s:get_children_for_tree_hierarchy(Callback, ...) dict abort
if a:0 == 0
call a:Callback('success', [l:self['root']])
return
else
call a:Callback('success', a:1['parents'])
endif
endfunction
function! s:get_parent_for_tree_hierarchy(...) dict abort
" TODO
endfunction
function! s:get_treeitem_for_tree_hierarchy(Callback, object) dict abort
call a:Callback('success', s:hierarchyitem_to_treeitem(a:object))
endfunction