Improved neovim floating windows, and misc. other improvements (#921)

This commit is contained in:
Subhaditya Nath
2020-11-14 22:07:01 +05:30
committed by GitHub
parent 594751093d
commit 45babeb947
4 changed files with 180 additions and 67 deletions

View File

@@ -2,12 +2,17 @@ let s:use_vim_popup = has('patch-8.1.1517') && !has('nvim')
let s:use_nvim_float = exists('*nvim_open_win') && has('nvim')
let s:last_popup_id = -1
let s:last_timer_id = v:false
function! s:complete_done() abort
function! s:complete_changed() abort
if !g:lsp_documentation_float | return | endif
" Use a timer to avoid textlock (see :h textlock).
let l:event = deepcopy(v:event)
call timer_start(0, {-> s:show_documentation(l:event)})
let l:event = copy(v:event)
if s:last_timer_id
call timer_stop(s:last_timer_id)
let s:last_timer_id = v:false
endif
let s:last_timer_id = timer_start(g:lsp_documentation_debounce, {-> s:show_documentation(l:event)})
endfunction
function! s:show_documentation(event) abort
@@ -17,16 +22,6 @@ function! s:show_documentation(event) abort
return
endif
let l:right = wincol() < winwidth(0) / 2
" TODO: Neovim
if l:right
let l:line = a:event['row'] + 1
let l:col = a:event['col'] + a:event['width'] + 1 + (a:event['scrollbar'] ? 1 : 0)
else
let l:line = a:event['row'] + 1
let l:col = a:event['col'] - 1
endif
" TODO: Support markdown
let l:data = split(a:event['completed_item']['info'], '\n')
@@ -34,37 +29,111 @@ function! s:show_documentation(event) abort
let l:syntax_lines = []
let l:ft = lsp#ui#vim#output#append(l:data, l:lines, l:syntax_lines)
let l:current_win_id = win_getid()
if s:use_vim_popup
let s:last_popup_id = popup_create('(no documentation available)', {'line': l:line, 'col': l:col, 'pos': l:right ? 'topleft' : 'topright', 'padding': [0, 1, 0, 1]})
elseif s:use_nvim_float
let l:height = float2nr(winheight(0) - l:line + 1)
let l:width = float2nr(l:right ? winwidth(0) - l:col + 1 : l:col)
if l:width <= 0
let l:width = 1
" Neovim
if s:use_nvim_float
let l:event = a:event
let l:event.row = float2nr(l:event.row)
let l:event.col = float2nr(l:event.col)
let l:buffer = nvim_create_buf(v:false, v:true)
let l:curpos = win_screenpos(nvim_get_current_win())[0] + winline() - 1
let g:lsp_documentation_float_docked = get(g:, 'lsp_documentation_float_docked', 0)
if g:lsp_documentation_float_docked
let g:lsp_documentation_float_docked_maxheight = get(g:, ':lsp_documentation_float_docked_maxheight', &previewheight)
let l:dock_downwards = max([screenrow(), l:curpos]) < (&lines / 2)
let l:height = min([len(l:data), g:lsp_documentation_float_docked_maxheight])
let l:width = &columns
let l:col = 0
if l:dock_downwards
let l:anchor = 'SW'
let l:row = &lines - &cmdheight - 1
let l:height = min([l:height, &lines - &cmdheight - l:event.row - l:event.height])
else " dock upwards
let l:anchor = 'NW'
let l:row = 0
let l:height = min([l:height, l:event.row - 1])
endif
else " not docked
let l:row = l:event['row']
let l:height = max([&lines - &cmdheight - l:row, &previewheight])
let l:right_area = &columns - l:event.col - l:event.width + 1 " 1 for the padding of popup
let l:left_area = l:event.col - 1
let l:right = l:right_area > l:left_area
if l:right
let l:anchor = 'NW'
let l:width = l:right_area - 1
let l:col = l:event.col + l:event.width + (l:event.scrollbar ? 1 : 0)
else
let l:anchor = 'NE'
let l:width = l:left_area
let l:col = l:event.col - 1 " 1 due to padding of completion popup
endif
endif
if l:height <= 0
let l:height = 1
call setbufvar(l:buffer, 'lsp_syntax_highlights', l:syntax_lines)
call setbufvar(l:buffer, 'lsp_do_conceal', 1)
" add padding on both sides of lines containing text
for l:index in range(len(l:lines))
if len(l:lines[l:index]) > 0
let l:lines[l:index] = ' ' . l:lines[l:index] . ' '
endif
endfor
call nvim_buf_set_lines(l:buffer, 0, -1, v:false, l:lines)
call nvim_buf_set_option(l:buffer, 'readonly', v:true)
call nvim_buf_set_option(l:buffer, 'modifiable', v:false)
call nvim_buf_set_option(l:buffer, 'filetype', l:ft.'.lsp-hover')
if !g:lsp_documentation_float_docked
let l:bufferlines = nvim_buf_line_count(l:buffer)
let l:maxwidth = max(map(getbufline(l:buffer, 1, '$'), 'strdisplaywidth(v:val)'))
if g:lsp_preview_max_width > 0
let l:maxwidth = min([g:lsp_preview_max_width, l:maxwidth])
endif
let l:width = min([float2nr(l:width), l:maxwidth])
let l:height = min([float2nr(l:height), l:bufferlines])
endif
let s:last_popup_id = lsp#ui#vim#output#floatingpreview([])
call nvim_win_set_config(s:last_popup_id, {'relative': 'win', 'anchor': l:right ? 'NW' : 'NE', 'row': l:line - 1, 'col': l:col - 1, 'height': l:height, 'width': l:width})
if g:lsp_preview_max_height > 0
let l:maxheight = g:lsp_preview_max_height
let l:height = min([l:height, l:maxheight])
endif
" Height and width must be atleast 1, otherwise error
let l:height = (l:height < 1 ? 1 : l:height)
let l:width = (l:width < 1 ? 1 : l:width)
let s:last_popup_id = nvim_open_win(l:buffer, v:false, {'relative': 'editor', 'anchor': l:anchor, 'row': l:row, 'col': l:col, 'height': l:height, 'width': l:width, 'style': 'minimal'})
return
endif
" Vim
let l:current_win_id = win_getid()
let l:right = wincol() < winwidth(0) / 2
if l:right
let l:line = a:event['row'] + 1
let l:col = a:event['col'] + a:event['width'] + 1 + (a:event['scrollbar'] ? 1 : 0)
else
let l:line = a:event['row'] + 1
let l:col = a:event['col'] - 1
endif
let s:last_popup_id = popup_create('(no documentation available)', {'line': l:line, 'col': l:col, 'pos': l:right ? 'topleft' : 'topright', 'padding': [0, 1, 0, 1]})
call setbufvar(winbufnr(s:last_popup_id), 'lsp_syntax_highlights', l:syntax_lines)
call setbufvar(winbufnr(s:last_popup_id), 'lsp_do_conceal', 1)
call lsp#ui#vim#output#setcontent(s:last_popup_id, l:lines, l:ft)
let [l:bufferlines, l:maxwidth] = lsp#ui#vim#output#get_size_info()
call win_gotoid(l:current_win_id)
if s:use_nvim_float
call lsp#ui#vim#output#adjust_float_placement(l:bufferlines, l:maxwidth)
call nvim_win_set_config(s:last_popup_id, {'relative': 'win', 'row': l:line - 1, 'col': l:col - 1})
endif
endfunction
function! s:close_popup() abort
if s:last_timer_id
call timer_stop(s:last_timer_id)
let s:last_timer_id = v:false
endif
if s:last_popup_id >= 0
if s:use_vim_popup | call popup_close(s:last_popup_id) | endif
if s:use_nvim_float && nvim_win_is_valid(s:last_popup_id) | call nvim_win_close(s:last_popup_id, 1) | endif
@@ -77,8 +146,10 @@ function! lsp#ui#vim#documentation#setup() abort
augroup lsp_documentation_popup
autocmd!
if exists('##CompleteChanged')
autocmd CompleteChanged * call s:complete_done()
autocmd CompleteChanged * call s:complete_changed()
endif
autocmd CompleteDone * call s:close_popup()
augroup end
endfunction
" vim: et ts=4

View File

@@ -75,29 +75,24 @@ 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('.')
" 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'
" Positioning is not window but screen relative
let l:opts = {
\ 'relative': 'win',
\ 'row': l:y,
\ 'relative': 'editor',
\ 'row': l:row,
\ 'col': l:col,
\ 'width': l:width,
\ 'height': l:height,
@@ -109,7 +104,6 @@ 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])
@@ -121,7 +115,7 @@ function! lsp#ui#vim#output#floatingpreview(data) abort
let l:opts = s:get_float_positioning(l:height, l:width)
let s:winid = nvim_open_win(l:buf, v:true, l:opts)
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)
@@ -129,8 +123,11 @@ function! lsp#ui#vim#output#floatingpreview(data) abort
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
nmap <buffer><silent> <esc> :pclose<cr>
call nvim_buf_set_keymap(l:buf, 'n', '<esc>', ':pclose<cr>', {'silent': v:true})
elseif s:use_vim_popup
let l:options = {
\ 'moved': 'any',
@@ -156,11 +153,15 @@ function! lsp#ui#vim#output#setcontent(winid, lines, ft) abort
" vim popup
call setbufline(winbufnr(a:winid), 1, a:lines)
call setbufvar(winbufnr(a:winid), '&filetype', a:ft . '.lsp-hover')
else
" nvim floating or preview
call setline(1, a:lines)
setlocal readonly nomodifiable
silent! let &l:filetype = a:ft . '.lsp-hover'
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])
endif
endfunction
@@ -281,21 +282,26 @@ function! s:align_preview(options) abort
endif
endfunction
function! lsp#ui#vim#output#get_size_info() abort
function! lsp#ui#vim#output#get_size_info(winid) abort
" Get size information while still having the buffer active
let l:maxwidth = max(map(getline(1, '$'), 'strdisplaywidth(v:val)'))
let l:buffer = winbufnr(a:winid)
let l:maxwidth = max(map(getbufline(l:buffer, 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, '$')
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
let l:bufferlines = line('$')
if s:use_vim_popup
let l:bufferlines = getbufinfo(l:buffer)[0].linecount
elseif s:use_nvim_float
let l:bufferlines = nvim_buf_line_count(winbufnr(a:winid))
endif
endif
return [l:bufferlines, l:maxwidth]
@@ -321,9 +327,7 @@ function! lsp#ui#vim#output#preview(server, data, options) abort
return call(g:lsp_preview_doubletap[0], [])
endif
" Close any previously opened preview window
if s:use_preview
pclose
endif
call lsp#ui#vim#output#closepreview()
let l:current_window_id = win_getid()
@@ -353,7 +357,7 @@ function! lsp#ui#vim#output#preview(server, data, options) abort
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()
let [l:bufferlines, l:maxwidth] = lsp#ui#vim#output#get_size_info(s:winid)
if s:use_preview
" Set statusline
@@ -379,7 +383,7 @@ function! lsp#ui#vim#output#preview(server, data, options) abort
" Vim popups
call s:set_cursor(l:current_window_id, a:options)
endif
doautocmd <nomodeline> User lsp_float_opened
doautocmd <nomodeline> User lsp_float_opened
endif
if !g:lsp_preview_keep_focus

View File

@@ -20,7 +20,11 @@ CONTENTS *vim-lsp-contents*
g:lsp_preview_doubletap |g:lsp_preview_doubletap|
g:lsp_insert_text_enabled |g:lsp_insert_text_enabled|
g:lsp_text_edit_enabled |g:lsp_text_edit_enabled|
g:lsp_documentation_debounce |g:lsp_documentation_debounce|
g:lsp_documentation_float |g:lsp_documentation_float|
g:lsp_documentation_float_docked |g:lsp_documentation_float_docked|
g:lsp_documentation_float_docked_maxheight
|g:lsp_documentation_float_docked_maxheight|
g:lsp_diagnostics_echo_cursor |g:lsp_diagnostics_echo_cursor|
g:lsp_diagnostics_echo_delay |g:lsp_diagnostics_echo_delay|
g:lsp_diagnostics_float_cursor |g:lsp_diagnostics_float_cursor|
@@ -367,6 +371,17 @@ g:lsp_text_edit_enabled *g:lsp_text_edit_enabled*
let g:lsp_text_edit_enabled = 1
let g:lsp_text_edit_enabled = 0
g:lsp_documentation_debounce *g:lsp_documentation_debounce*
Type: |Number|
Default: `80`
Time in milliseconds to delay the completion documentation popup. Might
help with performance. Set this to `0` to disable debouncing.
Example: >
let g:lsp_documentation_debounce = 120
let g:lsp_documentation_debounce = 0
g:lsp_documentation_float *g:lsp_documentation_float*
Type: |Number|
Default: `1`
@@ -377,6 +392,26 @@ g:lsp_documentation_float *g:lsp_documentation_float*
let g:lsp_documentation_float = 1
let g:lsp_documentation_float = 0
g:lsp_documentation_float_docked *g:lsp_documentation_float_docked*
Type: |Number|
Default: `0`
Dock the floating documentation window for complete items if enabled.
Example: >
let g:lsp_documentation_float_docked = 1
let g:lsp_documentation_float_docked = 0
g:lsp_documentation_float_docked_maxheight *g:lsp_documentation_float_docked_maxheight*
Type: |Number|
Default: `&previewheight`
The maximum height of the docked documentation window if enabled.
Example: >
let g:lsp_documentation_float_docked_maxheight = 1
let g:lsp_documentation_float_docked_maxheight = 0
g:lsp_diagnostics_echo_cursor *g:lsp_diagnostics_echo_cursor*
Type: |Number|
Default: `0`

View File

@@ -21,7 +21,10 @@ let g:lsp_signs_information = get(g:, 'lsp_signs_information', {})
let g:lsp_signs_hint = get(g:, 'lsp_signs_hint', {})
let g:lsp_signs_priority = get(g:, 'lsp_signs_priority', 10)
let g:lsp_signs_priority_map = get(g:, 'lsp_signs_priority_map', {})
let g:lsp_documentation_debounce = get(g:, 'lsp_documentation_debounce', 80)
let g:lsp_documentation_float = get(g:, 'lsp_documentation_float', 1)
let g:lsp_documentation_float_docked = get(g:, 'lsp_documentation_float_docked', 0)
let g:lsp_documentation_float_docked_maxheight = get(g:, ':lsp_documentation_float_docked_maxheight', &previewheight)
let g:lsp_diagnostics_enabled = get(g:, 'lsp_diagnostics_enabled', 1)
let g:lsp_diagnostics_echo_cursor = get(g:, 'lsp_diagnostics_echo_cursor', 0)
let g:lsp_diagnostics_echo_delay = get(g:, 'lsp_diagnostics_echo_delay', 500)