]> git.madduck.net Git - etc/vim.git/blob - .vim/bundle/vim-lsp/autoload/lsp/internal/ui/quickpick.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

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.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Merge commit 'd49e95aa7ba744f0a7f544aca43afdb6aab41f24' as '.vim/bundle/asyncomplete...
[etc/vim.git] / .vim / bundle / vim-lsp / autoload / lsp / internal / ui / quickpick.vim
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
3
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')
8
9 "
10 " is_floating
11 "
12 if has('nvim')
13   function! s:is_floating(winid) abort
14     if !s:win_exists(a:winid)
15       return 0
16     endif
17     let l:config = nvim_win_get_config(a:winid)
18     return empty(l:config) || !empty(get(l:config, 'relative', ''))
19   endfunction
20 else
21   function! s:is_floating(winid) abort
22     return s:win_exists(a:winid) && win_id2win(a:winid) == 0
23   endfunction
24 endif
25
26 function! s:win_exists(winid) abort
27   return winheight(a:winid) != -1
28 endfunction
29
30 function! lsp#internal#ui#quickpick#open(opt) abort
31   call lsp#internal#ui#quickpick#close() " hide existing picker if exists
32
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({
36       \ 'items': [],
37       \ 'highlights': [],
38       \ 'fitems': [],
39       \ 'key': '',
40       \ 'busy': 0,
41       \ 'busyframes': ['-', '\', '|', '/'],
42       \ 'filetype': 'lsp-quickpick',
43       \ 'promptfiletype': 'lsp-quickpick-filter',
44       \ 'input': '',
45       \ 'maxheight': 10,
46       \ 'debounce': 250,
47       \ 'filter': 1,
48       \ }, a:opt)
49
50   let s:inputecharpre = 0
51   let s:state['busyframe'] = 0
52
53   let s:state['bufnr'] = bufnr('%')
54   let s:state['winid'] = win_getid()
55   let s:state['wininfo'] = getwininfo()
56
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()
61   if s:has_proptype
62     call prop_type_add('highlight', { 'highlight': 'Directory', 'bufnr': s:state['resultsbufnr'] })
63   endif
64
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()
69
70   call win_gotoid(s:state['resultswinid'])
71   call s:set_buffer_options()
72   setlocal cursorline
73   call s:update_items()
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'] })
76
77   call win_gotoid(s:state['promptwinid'])
78   call s:set_buffer_options()
79   call setline(1, s:state['input'])
80
81   " map keys
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>
84
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>
87
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>
90
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>
93
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>
96
97   exec printf('setlocal filetype=' . s:state['promptfiletype'])
98
99   if !hasmapto('<Plug>(lsp-quickpick-accept)')
100     imap <buffer><cr> <Plug>(lsp-quickpick-accept)
101     nmap <buffer><cr> <Plug>(lsp-quickpick-accept)
102   endif
103
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)
109   endif
110
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)
116   endif
117
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)
123   endif
124
125   call cursor(line('$'), 0)
126   call feedkeys('i', 'n')
127
128   augroup lsp#internal#ui#quickpick
129     autocmd!
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()
134
135     if exists('##TextChangedP')
136       autocmd TextChangedP  <buffer> call s:on_inputchanged()
137     endif
138   augroup END
139
140   call s:notify_items()
141   call s:notify_selection()
142   call lsp#internal#ui#quickpick#busy(s:state['busy'])
143 endfunction
144
145 function! s:set_buffer_options() abort
146   " set buffer options
147   abc <buffer>
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
166 endfunction
167
168 function! lsp#internal#ui#quickpick#close() abort
169   if !exists('s:state')
170     return
171   endif
172
173   call lsp#internal#ui#quickpick#busy(0)
174
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'] })
177
178   augroup lsp#internal#ui#quickpick
179     autocmd!
180   augroup END
181
182   exe 'silent! bunload! ' . s:state['promptbufnr']
183   exe 'silent! bunload! ' . s:state['resultsbufnr']
184   call s:restore_windows()
185
186   let s:inputecharpre = 0
187
188   unlet s:state
189 endfunction
190
191 function! s:restore_windows() abort
192   let [l:tabnr, l:_] = win_id2tabwin(s:state['winid'])
193   if l:tabnr == 0
194     return
195   endif
196
197   let l:Resizable = {_, info ->
198         \ info.tabnr == l:tabnr &&
199         \ s:win_exists(info.winid) &&
200         \ !s:is_floating(info.winid)
201         \ }
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})
204
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
208       return
209     endif
210
211     let l:resize_cmd .= printf('%dresize %d | vert %dresize %d |', l:info.winnr, l:info.height, l:info.winnr, l:info.width)
212   endfor
213
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
218 endfunction
219
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()
225 endfunction
226
227 function! lsp#internal#ui#quickpick#busy(busy) abort
228   let s:state['busy'] = a:busy
229   if 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 })
233     endif
234   else
235     if has_key(s:state, 'busytimer')
236       call timer_stop(s:state['busytimer'])
237       call remove(s:state, 'busytimer')
238       redraw
239       echohl None
240       echo ''
241     endif
242   endif
243 endfunction
244
245 function! lsp#internal#ui#quickpick#results_winid() abort
246   if exists('s:state')
247     return s:state['resultswinid']
248   else
249     return 0
250   endif
251 endfunction
252
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
257   endif
258   redraw
259   echohl Question | echon s:state['busyframes'][s:state['busyframe']]
260   echohl None
261 endfunction
262
263 function! s:update_items() abort
264   call s:win_execute(s:state['resultswinid'], 'silent! %delete _')
265
266   let s:state['highlights'] = []
267
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']
271     else
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'])
281         else
282           let s:state['fitems'] = filter(copy(s:state['items']), 'stridx(toupper(v:val), toupper(s:state["input"])) >= 0')
283         endif
284       else " item is dict
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
293         else
294           let s:state['fitems'] = filter(copy(s:state['items']), 'stridx(toupper(v:val[s:state["key"]]), toupper(s:state["input"])) >= 0')
295         endif
296       endif
297     endif
298   else " if filter is disabled
299     let s:state['fitems'] = s:state['items']
300   endif
301
302
303   if empty(s:state['key']) " item is string
304     let l:lines = s:state['fitems']
305   else " item is dict
306     let l:lines = map(copy(s:state['fitems']), 'v:val[s:state["key"]]')
307   endif
308
309   call setbufline(s:state['resultsbufnr'], 1, l:lines)
310
311   if s:has_proptype && !empty(s:state['highlights'])
312     let l:i = 0
313     for l:line in s:state['highlights']
314       for l:pos in l:line
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'] })
319       endfor
320       let l:i += 1
321     endfor
322   endif
323
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')
326 endfunction
327
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
333       let l:items = []
334     else
335       let l:items = [l:fitems[l:index]]
336     endif
337     call win_gotoid(s:state['winid'])
338     call s:notify('accept', { 'items': l:items })
339   end
340 endfunction
341
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()
346 endfunction
347
348 function! s:on_move_next() abort
349   let l:col = col('.')
350   call s:win_execute(s:state['resultswinid'], 'normal! j')
351   call s:notify_selection()
352 endfunction
353
354 function! s:on_move_previous() abort
355   let l:col = col('.')
356   call s:win_execute(s:state['resultswinid'], 'normal! k')
357   call s:notify_selection()
358 endfunction
359
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'] })
363 endfunction
364
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']))
370     let l:items = []
371   else
372     let l:items = [s:state['fitems'][l:index]]
373   endif
374   let l:data = {
375     \ 'bufnr': s:state['bufnr'],
376     \ 'winid': s:state['winid'],
377     \ 'resultsbufnr': s:state['resultsbufnr'],
378     \ 'resultswinid': s:state['resultswinid'],
379     \ 'items': l:items,
380     \ }
381   noautocmd call win_gotoid(s:state['winid'])
382   call s:notify('selection', l:data)
383   noautocmd call win_gotoid(l:original_winid)
384 endfunction
385
386 function! s:on_inputchanged() abort
387   if s:inputecharpre
388     if s:has_timer && s:state['debounce'] > 0
389       call s:debounce_onchange()
390     else
391       call s:notify_onchange()
392     endif
393   endif
394 endfunction
395
396 function! s:on_insertcharpre() abort
397   let s:inputecharpre = 1
398 endfunction
399
400 function! s:on_insertenter() abort
401   let s:inputecharpre = 0
402 endfunction
403
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')
408   endif
409 endfunction
410
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')
415   endif
416   let s:state['debounce_onchange_timer'] = timer_start(s:state['debounce'], function('s:notify_onchange'))
417 endfunction
418
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'] })
422   if s:state['filter']
423     call s:update_items()
424     call s:notify_selection()
425   endif
426 endfunction
427
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
431 endfunction
432
433 if exists('*win_execute')
434   function! s:win_execute(win_id, cmd) abort
435     call win_execute(a:win_id, a:cmd)
436   endfunction
437 else
438   function! s:win_execute(winid, cmd) abort
439     let l:original_winid = win_getid()
440     if l:original_winid == a:winid
441       exec a:cmd
442     else
443       if win_gotoid(a:winid)
444         exec a:cmd
445         call win_gotoid(l:original_winid)
446       end
447     endif
448   endfunction
449 endif
450
451 if exists('*trim')
452   function! s:trim(str) abort
453     return trim(a:str)
454   endfunction
455 else
456   function! s:trim(str) abort
457     return substitute(a:str, '^\s*\|\s*$', '', 'g')
458   endfunction
459 endif
460
461 " vim: set sw=2 ts=2 sts=2 et tw=78 foldmarker={{{,}}} foldmethod=marker spell: