Skip to content

Commit

Permalink
preview information shown in hover at the cursor (#395)
Browse files Browse the repository at this point in the history
* preview information shown in hover at the cursor

* lint

* Fix hover float positioning

* Integrate vim8.1 popup for hover/preview

* lint

* Fix hover float positioning above cursor line

* Closing and focusing floating preview (nvim)

* Renamed variable of preview window id

* autocmd events for open/close floats

* Closing of floating preview & lightlinefix (vim8.1)

* Added doubletap functionality to preview
  • Loading branch information
jerdna-regeiz authored and prabirshrestha committed Jun 25, 2019
1 parent e0f8324 commit 1790680
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 15 deletions.
2 changes: 2 additions & 0 deletions autoload/lsp.vim
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ augroup _lsp_silent_
autocmd User lsp_server_init silent
autocmd User lsp_server_exit silent
autocmd User lsp_complete_done silent
autocmd User lsp_float_opened silent
autocmd User lsp_float_closed silent
augroup END

function! lsp#log_verbose(...) abort
Expand Down
223 changes: 209 additions & 14 deletions autoload/lsp/ui/vim/output.vim
Original file line number Diff line number Diff line change
@@ -1,18 +1,206 @@
let s:supports_floating = exists('*nvim_open_win') || has('patch-8.1.1517')
let s:winid = v:false
let s:prevwin = v:false
let s:preview_data = v:false

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:supports_floating && s:winid && g:lsp_preview_float && !has('nvim')
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 - 2 > 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('.')
" Positioning is not window but screen relative
let l:opts = {
\ 'relative': 'win',
\ 'row': l:y,
\ 'col': l:col,
\ 'width': l:width,
\ 'height': l:height,
\ }
return l:opts
endfunction

function! lsp#ui#vim#output#floatingpreview(data) abort
if has('nvim')
let l:buf = nvim_create_buf(v:false, v:true)
call setbufvar(l:buf, '&signcolumn', 'no')

" Try to get as much pace right-bolow the cursor, but at least 10x10
let l:width = max([s:bufwidth(), 10])
let l:height = max([&lines - winline() + 1, 10])

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>
else
let s:winid = popup_atcursor('...', {
\ 'moved': 'any',
\ 'border': [1, 1, 1, 1],
\})
endif
return s:winid
endfunction

function! s:setcontent(lines, ft) abort
if s:supports_floating && g:lsp_preview_float && !has('nvim')
" 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
call setline(1, a:lines)
setlocal readonly nomodifiable
let &l:filetype = a:ft . '.lsp-hover'
endif
endfunction

function! s:adjust_float_placement(bufferlines, maxwidth) abort
if has('nvim')
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:supports_floating && g:lsp_preview_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! lsp#ui#vim#output#preview(data) 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
echo ''
return call(g:lsp_preview_doubletap[0], [])
endif
" Close any previously opened preview window
pclose

let l:current_window_id = win_getid()

execute &previewheight.'new'

let l:ft = s:append(a:data)
" Delete first empty line
0delete _
let s:winid = s:open_preview(a:data)

setlocal readonly nomodifiable
let s:preview_data = a:data
let l:lines = []
let l:ft = s:append(a:data, l:lines)
call s:setcontent(l:lines, l:ft)

let &l:filetype = l:ft . '.lsp-hover'
" Get size information while still having the buffer active
let l:bufferlines = line('$')
let l:maxwidth = max(map(getline(1, '$'), 'strdisplaywidth(v:val)'))

if g:lsp_preview_keep_focus
" restore focus to the previous window
Expand All @@ -21,28 +209,35 @@ function! lsp#ui#vim#output#preview(data) abort

echo ''

if s:supports_floating && s:winid && g:lsp_preview_float
if has('nvim')
call s:adjust_float_placement(l:bufferlines, l:maxwidth)
call s:add_float_closing_hooks()
endif
doautocmd User lsp_float_opened
endif
return ''
endfunction

function! s:append(data) abort
function! s:append(data, lines) abort
if type(a:data) == type([])
for l:entry in a:data
call s:append(entry)
call s:append(entry, a:lines)
endfor

return 'markdown'
elseif type(a:data) == type('')
silent put =a:data
call extend(a:lines, split(a:data, "\n"))

return 'markdown'
elseif type(a:data) == type({}) && has_key(a:data, 'language')
silent put ='```'.a:data.language
silent put =a:data.value
silent put ='```'
call add(a:lines, '```'.a:data.language)
call extend(a:lines, split(a:data.value, '\n'))
call add(a:lines, '```')

return 'markdown'
elseif type(a:data) == type({}) && has_key(a:data, 'kind')
silent put =a:data.value
call add(a:lines, a:data.value)

return a:data.kind ==? 'plaintext' ? 'text' : a:data.kind
endif
Expand Down
83 changes: 83 additions & 0 deletions doc/vim-lsp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ CONTENTS *vim-lsp-contents*
g:lsp_diagnostics_enabled |g:lsp_diagnostics_enabled|
g:lsp_auto_enable |g:lsp_auto_enable|
g:lsp_preview_keep_focus |g:lsp_preview_keep_focus|
g:lsp_preview_float |g:lsp_preview_float|
g:lsp_preview_autoclose |g:lsp_preview_autoclose|
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_diagnostics_echo_cursor |g:lsp_diagnostics_echo_cursor|
Expand Down Expand Up @@ -53,6 +56,9 @@ CONTENTS *vim-lsp-contents*
Autocommands |vim-lsp-autocommands|
lsp_complete_done |lsp_complete_done|
Mappings |vim-lsp-mappings|
<plug>(lsp-preview-close) |<plug>(lsp-preview-close)|
<plug>(lsp-preview-focus) |<plug>(lsp-preview-focus)|

Autocomplete |vim-lsp-autocomplete|
omnifunc |vim-lsp-omnifunc|
asyncomplete.vim |vim-lsp-asyncomplete|
Expand Down Expand Up @@ -154,6 +160,68 @@ g:lsp_preview_keep_focus *g:lsp_preview_keep_focus*

* |preview-window| can be closed using the default vim mapping - `<c-w><c-z>`.

g:lsp_preview_float *g:lsp_preview_float*
Type: |Number|
Default: `1`

If set and nvim_win_open() or popup_create is available, hover information
are shown in a floating window as |preview-window| at the cursor position.
The |preview-window| is closed automatically on cursor moves, unless it is
focused. While focused it may be closed with <esc>.
After opening an autocmd User event lsp_float_opened is issued, as well as
and lsp_float_closed upon closing. This can be used to alter the preview
window (using lsp#ui#vim#output#getpreviewwinid() to get the window id), or
setup custom bindings while a preview is open.
This feature requires neovim 0.4.0 (current master) or
Vim8.1 with has('patch-8.1.1517').

Example:
" Opens preview windows as floating
let g:lsp_preview_float = 1

" Opens preview windows as normal windows
let g:lsp_preview_float = 0

" Close preview window with <esc>
autocmd User lsp_float_opened nmap <buffer> <silent> <esc>
\ <Plug>(lsp-preview-close)
autocmd User lsp_float_closed nunmap <buffer> <esc>

g:lsp_preview_autoclose *g:lsp_preview_autoclose*
Type: |Number|
Default: `1`

Indicates if an opened floating preview shall be automatically closed upon
movement of the cursor. If set to 1, the window will close automatically if
the cursor is moved and the preview is not focused. If set to 0, it will
remain open until explicitly closed (e.g. with <plug>(lsp-preview-close),
or <ESC> when focused).

Example:
" Preview closes on cursor move
let g:lsp_preview_autoclose = 1

" Preview remains open and waits for an explicit call
let g:lsp_preview_autoclose = 0

g:lsp_preview_doubletap *g:lsp_preview_doubletap*
Type: |List|
Default: `[function('lsp#ui#vim#output#focuspreview')]`

When preview is called twice with the same data while the preview is still
open, the function in `lsp_preview_doubletap` is called instead. To disable
this and just "refresh" the preview, set to ´0´.

Example:
" Focus preview on repeated preview (does not work for vim8.1 popups)
let g:lsp_preview_doubletap = [function('lsp#ui#vim#output#focuspreview')]

" Closes the preview window on the second call to preview
let g:lsp_preview_doubletap = [function('lsp#ui#vim#output#closepreview')]

" Disables double tap feature; refreshes the preview on consecutive taps
let g:lsp_preview_doubletap = 0

g:lsp_insert_text_enabled *g:lsp_insert_text_enabled*
Type: |Number|
Default: `1`
Expand Down Expand Up @@ -566,6 +634,9 @@ Gets the hover information and displays it in the |preview-window|.
* |preview-window| can be closed using the default vim mapping - `<c-w><c-z>`.
* To control the default focus of |preview-window| for |LspHover|
configure |g:lsp_preview_keep_focus|.
* If using neovim with nvim_win_open() available, |g:lsp_preview_float| can be
set to enable a floating preview at the cursor which is closed automatically
on cursormove if not focused and can be closed with <esc> if focused.


LspNextError *LspNextError*
Expand Down Expand Up @@ -640,6 +711,8 @@ Available plug mappings are following:
(lsp-hover)
(lsp-next-error)
(lsp-next-reference)
(lsp-preview-close)
(lsp-preview-focus)
(lsp-previous-error)
(lsp-previous-reference)
(lsp-references)
Expand All @@ -653,6 +726,16 @@ Available plug mappings are following:

See also |vim-lsp-commands|

<plug>(lsp-preview-close) *<plug>(lsp-preview-close)*

Closes an opened preview window

<plug>(lsp-preview-focus) *<plug>(lsp-preview-focus)*

Transfers focus to an opened preview window or back to the previous window if
focus is already on the preview window.


===============================================================================
Autocomplete *vim-lsp-autocomplete*

Expand Down
Loading

0 comments on commit 1790680

Please sign in to comment.