mirror of
https://github.com/prabirshrestha/vim-lsp.git
synced 2025-12-14 20:35:59 +01:00
Improved neovim floating windows, and misc. other improvements (#921)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user