completion/resolve support for documentation and use the new floating window (#1052)

* doc

* floating window with completion/resolve

* add comments

* use _split_by_eol

* copy event

* fix dupe

* Fix size/markdown/detail

* - Improve for exceptional case
- Fix code style
- Support detail only case

* Add lsp_float_opened/lsp_float_closed

* remove old documentation code

* update documenation and change flags to use g:lsp_completion_documentation

Co-authored-by: hrsh7th <hrsh7th@gmail.com>
This commit is contained in:
Prabir Shrestha
2021-01-23 09:11:27 -08:00
committed by GitHub
parent 7380d31c4f
commit cacfc79eb4
14 changed files with 586 additions and 292 deletions

View File

@@ -63,6 +63,7 @@ function! lsp#enable() abort
call lsp#internal#diagnostics#_enable()
call lsp#internal#show_message_request#_enable()
call lsp#internal#work_done_progress#_enable()
call lsp#internal#completion#documentation#_enable()
call s:register_events()
endfunction
@@ -76,6 +77,7 @@ function! lsp#disable() abort
call lsp#internal#diagnostics#_disable()
call lsp#internal#show_message_request#_disable()
call lsp#internal#work_done_progress#_disable()
call lsp#internal#completion#documentation#_disable()
call s:unregister_events()
let s:enabled = 0
endfunction
@@ -473,7 +475,7 @@ function! lsp#default_get_supported_capabilities(server_info) abort
\ 'completion': {
\ 'dynamicRegistration': v:false,
\ 'completionItem': {
\ 'documentationFormat': ['plaintext'],
\ 'documentationFormat': ['markdown', 'plaintext'],
\ 'snippetSupport': v:false,
\ 'resolveSupport': {
\ 'properties': ['additionalTextEdits']
@@ -857,8 +859,6 @@ function! s:handle_initialize(server_name, data) abort
call l:Init_callback(a:data)
endfor
call lsp#ui#vim#documentation#setup()
doautocmd <nomodeline> User lsp_server_init
endfunction

View File

@@ -191,3 +191,6 @@ function! lsp#capabilities#get_code_action_kinds(server_name) abort
return []
endfunction
function! lsp#capabilities#has_completion_resolve_provider(server_name) abort
return s:has_provider(a:server_name, 'completionProvider', 'resolveProvider')
endfunction

View File

@@ -0,0 +1,189 @@
" 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({})
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: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,
\ '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)
call s:doc_win.set_bufnr(s:Buffer.create())
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)
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
function! s:log(x) abort
echom json_encode(a:x)
endfunction

View File

@@ -346,6 +346,9 @@ function! lsp#omni#get_managed_user_data_from_completed_item(completed_item) abo
endif
let l:user_data_string = get(a:completed_item, 'user_data', '')
if type(l:user_data_string) != type('')
return {}
endif
" Check managed user_data.
if has_key(s:managed_user_data_map, l:user_data_string)

View File

@@ -1,190 +0,0 @@
let s:use_vim_popup = has('patch-8.1.1517') && !has('nvim')
let s:use_nvim_float = exists('*nvim_open_win') && has('nvim')
let s:last_popup_id = -1
let s:last_timer_id = v:false
function! s:complete_changed() abort
if !g:lsp_documentation_float | return | endif
" Use a timer to avoid textlock (see :h textlock).
let l:event = copy(v:event)
if s:last_timer_id
call timer_stop(s:last_timer_id)
let s:last_timer_id = v:false
endif
let s:last_timer_id = timer_start(g:lsp_documentation_debounce, {-> s:show_documentation(l:event)})
endfunction
function! s:show_documentation(event) abort
call s:close_popup()
let l:managed_data = lsp#omni#get_managed_user_data_from_completed_item(a:event['completed_item'])
if empty(l:managed_data)
return
endif
let l:completion_item = l:managed_data['completion_item']
if !has_key(l:completion_item, 'documentation')
return
endif
if type(l:completion_item['documentation']) == type('')
let l:documentation = l:completion_item['documentation']
elseif type(l:completion_item['documentation']) == type({}) && has_key(l:completion_item['documentation'], 'value')
" field is MarkupContent (hopefully plaintext)
let l:documentation = substitute(l:completion_item['documentation']['value'], '\r', '', 'g')
endif
" TODO: Support markdown
let l:data = split(l:documentation, '\n')
let l:lines = []
let l:syntax_lines = []
let l:ft = lsp#ui#vim#output#append(l:data, l:lines, l:syntax_lines)
" Neovim
if s:use_nvim_float
let l:event = a:event
let l:event.row = float2nr(l:event.row)
let l:event.col = float2nr(l:event.col)
let l:buffer = nvim_create_buf(v:false, v:true)
let l:curpos = screenrow()
let g:lsp_documentation_float_docked = get(g:, 'lsp_documentation_float_docked', 0)
call setbufvar(l:buffer, 'lsp_syntax_highlights', l:syntax_lines)
call setbufvar(l:buffer, 'lsp_do_conceal', 1)
call nvim_buf_set_lines(l:buffer, 0, -1, v:false, l:lines)
call nvim_buf_set_option(l:buffer, 'readonly', v:true)
call nvim_buf_set_option(l:buffer, 'modifiable', v:false)
call nvim_buf_set_option(l:buffer, 'filetype', l:ft.'.lsp-hover')
if g:lsp_documentation_float_docked
let g:lsp_documentation_float_docked_maxheight = get(g:, 'lsp_documentation_float_docked_maxheight', &previewheight)
let l:dock_downwards = (l:curpos + l:event.height) < (&lines / 2)
let l:height = min([len(l:data), g:lsp_documentation_float_docked_maxheight])
let l:width = &columns
let l:col = 0
if l:dock_downwards
let l:anchor = 'SW'
let l:row = &lines - &cmdheight - 1
let l:height = min([l:height, &lines - &cmdheight - l:event.row - l:event.height - 1]) - 1
" Extra -1 of height to distinguish between completion popup
" and documentation popup
else " dock upwards
let l:anchor = 'NW'
let l:row = 0
let l:height = min([l:height, l:event.row - 1])
endif
else " not docked
let l:bufferlines = nvim_buf_line_count(l:buffer)
let l:maxwidth = max(map(getbufline(l:buffer, 1, '$'), 'strdisplaywidth(v:val)'))
let l:row = l:event['row']
let l:height = max([&lines - &cmdheight - l:row, &previewheight])
let l:right_area = &columns - l:event.col - l:event.width + 1 " 1 for the padding of popup
let l:left_area = l:event.col - 1
let l:right = l:right_area > l:left_area
if l:right
let l:anchor = 'NW'
let l:width = l:right_area - 1
let l:col = l:event.col + l:event.width + (l:event.scrollbar ? 1 : 0)
else
let l:anchor = 'NE'
let l:width = l:left_area
let l:col = l:event.col - 1 " 1 due to padding of completion popup
endif
if l:width < 0.75 * l:maxwidth
let l:col = &columns
let l:width = l:maxwidth
let l:above = l:event.row > &lines - &cmdheight - l:event.row - l:event.height
if l:above
let l:anchor = 'SE'
let l:row = l:event.row
let l:height = l:row
else
let l:anchor = 'NE'
let l:row = l:event.row + l:event.height
let l:height = &lines - &cmdheight - l:row
endif
endif
endif
" add padding on both sides of lines containing text
for l:index in range(len(l:lines))
if len(l:lines[l:index]) > 0
let l:lines[l:index] = ' ' . l:lines[l:index] . ' '
endif
endfor
if !g:lsp_documentation_float_docked
let l:width = min([float2nr(l:width), l:maxwidth])
let l:height = min([float2nr(l:height), l:bufferlines])
endif
if g:lsp_preview_max_height > 0
let l:maxheight = g:lsp_preview_max_height
let l:height = min([l:height, l:maxheight])
endif
if g:lsp_preview_max_width > 0
let l:maxwidth = min([g:lsp_preview_max_width, l:width])
endif
" Height and width must be atleast 1, otherwise error
let l:height = (l:height < 1 ? 1 : l:height)
let l:width = (l:width < 1 ? 1 : l:width)
let s:last_popup_id = nvim_open_win(l:buffer, v:false, {'relative': 'editor', 'anchor': l:anchor, 'row': l:row, 'col': l:col, 'height': l:height, 'width': l:width, 'style': 'minimal'})
call nvim_win_set_option(s:last_popup_id, 'wrap', v:true)
doautocmd <nomodeline> User lsp_float_opened
return
endif
" Vim
let l:current_win_id = win_getid()
let l:right = wincol() < winwidth(0) / 2
if l:right
let l:line = a:event['row'] + 1
let l:col = a:event['col'] + a:event['width'] + 1 + (a:event['scrollbar'] ? 1 : 0)
if l:col > &columns - 2
let l:col -= winwidth(0) / 2
endif
else
let l:line = a:event['row'] + 1
let l:col = a:event['col'] - 1
endif
let s:last_popup_id = popup_create('(no documentation available)', {'line': l:line, 'col': l:col, 'pos': l:right ? 'topleft' : 'topright', 'padding': [0, 1, 0, 1], 'border': [1, 1, 1, 1]})
doautocmd <nomodeline> User lsp_float_opened
call setbufvar(winbufnr(s:last_popup_id), 'lsp_syntax_highlights', l:syntax_lines)
call setbufvar(winbufnr(s:last_popup_id), 'lsp_do_conceal', 1)
call lsp#ui#vim#output#setcontent(s:last_popup_id, l:lines, l:ft)
call win_gotoid(l:current_win_id)
endfunction
function! s:close_popup() abort
if s:last_timer_id
call timer_stop(s:last_timer_id)
let s:last_timer_id = v:false
endif
if s:last_popup_id >= 0
if s:use_vim_popup | call popup_close(s:last_popup_id) | endif
if s:use_nvim_float && nvim_win_is_valid(s:last_popup_id) | call nvim_win_close(s:last_popup_id, 1) | endif
let s:last_popup_id = -1
doautocmd <nomodeline> User lsp_float_closed
endif
endfunction
function! lsp#ui#vim#documentation#setup() abort
augroup lsp_documentation_popup
autocmd!
if exists('##CompleteChanged')
autocmd CompleteChanged * call s:complete_changed()
endif
autocmd CompleteDone * call s:close_popup()
augroup end
endfunction
" vim: et ts=4

View File

@@ -0,0 +1,63 @@
" ___vital___
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
" Do not modify the code nor insert new lines before '" ___vital___'
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
execute join(['function! vital#_lsp#VS#LSP#MarkupContent#import() abort', printf("return map({'_vital_depends': '', 'normalize': '', '_vital_loaded': ''}, \"vital#_lsp#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
delfunction s:_SID
" ___vital___
"
" _vital_loaded
"
function! s:_vital_loaded(V) abort
let s:Text = a:V.import('VS.LSP.Text')
endfunction
"
" _vital_depends
"
function! s:_vital_depends() abort
return ['VS.LSP.Text']
endfunction
"
" normalize
"
function! s:normalize(markup_content) abort
if type(a:markup_content) == type('')
return s:_compact(a:markup_content)
elseif type(a:markup_content) == type([])
return s:_compact(join(a:markup_content, "\n"))
elseif type(a:markup_content) == type({})
let l:string = a:markup_content.value
if has_key(a:markup_content, 'language')
let l:string = '```' . a:markup_content.language . ' ' . l:string . '```'
endif
return s:_compact(l:string)
endif
endfunction
let s:_compact_fenced_start = '\%(^\|' . "\n" . '\)\s*'
let s:_compact_fenced_end = '\s*\%($\|' . "\n" . '\)'
let s:_compact_fenced_empty = '\s*\%(\s\|' . "\n" . '\)\s*'
"
" _compact
"
function! s:_compact(string) abort
" normalize eol.
let l:string = s:Text.normalize_eol(a:string)
" compact fenced code block start.
let l:string = substitute(l:string, s:_compact_fenced_start . '```\s*\w\+\s*\zs' . s:_compact_fenced_empty, ' ', 'g')
" compact fenced code block end.
let l:string = substitute(l:string, s:_compact_fenced_empty . '\ze```' . s:_compact_fenced_end, ' ', 'g')
" trim first/last whitespace.
let l:string = substitute(l:string, '^' . s:_compact_fenced_empty . '\|' . s:_compact_fenced_empty . '$', '', 'g')
return l:string
endfunction

View File

@@ -0,0 +1,23 @@
" ___vital___
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
" Do not modify the code nor insert new lines before '" ___vital___'
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
execute join(['function! vital#_lsp#VS#LSP#Text#import() abort', printf("return map({'normalize_eol': '', 'split_by_eol': ''}, \"vital#_lsp#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
delfunction s:_SID
" ___vital___
"
" normalize_eol
"
function! s:normalize_eol(text) abort
return substitute(a:text, "\r\n\\|\r", "\n", 'g')
endfunction
"
" split_by_eol
"
function! s:split_by_eol(text) abort
return split(a:text, "\r\n\\|\r\\|\n", v:true)
endfunction

View File

@@ -49,11 +49,23 @@ endfunction
"
if exists('*bufload')
function! s:load(bufnr_or_path) abort
call bufload(bufnr(a:bufnr_or_path, v:true))
let l:bufnr = bufnr(a:bufnr_or_path, v:true)
silent call bufload(l:bufnr)
return l:bufnr
endfunction
else
function! s:load(bufnr_or_path) abort
call s:do(bufnr(a:bufnr_or_path, v:true), { -> {} })
let l:curr_bufnr = bufnr('%')
try
let l:bufnr = bufnr(a:bufnr_or_path, v:true)
execute printf('keepalt keepjumps silent %sbuffer', l:bufnr)
catch /.*/
echomsg string({ 'exception': v:exception, 'throwpoint': v:throwpoint })
finally
execute printf('noautocmd keepalt keepjumps silent %sbuffer', l:curr_bufnr)
endtry
return l:bufnr
endfunction
endif
@@ -68,12 +80,12 @@ function! s:do(bufnr, func) abort
endif
try
execute printf('noautocmd keepalt keepjumps %sbuffer', a:bufnr)
execute printf('noautocmd keepalt keepjumps silent %sbuffer', a:bufnr)
call a:func()
catch /.*/
echomsg string({ 'exception': v:exception, 'throwpoint': v:throwpoint })
finally
execute printf('noautocmd keepalt keepjumps %sbuffer', l:curr_bufnr)
execute printf('noautocmd keepalt keepjumps silent %sbuffer', l:curr_bufnr)
endtry
endfunction

View File

@@ -0,0 +1,95 @@
" ___vital___
" NOTE: lines between '" ___vital___' is generated by :Vitalize.
" Do not modify the code nor insert new lines before '" ___vital___'
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
execute join(['function! vital#_lsp#VS#Vim#Syntax#Markdown#import() abort', printf("return map({'apply': ''}, \"vital#_lsp#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
delfunction s:_SID
" ___vital___
function! s:apply(...) abort
if !exists('b:___VS_Vim_Syntax_Markdown')
runtime! syntax/markdown.vim
let b:___VS_Vim_Syntax_Markdown = {}
endif
let l:bufnr = bufnr('%')
try
for [l:mark, l:filetype] in items(s:_get_filetype_map(l:bufnr, get(a:000, 0, {})))
let l:group = substitute(toupper(l:mark), '\.', '_', 'g')
if has_key(b:___VS_Vim_Syntax_Markdown, l:group)
continue
endif
let b:___VS_Vim_Syntax_Markdown[l:group] = v:true
try
if exists('b:current_syntax')
unlet b:current_syntax
endif
execute printf('syntax include @%s syntax/%s.vim', l:group, l:filetype)
execute printf('syntax region %s matchgroup=Conceal start=/%s/rs=e matchgroup=Conceal end=/%s/re=s contains=@%s containedin=ALL keepend concealends',
\ l:group,
\ printf('^\s*```\s*%s\s*', l:mark),
\ '\s*```\s*$',
\ l:group
\ )
catch /.*/
echomsg printf('[VS.Vim.Syntax.Markdown] The `%s` is not valid filetype! You can add `"let g:markdown_fenced_languages = ["FILETYPE=%s"]`.', l:mark, l:mark)
endtry
endfor
catch /.*/
echomsg string({ 'exception': v:exception, 'throwpoint': v:throwpoint })
endtry
endfunction
"
" _get_filetype_map
"
function! s:_get_filetype_map(bufnr, filetype_map) abort
let l:filetype_map = {}
for l:mark in s:_find_marks(a:bufnr)
let l:filetype_map[l:mark] = s:_get_filetype_from_mark(l:mark, a:filetype_map)
endfor
return l:filetype_map
endfunction
"
" _find_marks
"
function! s:_find_marks(bufnr) abort
let l:marks = {}
" find from buffer contents.
let l:text = join(getbufline(a:bufnr, '^', '$'), "\n")
let l:pos = 0
while 1
let l:match = matchlist(l:text, '```\s*\(\w\+\)', l:pos, 1)
if empty(l:match)
break
endif
let l:marks[l:match[1]] = v:true
let l:pos = matchend(l:text, '```\s*\(\w\+\)', l:pos, 1)
endwhile
return keys(l:marks)
endfunction
"
" _get_filetype_from_mark
"
function! s:_get_filetype_from_mark(mark, filetype_map) abort
for l:config in get(g:, 'markdown_fenced_languages', [])
if l:config !~# '='
if l:config ==# a:mark
return a:mark
endif
else
let l:config = split(l:config, '=')
if l:config[1] ==# a:mark
return l:config[0]
endif
endif
endfor
return get(a:filetype_map, a:mark, a:mark)
endfunction

View File

@@ -4,7 +4,7 @@
function! s:_SID() abort
return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze__SID$')
endfunction
execute join(['function! vital#_lsp#VS#Vim#Window#import() abort', printf("return map({'info': '', 'do': '', 'find': '', 'scroll': '', 'screenpos': ''}, \"vital#_lsp#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
execute join(['function! vital#_lsp#VS#Vim#Window#import() abort', printf("return map({'info': '', 'do': '', 'is_floating': '', 'find': '', 'scroll': '', 'screenpos': ''}, \"vital#_lsp#function('<SNR>%s_' . v:key)\")", s:_SID()), 'endfunction'], "\n")
delfunction s:_SID
" ___vital___
let s:Do = { -> {} }
@@ -43,8 +43,8 @@ endfunction
" info
"
if has('nvim')
function! s:info(win) abort
let l:info = getwininfo(a:win)[0]
function! s:info(winid) abort
let l:info = getwininfo(a:winid)[0]
return {
\ 'width': l:info.width,
\ 'height': l:info.height,
@@ -52,9 +52,9 @@ if has('nvim')
\ }
endfunction
else
function! s:info(win) abort
if index(s:_get_visible_popup_winids(), a:win) >= 0
let l:info = popup_getpos(a:win)
function! s:info(winid) abort
if s:is_floating(a:winid)
let l:info = popup_getpos(a:winid)
return {
\ 'width': l:info.width,
\ 'height': l:info.height,
@@ -69,7 +69,7 @@ else
let self.info.height = winheight(0)
let self.info.topline = line('w0')
endfunction
call s:do(a:win, { -> l:ctx.callback() })
call s:do(a:winid, { -> l:ctx.callback() })
return l:ctx.info
endfunction
endif
@@ -84,6 +84,20 @@ function! s:find(callback) abort
return filter(l:winids, 'a:callback(v:val)')
endfunction
"
" is_floating
"
if has('nvim')
function! s:is_floating(winid) abort
let l:config = nvim_win_get_config(a:winid)
return empty(l:config) || !empty(get(l:config, 'relative', ''))
endfunction
else
function! s:is_floating(winid) abort
return winheight(a:winid) != -1 && win_id2win(a:winid) == 0
endfunction
endif
"
" scroll
"
@@ -99,7 +113,7 @@ function! s:scroll(winid, topline) abort
return
endif
if index(s:_get_visible_popup_winids(), a:winid) >= 0
if !has('nvim') && s:is_floating(a:winid)
call popup_setoptions(a:winid, {
\ 'firstline': l:topline,
\ })
@@ -118,13 +132,17 @@ endfunction
" @param {[number, number]} pos - position on the current buffer.
"
function! s:screenpos(pos) abort
let l:ui_x = wincol() - col('.')
let l:y = a:pos[0]
let l:x = a:pos[1] + get(a:pos, 2, 0)
let l:view = winsaveview()
let l:scroll_x = l:view.leftcol
let l:scroll_y = l:view.topline - 1
let l:scroll_y = l:view.topline
let l:winpos = win_screenpos(win_getid())
let l:origin1 = [l:winpos[0] + (a:pos[0] - l:scroll_y) - 1, l:winpos[1] + (a:pos[1] + a:pos[2] + l:ui_x - l:scroll_x) - 1]
return [l:origin1[0] - 1, l:origin1[1] - 1]
let l:y = l:winpos[0] + l:y - l:scroll_y
let l:x = l:winpos[1] + l:x - l:scroll_x
return [l:y, l:x + (wincol() - virtcol('.')) - 1]
endfunction
"

View File

@@ -33,7 +33,7 @@ function! s:is_available() abort
if has('nvim')
return v:true
endif
return exists('*popup_create') && exists('*popup_hide') && exists('*popup_move') && exists('*popup_getpos')
return exists('*popup_create') && exists('*popup_close') && exists('*popup_move') && exists('*popup_getpos')
endfunction
"
@@ -93,6 +93,7 @@ endfunction
" @param {number?} args.maxwidth
" @param {number?} args.minheight
" @param {number?} args.maxheight
" @param {boolean?} args.wrap
"
function! s:FloatingWindow.get_size(args) abort
if self._bufnr is# v:null
@@ -115,10 +116,14 @@ function! s:FloatingWindow.get_size(args) abort
let l:width = l:maxwidth == -1 ? l:width : min([l:maxwidth, l:width])
" height
let l:height = 0
for l:line in l:lines
let l:height += max([1, float2nr(ceil(strdisplaywidth(l:line) / str2float('' . l:width)))])
endfor
if get(a:args, 'wrap', get(self._vars, '&wrap', 0))
let l:height = 0
for l:line in l:lines
let l:height += max([1, float2nr(ceil(strdisplaywidth(l:line) / str2float('' . l:width)))])
endfor
else
let l:height = len(l:lines)
endif
let l:height = l:minheight == -1 ? l:height : max([l:minheight, l:height])
let l:height = l:maxheight == -1 ? l:height : min([l:maxheight, l:height])
@@ -154,6 +159,16 @@ function! s:FloatingWindow.get_winid() abort
return v:null
endfunction
"
" info
"
function! s:FloatingWindow.info() abort
if self.is_visible()
return s:_info(self._winid)
endif
return v:null
endfunction
"
" set_var
"
@@ -183,6 +198,8 @@ endfunction
" @param {number} args.col 0-based indexing
" @param {number} args.width
" @param {number} args.height
" @param {number?} args.topline
" @param {string?} args.origin - topleft/topright/botleft/botright
"
function! s:FloatingWindow.open(args) abort
let l:style = {
@@ -190,15 +207,20 @@ function! s:FloatingWindow.open(args) abort
\ 'col': a:args.col,
\ 'width': a:args.width,
\ 'height': a:args.height,
\ 'topline': get(a:args, 'topline', 1),
\ 'origin': get(a:args, 'origin', 'topleft'),
\ }
if self.is_visible()
call s:_move(self._winid, l:style)
let l:will_move = self.is_visible()
if l:will_move
let self._winid = s:_move(self, self._winid, self._bufnr, l:style)
else
let self._winid = s:_open(self._bufnr, l:style, { -> self._on_closed() })
for [l:key, l:value] in items(self._vars)
call setwinvar(self._winid, l:key, l:value)
endfor
endif
for [l:key, l:value] in items(self._vars)
call setwinvar(self._winid, l:key, l:value)
endfor
if !l:will_move
call s:_notify_opened(self._winid, self)
endif
endfunction
@@ -231,12 +253,14 @@ endfunction
" open
"
if has('nvim')
function! s:_open(buf, style, callback) abort
return nvim_open_win(a:buf, v:false, s:_style(a:style))
function! s:_open(bufnr, style, callback) abort
let l:winid = nvim_open_win(a:bufnr, v:false, s:_style(a:style))
call s:Window.scroll(l:winid, a:style.topline)
return l:winid
endfunction
else
function! s:_open(buf, style, callback) abort
return popup_create(a:buf, extend(s:_style(a:style), {
function! s:_open(bufnr, style, callback) abort
return popup_create(a:bufnr, extend(s:_style(a:style), {
\ 'callback': a:callback,
\ }, 'force'))
endfunction
@@ -246,13 +270,13 @@ endif
" close
"
if has('nvim')
function! s:_close(win) abort
call nvim_win_close(a:win, v:true)
function! s:_close(winid) abort
call nvim_win_close(a:winid, v:true)
call s:_notify_closed()
endfunction
else
function! s:_close(win) abort
call popup_close(a:win)
function! s:_close(winid) abort
call popup_close(a:winid)
endfunction
endif
@@ -260,12 +284,26 @@ endif
" move
"
if has('nvim')
function! s:_move(win, style) abort
call nvim_win_set_config(a:win, s:_style(a:style))
function! s:_move(self, winid, bufnr, style) abort
call nvim_win_set_config(a:winid, s:_style(a:style))
if a:bufnr != nvim_win_get_buf(a:winid)
call nvim_win_set_buf(a:winid, a:bufnr)
endif
call s:Window.scroll(a:winid, a:style.topline)
return a:winid
endfunction
else
function! s:_move(win, style) abort
call popup_move(a:win, s:_style(a:style))
function! s:_move(self, winid, bufnr, style) abort
" vim's popup window can't change bufnr so open new popup in here.
if a:bufnr != winbufnr(a:winid)
let l:On_closed = a:self._on_closed
let a:self._on_closed = { -> {} }
call s:_close(a:winid)
let a:self._on_closed = l:On_closed
return s:_open(a:bufnr, a:style, { -> a:self._on_closed() })
endif
call popup_move(a:winid, s:_style(a:style))
return a:winid
endfunction
endif
@@ -273,11 +311,11 @@ endif
" enter
"
if has('nvim')
function! s:_enter(win) abort
call win_gotoid(a:win)
function! s:_enter(winid) abort
call win_gotoid(a:winid)
endfunction
else
function! s:_enter(win) abort
function! s:_enter(winid) abort
" not supported.
endfunction
endif
@@ -286,12 +324,36 @@ endif
" exists
"
if has('nvim')
function! s:_exists(win) abort
return type(a:win) == type(0) && nvim_win_is_valid(a:win) && nvim_win_get_number(a:win) != -1
function! s:_exists(winid) abort
return type(a:winid) == type(0) && nvim_win_is_valid(a:winid) && nvim_win_get_number(a:winid) != -1
endfunction
else
function! s:_exists(win) abort
return type(a:win) == type(0) && winheight(a:win) != -1
function! s:_exists(winid) abort
return type(a:winid) == type(0) && winheight(a:winid) != -1
endfunction
endif
if has('nvim')
function! s:_info(winid) abort
let l:info = getwininfo(a:winid)[0]
return {
\ 'row': l:info.winrow,
\ 'col': l:info.wincol,
\ 'width': l:info.width,
\ 'height': l:info.height,
\ 'topline': l:info.topline,
\ }
endfunction
else
function! s:_info(winid) abort
let l:pos = popup_getpos(a:winid)
return {
\ 'row': l:pos.core_line,
\ 'col': l:pos.core_col,
\ 'width': l:pos.core_width,
\ 'height': l:pos.core_height,
\ 'topline': l:pos.firstline,
\ }
endfunction
endif
@@ -300,33 +362,69 @@ endif
"
if has('nvim')
function! s:_style(style) abort
let l:style = s:_resolve_style(a:style)
return {
\ 'relative': 'editor',
\ 'width': a:style.width,
\ 'height': a:style.height,
\ 'row': a:style.row,
\ 'col': a:style.col,
\ 'row': l:style.row - 1,
\ 'col': l:style.col - 1,
\ 'width': l:style.width,
\ 'height': l:style.height,
\ 'focusable': v:true,
\ 'style': 'minimal',
\ }
endfunction
else
function! s:_style(style) abort
let l:style = s:_resolve_style(a:style)
return {
\ 'line': a:style.row + 1,
\ 'col': a:style.col + 1,
\ 'line': l:style.row,
\ 'col': l:style.col,
\ 'pos': 'topleft',
\ 'wrap': v:false,
\ 'moved': [0, 0, 0],
\ 'scrollbar': 0,
\ 'maxwidth': a:style.width,
\ 'maxheight': a:style.height,
\ 'minwidth': a:style.width,
\ 'minheight': a:style.height,
\ 'maxwidth': l:style.width,
\ 'maxheight': l:style.height,
\ 'minwidth': l:style.width,
\ 'minheight': l:style.height,
\ 'tabpage': 0,
\ 'firstline': l:style.topline,
\ 'padding': [0, 0, 0, 0],
\ 'fixed': v:true,
\ }
endfunction
endif
"
" resolve_style
"
function! s:_resolve_style(style) abort
if index(['topleft', 'topright', 'bottomleft', 'bottomright', 'topcenter', 'bottomcenter'], a:style.origin) == -1
let a:style.origin = 'topleft'
endif
if a:style.origin ==# 'topleft'
let a:style.col = a:style.col
let a:style.row = a:style.row
elseif a:style.origin ==# 'topright'
let a:style.col = a:style.col - (a:style.width - 1)
let a:style.row = a:style.row
elseif a:style.origin ==# 'bottomleft'
let a:style.col = a:style.col
let a:style.row = a:style.row - (a:style.height - 1)
elseif a:style.origin ==# 'bottomright'
let a:style.col = a:style.col - (a:style.width - 1)
let a:style.row = a:style.row - (a:style.height - 1)
elseif a:style.origin ==# 'topcenter'
let a:style.col = a:style.col - float2nr(a:style.width / 2)
let a:style.row = a:style.row
elseif a:style.origin ==# 'bottomcenter'
let a:style.col = a:style.col - float2nr(a:style.width / 2)
let a:style.row = a:style.row - (a:style.height - 1)
endif
return a:style
endfunction
"
" init
"

View File

@@ -1,5 +1,8 @@
lsp
274654a8153a620b94c7e6869e838d43b8d9e701
621cda4f832d2f43f3b15cdbfb70ca36b219efc2
VS.LSP.MarkupContent
VS.Vim.Window.FloatingWindow
VS.Vim.Syntax.Markdown
VS.Vim.Buffer
VS.Vim.Window

View File

@@ -19,11 +19,10 @@ CONTENTS *vim-lsp-contents*
g:lsp_preview_doubletap |g:lsp_preview_doubletap|
g:lsp_insert_text_enabled |g:lsp_insert_text_enabled|
g:lsp_text_edit_enabled |g:lsp_text_edit_enabled|
g:lsp_documentation_debounce |g:lsp_documentation_debounce|
g:lsp_documentation_float |g:lsp_documentation_float|
g:lsp_documentation_float_docked |g:lsp_documentation_float_docked|
g:lsp_documentation_float_docked_maxheight
|g:lsp_documentation_float_docked_maxheight|
g:lsp_completion_documentation_enabled
|g:lsp_completion_documentation_enabled|
g:lsp_completion_documentation_delay
|g:lsp_completion_documentation_delay|
g:lsp_diagnostics_enabled |g:lsp_diagnostics_enabled|
g:lsp_diagnostics_echo_cursor |g:lsp_diagnostics_echo_cursor|
g:lsp_diagnostics_echo_delay |g:lsp_diagnostics_echo_delay|
@@ -398,7 +397,17 @@ g:lsp_text_edit_enabled *g:lsp_text_edit_enabled*
let g:lsp_text_edit_enabled = 1
let g:lsp_text_edit_enabled = 0
g:lsp_documentation_debounce *g:lsp_documentation_debounce*
g:lsp_completion_documentation_enabled *g:lsp_completion_documentation_enabled*
Type: |Number|
Default: `1`
Enables floating window documentation for complete items.
Example: >
let g:lsp_completion_documentation_enabled = 1
let g:lsp_completion_documentation_enabled = 0
g:lsp_completion_documentation_delay *g:lsp_completion_documentation_delay*
Type: |Number|
Default: `80`
@@ -406,39 +415,8 @@ g:lsp_documentation_debounce *g:lsp_documentation_debounce*
help with performance. Set this to `0` to disable debouncing.
Example: >
let g:lsp_documentation_debounce = 120
let g:lsp_documentation_debounce = 0
g:lsp_documentation_float *g:lsp_documentation_float*
Type: |Number|
Default: `1`
Enables floating window documentation for complete items.
Example: >
let g:lsp_documentation_float = 1
let g:lsp_documentation_float = 0
g:lsp_documentation_float_docked *g:lsp_documentation_float_docked*
Type: |Number|
Default: `0`
Dock the floating documentation window for complete items if enabled.
Example: >
let g:lsp_documentation_float_docked = 1
let g:lsp_documentation_float_docked = 0
g:lsp_documentation_float_docked_maxheight
*g:lsp_documentation_float_docked_maxheight*
Type: |Number|
Default: `&previewheight`
The maximum height of the docked documentation window if enabled.
Example: >
let g:lsp_documentation_float_docked_maxheight = 1
let g:lsp_documentation_float_docked_maxheight = 0
let g:lsp_completion_documentation_delay = 120
let g:lsp_completion_documentation_delay = 0
g:lsp_diagnostics_enabled *g:lsp_diagnostics_enabled*
Type: |Number|

View File

@@ -10,10 +10,9 @@ let g:lsp_log_file = get(g:, 'lsp_log_file', '')
let g:lsp_log_verbose = get(g:, 'lsp_log_verbose', 1)
let g:lsp_debug_servers = get(g:, 'lsp_debug_servers', [])
let g:lsp_format_sync_timeout = get(g:, 'lsp_format_sync_timeout', -1)
let g:lsp_documentation_debounce = get(g:, 'lsp_documentation_debounce', 80)
let g:lsp_documentation_float = get(g:, 'lsp_documentation_float', 1)
let g:lsp_documentation_float_docked = get(g:, 'lsp_documentation_float_docked', 0)
let g:lsp_documentation_float_docked_maxheight = get(g:, ':lsp_documentation_float_docked_maxheight', &previewheight)
let g:lsp_completion_documentation_enabled = get(g:, 'lsp_completion_documentation_enabled', 1)
let g:lsp_completion_documentation_delay = get(g:, 'lsp_completion_documention_delay', 80)
let g:lsp_diagnostics_enabled = get(g:, 'lsp_diagnostics_enabled', 1)
let g:lsp_diagnostics_echo_cursor = get(g:, 'lsp_diagnostics_echo_cursor', 0)