mirror of
https://github.com/prabirshrestha/vim-lsp.git
synced 2025-12-14 20:35:59 +01:00
Add an option to use a popup menu for code actions (#1361)
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.
This commit is contained in:
39
autoload/lsp/internal/ui/popupmenu.vim
Normal file
39
autoload/lsp/internal/ui/popupmenu.vim
Normal file
@@ -0,0 +1,39 @@
|
||||
let s:Markdown = vital#lsp#import('VS.Vim.Syntax.Markdown')
|
||||
let s:Window = vital#lsp#import('VS.Vim.Window')
|
||||
|
||||
function! lsp#internal#ui#popupmenu#open(opt) abort
|
||||
let l:Callback = remove(a:opt, 'callback')
|
||||
let l:items = remove(a:opt, 'items')
|
||||
|
||||
let l:items_with_shortcuts= map(l:items, {
|
||||
\ idx, item -> ((idx < 9) ? '['.(idx+1).'] ' : '').item
|
||||
\ })
|
||||
|
||||
function! Filter(id, key) abort closure
|
||||
if a:key >= 1 && a:key <= len(l:items)
|
||||
call popup_close(a:id, a:key)
|
||||
elseif a:key ==# "\<C-j>"
|
||||
call win_execute(a:id, 'normal! j')
|
||||
elseif a:key ==# "\<C-k>"
|
||||
call win_execute(a:id, 'normal! k')
|
||||
else
|
||||
return popup_filter_menu(a:id, a:key)
|
||||
endif
|
||||
|
||||
return v:true
|
||||
endfunction
|
||||
|
||||
let l:popup_opt = extend({
|
||||
\ 'callback': funcref('s:callback', [l:Callback]),
|
||||
\ 'filter': funcref('Filter'),
|
||||
\ }, a:opt)
|
||||
|
||||
let l:winid = popup_menu(l:items_with_shortcuts, l:popup_opt)
|
||||
call s:Window.do(l:winid, { -> s:Markdown.apply() })
|
||||
execute('doautocmd <nomodeline> User lsp_float_opened')
|
||||
endfunction
|
||||
|
||||
function! s:callback(callback, id, selected) abort
|
||||
call a:callback(a:id, a:selected)
|
||||
execute('doautocmd <nomodeline> User lsp_float_closed')
|
||||
endfunction
|
||||
@@ -331,12 +331,12 @@ function! s:handle_text_edit(server, last_command_id, type, data) abort
|
||||
redraw | echo 'Document formatted'
|
||||
endfunction
|
||||
|
||||
function! lsp#ui#vim#code_action() abort
|
||||
call lsp#ui#vim#code_action#do({
|
||||
function! lsp#ui#vim#code_action(opts) abort
|
||||
call lsp#ui#vim#code_action#do(extend({
|
||||
\ 'sync': v:false,
|
||||
\ 'selection': v:false,
|
||||
\ 'query': '',
|
||||
\ })
|
||||
\ }, a:opts))
|
||||
endfunction
|
||||
|
||||
function! lsp#ui#vim#code_lens() abort
|
||||
|
||||
@@ -14,12 +14,17 @@ endfunction
|
||||
" 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
|
||||
@@ -51,13 +56,13 @@ function! lsp#ui#vim#code_action#do(option) abort
|
||||
\ },
|
||||
\ },
|
||||
\ 'sync': l:sync,
|
||||
\ 'on_notification': function('s:handle_code_action', [l:ctx, l:server_name, l:command_id, l:sync, l:query, l:bufnr]),
|
||||
\ '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(ctx, server_name, command_id, sync, query, bufnr, data) abort
|
||||
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
|
||||
@@ -130,14 +135,34 @@ function! s:handle_code_action(ctx, server_name, command_id, sync, query, bufnr,
|
||||
endif
|
||||
call add(l:items, { 'title': l:title, 'item': l:action })
|
||||
endfor
|
||||
call lsp#internal#ui#quickpick#open({
|
||||
\ 'items': l:items,
|
||||
\ 'key': 'title',
|
||||
\ 'on_accept': funcref('s:accept_code_action', [a:sync, a:bufnr]),
|
||||
\ })
|
||||
|
||||
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:accept_code_action(sync, bufnr, data, ...) abort
|
||||
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']
|
||||
|
||||
@@ -34,6 +34,11 @@ function! lsp#utils#_has_highlights() abort
|
||||
return s:has_higlights
|
||||
endfunction
|
||||
|
||||
let s:has_popup_menu = exists('*popup_menu')
|
||||
function! lsp#utils#_has_popup_menu() abort
|
||||
return s:has_popup_menu
|
||||
endfunction
|
||||
|
||||
function! lsp#utils#is_file_uri(uri) abort
|
||||
return stridx(a:uri, 'file:///') == 0
|
||||
endfunction
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
function! lsp#utils#args#_parse(args, opt) abort
|
||||
function! lsp#utils#args#_parse(args, opt, remainder_key) abort
|
||||
let l:result = {}
|
||||
let l:is_opts = v:true
|
||||
let l:remainder = []
|
||||
for l:item in split(a:args, ' ')
|
||||
if l:item[:1] !=# '--'
|
||||
let l:is_opts = v:false
|
||||
endif
|
||||
|
||||
if l:is_opts == v:false
|
||||
call add(l:remainder, l:item)
|
||||
continue
|
||||
endif
|
||||
|
||||
let l:parts = split(l:item, '=')
|
||||
let l:key = l:parts[0]
|
||||
let l:value = get(l:parts, 1, '')
|
||||
let l:key = l:key[2:]
|
||||
|
||||
if has_key(a:opt, l:key)
|
||||
if has_key(a:opt[l:key], 'type')
|
||||
let l:type = a:opt[l:key]['type']
|
||||
@@ -21,5 +33,10 @@ function! lsp#utils#args#_parse(args, opt) abort
|
||||
endif
|
||||
let l:result[l:key] = l:value
|
||||
endfor
|
||||
|
||||
if a:remainder_key != v:null
|
||||
let l:result[a:remainder_key] = join(l:remainder)
|
||||
endif
|
||||
|
||||
return l:result
|
||||
endfunction
|
||||
|
||||
@@ -1561,7 +1561,7 @@ LspCallHierarchyOutgoing *:LspCallHierarchyOutgoing*
|
||||
|
||||
Find outgoing call hierarchy for the symbol under cursor.
|
||||
|
||||
LspCodeAction [{CodeActionKind}] *:LspCodeAction*
|
||||
LspCodeAction [--ui=float|preview] [{CodeActionKind}] *:LspCodeAction*
|
||||
|
||||
Gets a list of possible commands that can be applied to a file so it can be
|
||||
fixed (quick fix).
|
||||
@@ -1569,7 +1569,7 @@ fixed (quick fix).
|
||||
If the optional {CodeActionKind} specified, will invoke code action
|
||||
immediately when matched code action is one only.
|
||||
|
||||
LspCodeActionSync [{CodeActionKind}] *:LspCodeActionSync*
|
||||
LspCodeActionSync [--ui=float|preview] [{CodeActionKind}] *:LspCodeActionSync*
|
||||
|
||||
Same as |:LspCodeAction| but synchronous. Useful when running |:autocmd|
|
||||
commands such as organize imports before save.
|
||||
|
||||
@@ -74,6 +74,7 @@ let g:lsp_work_done_progress_enabled = get(g:, 'lsp_work_done_progress_enabled',
|
||||
let g:lsp_untitled_buffer_enabled = get(g:, 'lsp_untitled_buffer_enabled', 1)
|
||||
let g:lsp_inlay_hints_enabled = get(g:, 'lsp_inlay_hints_enabled', 0)
|
||||
let g:lsp_inlay_hints_delay = get(g:, 'lsp_inlay_hints_delay', 350)
|
||||
let g:lsp_code_action_ui = get(g:, 'lsp_code_action_ui', 'preview')
|
||||
|
||||
let g:lsp_get_supported_capabilities = get(g:, 'lsp_get_supported_capabilities', [function('lsp#default_get_supported_capabilities')])
|
||||
|
||||
@@ -89,16 +90,14 @@ endif
|
||||
command! LspAddTreeCallHierarchyIncoming call lsp#ui#vim#add_tree_call_hierarchy_incoming()
|
||||
command! LspCallHierarchyIncoming call lsp#ui#vim#call_hierarchy_incoming({})
|
||||
command! LspCallHierarchyOutgoing call lsp#ui#vim#call_hierarchy_outgoing()
|
||||
command! -range -nargs=* -complete=customlist,lsp#ui#vim#code_action#complete LspCodeAction call lsp#ui#vim#code_action#do({
|
||||
\ 'sync': v:false,
|
||||
\ 'selection': <range> != 0,
|
||||
\ 'query': '<args>'
|
||||
\ })
|
||||
command! -range -nargs=* -complete=customlist,lsp#ui#vim#code_action#complete LspCodeActionSync call lsp#ui#vim#code_action#do({
|
||||
\ 'sync': v:true,
|
||||
\ 'selection': <range> != 0,
|
||||
\ 'query': '<args>'
|
||||
\ })
|
||||
command! -range -nargs=* -complete=customlist,lsp#ui#vim#code_action#complete LspCodeAction call lsp#ui#vim#code_action#do(
|
||||
\ extend({ 'sync': v:false, 'selection': <range> != 0 }, lsp#utils#args#_parse(<q-args>, {
|
||||
\ 'ui': { 'type': type('') },
|
||||
\ }, 'query')))
|
||||
command! -range -nargs=* -complete=customlist,lsp#ui#vim#code_action#complete LspCodeActionSync call lsp#ui#vim#code_action#do(
|
||||
\ extend({ 'sync': v:true, 'selection': <range> != 0 }, lsp#utils#args#_parse(<q-args>, {
|
||||
\ 'ui': { 'type': type('') },
|
||||
\ }, 'query')))
|
||||
command! LspCodeLens call lsp#ui#vim#code_lens#do({})
|
||||
command! LspDeclaration call lsp#ui#vim#declaration(0, <q-mods>)
|
||||
command! LspPeekDeclaration call lsp#ui#vim#declaration(1)
|
||||
@@ -109,11 +108,11 @@ command! LspDocumentSymbolSearch call lsp#internal#document_symbol#search#do({})
|
||||
command! -nargs=? LspDocumentDiagnostics call lsp#internal#diagnostics#document_diagnostics_command#do(
|
||||
\ extend({}, lsp#utils#args#_parse(<q-args>, {
|
||||
\ 'buffers': {'type': type('')},
|
||||
\ })))
|
||||
\ }, v:null)))
|
||||
command! -nargs=? -complete=customlist,lsp#utils#empty_complete LspHover call lsp#internal#document_hover#under_cursor#do(
|
||||
\ extend({}, lsp#utils#args#_parse(<q-args>, {
|
||||
\ 'ui': { 'type': type('') },
|
||||
\ })))
|
||||
\ }, v:null)))
|
||||
command! -nargs=* LspNextError call lsp#internal#diagnostics#movement#_next_error(<f-args>)
|
||||
command! -nargs=* LspPreviousError call lsp#internal#diagnostics#movement#_previous_error(<f-args>)
|
||||
command! -nargs=* LspNextWarning call lsp#internal#diagnostics#movement#_next_warning(<f-args>)
|
||||
@@ -132,13 +131,13 @@ command! -range -nargs=? LspDocumentFormatSync call lsp#internal#document_format
|
||||
\ extend({'bufnr': bufnr('%'), 'sync': 1 }, lsp#utils#args#_parse(<q-args>, {
|
||||
\ 'timeout': {'type': type(0)},
|
||||
\ 'sleep': {'type': type(0)},
|
||||
\ })))
|
||||
\ }, v:null)))
|
||||
command! -range LspDocumentRangeFormat call lsp#internal#document_range_formatting#format({ 'bufnr': bufnr('%') })
|
||||
command! -range -nargs=? LspDocumentRangeFormatSync call lsp#internal#document_range_formatting#format(
|
||||
\ extend({'bufnr': bufnr('%'), 'sync': 1 }, lsp#utils#args#_parse(<q-args>, {
|
||||
\ 'timeout': {'type': type(0)},
|
||||
\ 'sleep': {'type': type(0)},
|
||||
\ })))
|
||||
\ }, v:null)))
|
||||
command! LspImplementation call lsp#ui#vim#implementation(0, <q-mods>)
|
||||
command! LspPeekImplementation call lsp#ui#vim#implementation(1)
|
||||
command! -nargs=0 LspStatus call lsp#print_server_status()
|
||||
@@ -153,7 +152,9 @@ command! -nargs=0 LspSemanticTokenModifiers echo lsp#internal#semantic#get_token
|
||||
|
||||
nnoremap <silent> <plug>(lsp-call-hierarchy-incoming) :<c-u>call lsp#ui#vim#call_hierarchy_incoming({})<cr>
|
||||
nnoremap <silent> <plug>(lsp-call-hierarchy-outgoing) :<c-u>call lsp#ui#vim#call_hierarchy_outgoing()<cr>
|
||||
nnoremap <silent> <plug>(lsp-code-action) :<c-u>call lsp#ui#vim#code_action()<cr>
|
||||
nnoremap <silent> <plug>(lsp-code-action) :<c-u>call lsp#ui#vim#code_action({})<cr>
|
||||
nnoremap <silent> <plug>(lsp-code-action-float) :<c-u>call lsp#ui#vim#code_action({ 'ui': 'float' })<cr>
|
||||
nnoremap <silent> <plug>(lsp-code-action-preview) :<c-u>call lsp#ui#vim#code_action({ 'ui': 'preview' })<cr>
|
||||
nnoremap <silent> <plug>(lsp-code-lens) :<c-u>call lsp#ui#vim#code_lens()<cr>
|
||||
nnoremap <silent> <plug>(lsp-declaration) :<c-u>call lsp#ui#vim#declaration(0)<cr>
|
||||
nnoremap <silent> <plug>(lsp-peek-declaration) :<c-u>call lsp#ui#vim#declaration(1)<cr>
|
||||
|
||||
Reference in New Issue
Block a user