]> git.madduck.net Git - etc/vim.git/blob - autoload/vital/_lsp/VS/Vim/Window/FloatingWindow.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:

Squashed '.vim/bundle/vim-lsp/' content from commit 04428c92
[etc/vim.git] / autoload / vital / _lsp / VS / Vim / Window / FloatingWindow.vim
1 " ___vital___
2 " NOTE: lines between '" ___vital___' is generated by :Vitalize.
3 " Do not modify the code nor insert new lines before '" ___vital___'
4 function! s:_SID() abort
5   return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
6 endfunction
7 execute join(['function! vital#_lsp#VS#Vim#Window#FloatingWindow#import() abort', printf("return map({'_vital_depends': '', 'is_available': '', 'new': '', '_vital_loaded': ''}, \"vital#_lsp#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
8 delfunction s:_SID
9 " ___vital___
10 "
11 " _vital_loaded
12 "
13 function! s:_vital_loaded(V) abort
14   let s:Window = a:V.import('VS.Vim.Window')
15 endfunction
16
17 "
18 " _vital_depends
19 "
20 function! s:_vital_depends() abort
21   return ['VS.Vim.Window']
22 endfunction
23
24 "
25 " managed floating windows.
26 "
27 let s:floating_windows = {}
28
29 "
30 " is_available
31 "
32 function! s:is_available() abort
33   if has('nvim')
34     return v:true
35   endif
36   return exists('*popup_create') && exists('*popup_close') && exists('*popup_move') && exists('*popup_getpos')
37 endfunction
38
39 "
40 " new
41 "
42 function! s:new(...) abort
43   call s:_init()
44
45   return s:FloatingWindow.new(get(a:000, 0, {}))
46 endfunction
47
48 "
49 " _notify_opened
50 "
51 " @param {number} winid
52 " @param {VS.Vim.Window.FloatingWindow} floating_window
53 "
54 function! s:_notify_opened(winid, floating_window) abort
55   let s:floating_windows[a:winid] = a:floating_window
56   call a:floating_window._on_opened()
57 endfunction
58
59 "
60 " _notify_closed
61 "
62 function! s:_notify_closed() abort
63   for [l:winid, l:floating_window] in items(s:floating_windows)
64     if winheight(l:winid) == -1
65       call l:floating_window._on_closed()
66       unlet s:floating_windows[l:winid]
67     endif
68   endfor
69 endfunction
70
71 let s:FloatingWindow = {}
72
73 "
74 " new
75 "
76 " @param {function?} args.on_opened
77 " @param {function?} args.on_closed
78 "
79 function! s:FloatingWindow.new(args) abort
80   return extend(deepcopy(s:FloatingWindow), {
81   \   '_winid': v:null,
82   \   '_bufnr': v:null,
83   \   '_vars': {},
84   \   '_on_opened': get(a:args, 'on_opened', { -> {} }),
85   \   '_on_closed': get(a:args, 'on_closed', { -> {} }),
86   \ })
87 endfunction
88
89 "
90 " get_size
91 "
92 " @param {number?} args.minwidth
93 " @param {number?} args.maxwidth
94 " @param {number?} args.minheight
95 " @param {number?} args.maxheight
96 " @param {boolean?} args.wrap
97 "
98 function! s:FloatingWindow.get_size(args) abort
99   if self._bufnr is# v:null
100     throw 'VS.Vim.Window.FloatingWindow: Failed to detect bufnr.'
101   endif
102
103   let l:maxwidth = get(a:args, 'maxwidth', -1)
104   let l:minwidth = get(a:args, 'minwidth', -1)
105   let l:maxheight = get(a:args, 'maxheight', -1)
106   let l:minheight = get(a:args, 'minheight', -1)
107   let l:lines = getbufline(self._bufnr, '^', '$')
108
109   " width
110   let l:width = 0
111   for l:line in l:lines
112     let l:width = max([l:width, strdisplaywidth(l:line)])
113   endfor
114
115   let l:width = l:minwidth == -1 ? l:width : max([l:minwidth, l:width])
116   let l:width = l:maxwidth == -1 ? l:width : min([l:maxwidth, l:width])
117
118   " height
119   if get(a:args, 'wrap', get(self._vars, '&wrap', 0))
120     let l:height = 0
121     for l:line in l:lines
122       let l:height += max([1, float2nr(ceil(strdisplaywidth(l:line) / str2float('' . l:width)))])
123     endfor
124   else
125     let l:height = len(l:lines)
126   endif
127   let l:height = l:minheight == -1 ? l:height : max([l:minheight, l:height])
128   let l:height = l:maxheight == -1 ? l:height : min([l:maxheight, l:height])
129
130   return {
131   \   'width': max([1, l:width]),
132   \   'height': max([1, l:height]),
133   \ }
134 endfunction
135
136 "
137 " set_bufnr
138 "
139 " @param {number} bufnr
140 "
141 function! s:FloatingWindow.set_bufnr(bufnr) abort
142   let self._bufnr = a:bufnr
143 endfunction
144
145 "
146 " get_bufnr
147 "
148 function! s:FloatingWindow.get_bufnr() abort
149   return self._bufnr
150 endfunction
151
152 "
153 " get_winid
154 "
155 function! s:FloatingWindow.get_winid() abort
156   if self.is_visible()
157     return self._winid
158   endif
159   return v:null
160 endfunction
161
162 "
163 " info
164 "
165 function! s:FloatingWindow.info() abort
166   if self.is_visible()
167     return s:_info(self._winid)
168   endif
169   return v:null
170 endfunction
171
172 "
173 " set_var
174 "
175 " @param {string}  key
176 " @param {unknown} value
177 "
178 function! s:FloatingWindow.set_var(key, value) abort
179   let self._vars[a:key] = a:value
180   if self.is_visible()
181     call setwinvar(self._winid, a:key, a:value)
182   endif
183 endfunction
184
185 "
186 " get_var
187 "
188 " @param {string} key
189 "
190 function! s:FloatingWindow.get_var(key) abort
191   return self._vars[a:key]
192 endfunction
193
194 "
195 " open
196 "
197 " @param {number} args.row 0-based indexing
198 " @param {number} args.col 0-based indexing
199 " @param {number} args.width
200 " @param {number} args.height
201 " @param {boolean|[string]?} args.border - boolean, or list of characters
202 "        clockwise from top-left (same as nvim_open_win() in neovim)
203 " @param {number?} args.topline
204 " @param {string?} args.origin - topleft/topright/botleft/botright
205 "
206 function! s:FloatingWindow.open(args) abort
207   let l:style = {
208   \   'row': a:args.row,
209   \   'col': a:args.col,
210   \   'width': a:args.width,
211   \   'height': a:args.height,
212   \   'border': get(a:args, 'border', v:false),
213   \   'topline': get(a:args, 'topline', 1),
214   \   'origin': get(a:args, 'origin', 'topleft'),
215   \ }
216
217   let l:will_move = self.is_visible()
218   if l:will_move
219     let self._winid = s:_move(self, self._winid, self._bufnr, l:style)
220   else
221     let self._winid = s:_open(self._bufnr, l:style, { -> self._on_closed() })
222   endif
223   for [l:key, l:value] in items(self._vars)
224     call setwinvar(self._winid, l:key, l:value)
225   endfor
226   if !l:will_move
227     call s:_notify_opened(self._winid, self)
228   endif
229 endfunction
230
231 "
232 " close
233 "
234 function! s:FloatingWindow.close() abort
235   if self.is_visible()
236     call s:_close(self._winid)
237   endif
238   let self._winid = v:null
239 endfunction
240
241 "
242 " enter
243 "
244 function! s:FloatingWindow.enter() abort
245   call s:_enter(self._winid)
246 endfunction
247
248 "
249 " is_visible
250 "
251 function! s:FloatingWindow.is_visible() abort
252   return s:_exists(self._winid) ? v:true : v:false
253 endfunction
254
255 "
256 " open
257 "
258 if has('nvim')
259   function! s:_open(bufnr, style, callback) abort
260     let l:winid = nvim_open_win(a:bufnr, v:false, s:_style(a:style))
261     call s:Window.scroll(l:winid, a:style.topline)
262     return l:winid
263   endfunction
264 else
265   function! s:_open(bufnr, style, callback) abort
266     return popup_create(a:bufnr, extend(s:_style(a:style), {
267     \  'callback': a:callback,
268     \ }, 'force'))
269   endfunction
270 endif
271
272 "
273 " close
274 "
275 if has('nvim')
276   function! s:_close(winid) abort
277     call nvim_win_close(a:winid, v:true)
278     call s:_notify_closed()
279   endfunction
280 else
281   function! s:_close(winid) abort
282     call popup_close(a:winid)
283   endfunction
284 endif
285
286 "
287 " move
288 "
289 if has('nvim')
290   function! s:_move(self, winid, bufnr, style) abort
291     call nvim_win_set_config(a:winid, s:_style(a:style))
292     if a:bufnr != nvim_win_get_buf(a:winid)
293       call nvim_win_set_buf(a:winid, a:bufnr)
294     endif
295     call s:Window.scroll(a:winid, a:style.topline)
296     return a:winid
297   endfunction
298 else
299   function! s:_move(self, winid, bufnr, style) abort
300     " vim's popup window can't change bufnr so open new popup in here.
301     if a:bufnr != winbufnr(a:winid)
302       let l:On_closed = a:self._on_closed
303       let a:self._on_closed = { -> {} }
304       call s:_close(a:winid)
305       let a:self._on_closed = l:On_closed
306       return s:_open(a:bufnr, a:style, { -> a:self._on_closed() })
307     endif
308     let l:style = s:_style(a:style)
309     call popup_move(a:winid, l:style)
310     call popup_setoptions(a:winid, l:style)
311     return a:winid
312   endfunction
313 endif
314
315 "
316 " enter
317 "
318 if has('nvim')
319   function! s:_enter(winid) abort
320     call win_gotoid(a:winid)
321   endfunction
322 else
323   function! s:_enter(winid) abort
324     " not supported.
325   endfunction
326 endif
327
328 "
329 " exists
330 "
331 if has('nvim')
332   function! s:_exists(winid) abort
333     try
334       return type(a:winid) == type(0) && nvim_win_is_valid(a:winid) && nvim_win_get_number(a:winid) != -1
335     catch /.*/
336       return v:false
337     endtry
338   endfunction
339 else
340   function! s:_exists(winid) abort
341     return type(a:winid) == type(0) && winheight(a:winid) != -1
342   endfunction
343 endif
344
345 "
346 " info
347 "
348 if has('nvim')
349   function! s:_info(winid) abort
350     let l:info = getwininfo(a:winid)[0]
351     return {
352     \   'row': l:info.winrow,
353     \   'col': l:info.wincol,
354     \   'width': l:info.width,
355     \   'height': l:info.height,
356     \   'topline': l:info.topline,
357     \ }
358   endfunction
359 else
360   function! s:_info(winid) abort
361     let l:pos = popup_getpos(a:winid)
362     return {
363     \   'row': l:pos.core_line,
364     \   'col': l:pos.core_col,
365     \   'width': l:pos.core_width,
366     \   'height': l:pos.core_height,
367     \   'topline': l:pos.firstline,
368     \ }
369   endfunction
370 endif
371
372 "
373 " style
374 "
375 if has('nvim')
376   function! s:_style(style) abort
377     let l:style = s:_resolve_origin(a:style)
378     let l:style = s:_resolve_border(l:style)
379     let l:style = {
380     \   'relative': 'editor',
381     \   'row': l:style.row - 1,
382     \   'col': l:style.col - 1,
383     \   'width': l:style.width,
384     \   'height': l:style.height,
385     \   'focusable': v:true,
386     \   'style': 'minimal',
387     \   'border': has_key(l:style, 'border') ? l:style.border : 'none',
388     \ }
389     if !exists('*win_execute') " We can't detect neovim features via patch version so we try it by function existence.
390       unlet l:style.border
391     endif
392     return l:style
393   endfunction
394 else
395   function! s:_style(style) abort
396     let l:style = s:_resolve_origin(a:style)
397     let l:style = s:_resolve_border(l:style)
398     return {
399     \   'line': l:style.row,
400     \   'col': l:style.col,
401     \   'pos': 'topleft',
402     \   'wrap': v:false,
403     \   'moved': [0, 0, 0],
404     \   'scrollbar': 1,
405     \   'maxwidth': l:style.width,
406     \   'maxheight': l:style.height,
407     \   'minwidth': l:style.width,
408     \   'minheight': l:style.height,
409     \   'tabpage': 0,
410     \   'firstline': l:style.topline,
411     \   'padding': [0, 0, 0, 0],
412     \   'border': has_key(l:style, 'border') ? [1, 1, 1, 1] : [0, 0, 0, 0],
413     \   'borderchars': get(l:style, 'border', []),
414     \   'fixed': v:true,
415     \ }
416   endfunction
417 endif
418
419 "
420 " _resolve_origin
421 "
422 function! s:_resolve_origin(style) abort
423   if index(['topleft', 'topright', 'bottomleft', 'bottomright', 'topcenter', 'bottomcenter'], a:style.origin) == -1
424     let a:style.origin = 'topleft'
425   endif
426
427   if a:style.origin ==# 'topleft'
428     let a:style.col = a:style.col
429     let a:style.row = a:style.row
430   elseif a:style.origin ==# 'topright'
431     let a:style.col = a:style.col - (a:style.width - 1)
432     let a:style.row = a:style.row
433   elseif a:style.origin ==# 'bottomleft'
434     let a:style.col = a:style.col
435     let a:style.row = a:style.row - (a:style.height - 1)
436   elseif a:style.origin ==# 'bottomright'
437     let a:style.col = a:style.col - (a:style.width - 1)
438     let a:style.row = a:style.row - (a:style.height - 1)
439   elseif a:style.origin ==# 'topcenter'
440     let a:style.col = a:style.col - float2nr(a:style.width / 2)
441     let a:style.row = a:style.row
442   elseif a:style.origin ==# 'bottomcenter'
443     let a:style.col = a:style.col - float2nr(a:style.width / 2)
444     let a:style.row = a:style.row - (a:style.height - 1)
445   elseif a:style.origin ==# 'centercenter'
446     let a:style.col = a:style.col - float2nr(a:style.width / 2)
447     let a:style.row = a:style.row - float2nr(a:style.height / 2)
448   endif
449   return a:style
450 endfunction
451
452 if has('nvim')
453   function! s:_resolve_border(style) abort
454     let l:border = get(a:style, 'border', v:null)
455     if !empty(l:border)
456       if type(l:border) != type([])
457         if &ambiwidth ==# 'single'
458           let a:style.border = ['┌', '─', '┐', '│', '┘', '─', '└', '│']
459         else
460           let a:style.border = ['+', '-', '+', '|', '+', '-', '+', '|']
461         endif
462       endif
463     elseif has_key(a:style, 'border')
464       unlet a:style.border
465     endif
466     return a:style
467   endfunction
468 else
469   function! s:_resolve_border(style) abort
470     let l:border = get(a:style, 'border', v:null)
471     if !empty(get(a:style, 'border', v:null))
472       if type(l:border) != type([])
473         if &ambiwidth ==# 'single'
474           let a:style.border = ['─', '│', '─', '│', '┌', '┐', '┘', '└']
475         else
476           let a:style.border = ['-', '|', '-', '|', '+', '+', '+', '+']
477         endif
478       else
479         " Emulate nvim behavior for lists of 1/2/4 elements
480         let l:topleft     = l:border[0]
481         let l:top         = get(l:border, 1, l:topleft)
482         let l:topright    = get(l:border, 2, l:topleft)
483         let l:right       = get(l:border, 3, l:top)
484         let l:bottomright = get(l:border, 4, l:topleft)
485         let l:bottom      = get(l:border, 5, l:top)
486         let l:bottomleft  = get(l:border, 6, l:topright)
487         let l:left        = get(l:border, 7, l:right)
488         let a:style.border = [
489         \   l:top, l:right, l:bottom, l:left,
490         \   l:topleft, l:topright, l:bottomright, l:bottomleft,
491         \ ]
492       endif
493     elseif has_key(a:style, 'border')
494       unlet a:style.border
495     endif
496     return a:style
497   endfunction
498 endif
499
500 "
501 " init
502 "
503 let s:has_init = v:false
504 let s:filepath = expand('<sfile>:p')
505 function! s:_init() abort
506   if s:has_init || !has('nvim')
507     return
508   endif
509   let s:has_init = v:true
510   execute printf('augroup VS_Vim_Window_FloatingWindow:%s', s:filepath)
511     autocmd!
512     autocmd WinEnter * call <SID>_notify_closed()
513   augroup END
514 endfunction
515