mirror of
https://github.com/prabirshrestha/vim-lsp.git
synced 2026-06-09 15:37:30 +02:00
06f2c12604
For code actions, some people might find it more convenient to use a popup menu that shows up at the cursor position, instead of using the quickpick window at the bottom of the screen.
208 lines
8.1 KiB
VimL
208 lines
8.1 KiB
VimL
" vint: -ProhibitUnusedVariable
|
|
|
|
function! lsp#ui#vim#code_action#complete(input, command, len) abort
|
|
let l:server_names = filter(lsp#get_allowed_servers(), 'lsp#capabilities#has_code_action_provider(v:val)')
|
|
let l:kinds = []
|
|
for l:server_name in l:server_names
|
|
let l:kinds += lsp#capabilities#get_code_action_kinds(l:server_name)
|
|
endfor
|
|
return filter(copy(l:kinds), { _, kind -> kind =~ '^' . a:input })
|
|
endfunction
|
|
|
|
"
|
|
" @param option = {
|
|
" selection: v:true | v:false = Provide by CommandLine like `:'<,'>LspCodeAction`
|
|
" sync: v:true | v:false = Specify enable synchronous request. Example use case is `BufWritePre`
|
|
" query: string = Specify code action kind query. If query provided and then filtered code action is only one, invoke code action immediately.
|
|
" ui: 'float' | 'preview'
|
|
" }
|
|
"
|
|
function! lsp#ui#vim#code_action#do(option) abort
|
|
let l:selection = get(a:option, 'selection', v:false)
|
|
let l:sync = get(a:option, 'sync', v:false)
|
|
let l:query = get(a:option, 'query', '')
|
|
let l:ui = get(a:option, 'ui', g:lsp_code_action_ui)
|
|
if empty(l:ui)
|
|
let l:ui = lsp#utils#_has_popup_menu() ? 'float' : 'preview'
|
|
endif
|
|
|
|
let l:server_names = filter(lsp#get_allowed_servers(), 'lsp#capabilities#has_code_action_provider(v:val)')
|
|
if len(l:server_names) == 0
|
|
return lsp#utils#error('Code action not supported for ' . &filetype)
|
|
endif
|
|
|
|
if l:selection
|
|
let l:range = lsp#utils#range#_get_recent_visual_range()
|
|
else
|
|
let l:range = lsp#utils#range#_get_current_line_range()
|
|
endif
|
|
|
|
let l:ctx = {
|
|
\ 'count': len(l:server_names),
|
|
\ 'results': [],
|
|
\}
|
|
let l:bufnr = bufnr('%')
|
|
let l:command_id = lsp#_new_command()
|
|
for l:server_name in l:server_names
|
|
let l:diagnostic = lsp#internal#diagnostics#under_cursor#get_diagnostic({'server': l:server_name})
|
|
call lsp#send_request(l:server_name, {
|
|
\ 'method': 'textDocument/codeAction',
|
|
\ 'params': {
|
|
\ 'textDocument': lsp#get_text_document_identifier(),
|
|
\ 'range': empty(l:diagnostic) || l:selection ? l:range : l:diagnostic['range'],
|
|
\ 'context': {
|
|
\ 'diagnostics' : empty(l:diagnostic) ? [] : [l:diagnostic],
|
|
\ 'only': ['', 'quickfix', 'refactor', 'refactor.extract', 'refactor.inline', 'refactor.rewrite', 'source', 'source.organizeImports'],
|
|
\ },
|
|
\ },
|
|
\ 'sync': l:sync,
|
|
\ 'on_notification': function('s:handle_code_action', [l:ui, l:ctx, l:server_name, l:command_id, l:sync, l:query, l:bufnr]),
|
|
\ })
|
|
endfor
|
|
echo 'Retrieving code actions ...'
|
|
endfunction
|
|
|
|
function! s:handle_code_action(ui, ctx, server_name, command_id, sync, query, bufnr, data) abort
|
|
" Ignore old request.
|
|
if a:command_id != lsp#_last_command()
|
|
return
|
|
endif
|
|
|
|
call add(a:ctx['results'], {
|
|
\ 'server_name': a:server_name,
|
|
\ 'data': a:data,
|
|
\})
|
|
let a:ctx['count'] -= 1
|
|
if a:ctx['count'] ># 0
|
|
return
|
|
endif
|
|
|
|
let l:total_code_actions = []
|
|
for l:result in a:ctx['results']
|
|
let l:server_name = l:result['server_name']
|
|
let l:data = l:result['data']
|
|
" Check response error.
|
|
if lsp#client#is_error(l:data['response'])
|
|
call lsp#utils#error('Failed to CodeAction for ' . l:server_name . ': ' . lsp#client#error_message(l:data['response']))
|
|
continue
|
|
endif
|
|
|
|
" Check code actions.
|
|
let l:code_actions = l:data['response']['result']
|
|
|
|
" Filter code actions.
|
|
if !empty(a:query)
|
|
let l:code_actions = filter(l:code_actions, { _, action -> get(action, 'kind', '') =~# '^' . a:query })
|
|
endif
|
|
if empty(l:code_actions)
|
|
continue
|
|
endif
|
|
|
|
for l:code_action in l:code_actions
|
|
let l:item = {
|
|
\ 'server_name': l:server_name,
|
|
\ 'code_action': l:code_action,
|
|
\ }
|
|
if get(l:code_action, 'isPreferred', v:false)
|
|
let l:total_code_actions = [l:item] + l:total_code_actions
|
|
else
|
|
call add(l:total_code_actions, l:item)
|
|
endif
|
|
endfor
|
|
endfor
|
|
|
|
if len(l:total_code_actions) == 0
|
|
echo 'No code actions found'
|
|
return
|
|
endif
|
|
call lsp#log('s:handle_code_action', l:total_code_actions)
|
|
|
|
if len(l:total_code_actions) == 1 && !empty(a:query)
|
|
let l:action = l:total_code_actions[0]
|
|
if s:handle_disabled_action(l:action) | return | endif
|
|
" Clear 'Retrieving code actions ...' message
|
|
echo ''
|
|
call s:handle_one_code_action(l:action['server_name'], a:sync, a:bufnr, l:action['code_action'])
|
|
return
|
|
endif
|
|
|
|
" Prompt to choose code actions when empty query provided.
|
|
let l:items = []
|
|
for l:action in l:total_code_actions
|
|
let l:title = printf('[%s] %s', l:action['server_name'], l:action['code_action']['title'])
|
|
if has_key(l:action['code_action'], 'kind') && l:action['code_action']['kind'] !=# ''
|
|
let l:title .= ' (' . l:action['code_action']['kind'] . ')'
|
|
endif
|
|
call add(l:items, { 'title': l:title, 'item': l:action })
|
|
endfor
|
|
|
|
if lsp#utils#_has_popup_menu() && a:ui ==? 'float'
|
|
call lsp#internal#ui#popupmenu#open({
|
|
\ 'title': 'Code actions',
|
|
\ 'items': mapnew(l:items, { idx, item -> item.title}),
|
|
\ 'pos': 'topleft',
|
|
\ 'line': 'cursor+1',
|
|
\ 'col': 'cursor',
|
|
\ 'callback': funcref('s:popup_accept_code_action', [a:sync, a:bufnr, l:items]),
|
|
\ })
|
|
else
|
|
call lsp#internal#ui#quickpick#open({
|
|
\ 'items': l:items,
|
|
\ 'key': 'title',
|
|
\ 'on_accept': funcref('s:quickpick_accept_code_action', [a:sync, a:bufnr]),
|
|
\ })
|
|
endif
|
|
endfunction
|
|
|
|
function! s:popup_accept_code_action(sync, bufnr, items, id, selected, ...) abort
|
|
if a:selected <= 0 | return | endif
|
|
let l:item = a:items[a:selected - 1]['item']
|
|
if s:handle_disabled_action(l:item) | return | endif
|
|
call s:handle_one_code_action(l:item['server_name'], a:sync, a:bufnr, l:item['code_action'])
|
|
execute('doautocmd <nomodeline> User lsp_float_closed')
|
|
endfunction
|
|
|
|
function! s:quickpick_accept_code_action(sync, bufnr, data, ...) abort
|
|
call lsp#internal#ui#quickpick#close()
|
|
if empty(a:data['items']) | return | endif
|
|
let l:selected = a:data['items'][0]['item']
|
|
if s:handle_disabled_action(l:selected) | return | endif
|
|
call s:handle_one_code_action(l:selected['server_name'], a:sync, a:bufnr, l:selected['code_action'])
|
|
endfunction
|
|
|
|
function! s:handle_disabled_action(code_action) abort
|
|
if has_key(a:code_action, 'disabled')
|
|
echo 'This action is disabled: ' . a:code_action['disabled']['reason']
|
|
return v:true
|
|
endif
|
|
return v:false
|
|
endfunction
|
|
|
|
function! s:handle_one_code_action(server_name, sync, bufnr, command_or_code_action) abort
|
|
" has WorkspaceEdit.
|
|
if has_key(a:command_or_code_action, 'edit')
|
|
call lsp#utils#workspace_edit#apply_workspace_edit(a:command_or_code_action['edit'])
|
|
endif
|
|
|
|
" Command.
|
|
if has_key(a:command_or_code_action, 'command') && type(a:command_or_code_action['command']) == type('')
|
|
call lsp#ui#vim#execute_command#_execute({
|
|
\ 'server_name': a:server_name,
|
|
\ 'command_name': get(a:command_or_code_action, 'command', ''),
|
|
\ 'command_args': get(a:command_or_code_action, 'arguments', v:null),
|
|
\ 'sync': a:sync,
|
|
\ 'bufnr': a:bufnr,
|
|
\ })
|
|
|
|
" has Command.
|
|
elseif has_key(a:command_or_code_action, 'command') && type(a:command_or_code_action['command']) == type({})
|
|
call lsp#ui#vim#execute_command#_execute({
|
|
\ 'server_name': a:server_name,
|
|
\ 'command_name': get(a:command_or_code_action['command'], 'command', ''),
|
|
\ 'command_args': get(a:command_or_code_action['command'], 'arguments', v:null),
|
|
\ 'sync': a:sync,
|
|
\ 'bufnr': a:bufnr,
|
|
\ })
|
|
endif
|
|
endfunction
|