Skip to content

Commit 1790680

Browse files
jerdna-regeizprabirshrestha
authored andcommitted
preview information shown in hover at the cursor (#395)
* 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
1 parent e0f8324 commit 1790680

File tree

5 files changed

+305
-15
lines changed

5 files changed

+305
-15
lines changed

autoload/lsp.vim

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ augroup _lsp_silent_
2626
autocmd User lsp_server_init silent
2727
autocmd User lsp_server_exit silent
2828
autocmd User lsp_complete_done silent
29+
autocmd User lsp_float_opened silent
30+
autocmd User lsp_float_closed silent
2931
augroup END
3032

3133
function! lsp#log_verbose(...) abort

autoload/lsp/ui/vim/output.vim

Lines changed: 209 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,206 @@
1+
let s:supports_floating = exists('*nvim_open_win') || has('patch-8.1.1517')
2+
let s:winid = v:false
3+
let s:prevwin = v:false
4+
let s:preview_data = v:false
5+
6+
function! lsp#ui#vim#output#closepreview() abort
7+
if win_getid() == s:winid
8+
" Don't close if window got focus
9+
return
10+
endif
11+
"closing floats in vim8.1 must use popup_close() (nvim could use nvim_win_close but pclose
12+
"works)
13+
if s:supports_floating && s:winid && g:lsp_preview_float && !has('nvim')
14+
call popup_close(s:winid)
15+
else
16+
pclose
17+
endif
18+
let s:winid = v:false
19+
let s:preview_data = v:false
20+
augroup lsp_float_preview_close
21+
augroup end
22+
autocmd! lsp_float_preview_close CursorMoved,CursorMovedI,VimResized *
23+
doautocmd User lsp_float_closed
24+
endfunction
25+
26+
function! lsp#ui#vim#output#focuspreview() abort
27+
" This does not work for vim8.1 popup but will work for nvim and old preview
28+
if s:winid
29+
if win_getid() != s:winid
30+
let s:prevwin = win_getid()
31+
call win_gotoid(s:winid)
32+
elseif s:prevwin
33+
" Temporarily disable hooks
34+
" TODO: remove this when closing logic is able to distinguish different move directions
35+
autocmd! lsp_float_preview_close CursorMoved,CursorMovedI,VimResized *
36+
call win_gotoid(s:prevwin)
37+
call s:add_float_closing_hooks()
38+
let s:prevwin = v:false
39+
endif
40+
endif
41+
endfunction
42+
43+
function! s:bufwidth() abort
44+
let width = winwidth(0)
45+
let numberwidth = max([&numberwidth, strlen(line('$'))+1])
46+
let numwidth = (&number || &relativenumber)? numberwidth : 0
47+
let foldwidth = &foldcolumn
48+
49+
if &signcolumn ==? 'yes'
50+
let signwidth = 2
51+
elseif &signcolumn ==? 'auto'
52+
let signs = execute(printf('sign place buffer=%d', bufnr('')))
53+
let signs = split(signs, "\n")
54+
let signwidth = len(signs)>2? 2: 0
55+
else
56+
let signwidth = 0
57+
endif
58+
return width - numwidth - foldwidth - signwidth
59+
endfunction
60+
61+
62+
function! s:get_float_positioning(height, width) abort
63+
let l:height = a:height
64+
let l:width = a:width
65+
" For a start show it below/above the cursor
66+
" TODO: add option to configure it 'docked' at the bottom/top/right
67+
let l:y = winline()
68+
if l:y + l:height >= winheight(0)
69+
" Float does not fit
70+
if l:y - 2 > l:height
71+
" Fits above
72+
let l:y = winline() - l:height -1
73+
elseif l:y - 2 > winheight(0) - l:y
74+
" Take space above cursor
75+
let l:y = 1
76+
let l:height = winline()-2
77+
else
78+
" Take space below cursor
79+
let l:height = winheight(0) -l:y
80+
endif
81+
endif
82+
let l:col = col('.')
83+
" Positioning is not window but screen relative
84+
let l:opts = {
85+
\ 'relative': 'win',
86+
\ 'row': l:y,
87+
\ 'col': l:col,
88+
\ 'width': l:width,
89+
\ 'height': l:height,
90+
\ }
91+
return l:opts
92+
endfunction
93+
94+
function! lsp#ui#vim#output#floatingpreview(data) abort
95+
if has('nvim')
96+
let l:buf = nvim_create_buf(v:false, v:true)
97+
call setbufvar(l:buf, '&signcolumn', 'no')
98+
99+
" Try to get as much pace right-bolow the cursor, but at least 10x10
100+
let l:width = max([s:bufwidth(), 10])
101+
let l:height = max([&lines - winline() + 1, 10])
102+
103+
let l:opts = s:get_float_positioning(l:height, l:width)
104+
105+
let s:winid = nvim_open_win(buf, v:true, l:opts)
106+
call nvim_win_set_option(s:winid, 'winhl', 'Normal:Pmenu,NormalNC:Pmenu')
107+
call nvim_win_set_option(s:winid, 'foldenable', v:false)
108+
call nvim_win_set_option(s:winid, 'wrap', v:true)
109+
call nvim_win_set_option(s:winid, 'statusline', '')
110+
call nvim_win_set_option(s:winid, 'number', v:false)
111+
call nvim_win_set_option(s:winid, 'relativenumber', v:false)
112+
call nvim_win_set_option(s:winid, 'cursorline', v:false)
113+
" Enable closing the preview with esc, but map only in the scratch buffer
114+
nmap <buffer><silent> <esc> :pclose<cr>
115+
else
116+
let s:winid = popup_atcursor('...', {
117+
\ 'moved': 'any',
118+
\ 'border': [1, 1, 1, 1],
119+
\})
120+
endif
121+
return s:winid
122+
endfunction
123+
124+
function! s:setcontent(lines, ft) abort
125+
if s:supports_floating && g:lsp_preview_float && !has('nvim')
126+
" vim popup
127+
call setbufline(winbufnr(s:winid), 1, a:lines)
128+
let l:lightline_toggle = v:false
129+
if exists('#lightline') && !has('nvim')
130+
" Lightline does not work in popups but does not recognize it yet.
131+
" It is ugly to have an check for an other plugin here, better fix lightline...
132+
let l:lightline_toggle = v:true
133+
call lightline#disable()
134+
endif
135+
call win_execute(s:winid, 'setlocal filetype=' . a:ft . '.lsp-hover')
136+
if l:lightline_toggle
137+
call lightline#enable()
138+
endif
139+
else
140+
" nvim floating
141+
call setline(1, a:lines)
142+
setlocal readonly nomodifiable
143+
let &l:filetype = a:ft . '.lsp-hover'
144+
endif
145+
endfunction
146+
147+
function! s:adjust_float_placement(bufferlines, maxwidth) abort
148+
if has('nvim')
149+
let l:win_config = {}
150+
let l:height = min([winheight(s:winid), a:bufferlines])
151+
let l:width = min([winwidth(s:winid), a:maxwidth])
152+
let l:win_config = s:get_float_positioning(l:height, l:width)
153+
call nvim_win_set_config(s:winid, l:win_config )
154+
endif
155+
endfunction
156+
157+
function! s:add_float_closing_hooks() abort
158+
if g:lsp_preview_autoclose
159+
augroup lsp_float_preview_close
160+
autocmd! lsp_float_preview_close CursorMoved,CursorMovedI,VimResized *
161+
autocmd CursorMoved,CursorMovedI,VimResized * call lsp#ui#vim#output#closepreview()
162+
augroup END
163+
endif
164+
endfunction
165+
166+
function! lsp#ui#vim#output#getpreviewwinid() abort
167+
return s:winid
168+
endfunction
169+
170+
function! s:open_preview(data) abort
171+
if s:supports_floating && g:lsp_preview_float
172+
let l:winid = lsp#ui#vim#output#floatingpreview(a:data)
173+
else
174+
execute &previewheight.'new'
175+
let l:winid = win_getid()
176+
endif
177+
return l:winid
178+
endfunction
179+
1180
function! lsp#ui#vim#output#preview(data) abort
181+
if s:winid && type(s:preview_data) == type(a:data)
182+
\ && s:preview_data == a:data
183+
\ && type(g:lsp_preview_doubletap) == 3
184+
\ && len(g:lsp_preview_doubletap) >= 1
185+
\ && type(g:lsp_preview_doubletap[0]) == 2
186+
echo ''
187+
return call(g:lsp_preview_doubletap[0], [])
188+
endif
2189
" Close any previously opened preview window
3190
pclose
4191

5192
let l:current_window_id = win_getid()
6193

7-
execute &previewheight.'new'
8-
9-
let l:ft = s:append(a:data)
10-
" Delete first empty line
11-
0delete _
194+
let s:winid = s:open_preview(a:data)
12195

13-
setlocal readonly nomodifiable
196+
let s:preview_data = a:data
197+
let l:lines = []
198+
let l:ft = s:append(a:data, l:lines)
199+
call s:setcontent(l:lines, l:ft)
14200

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

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

22210
echo ''
23211

212+
if s:supports_floating && s:winid && g:lsp_preview_float
213+
if has('nvim')
214+
call s:adjust_float_placement(l:bufferlines, l:maxwidth)
215+
call s:add_float_closing_hooks()
216+
endif
217+
doautocmd User lsp_float_opened
218+
endif
24219
return ''
25220
endfunction
26221

27-
function! s:append(data) abort
222+
function! s:append(data, lines) abort
28223
if type(a:data) == type([])
29224
for l:entry in a:data
30-
call s:append(entry)
225+
call s:append(entry, a:lines)
31226
endfor
32227

33228
return 'markdown'
34229
elseif type(a:data) == type('')
35-
silent put =a:data
230+
call extend(a:lines, split(a:data, "\n"))
36231

37232
return 'markdown'
38233
elseif type(a:data) == type({}) && has_key(a:data, 'language')
39-
silent put ='```'.a:data.language
40-
silent put =a:data.value
41-
silent put ='```'
234+
call add(a:lines, '```'.a:data.language)
235+
call extend(a:lines, split(a:data.value, '\n'))
236+
call add(a:lines, '```')
42237

43238
return 'markdown'
44239
elseif type(a:data) == type({}) && has_key(a:data, 'kind')
45-
silent put =a:data.value
240+
call add(a:lines, a:data.value)
46241

47242
return a:data.kind ==? 'plaintext' ? 'text' : a:data.kind
48243
endif

doc/vim-lsp.txt

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ CONTENTS *vim-lsp-contents*
1313
g:lsp_diagnostics_enabled |g:lsp_diagnostics_enabled|
1414
g:lsp_auto_enable |g:lsp_auto_enable|
1515
g:lsp_preview_keep_focus |g:lsp_preview_keep_focus|
16+
g:lsp_preview_float |g:lsp_preview_float|
17+
g:lsp_preview_autoclose |g:lsp_preview_autoclose|
18+
g:lsp_preview_doubletap |g:lsp_preview_doubletap|
1619
g:lsp_insert_text_enabled |g:lsp_insert_text_enabled|
1720
g:lsp_text_edit_enabled |g:lsp_text_edit_enabled|
1821
g:lsp_diagnostics_echo_cursor |g:lsp_diagnostics_echo_cursor|
@@ -53,6 +56,9 @@ CONTENTS *vim-lsp-contents*
5356
Autocommands |vim-lsp-autocommands|
5457
lsp_complete_done |lsp_complete_done|
5558
Mappings |vim-lsp-mappings|
59+
<plug>(lsp-preview-close) |<plug>(lsp-preview-close)|
60+
<plug>(lsp-preview-focus) |<plug>(lsp-preview-focus)|
61+
5662
Autocomplete |vim-lsp-autocomplete|
5763
omnifunc |vim-lsp-omnifunc|
5864
asyncomplete.vim |vim-lsp-asyncomplete|
@@ -154,6 +160,68 @@ g:lsp_preview_keep_focus *g:lsp_preview_keep_focus*
154160

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

163+
g:lsp_preview_float *g:lsp_preview_float*
164+
Type: |Number|
165+
Default: `1`
166+
167+
If set and nvim_win_open() or popup_create is available, hover information
168+
are shown in a floating window as |preview-window| at the cursor position.
169+
The |preview-window| is closed automatically on cursor moves, unless it is
170+
focused. While focused it may be closed with <esc>.
171+
After opening an autocmd User event lsp_float_opened is issued, as well as
172+
and lsp_float_closed upon closing. This can be used to alter the preview
173+
window (using lsp#ui#vim#output#getpreviewwinid() to get the window id), or
174+
setup custom bindings while a preview is open.
175+
This feature requires neovim 0.4.0 (current master) or
176+
Vim8.1 with has('patch-8.1.1517').
177+
178+
Example:
179+
" Opens preview windows as floating
180+
let g:lsp_preview_float = 1
181+
182+
" Opens preview windows as normal windows
183+
let g:lsp_preview_float = 0
184+
185+
" Close preview window with <esc>
186+
autocmd User lsp_float_opened nmap <buffer> <silent> <esc>
187+
\ <Plug>(lsp-preview-close)
188+
autocmd User lsp_float_closed nunmap <buffer> <esc>
189+
190+
g:lsp_preview_autoclose *g:lsp_preview_autoclose*
191+
Type: |Number|
192+
Default: `1`
193+
194+
Indicates if an opened floating preview shall be automatically closed upon
195+
movement of the cursor. If set to 1, the window will close automatically if
196+
the cursor is moved and the preview is not focused. If set to 0, it will
197+
remain open until explicitly closed (e.g. with <plug>(lsp-preview-close),
198+
or <ESC> when focused).
199+
200+
Example:
201+
" Preview closes on cursor move
202+
let g:lsp_preview_autoclose = 1
203+
204+
" Preview remains open and waits for an explicit call
205+
let g:lsp_preview_autoclose = 0
206+
207+
g:lsp_preview_doubletap *g:lsp_preview_doubletap*
208+
Type: |List|
209+
Default: `[function('lsp#ui#vim#output#focuspreview')]`
210+
211+
When preview is called twice with the same data while the preview is still
212+
open, the function in `lsp_preview_doubletap` is called instead. To disable
213+
this and just "refresh" the preview, set to ´0´.
214+
215+
Example:
216+
" Focus preview on repeated preview (does not work for vim8.1 popups)
217+
let g:lsp_preview_doubletap = [function('lsp#ui#vim#output#focuspreview')]
218+
219+
" Closes the preview window on the second call to preview
220+
let g:lsp_preview_doubletap = [function('lsp#ui#vim#output#closepreview')]
221+
222+
" Disables double tap feature; refreshes the preview on consecutive taps
223+
let g:lsp_preview_doubletap = 0
224+
157225
g:lsp_insert_text_enabled *g:lsp_insert_text_enabled*
158226
Type: |Number|
159227
Default: `1`
@@ -566,6 +634,9 @@ Gets the hover information and displays it in the |preview-window|.
566634
* |preview-window| can be closed using the default vim mapping - `<c-w><c-z>`.
567635
* To control the default focus of |preview-window| for |LspHover|
568636
configure |g:lsp_preview_keep_focus|.
637+
* If using neovim with nvim_win_open() available, |g:lsp_preview_float| can be
638+
set to enable a floating preview at the cursor which is closed automatically
639+
on cursormove if not focused and can be closed with <esc> if focused.
569640

570641

571642
LspNextError *LspNextError*
@@ -640,6 +711,8 @@ Available plug mappings are following:
640711
(lsp-hover)
641712
(lsp-next-error)
642713
(lsp-next-reference)
714+
(lsp-preview-close)
715+
(lsp-preview-focus)
643716
(lsp-previous-error)
644717
(lsp-previous-reference)
645718
(lsp-references)
@@ -653,6 +726,16 @@ Available plug mappings are following:
653726

654727
See also |vim-lsp-commands|
655728

729+
<plug>(lsp-preview-close) *<plug>(lsp-preview-close)*
730+
731+
Closes an opened preview window
732+
733+
<plug>(lsp-preview-focus) *<plug>(lsp-preview-focus)*
734+
735+
Transfers focus to an opened preview window or back to the previous window if
736+
focus is already on the preview window.
737+
738+
656739
===============================================================================
657740
Autocomplete *vim-lsp-autocomplete*
658741

0 commit comments

Comments
 (0)