All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
1 " https://microsoft.github.io/language-server-protocol/specification#textDocument_hover
3 let s:Markdown = vital#lsp#import('VS.Vim.Syntax.Markdown')
4 let s:MarkupContent = vital#lsp#import('VS.LSP.MarkupContent')
5 let s:FloatingWindow = vital#lsp#import('VS.Vim.Window.FloatingWindow')
6 let s:Window = vital#lsp#import('VS.Vim.Window')
7 let s:Buffer = vital#lsp#import('VS.Vim.Buffer')
10 " server - 'server_name' " optional
11 " ui - 'float' | 'preview'
13 function! lsp#internal#document_hover#under_cursor#do(options) abort
14 let l:bufnr = bufnr('%')
15 let l:ui = get(a:options, 'ui', g:lsp_hover_ui)
17 let l:ui = s:FloatingWindow.is_available() ? 'float' : 'preview'
21 let l:doc_win = s:get_doc_win()
22 if l:doc_win.is_visible()
23 if bufnr('%') ==# l:doc_win.get_bufnr()
24 call s:close_floating_window()
26 call l:doc_win.enter()
27 inoremap <buffer><silent> <Plug>(lsp-float-close) <ESC>:<C-u>call <SID>close_floating_window()<CR>
28 nnoremap <buffer><silent> <Plug>(lsp-float-close) :<C-u>call <SID>close_floating_window()<CR>
29 execute('doautocmd <nomodeline> User lsp_float_focused')
30 if !hasmapto('<Plug>(lsp-float-close)')
31 imap <silent> <buffer> <C-c> <Plug>(lsp-float-close)
32 nmap <silent> <buffer> <C-c> <Plug>(lsp-float-close)
39 if has_key(a:options, 'server')
40 let l:servers = [a:options['server']]
42 let l:servers = filter(lsp#get_allowed_servers(), 'lsp#capabilities#has_hover_provider(v:val)')
45 if len(l:servers) == 0
46 let l:filetype = getbufvar(l:bufnr, '&filetype')
47 call lsp#utils#error('textDocument/hover not supported for ' . l:filetype)
51 redraw | echo 'Retrieving hover ...'
53 call lsp#_new_command()
55 " TODO: ask user to select server for formatting if there are multiple servers
57 \ 'method': 'textDocument/hover',
59 \ 'textDocument': lsp#get_text_document_identifier(),
60 \ 'position': lsp#get_position(),
63 call lsp#callbag#pipe(
64 \ lsp#callbag#fromList(l:servers),
65 \ lsp#callbag#flatMap({server->
66 \ lsp#request(server, l:request)
68 \ lsp#callbag#tap({x->s:show_hover(l:ui, x['server_name'], x['request'], x['response'])}),
69 \ lsp#callbag#takeUntil(lsp#callbag#pipe(
71 \ lsp#callbag#filter({x->has_key(x, 'command')}),
73 \ lsp#callbag#subscribe(),
77 function! lsp#internal#document_hover#under_cursor#getpreviewwinid() abort
78 if exists('s:doc_win')
79 return s:doc_win.get_winid()
84 function! s:show_hover(ui, server_name, request, response) abort
85 if !has_key(a:response, 'result') || empty(a:response['result']) ||
86 \ empty(a:response['result']['contents'])
87 call lsp#utils#error('No hover information found in server - ' . a:server_name)
93 if s:FloatingWindow.is_available() && a:ui ==? 'float'
94 call s:show_floating_window(a:server_name, a:request, a:response)
96 call s:show_preview_window(a:server_name, a:request, a:response)
100 function! s:show_preview_window(server_name, request, response) abort
101 let l:contents = s:get_contents(a:response['result']['contents'])
103 " Ignore if contents is empty.
105 call lsp#utils#error('Empty contents for LspHover')
109 let l:lines = lsp#utils#_split_by_eol(join(l:contents, "\n\n"))
110 let l:view = winsaveview()
114 execute 'resize '.min([len(l:lines), &previewheight])
116 setlocal conceallevel=2
117 setlocal bufhidden=hide
119 setlocal buftype=nofile
122 call setline(1, l:lines)
123 call s:Window.do(win_getid(), {->s:Markdown.apply()})
124 execute "normal \<c-w>p"
125 call winrestview(l:view)
129 function! s:show_floating_window(server_name, request, response) abort
130 call s:close_floating_window()
132 let l:contents = s:get_contents(a:response['result']['contents'])
134 " Ignore if contents is empty.
136 return s:close_floating_window()
140 let l:doc_win = s:get_doc_win()
141 silent! call deletebufline(l:doc_win.get_bufnr(), 1, '$')
142 call setbufline(l:doc_win.get_bufnr(), 1, lsp#utils#_split_by_eol(join(l:contents, "\n\n")))
145 if g:lsp_float_max_width >= 1
146 let l:maxwidth = g:lsp_float_max_width
147 elseif g:lsp_float_max_width == 0
148 let l:maxwidth = &columns
150 let l:maxwidth = float2nr(&columns * 0.4)
152 let l:size = l:doc_win.get_size({
153 \ 'maxwidth': l:maxwidth,
154 \ 'maxheight': float2nr(&lines * 0.4),
156 let l:pos = s:compute_position(l:size)
158 call s:close_floating_window()
162 execute printf('augroup vim_lsp_hover_close_on_move_%d', bufnr('%'))
163 " vint: -ProhibitAutocmdWithNoGroup
165 autocmd InsertEnter,BufLeave <buffer> call s:close_floating_window()
166 execute printf('autocmd CursorMoved <buffer> call s:close_floating_window_on_move(%s)', getcurpos())
167 " vint: +ProhibitAutocmdWithNoGroup
170 " Show popupmenu and apply markdown syntax.
171 call l:doc_win.open({
174 \ 'width': l:size.width,
175 \ 'height': l:size.height,
178 call s:Window.do(l:doc_win.get_winid(), { -> s:Markdown.apply() })
180 " Format contents to fit window
181 call setbufvar(l:doc_win.get_bufnr(), '&textwidth', l:size.width)
182 call s:Window.do(l:doc_win.get_winid(), { -> s:format_window() })
185 function! s:format_window() abort
186 global/^/normal! gqgq
189 function! s:get_contents(contents) abort
190 if type(a:contents) == type('')
192 elseif type(a:contents) == type([])
194 for l:content in a:contents
195 let l:result += s:get_contents(l:content)
198 elseif type(a:contents) == type({})
199 if has_key(a:contents, 'value')
200 if has_key(a:contents, 'kind')
201 if a:contents['kind'] ==? 'markdown'
202 let l:detail = s:MarkupContent.normalize(a:contents['value'], {
203 \ 'compact': !g:lsp_preview_fixup_conceal
207 return [a:contents['value']]
209 elseif has_key(a:contents, 'language')
210 let l:detail = s:MarkupContent.normalize(a:contents, {
211 \ 'compact': !g:lsp_preview_fixup_conceal
225 function! s:close_floating_window() abort
226 call s:get_doc_win().close()
229 function! s:close_floating_window_on_move(curpos) abort
230 if a:curpos != getcurpos() | call s:close_floating_window() | endif
233 function! s:on_opened() abort
234 inoremap <buffer><silent> <Plug>(lsp-float-close) <ESC>:<C-u>call <SID>close_floating_window()<CR>
235 nnoremap <buffer><silent> <Plug>(lsp-float-close) :<C-u>call <SID>close_floating_window()<CR>
236 execute('doautocmd <nomodeline> User lsp_float_opened')
237 if !hasmapto('<Plug>(lsp-float-close)')
238 imap <silent> <buffer> <C-c> <Plug>(lsp-float-close)
239 nmap <silent> <buffer> <C-c> <Plug>(lsp-float-close)
243 function! s:on_closed() abort
244 execute('doautocmd <nomodeline> User lsp_float_closed')
247 function! s:get_doc_win() abort
248 if exists('s:doc_win')
252 let s:doc_win = s:FloatingWindow.new({
253 \ 'on_opened': function('s:on_opened'),
254 \ 'on_closed': function('s:on_closed')
256 call s:doc_win.set_var('&wrap', 1)
257 call s:doc_win.set_var('&conceallevel', 2)
258 call s:doc_win.set_bufnr(s:Buffer.create())
259 call setbufvar(s:doc_win.get_bufnr(), '&buftype', 'nofile')
260 call setbufvar(s:doc_win.get_bufnr(), '&bufhidden', 'hide')
261 call setbufvar(s:doc_win.get_bufnr(), '&buflisted', 0)
262 call setbufvar(s:doc_win.get_bufnr(), '&swapfile', 0)
266 function! s:compute_position(size) abort
267 let l:pos = screenpos(0, line('.'), col('.'))
268 if l:pos.row == 0 && l:pos.col == 0
269 " workaround for float position
270 let l:pos = {'curscol': wincol(), 'row': winline()}
272 let l:pos = [l:pos.row + 1, l:pos.curscol + 1]
273 if l:pos[0] + a:size.height > &lines
274 let l:pos[0] = l:pos[0] - a:size.height - 3
276 if l:pos[1] + a:size.width > &columns
277 let l:pos[1] = l:pos[1] - a:size.width - 3