Skip to content

Commit 25ba848

Browse files
committed
Introduce floatwin/popupwin compatible layer
1 parent 45d5bff commit 25ba848

File tree

6 files changed

+393
-53
lines changed

6 files changed

+393
-53
lines changed

autoload/lsp/ui/vim/floatwin.vim

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
let s:floatwin_id = 0
2+
3+
let s:namespace = has('nvim') ? 'nvim' : 'vim'
4+
5+
"
6+
" lsp#ui#vim#floatwin#screenpos
7+
"
8+
function! lsp#ui#vim#floatwin#screenpos(line, col) abort
9+
let l:pos = getpos('.')
10+
let l:scroll_x = (l:pos[2] + l:pos[3]) - wincol()
11+
let l:scroll_y = l:pos[1] - winline()
12+
let l:winpos = win_screenpos(win_getid())
13+
return [l:winpos[0] + (a:line - l:scroll_y) - 1, l:winpos[1] + (a:col - l:scroll_x) - 1]
14+
endfunction
15+
16+
"
17+
" lsp#ui#vim#floatwin#import
18+
"
19+
function! lsp#ui#vim#floatwin#import() abort
20+
return s:Floatwin
21+
endfunction
22+
23+
"
24+
" lsp#ui#vim#floatwin#normalize_markup_content
25+
"
26+
function! lsp#ui#vim#floatwin#normalize_markup_content(markup_content) abort
27+
let l:normalized = []
28+
for l:markup_content in type(a:markup_content) == type([]) ? a:markup_content : [a:markup_content]
29+
30+
" { 'language': ..., 'value': ... }
31+
if type(l:markup_content) == type({})
32+
let l:string = get(l:markup_content, 'value', '')
33+
if has_key(l:markup_content, 'language')
34+
let l:string = printf('```%s' . "\n" . '%s' . "\n" . '```', l:markup_content.language, l:string)
35+
endif
36+
37+
" just string.
38+
elseif type(l:markup_content) == type('')
39+
let l:string = l:markup_content
40+
endif
41+
42+
call add(l:normalized, { 'lines': split(l:string, "\n") })
43+
endfor
44+
45+
return l:normalized
46+
endfunction
47+
48+
let s:Floatwin = {}
49+
50+
"
51+
" new
52+
"
53+
function! s:Floatwin.new(option) abort
54+
let s:floatwin_id += 1
55+
let l:bufname = printf('lsp-hover-%s.lsp-hover', s:floatwin_id)
56+
let l:bufnr = bufnr(l:bufname, v:true)
57+
call setbufvar(l:bufnr, '&buflisted', 0)
58+
call setbufvar(l:bufnr, '&buftype', 'nofile')
59+
call setbufvar(l:bufnr, '&filetype', 'lsp-hover')
60+
return extend(deepcopy(s:Floatwin), {
61+
\ 'id': s:floatwin_id,
62+
\ 'bufnr': l:bufnr,
63+
\ 'max_width': get(a:option, 'max_width', &columns / 3),
64+
\ 'max_height': get(a:option, 'max_height', &lines / 2),
65+
\ 'close_on': get(a:option, 'close_on', []),
66+
\ 'screenpos': [0, 0],
67+
\ 'contents': []
68+
\ })
69+
endfunction
70+
71+
"
72+
" show_tooltip
73+
"
74+
function! s:Floatwin.show_tooltip(screenpos, contents) abort
75+
let l:width = self.get_width(a:contents)
76+
let l:height = self.get_height(a:contents)
77+
78+
let l:screenpos = copy(a:screenpos)
79+
let l:screenpos[0] -= 1
80+
let l:screenpos[1] -= 1
81+
82+
" fix height.
83+
if l:screenpos[0] - l:height >= 0
84+
let l:screenpos[0] -= l:height
85+
else
86+
let l:screenpos[0] += 1
87+
endif
88+
89+
" fix width.
90+
if &columns < l:screenpos[1] + l:width
91+
let l:screenpos[1] -= l:screenpos[1] + l:width - &columns
92+
endif
93+
94+
call self.show(l:screenpos, a:contents)
95+
endfunction
96+
97+
"
98+
" show
99+
"
100+
function! s:Floatwin.show(screenpos, contents) abort
101+
let self.screenpos = a:screenpos
102+
let self.contents = a:contents
103+
104+
" create lines.
105+
let l:lines = []
106+
for l:content in a:contents
107+
let l:lines += l:content.lines
108+
endfor
109+
110+
" update bufvars.
111+
call setbufvar(self.bufnr, 'lsp_do_conceal', 1)
112+
call setbufvar(self.bufnr, 'lsp_floatwin_lines', l:lines)
113+
114+
" show or move
115+
call lsp#ui#vim#floatwin#{s:namespace}#show(self)
116+
117+
" write lines
118+
call lsp#ui#vim#floatwin#{s:namespace}#write(self, l:lines)
119+
120+
call self.set_close_events()
121+
endfunction
122+
123+
"
124+
" hide
125+
"
126+
function! s:Floatwin.hide() abort
127+
augroup printf('lsp#ui#vim#floatwin#hide_%s', self.id)
128+
autocmd!
129+
augroup END
130+
call lsp#ui#vim#floatwin#{s:namespace}#hide(self)
131+
endfunction
132+
133+
"
134+
" enter
135+
"
136+
function! s:Floatwin.enter() abort
137+
call lsp#ui#vim#floatwin#{s:namespace}#enter(self)
138+
endfunction
139+
140+
"
141+
" is_showing
142+
"
143+
function! s:Floatwin.is_showing() abort
144+
return lsp#ui#vim#floatwin#{s:namespace}#is_showing(self)
145+
endfunction
146+
147+
"
148+
" winid
149+
"
150+
function! s:Floatwin.winid() abort
151+
return lsp#ui#vim#floatwin#{s:namespace}#winid(self)
152+
endfunction
153+
154+
"
155+
" set_close_events
156+
"
157+
function! s:Floatwin.set_close_events() abort
158+
let l:close_fn = printf('lsp_floatwin_close_%s', self.id)
159+
let b:[l:close_fn] = { -> self.hide() }
160+
161+
augroup printf('lsp#ui#vim#floatwin#hide_%s', self.id)
162+
autocmd!
163+
for l:event in self.close_on
164+
execute printf('autocmd %s <buffer> call b:%s()', l:event, l:close_fn)
165+
endfor
166+
augroup END
167+
endfunction
168+
169+
"
170+
" get_width
171+
"
172+
function! s:Floatwin.get_width(contents) abort
173+
let l:width = 0
174+
for l:content in a:contents
175+
let l:width = max([l:width] + map(copy(l:content.lines), { k, v -> strdisplaywidth(v) }))
176+
endfor
177+
178+
if self.max_width != -1
179+
return max([min([self.max_width, l:width]), 1])
180+
endif
181+
return max([l:width, 1])
182+
endfunction
183+
184+
"
185+
" get_height
186+
"
187+
function! s:Floatwin.get_height(contents) abort
188+
let l:width = self.get_width(a:contents)
189+
190+
let l:height = len(a:contents) - 1
191+
for l:content in a:contents
192+
for l:line in l:content.lines
193+
let l:height += max([1, float2nr(ceil(strdisplaywidth(l:line) / str2float('' . l:width)))])
194+
endfor
195+
endfor
196+
197+
if self.max_height != -1
198+
return max([min([self.max_height, l:height]), 1])
199+
endif
200+
return max([l:height, 1])
201+
endfunction
202+

autoload/lsp/ui/vim/floatwin/nvim.vim

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"
2+
" lsp#ui#vim#floatwin#nvim#show
3+
"
4+
function! lsp#ui#vim#floatwin#nvim#show(floatwin) abort
5+
if lsp#ui#vim#floatwin#nvim#is_showing(a:floatwin)
6+
call nvim_win_set_config(a:floatwin.nvim_window, s:get_config(a:floatwin))
7+
else
8+
let a:floatwin.nvim_window = nvim_open_win(a:floatwin.bufnr, v:false, s:get_config(a:floatwin))
9+
endif
10+
endfunction
11+
12+
"
13+
" lsp#ui#vim#floatwin#nvim#hide
14+
"
15+
function! lsp#ui#vim#floatwin#nvim#hide(floatwin) abort
16+
if lsp#ui#vim#floatwin#nvim#is_showing(a:floatwin)
17+
call nvim_win_close(a:floatwin.nvim_window, v:true)
18+
let a:floatwin.nvim_window = v:null
19+
endif
20+
endfunction
21+
22+
"
23+
" lsp#ui#vim#floatwin#nvim#write
24+
"
25+
function! lsp#ui#vim#floatwin#nvim#write(floatwin, lines) abort
26+
call nvim_buf_set_lines(a:floatwin.bufnr, 0, -1, v:true, a:lines)
27+
endfunction
28+
29+
"
30+
" lsp#ui#vim#floatwin#nvim#enter
31+
"
32+
function! lsp#ui#vim#floatwin#nvim#enter(floatwin) abort
33+
if lsp#ui#vim#floatwin#nvim#is_showing(a:floatwin)
34+
execute printf('%swincmd w', win_id2win(lsp#ui#vim#floatwin#nvim#winid(a:floatwin)))
35+
endif
36+
endfunction
37+
38+
"
39+
" lsp#ui#vim#floatwin#nvim#is_showing
40+
"
41+
function! lsp#ui#vim#floatwin#nvim#is_showing(floatwin) abort
42+
if !has_key(a:floatwin,'nvim_window') || a:floatwin.nvim_window is v:null
43+
return v:false
44+
endif
45+
46+
try
47+
return nvim_win_get_number(a:floatwin.nvim_window) != -1
48+
catch /.*/
49+
let a:floatwin.nvim_window = v:null
50+
endtry
51+
return v:false
52+
endfunction
53+
54+
"
55+
" lsp#ui#vim#floatwin#nvim#winid
56+
"
57+
function! lsp#ui#vim#floatwin#nvim#winid(floatwin) abort
58+
if lsp#ui#vim#floatwin#nvim#is_showing(a:floatwin)
59+
return win_getid(nvim_win_get_number(a:floatwin.nvim_window))
60+
endif
61+
return -1
62+
endfunction
63+
64+
"
65+
" s:get_config
66+
"
67+
function! s:get_config(floatwin) abort
68+
return {
69+
\ 'relative': 'editor',
70+
\ 'width': a:floatwin.get_width(a:floatwin.contents),
71+
\ 'height': a:floatwin.get_height(a:floatwin.contents),
72+
\ 'row': a:floatwin.screenpos[0],
73+
\ 'col': a:floatwin.screenpos[1],
74+
\ 'focusable': v:true,
75+
\ 'style': 'minimal'
76+
\ }
77+
endfunction
78+

autoload/lsp/ui/vim/floatwin/vim.vim

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"
2+
" lsp#ui#vim#floatwin#vim#show
3+
"
4+
function! lsp#ui#vim#floatwin#vim#show(floatwin) abort
5+
if lsp#ui#vim#floatwin#vim#is_showing(a:floatwin)
6+
call popup_move(a:floatwin.vim_winid, s:get_config(a:floatwin))
7+
else
8+
let a:floatwin.vim_winid = popup_create(a:floatwin.bufnr, s:get_config(a:floatwin))
9+
endif
10+
endfunction
11+
12+
"
13+
" lsp#ui#vim#floatwin#vim#hide
14+
"
15+
function! lsp#ui#vim#floatwin#vim#hide(floatwin) abort
16+
try
17+
call popup_hide(a:floatwin.vim_winid)
18+
catch /.*/
19+
endtry
20+
let a:floatwin.vim_winid = v:null
21+
endfunction
22+
23+
"
24+
" lsp#ui#vim#floatwin#vim#write
25+
"
26+
function! lsp#ui#vim#floatwin#vim#write(floatwin, lines) abort
27+
call deletebufline(a:floatwin.bufnr, '^', '$')
28+
for l:line in reverse(a:lines)
29+
call appendbufline(a:floatwin.bufnr, 0, l:line)
30+
endfor
31+
call deletebufline(a:floatwin.bufnr, '$')
32+
endfunction
33+
34+
"
35+
" lsp#ui#vim#floatwin#vim#enter
36+
"
37+
function! lsp#ui#vim#floatwin#vim#enter(floatwin) abort
38+
" noop
39+
endfunction
40+
41+
"
42+
" lsp#ui#vim#floatwin#vim#is_showing
43+
"
44+
function! lsp#ui#vim#floatwin#vim#is_showing(floatwin) abort
45+
if !has_key(a:floatwin, 'vim_winid') || a:floatwin.vim_winid is v:null
46+
return v:false
47+
endif
48+
49+
if win_id2win(a:floatwin.vim_winid) == -1
50+
let a:floatwin.vim_winid = v:null
51+
return v:false
52+
endif
53+
return v:true
54+
endfunction
55+
56+
"
57+
" lsp#ui#vim#floatwin#vim#winid
58+
"
59+
function! lsp#ui#vim#floatwin#vim#winid(floatwin) abort
60+
if lsp#ui#vim#floatwin#vim#is_showing(a:floatwin)
61+
return a:floatwin.vim_winid
62+
endif
63+
return -1
64+
endfunction
65+
66+
"
67+
" s:get_config
68+
"
69+
function! s:get_config(floatwin) abort
70+
return {
71+
\ 'line': a:floatwin.screenpos[0] + 1,
72+
\ 'col': a:floatwin.screenpos[1] + 1,
73+
\ 'pos': 'topleft',
74+
\ 'moved': [0, 100000],
75+
\ 'scrollbar': 0,
76+
\ 'maxwidth': a:floatwin.get_width(a:floatwin.contents),
77+
\ 'maxheight': a:floatwin.get_height(a:floatwin.contents),
78+
\ 'minwidth': a:floatwin.get_width(a:floatwin.contents),
79+
\ 'minheight': a:floatwin.get_height(a:floatwin.contents),
80+
\ 'tabpage': 0
81+
\ }
82+
endfunction
83+

autoload/lsp/ui/vim/hover.vim

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
let s:Floatwin = lsp#ui#vim#floatwin#import()
2+
let s:floatwin = s:Floatwin.new({
3+
\ 'close_on': ['CursorMoved', 'InsertEnter']
4+
\ })
5+
16
function! s:not_supported(what) abort
27
return lsp#utils#error(a:what.' not supported for '.&filetype)
38
endfunction
@@ -35,7 +40,8 @@ function! s:handle_hover(server, data) abort
3540
endif
3641

3742
if !empty(a:data['response']['result']) && !empty(a:data['response']['result']['contents'])
38-
call lsp#ui#vim#output#preview(a:server, a:data['response']['result']['contents'], {'statusline': ' LSP Hover'})
43+
let l:screenpos = lsp#ui#vim#floatwin#screenpos(line('.'), col('.'))
44+
call s:floatwin.show_tooltip(l:screenpos, lsp#ui#vim#floatwin#normalize_markup_content(a:data['response']['result']['contents']))
3945
return
4046
else
4147
call lsp#utils#error('No hover information found')

0 commit comments

Comments
 (0)