All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
1 " https://github.com/prabirshrestha/quickpick.vim#968f00787c1a118228aee869351e754bec555298
2 " :QuickpickEmbed path=autoload/lsp/internal/ui/quickpick.vim namespace=lsp#internal#ui#quickpick prefix=lsp-quickpick
4 let s:has_timer = exists('*timer_start') && exists('*timer_stop')
5 let s:has_matchfuzzy = exists('*matchfuzzy')
6 let s:has_matchfuzzypos = exists('*matchfuzzypos')
7 let s:has_proptype = exists('*prop_type_add') && exists('*prop_type_delete')
13 function! s:is_floating(winid) abort
14 if !s:win_exists(a:winid)
17 let l:config = nvim_win_get_config(a:winid)
18 return empty(l:config) || !empty(get(l:config, 'relative', ''))
21 function! s:is_floating(winid) abort
22 return s:win_exists(a:winid) && win_id2win(a:winid) == 0
26 function! s:win_exists(winid) abort
27 return winheight(a:winid) != -1
30 function! lsp#internal#ui#quickpick#open(opt) abort
31 call lsp#internal#ui#quickpick#close() " hide existing picker if exists
33 " when key is empty, item is a string else it is a dict
34 " fitems is filtered items and is the item that is filtered
35 let s:state = extend({
41 \ 'busyframes': ['-', '\', '|', '/'],
42 \ 'filetype': 'lsp-quickpick',
43 \ 'promptfiletype': 'lsp-quickpick-filter',
50 let s:inputecharpre = 0
51 let s:state['busyframe'] = 0
53 let s:state['bufnr'] = bufnr('%')
54 let s:state['winid'] = win_getid()
55 let s:state['wininfo'] = getwininfo()
57 " create result buffer
58 exe printf('keepalt botright 3new %s', s:state['filetype'])
59 let s:state['resultsbufnr'] = bufnr('%')
60 let s:state['resultswinid'] = win_getid()
62 call prop_type_add('highlight', { 'highlight': 'Directory', 'bufnr': s:state['resultsbufnr'] })
65 " create prompt buffer
66 exe printf('keepalt botright 1new %s', s:state['promptfiletype'])
67 let s:state['promptbufnr'] = bufnr('%')
68 let s:state['promptwinid'] = win_getid()
70 call win_gotoid(s:state['resultswinid'])
71 call s:set_buffer_options()
74 exec printf('setlocal filetype=' . s:state['filetype'])
75 call s:notify('open', { 'bufnr': s:state['bufnr'], 'winid': s:state['winid'] , 'resultsbufnr': s:state['resultsbufnr'], 'resultswinid': s:state['resultswinid'] })
77 call win_gotoid(s:state['promptwinid'])
78 call s:set_buffer_options()
79 call setline(1, s:state['input'])
82 inoremap <buffer><silent> <Plug>(lsp-quickpick-accept) <ESC>:<C-u>call <SID>on_accept()<CR>
83 nnoremap <buffer><silent> <Plug>(lsp-quickpick-accept) :<C-u>call <SID>on_accept()<CR>
85 inoremap <buffer><silent> <Plug>(lsp-quickpick-close) <ESC>:<C-u>call lsp#internal#ui#quickpick#close()<CR>
86 nnoremap <buffer><silent> <Plug>(lsp-quickpick-close) :<C-u>call lsp#internal#ui#quickpick#close()<CR>
88 inoremap <buffer><silent> <Plug>(lsp-quickpick-cancel) <ESC>:<C-u>call <SID>on_cancel()<CR>
89 nnoremap <buffer><silent> <Plug>(lsp-quickpick-cancel) :<C-u>call <SID>on_cancel()<CR>
91 inoremap <buffer><silent> <Plug>(lsp-quickpick-move-next) <C-o>:<C-u>call <SID>on_move_next()<CR>
92 nnoremap <buffer><silent> <Plug>(lsp-quickpick-move-next) :<C-u>call <SID>on_move_next()<CR>
94 inoremap <buffer><silent> <Plug>(lsp-quickpick-move-previous) <C-o>:<C-u>call <SID>on_move_previous()<CR>
95 nnoremap <buffer><silent> <Plug>(lsp-quickpick-move-previous) :<C-u>call <SID>on_move_previous()<CR>
97 exec printf('setlocal filetype=' . s:state['promptfiletype'])
99 if !hasmapto('<Plug>(lsp-quickpick-accept)')
100 imap <buffer><cr> <Plug>(lsp-quickpick-accept)
101 nmap <buffer><cr> <Plug>(lsp-quickpick-accept)
104 if !hasmapto('<Plug>(lsp-quickpick-cancel)')
105 imap <silent> <buffer> <C-c> <Plug>(lsp-quickpick-cancel)
106 map <silent> <buffer> <C-c> <Plug>(lsp-quickpick-cancel)
107 imap <silent> <buffer> <Esc> <Plug>(lsp-quickpick-cancel)
108 map <silent> <buffer> <Esc> <Plug>(lsp-quickpick-cancel)
111 if !hasmapto('<Plug>(lsp-quickpick-move-next)')
112 imap <silent> <buffer> <C-n> <Plug>(lsp-quickpick-move-next)
113 nmap <silent> <buffer> <C-n> <Plug>(lsp-quickpick-move-next)
114 imap <silent> <buffer> <C-j> <Plug>(lsp-quickpick-move-next)
115 nmap <silent> <buffer> <C-j> <Plug>(lsp-quickpick-move-next)
118 if !hasmapto('<Plug>(lsp-quickpick-move-previous)')
119 imap <silent> <buffer> <C-p> <Plug>(lsp-quickpick-move-previous)
120 nmap <silent> <buffer> <C-p> <Plug>(lsp-quickpick-move-previous)
121 imap <silent> <buffer> <C-k> <Plug>(lsp-quickpick-move-previous)
122 nmap <silent> <buffer> <C-k> <Plug>(lsp-quickpick-move-previous)
125 call cursor(line('$'), 0)
126 call feedkeys('i', 'n')
128 augroup lsp#internal#ui#quickpick
130 autocmd InsertCharPre <buffer> call s:on_insertcharpre()
131 autocmd TextChangedI <buffer> call s:on_inputchanged()
132 autocmd InsertEnter <buffer> call s:on_insertenter()
133 autocmd InsertLeave <buffer> call s:on_insertleave()
135 if exists('##TextChangedP')
136 autocmd TextChangedP <buffer> call s:on_inputchanged()
140 call s:notify_items()
141 call s:notify_selection()
142 call lsp#internal#ui#quickpick#busy(s:state['busy'])
145 function! s:set_buffer_options() abort
148 setlocal bufhidden=unload " unload buf when no longer displayed
149 setlocal buftype=nofile " buffer is not related to any file<Paste>
150 setlocal noswapfile " don't create swap file
151 setlocal nowrap " don't soft-wrap
152 setlocal nonumber " don't show line numbers
153 setlocal nolist " don't use list mode (visible tabs etc)
154 setlocal foldcolumn=0 " don't show a fold column at side
155 setlocal foldlevel=99 " don't fold anything
156 setlocal nospell " spell checking off
157 setlocal nobuflisted " don't show up in the buffer list
158 setlocal textwidth=0 " don't hardwarp (break long lines)
159 setlocal nocursorline " highlight the line cursor is off
160 setlocal nocursorcolumn " disable cursor column
161 setlocal noundofile " don't enable undo
162 setlocal winfixheight
163 if exists('+colorcolumn') | setlocal colorcolumn=0 | endif
164 if exists('+relativenumber') | setlocal norelativenumber | endif
165 setlocal signcolumn=yes " for prompt
168 function! lsp#internal#ui#quickpick#close() abort
169 if !exists('s:state')
173 call lsp#internal#ui#quickpick#busy(0)
175 call win_gotoid(s:state['winid'])
176 call s:notify('close', { 'bufnr': s:state['bufnr'], 'winid': s:state['winid'], 'resultsbufnr': s:state['resultsbufnr'], 'resultswinid': s:state['winid'] })
178 augroup lsp#internal#ui#quickpick
182 exe 'silent! bunload! ' . s:state['promptbufnr']
183 exe 'silent! bunload! ' . s:state['resultsbufnr']
184 call s:restore_windows()
186 let s:inputecharpre = 0
191 function! s:restore_windows() abort
192 let [l:tabnr, l:_] = win_id2tabwin(s:state['winid'])
197 let l:Resizable = {_, info ->
198 \ info.tabnr == l:tabnr &&
199 \ s:win_exists(info.winid) &&
200 \ !s:is_floating(info.winid)
202 let l:wins_to_resize = sort(filter(s:state['wininfo'], l:Resizable), {l, r -> l.winnr - r.winnr})
203 let l:open_winids_to_resize = map(filter(getwininfo(), l:Resizable), {_, info -> info.winid})
205 let l:resize_cmd = ''
206 for l:info in l:wins_to_resize
207 if index(l:open_winids_to_resize, l:info.winid) == -1
211 let l:resize_cmd .= printf('%dresize %d | vert %dresize %d |', l:info.winnr, l:info.height, l:info.winnr, l:info.width)
214 " winrestcmd repeats :resize commands twice after patch-8.2.2631.
215 " To simulate this behavior, execute the :resize commands twice.
216 " see https://github.com/vim/vim/issues/7988
217 exe 'silent! ' . l:resize_cmd . l:resize_cmd
220 function! lsp#internal#ui#quickpick#items(items) abort
221 let s:state['items'] = a:items
222 call s:update_items()
223 call s:notify_items()
224 call s:notify_selection()
227 function! lsp#internal#ui#quickpick#busy(busy) abort
228 let s:state['busy'] = a:busy
230 if !has_key(s:state, 'busytimer')
231 let s:state['busyframe'] = 0
232 let s:state['busytimer'] = timer_start(60, function('s:busy_tick'), { 'repeat': -1 })
235 if has_key(s:state, 'busytimer')
236 call timer_stop(s:state['busytimer'])
237 call remove(s:state, 'busytimer')
245 function! lsp#internal#ui#quickpick#results_winid() abort
247 return s:state['resultswinid']
253 function! s:busy_tick(...) abort
254 let s:state['busyframe'] = s:state['busyframe'] + 1
255 if s:state['busyframe'] >= len(s:state['busyframes'])
256 let s:state['busyframe'] = 0
259 echohl Question | echon s:state['busyframes'][s:state['busyframe']]
263 function! s:update_items() abort
264 call s:win_execute(s:state['resultswinid'], 'silent! %delete _')
266 let s:state['highlights'] = []
268 if s:state['filter'] " if filter is enabled
269 if empty(s:trim(s:state['input']))
270 let s:state['fitems'] = s:state['items']
272 if empty(s:state['key']) " item is string
273 if s:has_matchfuzzypos
274 let l:matchfuzzyresult = matchfuzzypos(s:state['items'], s:state['input'])
275 let l:fitems = l:matchfuzzyresult[0]
276 let l:highlights = l:matchfuzzyresult[1]
277 let s:state['fitems'] = l:fitems
278 let s:state['highlights'] = l:highlights
279 elseif s:has_matchfuzzy
280 let s:state['fitems'] = matchfuzzy(s:state['items'], s:state['input'])
282 let s:state['fitems'] = filter(copy(s:state['items']), 'stridx(toupper(v:val), toupper(s:state["input"])) >= 0')
285 if s:has_matchfuzzypos
286 " vim requires matchfuzzypos to have highlights.
287 " matchfuzzy only patch doesn't support dict search
288 let l:matchfuzzyresult = matchfuzzypos(s:state['items'], s:state['input'], { 'key': s:state['key'] })
289 let l:fitems = l:matchfuzzyresult[0]
290 let l:highlights = l:matchfuzzyresult[1]
291 let s:state['fitems'] = l:fitems
292 let s:state['highlights'] = l:highlights
294 let s:state['fitems'] = filter(copy(s:state['items']), 'stridx(toupper(v:val[s:state["key"]]), toupper(s:state["input"])) >= 0')
298 else " if filter is disabled
299 let s:state['fitems'] = s:state['items']
303 if empty(s:state['key']) " item is string
304 let l:lines = s:state['fitems']
306 let l:lines = map(copy(s:state['fitems']), 'v:val[s:state["key"]]')
309 call setbufline(s:state['resultsbufnr'], 1, l:lines)
311 if s:has_proptype && !empty(s:state['highlights'])
313 for l:line in s:state['highlights']
315 let l:cs = split(getbufline(s:state['resultsbufnr'], l:i + 1)[0], '\zs')
316 let l:mpos = strlen(join(l:cs[: l:pos - 1], ''))
317 let l:len = strlen(l:cs[l:pos])
318 call prop_add(l:i + 1, l:mpos + 1, { 'length': l:len, 'type': 'highlight', 'bufnr': s:state['resultsbufnr'] })
324 call s:win_execute(s:state['resultswinid'], printf('resize %d', min([len(s:state['fitems']), s:state['maxheight']])))
325 call s:win_execute(s:state['promptwinid'], 'resize 1')
328 function! s:on_accept() abort
329 if win_gotoid(s:state['resultswinid'])
330 let l:index = line('.') - 1 " line is 1 index, list is 0 index
331 let l:fitems = s:state['fitems']
332 if l:index < 0 || len(l:fitems) <= l:index
335 let l:items = [l:fitems[l:index]]
337 call win_gotoid(s:state['winid'])
338 call s:notify('accept', { 'items': l:items })
342 function! s:on_cancel() abort
343 call win_gotoid(s:state['winid'])
344 call s:notify('cancel', {})
345 call lsp#internal#ui#quickpick#close()
348 function! s:on_move_next() abort
350 call s:win_execute(s:state['resultswinid'], 'normal! j')
351 call s:notify_selection()
354 function! s:on_move_previous() abort
356 call s:win_execute(s:state['resultswinid'], 'normal! k')
357 call s:notify_selection()
360 function! s:notify_items() abort
361 " items could be huge, so don't send the items as part of data
362 call s:notify('items', { 'bufnr': s:state['bufnr'], 'winid': s:state['winid'], 'resultsbufnr': s:state['resultsbufnr'], 'resultswinid': s:state['resultswinid'] })
365 function! s:notify_selection() abort
366 let l:original_winid = win_getid()
367 call win_gotoid(s:state['resultswinid'])
368 let l:index = line('.') - 1 " line is 1 based, list is 0 based
369 if l:index < 0 || ((l:index + 1) > len(s:state['fitems']))
372 let l:items = [s:state['fitems'][l:index]]
375 \ 'bufnr': s:state['bufnr'],
376 \ 'winid': s:state['winid'],
377 \ 'resultsbufnr': s:state['resultsbufnr'],
378 \ 'resultswinid': s:state['resultswinid'],
381 noautocmd call win_gotoid(s:state['winid'])
382 call s:notify('selection', l:data)
383 noautocmd call win_gotoid(l:original_winid)
386 function! s:on_inputchanged() abort
388 if s:has_timer && s:state['debounce'] > 0
389 call s:debounce_onchange()
391 call s:notify_onchange()
396 function! s:on_insertcharpre() abort
397 let s:inputecharpre = 1
400 function! s:on_insertenter() abort
401 let s:inputecharpre = 0
404 function! s:on_insertleave() abort
405 if s:has_timer && has_key(s:state, 'debounce_onchange_timer')
406 call timer_stop(s:state['debounce_onchange_timer'])
407 call remove(s:state, 'debounce_onchange_timer')
411 function! s:debounce_onchange() abort
412 if has_key(s:state, 'debounce_onchange_timer')
413 call timer_stop(s:state['debounce_onchange_timer'])
414 call remove(s:state, 'debounce_onchange_timer')
416 let s:state['debounce_onchange_timer'] = timer_start(s:state['debounce'], function('s:notify_onchange'))
419 function! s:notify_onchange(...) abort
420 let s:state['input'] = getbufline(s:state['promptbufnr'], 1)[0]
421 call s:notify('change', { 'input': s:state['input'] })
423 call s:update_items()
424 call s:notify_selection()
428 function! s:notify(name, data) abort
429 if has_key(s:state, 'on_event') | call s:state['on_event'](a:data, a:name) | endif
430 if has_key(s:state, 'on_' . a:name) | call s:state['on_' . a:name](a:data, a:name) | endif
433 if exists('*win_execute')
434 function! s:win_execute(win_id, cmd) abort
435 call win_execute(a:win_id, a:cmd)
438 function! s:win_execute(winid, cmd) abort
439 let l:original_winid = win_getid()
440 if l:original_winid == a:winid
443 if win_gotoid(a:winid)
445 call win_gotoid(l:original_winid)
452 function! s:trim(str) abort
456 function! s:trim(str) abort
457 return substitute(a:str, '^\s*\|\s*$', '', 'g')
461 " vim: set sw=2 ts=2 sts=2 et tw=78 foldmarker={{{,}}} foldmethod=marker spell: