]> git.madduck.net Git - etc/vim.git/blob - .vim/bundle/vim-lsp/autoload/lsp/ui/vim/output.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 '56df844d3c39ec494dacc69eae34272b27db185a' as '.vim/bundle/asyncomplete'
[etc/vim.git] / .vim / bundle / vim-lsp / autoload / lsp / ui / vim / output.vim
1 let s:use_vim_popup = has('patch-8.1.1517') && g:lsp_preview_float && !has('nvim')
2 let s:use_nvim_float = exists('*nvim_open_win') && g:lsp_preview_float && has('nvim')
3 let s:use_preview = !s:use_vim_popup && !s:use_nvim_float
4
5 function! s:import_modules() abort
6     if exists('s:Markdown') | return | endif
7     let s:Markdown = vital#lsp#import('VS.Vim.Syntax.Markdown')
8     let s:MarkupContent = vital#lsp#import('VS.LSP.MarkupContent')
9     let s:Window = vital#lsp#import('VS.Vim.Window')
10     let s:Text = vital#lsp#import('VS.LSP.Text')
11 endfunction
12
13 let s:winid = v:false
14 let s:prevwin = v:false
15 let s:preview_data = v:false
16
17 function! s:vim_popup_closed(...) abort
18     let s:preview_data = v:false
19 endfunction
20
21 function! lsp#ui#vim#output#closepreview() abort
22     if win_getid() ==# s:winid
23         " Don't close if window got focus
24         return
25     endif
26
27     if s:winid == v:false
28         return
29     endif
30
31     "closing floats in vim8.1 must use popup_close()
32     "nvim must use nvim_win_close. pclose is not reliable and does not always work
33     if s:use_vim_popup && s:winid
34         call popup_close(s:winid)
35     elseif s:use_nvim_float && s:winid
36         silent! call nvim_win_close(s:winid, 0)
37     else
38         pclose
39     endif
40     let s:winid = v:false
41     let s:preview_data = v:false
42     augroup lsp_float_preview_close
43     augroup end
44     autocmd! lsp_float_preview_close CursorMoved,CursorMovedI,VimResized *
45     doautocmd <nomodeline> User lsp_float_closed
46 endfunction
47
48 function! lsp#ui#vim#output#focuspreview() abort
49     if s:is_cmdwin()
50         return
51     endif
52
53     " This does not work for vim8.1 popup but will work for nvim and old preview
54     if s:winid
55         if win_getid() !=# s:winid
56             let s:prevwin = win_getid()
57             call win_gotoid(s:winid)
58         elseif s:prevwin
59             " Temporarily disable hooks
60             " TODO: remove this when closing logic is able to distinguish different move directions
61             autocmd! lsp_float_preview_close CursorMoved,CursorMovedI,VimResized *
62             call win_gotoid(s:prevwin)
63             call s:add_float_closing_hooks()
64             let s:prevwin = v:false
65         endif
66     endif
67 endfunction
68
69 function! s:bufwidth() abort
70     let l:width = winwidth(0)
71     let l:numberwidth = max([&numberwidth, strlen(line('$'))+1])
72     let l:numwidth = (&number || &relativenumber)? l:numberwidth : 0
73     let l:foldwidth = &foldcolumn
74
75     if &signcolumn ==? 'yes'
76         let l:signwidth = 2
77     elseif &signcolumn ==? 'auto'
78         let l:signs = execute(printf('sign place buffer=%d', bufnr('')))
79         let l:signs = split(l:signs, "\n")
80         let l:signwidth = len(l:signs)>2? 2: 0
81     else
82         let l:signwidth = 0
83     endif
84     return l:width - l:numwidth - l:foldwidth - l:signwidth
85 endfunction
86
87
88 function! s:get_float_positioning(height, width) abort
89     let l:height = a:height
90     let l:width = a:width
91     " TODO: add option to configure it 'docked' at the bottom/top/right
92
93     " NOTE: screencol() and screenrow() start from (1,1)
94     " but the popup window co-ordinates start from (0,0)
95     " Very convenient!
96     " For a simple single-line 'tooltip', the following
97     " two lines are enough to determine the position
98
99     let l:col = screencol()
100     let l:row = screenrow()
101
102     let l:height = min([l:height, max([&lines - &cmdheight - l:row, &previewheight])])
103
104     let l:style = 'minimal'
105     let l:border = 'double'
106     " Positioning is not window but screen relative
107     let l:opts = {
108         \ 'relative': 'editor',
109         \ 'row': l:row,
110         \ 'col': l:col,
111         \ 'width': l:width,
112         \ 'height': l:height,
113         \ 'style': l:style,
114         \ 'border': l:border,
115         \ }
116     return l:opts
117 endfunction
118
119 function! lsp#ui#vim#output#floatingpreview(data) abort
120     if s:use_nvim_float
121         let l:buf = nvim_create_buf(v:false, v:true)
122
123         " Try to get as much space around the cursor, but at least 10x10
124         let l:width = max([s:bufwidth(), 10])
125         let l:height = max([&lines - winline() + 1, winline() - 1, 10])
126
127         if g:lsp_preview_max_height > 0
128             let l:height = min([g:lsp_preview_max_height, l:height])
129         endif
130
131         let l:opts = s:get_float_positioning(l:height, l:width)
132
133         let s:winid = nvim_open_win(l:buf, v:false, l:opts)
134         call nvim_win_set_option(s:winid, 'winhl', 'Normal:Pmenu,NormalNC:Pmenu')
135         call nvim_win_set_option(s:winid, 'foldenable', v:false)
136         call nvim_win_set_option(s:winid, 'wrap', v:true)
137         call nvim_win_set_option(s:winid, 'statusline', '')
138         call nvim_win_set_option(s:winid, 'number', v:false)
139         call nvim_win_set_option(s:winid, 'relativenumber', v:false)
140         call nvim_win_set_option(s:winid, 'cursorline', v:false)
141         call nvim_win_set_option(s:winid, 'cursorcolumn', v:false)
142         call nvim_win_set_option(s:winid, 'colorcolumn', '')
143         call nvim_win_set_option(s:winid, 'signcolumn', 'no')
144         " Enable closing the preview with esc, but map only in the scratch buffer
145         call nvim_buf_set_keymap(l:buf, 'n', '<esc>', ':pclose<cr>', {'silent': v:true})
146     elseif s:use_vim_popup
147         let l:options = {
148             \ 'moved': 'any',
149             \ 'border': [1, 1, 1, 1],
150             \ 'callback': function('s:vim_popup_closed')
151             \ }
152
153         if g:lsp_preview_max_width > 0
154             let l:options['maxwidth'] = g:lsp_preview_max_width
155         endif
156
157         if g:lsp_preview_max_height > 0
158             let l:options['maxheight'] = g:lsp_preview_max_height
159         endif
160
161         let s:winid = popup_atcursor('...', l:options)
162     endif
163     return s:winid
164 endfunction
165
166 function! lsp#ui#vim#output#setcontent(winid, lines, ft) abort
167     if s:use_vim_popup
168         " vim popup
169         call setbufline(winbufnr(a:winid), 1, a:lines)
170         call setbufvar(winbufnr(a:winid), '&filetype', a:ft . '.lsp-hover')
171     elseif s:use_nvim_float
172         " nvim floating
173         call nvim_buf_set_lines(winbufnr(a:winid), 0, -1, v:false, a:lines)
174         call nvim_buf_set_option(winbufnr(a:winid), 'readonly', v:true)
175         call nvim_buf_set_option(winbufnr(a:winid), 'modifiable', v:false)
176         call nvim_buf_set_option(winbufnr(a:winid), 'filetype', a:ft.'.lsp-hover')
177         call nvim_win_set_cursor(a:winid, [1, 0])
178     elseif s:use_preview
179         " preview window
180         call setbufline(winbufnr(a:winid), 1, a:lines)
181         call setbufvar(winbufnr(a:winid), '&filetype', a:ft . '.lsp-hover')
182     endif
183 endfunction
184
185 function! lsp#ui#vim#output#adjust_float_placement(bufferlines, maxwidth) abort
186     if s:use_nvim_float
187         let l:win_config = {}
188         let l:height = min([winheight(s:winid), a:bufferlines])
189         let l:width = min([winwidth(s:winid), a:maxwidth])
190         let l:win_config = s:get_float_positioning(l:height, l:width)
191         call nvim_win_set_config(s:winid, l:win_config )
192     endif
193 endfunction
194
195 function! s:add_float_closing_hooks() abort
196     if g:lsp_preview_autoclose
197         augroup lsp_float_preview_close
198             autocmd! lsp_float_preview_close CursorMoved,CursorMovedI,VimResized *
199             autocmd CursorMoved,CursorMovedI,VimResized * call lsp#ui#vim#output#closepreview()
200         augroup END
201     endif
202 endfunction
203
204 function! lsp#ui#vim#output#getpreviewwinid() abort
205     return s:winid
206 endfunction
207
208 function! s:open_preview(data) abort
209     if s:use_vim_popup || s:use_nvim_float
210         let l:winid = lsp#ui#vim#output#floatingpreview(a:data)
211     else
212         execute &previewheight.'new'
213         let l:winid = win_getid()
214     endif
215     return l:winid
216 endfunction
217
218 function! s:set_cursor(current_window_id, options) abort
219     if !has_key(a:options, 'cursor')
220         return
221     endif
222
223     if s:use_nvim_float
224         " Neovim floats
225         " Go back to the preview window to set the cursor
226         call win_gotoid(s:winid)
227         let l:old_scrolloff = &scrolloff
228         let &scrolloff = 0
229
230         call nvim_win_set_cursor(s:winid, [a:options['cursor']['line'], a:options['cursor']['col']])
231         call s:align_preview(a:options)
232
233         " Finally, go back to the original window
234         call win_gotoid(a:current_window_id)
235
236         let &scrolloff = l:old_scrolloff
237     elseif s:use_vim_popup
238         " Vim popups
239         function! AlignVimPopup(timer) closure abort
240             call s:align_preview(a:options)
241         endfunction
242         call timer_start(0, function('AlignVimPopup'))
243     else
244         " Preview
245         " Don't use 'scrolloff', it might mess up the cursor's position
246         let &l:scrolloff = 0
247         call cursor(a:options['cursor']['line'], a:options['cursor']['col'])
248         call s:align_preview(a:options)
249     endif
250 endfunction
251
252 function! s:align_preview(options) abort
253     if !has_key(a:options, 'cursor') ||
254         \ !has_key(a:options['cursor'], 'align')
255         return
256     endif
257
258     let l:align = a:options['cursor']['align']
259
260     if s:use_vim_popup
261         " Vim popups
262         let l:pos = popup_getpos(s:winid)
263         let l:below = winline() < winheight(0) / 2
264         if l:below
265             let l:height = min([l:pos['core_height'], winheight(0) - winline() - 2])
266         else
267             let l:height = min([l:pos['core_height'], winline() - 3])
268         endif
269         let l:width = l:pos['core_width']
270
271         let l:options = {
272             \ 'minwidth': l:width,
273             \ 'maxwidth': l:width,
274             \ 'minheight': l:height,
275             \ 'maxheight': l:height,
276             \ 'pos': l:below ? 'topleft' : 'botleft',
277             \ 'line': l:below ? 'cursor+1' : 'cursor-1'
278             \ }
279
280         if l:align ==? 'top'
281             let l:options['firstline'] = a:options['cursor']['line']
282         elseif l:align ==? 'center'
283             let l:options['firstline'] = a:options['cursor']['line'] - (l:height - 1) / 2
284         elseif l:align ==? 'bottom'
285             let l:options['firstline'] = a:options['cursor']['line'] - l:height + 1
286         endif
287
288         call popup_setoptions(s:winid, l:options)
289         redraw!
290     else
291         " Preview and Neovim floats
292         if l:align ==? 'top'
293             normal! zt
294         elseif l:align ==? 'center'
295             normal! zz
296         elseif l:align ==? 'bottom'
297             normal! zb
298         endif
299     endif
300 endfunction
301
302 function! lsp#ui#vim#output#get_size_info(winid) abort
303     " Get size information while still having the buffer active
304     let l:buffer = winbufnr(a:winid)
305     let l:maxwidth = max(map(getbufline(l:buffer, 1, '$'), 'strdisplaywidth(v:val)'))
306     let l:bufferlines = 0
307     if g:lsp_preview_max_width > 0
308       let l:maxwidth = min([g:lsp_preview_max_width, l:maxwidth])
309
310       " Determine, for each line, how many "virtual" lines it spans, and add
311       " these together for all lines in the buffer
312       for l:line in getbufline(l:buffer, 1, '$')
313         let l:num_lines = str2nr(string(ceil(strdisplaywidth(l:line) * 1.0 / g:lsp_preview_max_width)))
314         let l:bufferlines += max([l:num_lines, 1])
315       endfor
316     else
317       if s:use_vim_popup
318         let l:bufferlines = line('$', a:winid)
319       elseif s:use_nvim_float
320         let l:bufferlines = nvim_buf_line_count(winbufnr(a:winid))
321       endif
322     endif
323
324     return [l:bufferlines, l:maxwidth]
325 endfunction
326
327 function! lsp#ui#vim#output#float_supported() abort
328     return s:use_vim_popup || s:use_nvim_float
329 endfunction
330
331 function! lsp#ui#vim#output#preview(server, data, options) abort
332     if s:is_cmdwin()
333         return
334     endif
335
336     if s:winid && type(s:preview_data) ==# type(a:data)
337         \ && s:preview_data ==# a:data
338         \ && type(g:lsp_preview_doubletap) ==# 3
339         \ && len(g:lsp_preview_doubletap) >= 1
340         \ && type(g:lsp_preview_doubletap[0]) ==# 2
341         \ && index(['i', 's'], mode()[0]) ==# -1
342         echo ''
343         return call(g:lsp_preview_doubletap[0], [])
344     endif
345     " Close any previously opened preview window
346     call lsp#ui#vim#output#closepreview()
347
348     let l:current_window_id = win_getid()
349
350     let s:winid = s:open_preview(a:data)
351
352     let s:preview_data = a:data
353     let l:lines = []
354     let l:syntax_lines = []
355     let l:ft = lsp#ui#vim#output#append(a:data, l:lines, l:syntax_lines)
356
357     if has_key(a:options, 'filetype')
358         let l:ft = a:options['filetype']
359     endif
360
361     let l:do_conceal = g:lsp_hover_conceal
362     let l:server_info = a:server !=# '' ? lsp#get_server_info(a:server) : {}
363     let l:config = get(l:server_info, 'config', {})
364     let l:do_conceal = get(l:config, 'hover_conceal', l:do_conceal)
365
366     call setbufvar(winbufnr(s:winid), 'lsp_syntax_highlights', l:syntax_lines)
367     call setbufvar(winbufnr(s:winid), 'lsp_do_conceal', l:do_conceal)
368     call lsp#ui#vim#output#setcontent(s:winid, l:lines, l:ft)
369
370     let [l:bufferlines, l:maxwidth] = lsp#ui#vim#output#get_size_info(s:winid)
371
372     if s:use_preview
373         " Set statusline
374         if has_key(a:options, 'statusline')
375             let &l:statusline = a:options['statusline']
376         endif
377
378         call s:set_cursor(l:current_window_id, a:options)
379     endif
380
381     " Go to the previous window to adjust positioning
382     call win_gotoid(l:current_window_id)
383
384     echo ''
385
386     if s:winid && (s:use_vim_popup || s:use_nvim_float)
387       if s:use_nvim_float
388         " Neovim floats
389         call lsp#ui#vim#output#adjust_float_placement(l:bufferlines, l:maxwidth)
390         call s:set_cursor(l:current_window_id, a:options)
391         call s:add_float_closing_hooks()
392       elseif s:use_vim_popup
393         " Vim popups
394         call s:set_cursor(l:current_window_id, a:options)
395       endif
396       doautocmd <nomodeline> User lsp_float_opened
397     endif
398
399     if l:ft ==? 'markdown'
400         call s:import_modules()
401         call s:Window.do(s:winid, {->s:Markdown.apply()})
402     endif
403
404     if !g:lsp_preview_keep_focus
405         " set the focus to the preview window
406         call win_gotoid(s:winid)
407     endif
408     return ''
409 endfunction
410
411 function! s:escape_string_for_display(str) abort
412     return substitute(substitute(a:str, '\r\n', '\n', 'g'), '\r', '\n', 'g')
413 endfunction
414
415 function! lsp#ui#vim#output#append(data, lines, syntax_lines) abort
416     if type(a:data) == type([])
417         for l:entry in a:data
418             call lsp#ui#vim#output#append(l:entry, a:lines, a:syntax_lines)
419         endfor
420
421         return 'markdown'
422     elseif type(a:data) ==# type('')
423         call extend(a:lines, split(s:escape_string_for_display(a:data), "\n", v:true))
424         return 'markdown'
425     elseif type(a:data) ==# type({}) && has_key(a:data, 'language')
426         let l:new_lines = split(s:escape_string_for_display(a:data.value), '\n')
427
428         let l:i = 1
429         while l:i <= len(l:new_lines)
430             call add(a:syntax_lines, { 'line': len(a:lines) + l:i, 'language': a:data.language })
431             let l:i += 1
432         endwhile
433
434         call extend(a:lines, l:new_lines)
435         return 'markdown'
436     elseif type(a:data) ==# type({}) && has_key(a:data, 'kind')
437         if a:data.kind ==? 'markdown'
438             call s:import_modules()
439             let l:detail = s:MarkupContent.normalize(a:data.value, {
440             \     'compact': !g:lsp_preview_fixup_conceal
441             \ })
442             call extend(a:lines, s:Text.split_by_eol(l:detail))
443         else
444             call extend(a:lines, split(s:escape_string_for_display(a:data.value), '\n', v:true))
445         endif
446         return a:data.kind ==? 'plaintext' ? 'text' : a:data.kind
447     endif
448 endfunction
449
450 function! s:is_cmdwin() abort
451     return getcmdwintype() !=# ''
452 endfunction