Files
vim-lsp-mirror/autoload/lsp/ui/vim/output.vim
Timur Celik 2a346106de Don't allow hover window to close preview window (#705)
If g:lsp_preview_float is enabled there is no reason to close the
preview window on LspHover and LspSignatureHelp. Moreover the preview
window may be in use by other plugins or by the user.

Co-authored-by: mattn <mattn.jp@gmail.com>
2020-02-08 23:16:33 +09:00

424 lines
13 KiB
VimL

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
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
"closing floats in vim8.1 must use popup_close() (nvim could use nvim_win_close but pclose
"works)
if s:use_vim_popup && s:winid
call popup_close(s:winid)
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
" 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 width = winwidth(0)
let numberwidth = max([&numberwidth, strlen(line('$'))+1])
let numwidth = (&number || &relativenumber)? numberwidth : 0
let foldwidth = &foldcolumn
if &signcolumn ==? 'yes'
let signwidth = 2
elseif &signcolumn ==? 'auto'
let signs = execute(printf('sign place buffer=%d', bufnr('')))
let signs = split(signs, "\n")
let signwidth = len(signs)>2? 2: 0
else
let signwidth = 0
endif
return width - numwidth - foldwidth - signwidth
endfunction
function! s:get_float_positioning(height, width) abort
let l:height = a:height
let l:width = a:width
" For a start show it below/above the cursor
" TODO: add option to configure it 'docked' at the bottom/top/right
let l:y = winline()
if l:y + l:height >= winheight(0)
" Float does not fit
if l:y > l:height
" Fits above
let l:y = winline() - l:height - 1
elseif l:y - 2 > winheight(0) - l:y
" Take space above cursor
let l:y = 1
let l:height = winline()-2
else
" Take space below cursor
let l:height = winheight(0) -l:y
endif
endif
let l:col = col('.')
let l:style = 'minimal'
" Positioning is not window but screen relative
let l:opts = {
\ 'relative': 'win',
\ 'row': l:y,
\ 'col': l:col,
\ 'width': l:width,
\ 'height': l:height,
\ 'style': l:style,
\ }
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)
call setbufvar(l:buf, '&signcolumn', 'no')
" 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(buf, v:true, 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)
" Enable closing the preview with esc, but map only in the scratch buffer
nmap <buffer><silent> <esc> :pclose<cr>
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! s:setcontent(lines, ft) abort
if s:use_vim_popup
" vim popup
call setbufline(winbufnr(s:winid), 1, a:lines)
let l:lightline_toggle = v:false
if exists('#lightline') && !has('nvim')
" Lightline does not work in popups but does not recognize it yet.
" It is ugly to have an check for an other plugin here, better fix lightline...
let l:lightline_toggle = v:true
call lightline#disable()
endif
call win_execute(s:winid, 'setlocal filetype=' . a:ft . '.lsp-hover')
if l:lightline_toggle
call lightline#enable()
endif
else
" nvim floating or preview
call setline(1, a:lines)
setlocal readonly nomodifiable
silent! let &l:filetype = a:ft . '.lsp-hover'
endif
endfunction
function! s: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#preview(server, data, options) abort
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
if !g:lsp_preview_float
pclose
endif
let l:current_window_id = win_getid()
let s:preview_data = a:data
let l:lines = []
let l:syntax_lines = []
let l:ft = s:append(a:data, l:lines, l:syntax_lines)
" If the server response is empty content, we don't display anything.
if empty(l:lines) && empty(l:syntax_lines)
echo ''
return
endif
let s:winid = s:open_preview(a:data)
if has_key(a:options, 'filetype')
let l:ft = a:options['filetype']
endif
let l:server_info = lsp#get_server_info(a:server)
try
let l:do_conceal = l:server_info['config']['hover_conceal']
catch
let l:do_conceal = g:lsp_hover_conceal
endtry
call setbufvar(winbufnr(s:winid), 'lsp_syntax_highlights', l:syntax_lines)
call setbufvar(winbufnr(s:winid), 'lsp_do_conceal', l:do_conceal)
call s:setcontent(l:lines, l:ft)
" Get size information while still having the buffer active
let l:maxwidth = max(map(getline(1, '$'), 'strdisplaywidth(v:val)'))
if g:lsp_preview_max_width > 0
let l:bufferlines = 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 getline(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
let l:bufferlines = line('$')
endif
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 s: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 !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! s:append(data, lines, syntax_lines) abort
if type(a:data) == type([])
for l:entry in a:data
call s:append(entry, a:lines, a:syntax_lines)
endfor
return 'markdown'
elseif type(a:data) == type('')
if !empty(a:data)
call extend(a:lines, split(s:escape_string_for_display(a:data), "\n", v:true))
endif
return 'markdown'
elseif type(a:data) == type({}) && has_key(a:data, 'language')
if !empty(a:data.value)
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)
endif
return 'markdown'
elseif type(a:data) == type({}) && has_key(a:data, 'kind')
if !empty(a:data.value)
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