embed quickpick.vim and add experimental quickpick support for codelens (#1019)

This commit is contained in:
Prabir Shrestha
2021-01-04 21:26:54 -08:00
committed by GitHub
parent 58dc3b183c
commit 89135264c2
3 changed files with 456 additions and 9 deletions

View File

@@ -0,0 +1,402 @@
" https://github.com/prabirshrestha/quickpick.vim#30c74fe10098ad9e81ee438406b3e1ba6508187a
" :QuickpickEmbed path=autoload/lsp/internal/ui/quickpick.vim namespace=lsp#internal#ui#quickpick prefix=lsp-quickpick
let s:has_timer = exists('*timer_start') && exists('*timer_stop')
let s:has_matchfuzzy = exists('*matchfuzzy')
let s:has_matchfuzzypos = exists('*matchfuzzypos')
let s:has_proptype = exists('*prop_type_add') && exists('*prop_type_delete')
function! lsp#internal#ui#quickpick#open(opt) abort
call lsp#internal#ui#quickpick#close() " hide existing picker if exists
" when key is empty, item is a string else it is a dict
" fitems is filtered items and is the item that is filtered
let s:state = extend({
\ 'items': [],
\ 'highlights': [],
\ 'fitems': [],
\ 'key': '',
\ 'busy': 0,
\ 'busyframes': ['-', '\', '|', '/'],
\ 'filetype': 'lsp-quickpick',
\ 'promptfiletype': 'lsp-quickpick-filter',
\ 'input': '',
\ 'maxheight': 10,
\ 'debounce': 250,
\ 'filter': 1,
\ }, a:opt)
let s:inputecharpre = 0
let s:state['busyframe'] = 0
let s:state['bufnr'] = bufnr('%')
let s:state['winid'] = win_getid()
" create result buffer
exe printf('keepalt botright 1new %s', s:state['filetype'])
let s:state['resultsbufnr'] = bufnr('%')
let s:state['resultswinid'] = win_getid()
if s:has_proptype
call prop_type_add('highlight', { 'highlight': 'Directory', 'bufnr': s:state['resultsbufnr'] })
endif
" create prompt buffer
exe printf('keepalt botright 1new %s', s:state['promptfiletype'])
let s:state['promptbufnr'] = bufnr('%')
let s:state['promptwinid'] = win_getid()
call win_gotoid(s:state['resultswinid'])
call s:set_buffer_options()
setlocal cursorline
call s:update_items()
exec printf('setlocal filetype=' . s:state['filetype'])
call s:notify('open', { 'bufnr': s:state['bufnr'], 'winid': s:state['winid'] , 'resultsbufnr': s:state['resultsbufnr'], 'resultswinid': s:state['resultswinid'] })
call win_gotoid(s:state['promptwinid'])
call s:set_buffer_options()
call setline(1, s:state['input'])
" map keys
inoremap <buffer><silent> <Plug>(lsp-quickpick-accept) <ESC>:<C-u>call <SID>on_accept()<CR>
nnoremap <buffer><silent> <Plug>(lsp-quickpick-accept) :<C-u>call <SID>on_accept()<CR>
inoremap <buffer><silent> <Plug>(lsp-quickpick-close) <ESC>:<C-u>call lsp#internal#ui#quickpick#close()<CR>
nnoremap <buffer><silent> <Plug>(lsp-quickpick-close) :<C-u>call lsp#internal#ui#quickpick#close()<CR>
inoremap <buffer><silent> <Plug>(lsp-quickpick-cancel) <ESC>:<C-u>call <SID>on_cancel()<CR>
nnoremap <buffer><silent> <Plug>(lsp-quickpick-cancel) :<C-u>call <SID>on_cancel()<CR>
inoremap <buffer><silent> <Plug>(lsp-quickpick-move-next) <ESC>:<C-u>call <SID>on_move_next(1)<CR>
nnoremap <buffer><silent> <Plug>(lsp-quickpick-move-next) :<C-u>call <SID>on_move_next(0)<CR>
inoremap <buffer><silent> <Plug>(lsp-quickpick-move-previous) <ESC>:<C-u>call <SID>on_move_previous(1)<CR>
nnoremap <buffer><silent> <Plug>(lsp-quickpick-move-previous) :<C-u>call <SID>on_move_previous(0)<CR>
exec printf('setlocal filetype=' . s:state['promptfiletype'])
if !hasmapto('<Plug>(lsp-quickpick-accept)')
imap <buffer><cr> <Plug>(lsp-quickpick-accept)
nmap <buffer><cr> <Plug>(lsp-quickpick-accept)
endif
if !hasmapto('<Plug>(lsp-quickpick-cancel)')
imap <silent> <buffer> <C-c> <Plug>(lsp-quickpick-cancel)
map <silent> <buffer> <C-c> <Plug>(lsp-quickpick-cancel)
imap <silent> <buffer> <Esc> <Plug>(lsp-quickpick-cancel)
map <silent> <buffer> <Esc> <Plug>(lsp-quickpick-cancel)
endif
if !hasmapto('<Plug>(lsp-quickpick-move-next)')
imap <silent> <buffer> <C-n> <Plug>(lsp-quickpick-move-next)
nmap <silent> <buffer> <C-n> <Plug>(lsp-quickpick-move-next)
imap <silent> <buffer> <C-j> <Plug>(lsp-quickpick-move-next)
nmap <silent> <buffer> <C-j> <Plug>(lsp-quickpick-move-next)
endif
if !hasmapto('<Plug>(lsp-quickpick-move-previous)')
imap <silent> <buffer> <C-p> <Plug>(lsp-quickpick-move-previous)
nmap <silent> <buffer> <C-p> <Plug>(lsp-quickpick-move-previous)
imap <silent> <buffer> <C-k> <Plug>(lsp-quickpick-move-previous)
nmap <silent> <buffer> <C-k> <Plug>(lsp-quickpick-move-previous)
endif
call cursor(line('$'), 0)
startinsert!
augroup lsp#internal#ui#quickpick
autocmd!
autocmd InsertCharPre <buffer> call s:on_insertcharpre()
autocmd TextChangedI <buffer> call s:on_inputchanged()
autocmd InsertEnter <buffer> call s:on_insertenter()
autocmd InsertLeave <buffer> call s:on_insertleave()
if exists('##TextChangedP')
autocmd TextChangedP <buffer> call s:on_inputchanged()
endif
augroup END
call s:notify_items()
call s:notify_selection()
call lsp#internal#ui#quickpick#busy(s:state['busy'])
endfunction
function! s:set_buffer_options() abort
" set buffer options
abc <buffer>
setlocal bufhidden=unload " unload buf when no longer displayed
setlocal buftype=nofile " buffer is not related to any file<Paste>
setlocal noswapfile " don't create swap file
setlocal nowrap " don't soft-wrap
setlocal nonumber " don't show line numbers
setlocal nolist " don't use list mode (visible tabs etc)
setlocal foldcolumn=0 " don't show a fold column at side
setlocal foldlevel=99 " don't fold anything
setlocal nospell " spell checking off
setlocal nobuflisted " don't show up in the buffer list
setlocal textwidth=0 " don't hardwarp (break long lines)
setlocal nocursorline " highlight the line cursor is off
setlocal nocursorcolumn " disable cursor column
setlocal noundofile " don't enable undo
setlocal winfixheight
if exists('+colorcolumn') | setlocal colorcolumn=0 | endif
if exists('+relativenumber') | setlocal norelativenumber | endif
setlocal signcolumn=yes " for prompt
endfunction
function! lsp#internal#ui#quickpick#close() abort
if !exists('s:state')
return
endif
call lsp#internal#ui#quickpick#busy(0)
call win_gotoid(s:state['bufnr'])
call s:notify('close', { 'bufnr': s:state['bufnr'], 'winid': s:state['winid'], 'resultsbufnr': s:state['resultsbufnr'], 'resultswinid': s:state['winid'] })
augroup lsp#internal#ui#quickpick
autocmd!
augroup END
mapclear <buffer>
exe 'silent! bunload! ' . s:state['promptbufnr']
mapclear <buffer>
exe 'silent! bunload! ' . s:state['resultsbufnr']
let s:inputecharpre = 0
unlet s:state
endfunction
function! lsp#internal#ui#quickpick#items(items) abort
let s:state['items'] = a:items
call s:update_items()
call s:notify_items()
call s:notify_selection()
endfunction
function! lsp#internal#ui#quickpick#busy(busy) abort
let s:state['busy'] = a:busy
if a:busy
if !has_key(s:state, 'busytimer')
let s:state['busyframe'] = 0
let s:state['busytimer'] = timer_start(60, function('s:busy_tick'), { 'repeat': -1 })
endif
else
if has_key(s:state, 'busytimer')
call timer_stop(s:state['busytimer'])
call remove(s:state, 'busytimer')
redraw
echohl None
echo ''
endif
endif
endfunction
function! s:busy_tick(...) abort
let s:state['busyframe'] = s:state['busyframe'] + 1
if s:state['busyframe'] >= len(s:state['busyframes'])
let s:state['busyframe'] = 0
endif
redraw
echohl Question | echon s:state['busyframes'][s:state['busyframe']]
echohl None
endfunction
function! s:update_items() abort
call s:win_execute(s:state['resultswinid'], 'silent! %delete')
let s:state['highlights'] = []
if s:state['filter'] " if filter is enabled
if empty(s:trim(s:state['input']))
let s:state['fitems'] = s:state['items']
else
if empty(s:state['key']) " item is string
if s:has_matchfuzzypos
let [l:fitems, l:highlights] = matchfuzzypos(s:state['items'], s:state['input'])
let s:state['fitems'] = l:fitems
let s:state['highlights'] = l:highlights
elseif s:has_matchfuzzy
let s:state['fitems'] = matchfuzzy(s:state['items'], s:state['input'])
else
let s:state['fitems'] = filter(copy(s:state['items']), 'stridx(toupper(v:val), toupper(s:state["input"])) >= 0')
endif
else " item is dict
if s:has_matchfuzzypos
" vim requires matchfuzzypos to have highlights.
" matchfuzzy only patch doesn't support dict search
let [l:fitems, l:highlights] = matchfuzzypos(s:state['items'], s:state['input'], { 'key': s:state['key'] })
let s:state['fitems'] = l:fitems
let s:state['highlights'] = l:highlights
else
let s:state['fitems'] = filter(copy(s:state['items']), 'stridx(toupper(v:val[s:state["key"]]), toupper(s:state["input"])) >= 0')
endif
endif
endif
else " if filter is disabled
let s:state['fitems'] = s:state['items']
endif
if empty(s:state['key']) " item is string
let l:lines = s:state['fitems']
else " item is dict
let l:lines = map(copy(s:state['fitems']), 'v:val[s:state["key"]]')
endif
call setbufline(s:state['resultsbufnr'], 1, l:lines)
if s:has_proptype && !empty(s:state['highlights'])
let l:i = 0
for l:line in s:state['highlights']
for l:pos in l:line
call prop_add(l:i + 1, l:pos + 1, { 'length': 1, 'type': 'highlight', 'bufnr': s:state['resultsbufnr'] })
endfor
let l:i += 1
endfor
endif
call s:win_execute(s:state['resultswinid'], printf('resize %d', min([len(s:state['fitems']), s:state['maxheight']])))
call s:win_execute(s:state['promptwinid'], 'resize 1')
endfunction
function! s:on_accept() abort
if win_gotoid(s:state['resultswinid'])
let l:index = line('.') - 1 " line is 1 index, list is 0 index
if l:index < 0
let l:items = []
else
let l:items = [s:state['fitems'][l:index]]
endif
call win_gotoid(s:state['winid'])
call s:notify('accept', { 'items': l:items })
end
endfunction
function! s:on_cancel() abort
call win_gotoid(s:state['winid'])
call s:notify('cancel', {})
call lsp#internal#ui#quickpick#close()
endfunction
function! s:on_move_next(insertmode) abort
let l:col = col('.')
call s:win_execute(s:state['resultswinid'], 'normal! j')
if a:insertmode
call s:win_execute(s:state['promptwinid'], 'startinsert | call setpos(".", [0, 1, ' . (l:col + 1) .', 1])')
endif
call s:notify_selection()
endfunction
function! s:on_move_previous(insertmode) abort
let l:col = col('.')
call s:win_execute(s:state['resultswinid'], 'normal! k')
if a:insertmode
call s:win_execute(s:state['promptwinid'], 'startinsert | call setpos(".", [0, 1, ' . (l:col + 1) .', 1])')
endif
call s:notify_selection()
endfunction
function! s:notify_items() abort
" items could be huge, so don't send the items as part of data
call s:notify('items', { 'bufnr': s:state['bufnr'], 'winid': s:state['winid'], 'resultsbufnr': s:state['resultsbufnr'], 'resultswinid': s:state['resultswinid'] })
endfunction
function! s:notify_selection() abort
let l:original_winid = win_getid()
call win_gotoid(s:state['resultswinid'])
let l:index = line('.') - 1 " line is 1 based, list is 0 based
if l:index < 0 || ((l:index + 1) > len(s:state['fitems']))
let l:items = []
else
let l:items = [s:state['fitems'][l:index]]
endif
let l:data = {
\ 'bufnr': s:state['bufnr'],
\ 'winid': s:state['winid'],
\ 'resultsbufnr': s:state['resultsbufnr'],
\ 'resultswinid': s:state['resultswinid'],
\ 'items': l:items,
\ }
call win_gotoid(s:state['winid'])
call s:notify('selection', l:data)
call win_gotoid(l:original_winid)
endfunction
function! s:on_inputchanged() abort
if s:inputecharpre
if s:has_timer && s:state['debounce'] > 0
call s:debounce_onchange()
else
call s:notify_onchange()
endif
endif
endfunction
function! s:on_insertcharpre() abort
let s:inputecharpre = 1
endfunction
function! s:on_insertenter() abort
let s:inputecharpre = 0
endfunction
function! s:on_insertleave() abort
if s:has_timer && has_key(s:state, 'debounce_onchange_timer')
call timer_stop(s:state['debounce_onchange_timer'])
call remove(s:state, 'debounce_onchange_timer')
endif
endfunction
function! s:debounce_onchange() abort
if has_key(s:state, 'debounce_onchange_timer')
call timer_stop(s:state['debounce_onchange_timer'])
call remove(s:state, 'debounce_onchange_timer')
endif
let s:state['debounce_onchange_timer'] = timer_start(s:state['debounce'], function('s:notify_onchange'))
endfunction
function! s:notify_onchange(...) abort
let s:state['input'] = getbufline(s:state['promptbufnr'], 1)[0]
call s:notify('change', { 'input': s:state['input'] })
if s:state['filter']
call s:update_items()
call s:notify_selection()
endif
endfunction
function! s:notify(name, data) abort
if has_key(s:state, 'on_event') | call s:state['on_event'](a:data, a:name) | endif
if has_key(s:state, 'on_' . a:name) | call s:state['on_' . a:name](a:data, a:name) | endif
endfunction
if exists('*win_execute')
function! s:win_execute(win_id, cmd) abort
call win_execute(a:win_id, a:cmd)
endfunction
else
function! s:win_execute(winid, cmd) abort
let l:original_winid = win_getid()
if l:original_winid == a:winid
exec a:cmd
else
if win_gotoid(a:winid)
exec a:cmd
call win_gotoid(l:original_winid)
end
endif
endfunction
endif
if exists('*trim')
function! s:trim(str) abort
return trim(a:str)
endfunction
else
function! s:trim(str) abort
return substitute(a:string, '^\s*\|\s*$', '', 'g')
endfunction
endif
" vim: set sw=2 ts=2 sts=2 et tw=78 foldmarker={{{,}}} foldmethod=marker spell:

View File

@@ -71,15 +71,58 @@ function! s:chooseCodeLens(items, bufnr) abort
if empty(a:items)
return lsp#callbag#throwError('No codelens found')
endif
let l:index = inputlist(map(copy(a:items), {i, value ->
\ printf("%s - [%s] %s\t| L%s:%s", i + 1, value['server'], value['codelens']['command']['title'],
\ lsp#utils#position#lsp_line_to_vim(a:bufnr, value['codelens']['range']['start']),
\ getbufline(a:bufnr, lsp#utils#position#lsp_line_to_vim(a:bufnr, value['codelens']['range']['start']))[0][:50])
\ }))
if l:index > 0 && l:index <= len(a:items)
let l:selected = a:items[l:index - 1]
return lsp#callbag#of(l:selected)
if g:lsp_experimental_quickpick_ui
return lsp#callbag#create(function('s:quickpick_open', [a:items, a:bufnr]))
else
return lsp#callbag#empty()
let l:index = inputlist(map(copy(a:items), {i, value ->
\ printf("%s - [%s] %s\t| L%s:%s", i + 1, value['server'], value['codelens']['command']['title'],
\ lsp#utils#position#lsp_line_to_vim(a:bufnr, value['codelens']['range']['start']),
\ getbufline(a:bufnr, lsp#utils#position#lsp_line_to_vim(a:bufnr, value['codelens']['range']['start']))[0][:50])
\ }))
if l:index > 0 && l:index <= len(a:items)
let l:selected = a:items[l:index - 1]
return lsp#callbag#of(l:selected)
else
return lsp#callbag#empty()
endif
endif
endfunction
function! s:quickpick_open(items, bufnr, next, error, complete) abort
if empty(a:items)
return lsp#callbag#empty()
endif
let l:items = []
for l:item in a:items
let l:title = printf("[%s] %s\t| L%s:%s",
\ l:item['server'],
\ l:item['codelens']['command']['title'],
\ lsp#utils#position#lsp_line_to_vim(a:bufnr, l:item['codelens']['range']['start']),
\ getbufline(a:bufnr, lsp#utils#position#lsp_line_to_vim(a:bufnr, l:item['codelens']['range']['start']))[0])
call add(l:items, { 'title': l:title, 'item': l:item })
endfor
call lsp#internal#ui#quickpick#open({
\ 'items': l:items,
\ 'key': 'title',
\ 'on_accept': function('s:quickpick_accept', [a:next, a:error, a:complete]),
\ 'on_cancel': function('s:quickpick_cancel', [a:next, a:error, a:complete]),
\ })
return function('s:quickpick_dispose')
endfunction
function! s:quickpick_dispose() abort
call lsp#internal#ui#quickpick#close()
endfunction
function! s:quickpick_accept(next, error, complete, data, ...) abort
call lsp#internal#ui#quickpick#close()
call a:next(a:data['items'][0]['item'])
call a:complete()
endfunction
function! s:quickpick_cancel(next, error, complete, ...) abort
call a:complete()
endfunction

View File

@@ -63,6 +63,8 @@ let g:lsp_work_done_progress_enabled = get(g:, 'lsp_work_done_progress_enabled',
let g:lsp_get_supported_capabilities = get(g:, 'lsp_get_supported_capabilities', [function('lsp#default_get_supported_capabilities')])
let g:lsp_experimental_quickpick_ui = get(g:, 'lsp_experimental_quickpick_ui', 0)
if g:lsp_auto_enable
augroup lsp_auto_enable
autocmd!