" https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion let s:enabled = 0 let s:Markdown = vital#lsp#import('VS.Vim.Syntax.Markdown') let s:MarkupContent = vital#lsp#import('VS.LSP.MarkupContent') let s:FloatingWindow = vital#lsp#import('VS.Vim.Window.FloatingWindow') let s:Window = vital#lsp#import('VS.Vim.Window') let s:Buffer = vital#lsp#import('VS.Vim.Buffer') function! lsp#internal#completion#documentation#_enable() abort " don't even bother registering if the feature is disabled if !g:lsp_completion_documentation_enabled | return | endif if !s:FloatingWindow.is_available() | return | endif if !exists('##CompleteChanged') | return | endif if s:enabled | return | endif let s:enabled = 1 let s:Dispose = lsp#callbag#pipe( \ lsp#callbag#merge( \ lsp#callbag#pipe( \ lsp#callbag#fromEvent('CompleteChanged'), \ lsp#callbag#filter({_->g:lsp_completion_documentation_enabled}), \ lsp#callbag#map({->copy(v:event)}), \ lsp#callbag#debounceTime(g:lsp_completion_documentation_delay), \ lsp#callbag#switchMap({event-> \ lsp#callbag#pipe( \ s:resolve_completion(event), \ lsp#callbag#tap({managed_user_data->s:show_floating_window(event, managed_user_data)}), \ lsp#callbag#takeUntil(lsp#callbag#fromEvent('CompleteDone')) \ ) \ }) \ ), \ lsp#callbag#pipe( \ lsp#callbag#fromEvent('CompleteDone'), \ lsp#callbag#tap({_->s:close_floating_window(v:false)}), \ ) \ ), \ lsp#callbag#subscribe(), \ ) endfunction function! s:resolve_completion(event) abort let l:managed_user_data = lsp#omni#get_managed_user_data_from_completed_item(a:event['completed_item']) if empty(l:managed_user_data) return lsp#callbag#of({}) endif let l:completion_item = l:managed_user_data['completion_item'] if has_key(l:completion_item, 'documentation') return lsp#callbag#of(l:managed_user_data) elseif lsp#capabilities#has_completion_resolve_provider(l:managed_user_data['server_name']) return lsp#callbag#pipe( \ lsp#request(l:managed_user_data['server_name'], { \ 'method': 'completionItem/resolve', \ 'params': l:completion_item, \ }), \ lsp#callbag#map({x->{ \ 'server_name': l:managed_user_data['server_name'], \ 'completion_item': x['response']['result'], \ 'complete_position': l:managed_user_data['complete_position'], \ }}) \ ) else return lsp#callbag#of(l:managed_user_data) endif endfunction function! s:show_floating_window(event, managed_user_data) abort if empty(a:managed_user_data) || !pumvisible() call s:close_floating_window(v:true) return endif let l:completion_item = a:managed_user_data['completion_item'] let l:contents = [] " Add detail field if provided. if type(get(l:completion_item, 'detail', v:null)) == type('') if !empty(l:completion_item.detail) let l:detail = s:MarkupContent.normalize({ \ 'language': &filetype, \ 'value': l:completion_item['detail'], \ }, { \ 'compact': !g:lsp_preview_fixup_conceal \ }) let l:contents += [l:detail] endif endif " Add documentation filed if provided. let l:documentation = s:MarkupContent.normalize(get(l:completion_item, 'documentation', ''), { \ 'compact': !g:lsp_preview_fixup_conceal \ }) if !empty(l:documentation) let l:contents += [l:documentation] endif " Ignore if contents is empty. if empty(l:contents) return s:close_floating_window(v:true) endif " Update contents. let l:doc_win = s:get_doc_win() call deletebufline(l:doc_win.get_bufnr(), 1, '$') call setbufline(l:doc_win.get_bufnr(), 1, lsp#utils#_split_by_eol(join(l:contents, "\n\n"))) " Calculate layout. if g:lsp_float_max_width >= 1 let l:maxwidth = g:lsp_float_max_width elseif g:lsp_float_max_width == 0 let l:maxwidth = &columns else let l:maxwidth = float2nr(&columns * 0.4) endif let l:size = l:doc_win.get_size({ \ 'maxwidth': l:maxwidth, \ 'maxheight': float2nr(&lines * 0.4), \ }) let l:margin_right = &columns - 1 - (float2nr(a:event.col) + float2nr(a:event.width) + 1 + (a:event.scrollbar ? 1 : 0)) let l:margin_left = float2nr(a:event.col) - 3 if l:size.width < l:margin_right " do nothing elseif l:margin_left <= l:margin_right let l:size.width = l:margin_right else let l:size.width = l:margin_left endif let l:pos = s:compute_position(a:event, l:size) if empty(l:pos) call s:close_floating_window(v:true) return endif " Show popupmenu and apply markdown syntax. call l:doc_win.open({ \ 'row': l:pos[0] + 1, \ 'col': l:pos[1] + 1, \ 'width': l:size.width, \ 'height': l:size.height, \ 'border': v:true, \ 'topline': 1, \ }) call s:Window.do(l:doc_win.get_winid(), { -> s:Markdown.apply() }) endfunction function! s:close_floating_window(force) abort " Ignore `CompleteDone` if it occurred by `complete()` because in this case, the popup menu will re-appear immediately. let l:ctx = {} function! l:ctx.callback(force) abort if !pumvisible() || a:force call s:get_doc_win().close() endif endfunction call timer_start(1, { -> l:ctx.callback(a:force) }) endfunction function! s:compute_position(event, size) abort let l:col_if_right = a:event.col + a:event.width + 1 + (a:event.scrollbar ? 1 : 0) let l:col_if_left = a:event.col - a:size.width - 2 if a:size.width >= (&columns - l:col_if_right) let l:col = l:col_if_left else let l:col = l:col_if_right endif if l:col <= 0 return [] endif if &columns <= l:col + a:size.width return [] endif return [a:event.row, l:col] endfunction function! s:get_doc_win() abort if exists('s:doc_win') return s:doc_win endif let s:doc_win = s:FloatingWindow.new({ \ 'on_opened': { -> execute('doautocmd User lsp_float_opened') }, \ 'on_closed': { -> execute('doautocmd User lsp_float_closed') } \ }) call s:doc_win.set_var('&wrap', 1) call s:doc_win.set_var('&conceallevel', 2) noautocmd silent let l:bufnr = s:Buffer.create() call s:doc_win.set_bufnr(l:bufnr) call setbufvar(s:doc_win.get_bufnr(), '&buftype', 'nofile') call setbufvar(s:doc_win.get_bufnr(), '&bufhidden', 'hide') call setbufvar(s:doc_win.get_bufnr(), '&buflisted', 0) call setbufvar(s:doc_win.get_bufnr(), '&swapfile', 0) return s:doc_win endfunction function! lsp#internal#completion#documentation#_disable() abort if !s:enabled | return | endif if exists('s:Dispose') call s:Dispose() unlet s:Dispose endif endfunction