X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/5a4872f466ebd76ddd532bdf2798554421c53df4..fe3919e725e156d751069662d11e38f7b4791de1:/.vim/bundle/vim-lsp/autoload/lsp/ui/vim/output.vim diff --git a/.vim/bundle/vim-lsp/autoload/lsp/ui/vim/output.vim b/.vim/bundle/vim-lsp/autoload/lsp/ui/vim/output.vim new file mode 100644 index 00000000..9955f3be --- /dev/null +++ b/.vim/bundle/vim-lsp/autoload/lsp/ui/vim/output.vim @@ -0,0 +1,452 @@ +let s:use_vim_popup = has('patch-8.1.1517') && g:lsp_preview_float && !has('nvim') +let s:use_nvim_float = exists('*nvim_open_win') && g:lsp_preview_float && has('nvim') +let s:use_preview = !s:use_vim_popup && !s:use_nvim_float + +function! s:import_modules() abort + if exists('s:Markdown') | return | endif + let s:Markdown = vital#lsp#import('VS.Vim.Syntax.Markdown') + let s:MarkupContent = vital#lsp#import('VS.LSP.MarkupContent') + let s:Window = vital#lsp#import('VS.Vim.Window') + let s:Text = vital#lsp#import('VS.LSP.Text') +endfunction + +let s:winid = v:false +let s:prevwin = v:false +let s:preview_data = v:false + +function! s:vim_popup_closed(...) abort + let s:preview_data = v:false +endfunction + +function! lsp#ui#vim#output#closepreview() abort + if win_getid() ==# s:winid + " Don't close if window got focus + return + endif + + if s:winid == v:false + return + endif + + "closing floats in vim8.1 must use popup_close() + "nvim must use nvim_win_close. pclose is not reliable and does not always work + if s:use_vim_popup && s:winid + call popup_close(s:winid) + elseif s:use_nvim_float && s:winid + silent! call nvim_win_close(s:winid, 0) + else + pclose + endif + let s:winid = v:false + let s:preview_data = v:false + augroup lsp_float_preview_close + augroup end + autocmd! lsp_float_preview_close CursorMoved,CursorMovedI,VimResized * + doautocmd User lsp_float_closed +endfunction + +function! lsp#ui#vim#output#focuspreview() abort + if s:is_cmdwin() + return + endif + + " This does not work for vim8.1 popup but will work for nvim and old preview + if s:winid + if win_getid() !=# s:winid + let s:prevwin = win_getid() + call win_gotoid(s:winid) + elseif s:prevwin + " Temporarily disable hooks + " TODO: remove this when closing logic is able to distinguish different move directions + autocmd! lsp_float_preview_close CursorMoved,CursorMovedI,VimResized * + call win_gotoid(s:prevwin) + call s:add_float_closing_hooks() + let s:prevwin = v:false + endif + endif +endfunction + +function! s:bufwidth() abort + let l:width = winwidth(0) + let l:numberwidth = max([&numberwidth, strlen(line('$'))+1]) + let l:numwidth = (&number || &relativenumber)? l:numberwidth : 0 + let l:foldwidth = &foldcolumn + + if &signcolumn ==? 'yes' + let l:signwidth = 2 + elseif &signcolumn ==? 'auto' + let l:signs = execute(printf('sign place buffer=%d', bufnr(''))) + let l:signs = split(l:signs, "\n") + let l:signwidth = len(l:signs)>2? 2: 0 + else + let l:signwidth = 0 + endif + return l:width - l:numwidth - l:foldwidth - l:signwidth +endfunction + + +function! s:get_float_positioning(height, width) abort + let l:height = a:height + let l:width = a:width + " TODO: add option to configure it 'docked' at the bottom/top/right + + " NOTE: screencol() and screenrow() start from (1,1) + " but the popup window co-ordinates start from (0,0) + " Very convenient! + " For a simple single-line 'tooltip', the following + " two lines are enough to determine the position + + let l:col = screencol() + let l:row = screenrow() + + let l:height = min([l:height, max([&lines - &cmdheight - l:row, &previewheight])]) + + let l:style = 'minimal' + let l:border = 'double' + " Positioning is not window but screen relative + let l:opts = { + \ 'relative': 'editor', + \ 'row': l:row, + \ 'col': l:col, + \ 'width': l:width, + \ 'height': l:height, + \ 'style': l:style, + \ 'border': l:border, + \ } + return l:opts +endfunction + +function! lsp#ui#vim#output#floatingpreview(data) abort + if s:use_nvim_float + let l:buf = nvim_create_buf(v:false, v:true) + + " Try to get as much space around the cursor, but at least 10x10 + let l:width = max([s:bufwidth(), 10]) + let l:height = max([&lines - winline() + 1, winline() - 1, 10]) + + if g:lsp_preview_max_height > 0 + let l:height = min([g:lsp_preview_max_height, l:height]) + endif + + let l:opts = s:get_float_positioning(l:height, l:width) + + let s:winid = nvim_open_win(l:buf, v:false, l:opts) + call nvim_win_set_option(s:winid, 'winhl', 'Normal:Pmenu,NormalNC:Pmenu') + call nvim_win_set_option(s:winid, 'foldenable', v:false) + call nvim_win_set_option(s:winid, 'wrap', v:true) + call nvim_win_set_option(s:winid, 'statusline', '') + call nvim_win_set_option(s:winid, 'number', v:false) + call nvim_win_set_option(s:winid, 'relativenumber', v:false) + call nvim_win_set_option(s:winid, 'cursorline', v:false) + call nvim_win_set_option(s:winid, 'cursorcolumn', v:false) + call nvim_win_set_option(s:winid, 'colorcolumn', '') + call nvim_win_set_option(s:winid, 'signcolumn', 'no') + " Enable closing the preview with esc, but map only in the scratch buffer + call nvim_buf_set_keymap(l:buf, 'n', '', ':pclose', {'silent': v:true}) + elseif s:use_vim_popup + let l:options = { + \ 'moved': 'any', + \ 'border': [1, 1, 1, 1], + \ 'callback': function('s:vim_popup_closed') + \ } + + if g:lsp_preview_max_width > 0 + let l:options['maxwidth'] = g:lsp_preview_max_width + endif + + if g:lsp_preview_max_height > 0 + let l:options['maxheight'] = g:lsp_preview_max_height + endif + + let s:winid = popup_atcursor('...', l:options) + endif + return s:winid +endfunction + +function! lsp#ui#vim#output#setcontent(winid, lines, ft) abort + if s:use_vim_popup + " vim popup + call setbufline(winbufnr(a:winid), 1, a:lines) + call setbufvar(winbufnr(a:winid), '&filetype', a:ft . '.lsp-hover') + elseif s:use_nvim_float + " nvim floating + call nvim_buf_set_lines(winbufnr(a:winid), 0, -1, v:false, a:lines) + call nvim_buf_set_option(winbufnr(a:winid), 'readonly', v:true) + call nvim_buf_set_option(winbufnr(a:winid), 'modifiable', v:false) + call nvim_buf_set_option(winbufnr(a:winid), 'filetype', a:ft.'.lsp-hover') + call nvim_win_set_cursor(a:winid, [1, 0]) + elseif s:use_preview + " preview window + call setbufline(winbufnr(a:winid), 1, a:lines) + call setbufvar(winbufnr(a:winid), '&filetype', a:ft . '.lsp-hover') + endif +endfunction + +function! lsp#ui#vim#output#adjust_float_placement(bufferlines, maxwidth) abort + if s:use_nvim_float + let l:win_config = {} + let l:height = min([winheight(s:winid), a:bufferlines]) + let l:width = min([winwidth(s:winid), a:maxwidth]) + let l:win_config = s:get_float_positioning(l:height, l:width) + call nvim_win_set_config(s:winid, l:win_config ) + endif +endfunction + +function! s:add_float_closing_hooks() abort + if g:lsp_preview_autoclose + augroup lsp_float_preview_close + autocmd! lsp_float_preview_close CursorMoved,CursorMovedI,VimResized * + autocmd CursorMoved,CursorMovedI,VimResized * call lsp#ui#vim#output#closepreview() + augroup END + endif +endfunction + +function! lsp#ui#vim#output#getpreviewwinid() abort + return s:winid +endfunction + +function! s:open_preview(data) abort + if s:use_vim_popup || s:use_nvim_float + let l:winid = lsp#ui#vim#output#floatingpreview(a:data) + else + execute &previewheight.'new' + let l:winid = win_getid() + endif + return l:winid +endfunction + +function! s:set_cursor(current_window_id, options) abort + if !has_key(a:options, 'cursor') + return + endif + + if s:use_nvim_float + " Neovim floats + " Go back to the preview window to set the cursor + call win_gotoid(s:winid) + let l:old_scrolloff = &scrolloff + let &scrolloff = 0 + + call nvim_win_set_cursor(s:winid, [a:options['cursor']['line'], a:options['cursor']['col']]) + call s:align_preview(a:options) + + " Finally, go back to the original window + call win_gotoid(a:current_window_id) + + let &scrolloff = l:old_scrolloff + elseif s:use_vim_popup + " Vim popups + function! AlignVimPopup(timer) closure abort + call s:align_preview(a:options) + endfunction + call timer_start(0, function('AlignVimPopup')) + else + " Preview + " Don't use 'scrolloff', it might mess up the cursor's position + let &l:scrolloff = 0 + call cursor(a:options['cursor']['line'], a:options['cursor']['col']) + call s:align_preview(a:options) + endif +endfunction + +function! s:align_preview(options) abort + if !has_key(a:options, 'cursor') || + \ !has_key(a:options['cursor'], 'align') + return + endif + + let l:align = a:options['cursor']['align'] + + if s:use_vim_popup + " Vim popups + let l:pos = popup_getpos(s:winid) + let l:below = winline() < winheight(0) / 2 + if l:below + let l:height = min([l:pos['core_height'], winheight(0) - winline() - 2]) + else + let l:height = min([l:pos['core_height'], winline() - 3]) + endif + let l:width = l:pos['core_width'] + + let l:options = { + \ 'minwidth': l:width, + \ 'maxwidth': l:width, + \ 'minheight': l:height, + \ 'maxheight': l:height, + \ 'pos': l:below ? 'topleft' : 'botleft', + \ 'line': l:below ? 'cursor+1' : 'cursor-1' + \ } + + if l:align ==? 'top' + let l:options['firstline'] = a:options['cursor']['line'] + elseif l:align ==? 'center' + let l:options['firstline'] = a:options['cursor']['line'] - (l:height - 1) / 2 + elseif l:align ==? 'bottom' + let l:options['firstline'] = a:options['cursor']['line'] - l:height + 1 + endif + + call popup_setoptions(s:winid, l:options) + redraw! + else + " Preview and Neovim floats + if l:align ==? 'top' + normal! zt + elseif l:align ==? 'center' + normal! zz + elseif l:align ==? 'bottom' + normal! zb + endif + endif +endfunction + +function! lsp#ui#vim#output#get_size_info(winid) abort + " Get size information while still having the buffer active + let l:buffer = winbufnr(a:winid) + let l:maxwidth = max(map(getbufline(l:buffer, 1, '$'), 'strdisplaywidth(v:val)')) + let l:bufferlines = 0 + if g:lsp_preview_max_width > 0 + let l:maxwidth = min([g:lsp_preview_max_width, l:maxwidth]) + + " Determine, for each line, how many "virtual" lines it spans, and add + " these together for all lines in the buffer + for l:line in getbufline(l:buffer, 1, '$') + let l:num_lines = str2nr(string(ceil(strdisplaywidth(l:line) * 1.0 / g:lsp_preview_max_width))) + let l:bufferlines += max([l:num_lines, 1]) + endfor + else + if s:use_vim_popup + let l:bufferlines = line('$', a:winid) + elseif s:use_nvim_float + let l:bufferlines = nvim_buf_line_count(winbufnr(a:winid)) + endif + endif + + return [l:bufferlines, l:maxwidth] +endfunction + +function! lsp#ui#vim#output#float_supported() abort + return s:use_vim_popup || s:use_nvim_float +endfunction + +function! lsp#ui#vim#output#preview(server, data, options) abort + if s:is_cmdwin() + return + endif + + if s:winid && type(s:preview_data) ==# type(a:data) + \ && s:preview_data ==# a:data + \ && type(g:lsp_preview_doubletap) ==# 3 + \ && len(g:lsp_preview_doubletap) >= 1 + \ && type(g:lsp_preview_doubletap[0]) ==# 2 + \ && index(['i', 's'], mode()[0]) ==# -1 + echo '' + return call(g:lsp_preview_doubletap[0], []) + endif + " Close any previously opened preview window + call lsp#ui#vim#output#closepreview() + + let l:current_window_id = win_getid() + + let s:winid = s:open_preview(a:data) + + let s:preview_data = a:data + let l:lines = [] + let l:syntax_lines = [] + let l:ft = lsp#ui#vim#output#append(a:data, l:lines, l:syntax_lines) + + if has_key(a:options, 'filetype') + let l:ft = a:options['filetype'] + endif + + let l:do_conceal = g:lsp_hover_conceal + let l:server_info = a:server !=# '' ? lsp#get_server_info(a:server) : {} + let l:config = get(l:server_info, 'config', {}) + let l:do_conceal = get(l:config, 'hover_conceal', l:do_conceal) + + call setbufvar(winbufnr(s:winid), 'lsp_syntax_highlights', l:syntax_lines) + call setbufvar(winbufnr(s:winid), 'lsp_do_conceal', l:do_conceal) + call lsp#ui#vim#output#setcontent(s:winid, l:lines, l:ft) + + let [l:bufferlines, l:maxwidth] = lsp#ui#vim#output#get_size_info(s:winid) + + if s:use_preview + " Set statusline + if has_key(a:options, 'statusline') + let &l:statusline = a:options['statusline'] + endif + + call s:set_cursor(l:current_window_id, a:options) + endif + + " Go to the previous window to adjust positioning + call win_gotoid(l:current_window_id) + + echo '' + + if s:winid && (s:use_vim_popup || s:use_nvim_float) + if s:use_nvim_float + " Neovim floats + call lsp#ui#vim#output#adjust_float_placement(l:bufferlines, l:maxwidth) + call s:set_cursor(l:current_window_id, a:options) + call s:add_float_closing_hooks() + elseif s:use_vim_popup + " Vim popups + call s:set_cursor(l:current_window_id, a:options) + endif + doautocmd User lsp_float_opened + endif + + if l:ft ==? 'markdown' + call s:import_modules() + call s:Window.do(s:winid, {->s:Markdown.apply()}) + endif + + if !g:lsp_preview_keep_focus + " set the focus to the preview window + call win_gotoid(s:winid) + endif + return '' +endfunction + +function! s:escape_string_for_display(str) abort + return substitute(substitute(a:str, '\r\n', '\n', 'g'), '\r', '\n', 'g') +endfunction + +function! lsp#ui#vim#output#append(data, lines, syntax_lines) abort + if type(a:data) == type([]) + for l:entry in a:data + call lsp#ui#vim#output#append(l:entry, a:lines, a:syntax_lines) + endfor + + return 'markdown' + elseif type(a:data) ==# type('') + call extend(a:lines, split(s:escape_string_for_display(a:data), "\n", v:true)) + return 'markdown' + elseif type(a:data) ==# type({}) && has_key(a:data, 'language') + let l:new_lines = split(s:escape_string_for_display(a:data.value), '\n') + + let l:i = 1 + while l:i <= len(l:new_lines) + call add(a:syntax_lines, { 'line': len(a:lines) + l:i, 'language': a:data.language }) + let l:i += 1 + endwhile + + call extend(a:lines, l:new_lines) + return 'markdown' + elseif type(a:data) ==# type({}) && has_key(a:data, 'kind') + if a:data.kind ==? 'markdown' + call s:import_modules() + let l:detail = s:MarkupContent.normalize(a:data.value, { + \ 'compact': !g:lsp_preview_fixup_conceal + \ }) + call extend(a:lines, s:Text.split_by_eol(l:detail)) + else + call extend(a:lines, split(s:escape_string_for_display(a:data.value), '\n', v:true)) + endif + return a:data.kind ==? 'plaintext' ? 'text' : a:data.kind + endif +endfunction + +function! s:is_cmdwin() abort + return getcmdwintype() !=# '' +endfunction