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.
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$')
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")
13 function! s:_vital_loaded(V) abort
14 let s:Window = a:V.import('VS.Vim.Window')
20 function! s:_vital_depends() abort
21 return ['VS.Vim.Window']
25 " managed floating windows.
27 let s:floating_windows = {}
32 function! s:is_available() abort
36 return exists('*popup_create') && exists('*popup_close') && exists('*popup_move') && exists('*popup_getpos')
42 function! s:new(...) abort
45 return s:FloatingWindow.new(get(a:000, 0, {}))
51 " @param {number} winid
52 " @param {VS.Vim.Window.FloatingWindow} floating_window
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()
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]
71 let s:FloatingWindow = {}
76 " @param {function?} args.on_opened
77 " @param {function?} args.on_closed
79 function! s:FloatingWindow.new(args) abort
80 return extend(deepcopy(s:FloatingWindow), {
84 \ '_on_opened': get(a:args, 'on_opened', { -> {} }),
85 \ '_on_closed': get(a:args, 'on_closed', { -> {} }),
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
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.'
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, '^', '$')
111 for l:line in l:lines
112 let l:width = max([l:width, strdisplaywidth(l:line)])
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])
119 if get(a:args, 'wrap', get(self._vars, '&wrap', 0))
121 for l:line in l:lines
122 let l:height += max([1, float2nr(ceil(strdisplaywidth(l:line) / str2float('' . l:width)))])
125 let l:height = len(l:lines)
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])
131 \ 'width': max([1, l:width]),
132 \ 'height': max([1, l:height]),
139 " @param {number} bufnr
141 function! s:FloatingWindow.set_bufnr(bufnr) abort
142 let self._bufnr = a:bufnr
148 function! s:FloatingWindow.get_bufnr() abort
155 function! s:FloatingWindow.get_winid() abort
165 function! s:FloatingWindow.info() abort
167 return s:_info(self._winid)
175 " @param {string} key
176 " @param {unknown} value
178 function! s:FloatingWindow.set_var(key, value) abort
179 let self._vars[a:key] = a:value
181 call setwinvar(self._winid, a:key, a:value)
188 " @param {string} key
190 function! s:FloatingWindow.get_var(key) abort
191 return self._vars[a:key]
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
206 function! s:FloatingWindow.open(args) abort
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'),
217 let l:will_move = self.is_visible()
219 let self._winid = s:_move(self, self._winid, self._bufnr, l:style)
221 let self._winid = s:_open(self._bufnr, l:style, { -> self._on_closed() })
223 for [l:key, l:value] in items(self._vars)
224 call setwinvar(self._winid, l:key, l:value)
227 call s:_notify_opened(self._winid, self)
234 function! s:FloatingWindow.close() abort
236 call s:_close(self._winid)
238 let self._winid = v:null
244 function! s:FloatingWindow.enter() abort
245 call s:_enter(self._winid)
251 function! s:FloatingWindow.is_visible() abort
252 return s:_exists(self._winid) ? v:true : v:false
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)
265 function! s:_open(bufnr, style, callback) abort
266 return popup_create(a:bufnr, extend(s:_style(a:style), {
267 \ 'callback': a:callback,
276 function! s:_close(winid) abort
277 call nvim_win_close(a:winid, v:true)
278 call s:_notify_closed()
281 function! s:_close(winid) abort
282 call popup_close(a:winid)
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)
295 call s:Window.scroll(a:winid, a:style.topline)
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() })
308 let l:style = s:_style(a:style)
309 call popup_move(a:winid, l:style)
310 call popup_setoptions(a:winid, l:style)
319 function! s:_enter(winid) abort
320 call win_gotoid(a:winid)
323 function! s:_enter(winid) abort
332 function! s:_exists(winid) abort
334 return type(a:winid) == type(0) && nvim_win_is_valid(a:winid) && nvim_win_get_number(a:winid) != -1
340 function! s:_exists(winid) abort
341 return type(a:winid) == type(0) && winheight(a:winid) != -1
349 function! s:_info(winid) abort
350 let l:info = getwininfo(a:winid)[0]
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,
360 function! s:_info(winid) abort
361 let l:pos = popup_getpos(a:winid)
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,
376 function! s:_style(style) abort
377 let l:style = s:_resolve_origin(a:style)
378 let l:style = s:_resolve_border(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',
389 if !exists('*win_execute') " We can't detect neovim features via patch version so we try it by function existence.
395 function! s:_style(style) abort
396 let l:style = s:_resolve_origin(a:style)
397 let l:style = s:_resolve_border(l:style)
399 \ 'line': l:style.row,
400 \ 'col': l:style.col,
403 \ 'moved': [0, 0, 0],
405 \ 'maxwidth': l:style.width,
406 \ 'maxheight': l:style.height,
407 \ 'minwidth': l:style.width,
408 \ 'minheight': l:style.height,
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', []),
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'
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)
453 function! s:_resolve_border(style) abort
454 let l:border = get(a:style, 'border', v:null)
456 if type(l:border) != type([])
457 if &ambiwidth ==# 'single'
458 let a:style.border = ['┌', '─', '┐', '│', '┘', '─', '└', '│']
460 let a:style.border = ['+', '-', '+', '|', '+', '-', '+', '|']
463 elseif has_key(a:style, 'border')
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 = ['─', '│', '─', '│', '┌', '┐', '┘', '└']
476 let a:style.border = ['-', '|', '-', '|', '+', '+', '+', '+']
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,
493 elseif has_key(a:style, 'border')
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')
509 let s:has_init = v:true
510 execute printf('augroup VS_Vim_Window_FloatingWindow:%s', s:filepath)
512 autocmd WinEnter * call <SID>_notify_closed()