X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/5a4872f466ebd76ddd532bdf2798554421c53df4..fe3919e725e156d751069662d11e38f7b4791de1:/.vim/bundle/vim-lsp/autoload/lsp/internal/ui/quickpick.vim diff --git a/.vim/bundle/vim-lsp/autoload/lsp/internal/ui/quickpick.vim b/.vim/bundle/vim-lsp/autoload/lsp/internal/ui/quickpick.vim new file mode 100644 index 00000000..3fe37558 --- /dev/null +++ b/.vim/bundle/vim-lsp/autoload/lsp/internal/ui/quickpick.vim @@ -0,0 +1,461 @@ +" https://github.com/prabirshrestha/quickpick.vim#968f00787c1a118228aee869351e754bec555298 +" :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') + +" +" is_floating +" +if has('nvim') + function! s:is_floating(winid) abort + if !s:win_exists(a:winid) + return 0 + endif + let l:config = nvim_win_get_config(a:winid) + return empty(l:config) || !empty(get(l:config, 'relative', '')) + endfunction +else + function! s:is_floating(winid) abort + return s:win_exists(a:winid) && win_id2win(a:winid) == 0 + endfunction +endif + +function! s:win_exists(winid) abort + return winheight(a:winid) != -1 +endfunction + +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() + let s:state['wininfo'] = getwininfo() + + " create result buffer + exe printf('keepalt botright 3new %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 (lsp-quickpick-accept) :call on_accept() + nnoremap (lsp-quickpick-accept) :call on_accept() + + inoremap (lsp-quickpick-close) :call lsp#internal#ui#quickpick#close() + nnoremap (lsp-quickpick-close) :call lsp#internal#ui#quickpick#close() + + inoremap (lsp-quickpick-cancel) :call on_cancel() + nnoremap (lsp-quickpick-cancel) :call on_cancel() + + inoremap (lsp-quickpick-move-next) :call on_move_next() + nnoremap (lsp-quickpick-move-next) :call on_move_next() + + inoremap (lsp-quickpick-move-previous) :call on_move_previous() + nnoremap (lsp-quickpick-move-previous) :call on_move_previous() + + exec printf('setlocal filetype=' . s:state['promptfiletype']) + + if !hasmapto('(lsp-quickpick-accept)') + imap (lsp-quickpick-accept) + nmap (lsp-quickpick-accept) + endif + + if !hasmapto('(lsp-quickpick-cancel)') + imap (lsp-quickpick-cancel) + map (lsp-quickpick-cancel) + imap (lsp-quickpick-cancel) + map (lsp-quickpick-cancel) + endif + + if !hasmapto('(lsp-quickpick-move-next)') + imap (lsp-quickpick-move-next) + nmap (lsp-quickpick-move-next) + imap (lsp-quickpick-move-next) + nmap (lsp-quickpick-move-next) + endif + + if !hasmapto('(lsp-quickpick-move-previous)') + imap (lsp-quickpick-move-previous) + nmap (lsp-quickpick-move-previous) + imap (lsp-quickpick-move-previous) + nmap (lsp-quickpick-move-previous) + endif + + call cursor(line('$'), 0) + call feedkeys('i', 'n') + + augroup lsp#internal#ui#quickpick + autocmd! + autocmd InsertCharPre call s:on_insertcharpre() + autocmd TextChangedI call s:on_inputchanged() + autocmd InsertEnter call s:on_insertenter() + autocmd InsertLeave call s:on_insertleave() + + if exists('##TextChangedP') + autocmd TextChangedP 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 + setlocal bufhidden=unload " unload buf when no longer displayed + setlocal buftype=nofile " buffer is not related to any file + 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['winid']) + 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 + + exe 'silent! bunload! ' . s:state['promptbufnr'] + exe 'silent! bunload! ' . s:state['resultsbufnr'] + call s:restore_windows() + + let s:inputecharpre = 0 + + unlet s:state +endfunction + +function! s:restore_windows() abort + let [l:tabnr, l:_] = win_id2tabwin(s:state['winid']) + if l:tabnr == 0 + return + endif + + let l:Resizable = {_, info -> + \ info.tabnr == l:tabnr && + \ s:win_exists(info.winid) && + \ !s:is_floating(info.winid) + \ } + let l:wins_to_resize = sort(filter(s:state['wininfo'], l:Resizable), {l, r -> l.winnr - r.winnr}) + let l:open_winids_to_resize = map(filter(getwininfo(), l:Resizable), {_, info -> info.winid}) + + let l:resize_cmd = '' + for l:info in l:wins_to_resize + if index(l:open_winids_to_resize, l:info.winid) == -1 + return + endif + + let l:resize_cmd .= printf('%dresize %d | vert %dresize %d |', l:info.winnr, l:info.height, l:info.winnr, l:info.width) + endfor + + " winrestcmd repeats :resize commands twice after patch-8.2.2631. + " To simulate this behavior, execute the :resize commands twice. + " see https://github.com/vim/vim/issues/7988 + exe 'silent! ' . l:resize_cmd . l:resize_cmd +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! lsp#internal#ui#quickpick#results_winid() abort + if exists('s:state') + return s:state['resultswinid'] + else + return 0 + 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:matchfuzzyresult = matchfuzzypos(s:state['items'], s:state['input']) + let l:fitems = l:matchfuzzyresult[0] + let l:highlights = l:matchfuzzyresult[1] + 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:matchfuzzyresult = matchfuzzypos(s:state['items'], s:state['input'], { 'key': s:state['key'] }) + let l:fitems = l:matchfuzzyresult[0] + let l:highlights = l:matchfuzzyresult[1] + 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 + let l:cs = split(getbufline(s:state['resultsbufnr'], l:i + 1)[0], '\zs') + let l:mpos = strlen(join(l:cs[: l:pos - 1], '')) + let l:len = strlen(l:cs[l:pos]) + call prop_add(l:i + 1, l:mpos + 1, { 'length': l:len, '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 + let l:fitems = s:state['fitems'] + if l:index < 0 || len(l:fitems) <= l:index + let l:items = [] + else + let l:items = [l: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() abort + let l:col = col('.') + call s:win_execute(s:state['resultswinid'], 'normal! j') + call s:notify_selection() +endfunction + +function! s:on_move_previous() abort + let l:col = col('.') + call s:win_execute(s:state['resultswinid'], 'normal! k') + 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, + \ } + noautocmd call win_gotoid(s:state['winid']) + call s:notify('selection', l:data) + noautocmd 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:str, '^\s*\|\s*$', '', 'g') + endfunction +endif + +" vim: set sw=2 ts=2 sts=2 et tw=78 foldmarker={{{,}}} foldmethod=marker spell: