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/specifications/specification-current/#textDocument_completion
4 let s:Markdown = vital#lsp#import('VS.Vim.Syntax.Markdown')
5 let s:MarkupContent = vital#lsp#import('VS.LSP.MarkupContent')
6 let s:FloatingWindow = vital#lsp#import('VS.Vim.Window.FloatingWindow')
7 let s:Window = vital#lsp#import('VS.Vim.Window')
8 let s:Buffer = vital#lsp#import('VS.Vim.Buffer')
10 function! lsp#internal#completion#documentation#_enable() abort
11 " don't even bother registering if the feature is disabled
12 if !g:lsp_completion_documentation_enabled | return | endif
14 if !s:FloatingWindow.is_available() | return | endif
15 if !exists('##CompleteChanged') | return | endif
17 if s:enabled | return | endif
20 let s:Dispose = lsp#callbag#pipe(
23 \ lsp#callbag#fromEvent('CompleteChanged'),
24 \ lsp#callbag#filter({_->g:lsp_completion_documentation_enabled}),
25 \ lsp#callbag#map({->copy(v:event)}),
26 \ lsp#callbag#debounceTime(g:lsp_completion_documentation_delay),
27 \ lsp#callbag#switchMap({event->
29 \ s:resolve_completion(event),
30 \ lsp#callbag#tap({managed_user_data->s:show_floating_window(event, managed_user_data)}),
31 \ lsp#callbag#takeUntil(lsp#callbag#fromEvent('CompleteDone'))
36 \ lsp#callbag#fromEvent('CompleteDone'),
37 \ lsp#callbag#tap({_->s:close_floating_window(v:false)}),
40 \ lsp#callbag#subscribe(),
44 function! s:resolve_completion(event) abort
45 let l:managed_user_data = lsp#omni#get_managed_user_data_from_completed_item(a:event['completed_item'])
46 if empty(l:managed_user_data)
47 return lsp#callbag#of({})
50 let l:completion_item = l:managed_user_data['completion_item']
52 if has_key(l:completion_item, 'documentation')
53 return lsp#callbag#of(l:managed_user_data)
54 elseif lsp#capabilities#has_completion_resolve_provider(l:managed_user_data['server_name'])
55 return lsp#callbag#pipe(
56 \ lsp#request(l:managed_user_data['server_name'], {
57 \ 'method': 'completionItem/resolve',
58 \ 'params': l:completion_item,
60 \ lsp#callbag#map({x->{
61 \ 'server_name': l:managed_user_data['server_name'],
62 \ 'completion_item': x['response']['result'],
63 \ 'complete_position': l:managed_user_data['complete_position'],
67 return lsp#callbag#of(l:managed_user_data)
71 function! s:show_floating_window(event, managed_user_data) abort
72 if empty(a:managed_user_data) || !pumvisible()
73 call s:close_floating_window(v:true)
76 let l:completion_item = a:managed_user_data['completion_item']
80 " Add detail field if provided.
81 if type(get(l:completion_item, 'detail', v:null)) == type('')
82 if !empty(l:completion_item.detail)
83 let l:detail = s:MarkupContent.normalize({
84 \ 'language': &filetype,
85 \ 'value': l:completion_item['detail'],
87 \ 'compact': !g:lsp_preview_fixup_conceal
89 let l:contents += [l:detail]
93 " Add documentation filed if provided.
94 let l:documentation = s:MarkupContent.normalize(get(l:completion_item, 'documentation', ''), {
95 \ 'compact': !g:lsp_preview_fixup_conceal
97 if !empty(l:documentation)
98 let l:contents += [l:documentation]
101 " Ignore if contents is empty.
103 return s:close_floating_window(v:true)
107 let l:doc_win = s:get_doc_win()
108 call deletebufline(l:doc_win.get_bufnr(), 1, '$')
109 call setbufline(l:doc_win.get_bufnr(), 1, lsp#utils#_split_by_eol(join(l:contents, "\n\n")))
112 if g:lsp_float_max_width >= 1
113 let l:maxwidth = g:lsp_float_max_width
114 elseif g:lsp_float_max_width == 0
115 let l:maxwidth = &columns
117 let l:maxwidth = float2nr(&columns * 0.4)
119 let l:size = l:doc_win.get_size({
120 \ 'maxwidth': l:maxwidth,
121 \ 'maxheight': float2nr(&lines * 0.4),
123 let l:margin_right = &columns - 1 - (float2nr(a:event.col) + float2nr(a:event.width) + 1 + (a:event.scrollbar ? 1 : 0))
124 let l:margin_left = float2nr(a:event.col) - 3
125 if l:size.width < l:margin_right
127 elseif l:margin_left <= l:margin_right
128 let l:size.width = l:margin_right
130 let l:size.width = l:margin_left
132 let l:pos = s:compute_position(a:event, l:size)
134 call s:close_floating_window(v:true)
138 " Show popupmenu and apply markdown syntax.
139 call l:doc_win.open({
140 \ 'row': l:pos[0] + 1,
141 \ 'col': l:pos[1] + 1,
142 \ 'width': l:size.width,
143 \ 'height': l:size.height,
147 call s:Window.do(l:doc_win.get_winid(), { -> s:Markdown.apply() })
150 function! s:close_floating_window(force) abort
151 " Ignore `CompleteDone` if it occurred by `complete()` because in this case, the popup menu will re-appear immediately.
153 function! l:ctx.callback(force) abort
154 if !pumvisible() || a:force
155 call s:get_doc_win().close()
158 call timer_start(1, { -> l:ctx.callback(a:force) })
161 function! s:compute_position(event, size) abort
162 let l:col_if_right = a:event.col + a:event.width + 1 + (a:event.scrollbar ? 1 : 0)
163 let l:col_if_left = a:event.col - a:size.width - 2
165 if a:size.width >= (&columns - l:col_if_right)
166 let l:col = l:col_if_left
168 let l:col = l:col_if_right
174 if &columns <= l:col + a:size.width
178 return [a:event.row, l:col]
181 function! s:get_doc_win() abort
182 if exists('s:doc_win')
186 let s:doc_win = s:FloatingWindow.new({
187 \ 'on_opened': { -> execute('doautocmd <nomodeline> User lsp_float_opened') },
188 \ 'on_closed': { -> execute('doautocmd <nomodeline> User lsp_float_closed') }
190 call s:doc_win.set_var('&wrap', 1)
191 call s:doc_win.set_var('&conceallevel', 2)
192 noautocmd silent let l:bufnr = s:Buffer.create()
193 call s:doc_win.set_bufnr(l:bufnr)
194 call setbufvar(s:doc_win.get_bufnr(), '&buftype', 'nofile')
195 call setbufvar(s:doc_win.get_bufnr(), '&bufhidden', 'hide')
196 call setbufvar(s:doc_win.get_bufnr(), '&buflisted', 0)
197 call setbufvar(s:doc_win.get_bufnr(), '&swapfile', 0)
201 function! lsp#internal#completion#documentation#_disable() abort
202 if !s:enabled | return | endif
203 if exists('s:Dispose')