mirror of
https://github.com/prabirshrestha/vim-lsp.git
synced 2025-12-14 20:35:59 +01:00
197 lines
6.8 KiB
VimL
197 lines
6.8 KiB
VimL
" 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'],
|
|
\ })
|
|
let l:contents += [l:detail]
|
|
endif
|
|
endif
|
|
|
|
" Add documentation filed if provided.
|
|
let l:documentation = s:MarkupContent.normalize(get(l:completion_item, 'documentation', ''))
|
|
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.
|
|
let l:size = l:doc_win.get_size({
|
|
\ 'maxwidth': float2nr(&columns * 0.4),
|
|
\ 'maxheight': float2nr(&lines * 0.4),
|
|
\ })
|
|
let l:margin_right = &columns - 1 - (a:event.col + a:event.width + 1 + (a:event.scrollbar ? 1 : 0))
|
|
let l:margin_left = 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 <nomodeline> User lsp_float_opened') },
|
|
\ 'on_closed': { -> execute('doautocmd <nomodeline> 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
|