Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions autoload/lsp.vim
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,9 @@ function! lsp#default_get_supported_capabilities(server_info) abort
\ },
\ 'textDocument': {
\ 'completion': {
\ 'completionItem': {
\ 'documentationFormat': ['plaintext']
\ },
\ 'completionItemKind': {
\ 'valueSet': lsp#omni#get_completion_item_kinds()
\ }
Expand Down Expand Up @@ -696,6 +699,8 @@ function! s:handle_initialize(server_name, data) abort
call lsp#ui#vim#signature_help#setup()
endif

call lsp#ui#vim#documentation#setup()

doautocmd User lsp_server_init
endfunction

Expand Down
6 changes: 5 additions & 1 deletion autoload/lsp/omni.vim
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,12 @@ function! lsp#omni#default_get_vim_completion_item(item, ...) abort
endif

if has_key(a:item, 'documentation')
if type(a:item['documentation']) == type('')
if type(a:item['documentation']) == type('') " field is string
let l:completion['info'] .= a:item['documentation']
elseif type(a:item['documentation']) == type({}) &&
\ has_key(a:item['documentation'], 'value')
" field is MarkupContent (hopefully 'plaintext')
let l:completion['info'] .= a:item['documentation']['value']
endif
endif

Expand Down
75 changes: 75 additions & 0 deletions autoload/lsp/ui/vim/documentation.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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

function! s:complete_done() abort
" Use a timer to avoid textlock (see :h textlock).
let l:event = deepcopy(v:event)
call timer_start(0, {-> s:show_documentation(l:event)})
endfunction

function! s:show_documentation(event) abort
call s:close_popup()

if !has_key(a:event['completed_item'], 'info') || empty(a:event['completed_item']['info'])
return
endif

let l:right = wincol() < winwidth(0) / 2

" TODO: Neovim
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)
else
let l:line = a:event['row'] + 1
let l:col = a:event['col'] - 1
endif

" TODO: Support markdown
let l:data = split(a:event['completed_item']['info'], '\n')
let l:lines = []
let l:syntax_lines = []
let l:ft = lsp#ui#vim#output#append(l:data, l:lines, l:syntax_lines)

let l:current_win_id = win_getid()

if s:use_vim_popup
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]})
elseif s:use_nvim_float
let l:height = winheight(0) - l:line + 1
let l:width = l:right ? winwidth(0) - l:col + 1 : l:col
let s:last_popup_id = lsp#ui#vim#output#floatingpreview([])
call nvim_win_set_config(s:last_popup_id, {'relative': 'win', 'anchor': l:right ? 'NW' : 'NE', 'row': l:line - 1, 'col': l:col - 1, 'height': l:height, 'width': l:width})
endif

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)
let [l:bufferlines, l:maxwidth] = lsp#ui#vim#output#get_size_info(getline(1, '$'))

call win_gotoid(l:current_win_id)

if s:use_nvim_float
call lsp#ui#vim#output#adjust_float_placement(l:bufferlines, l:maxwidth)
call nvim_win_set_config(s:last_popup_id, {'relative': 'win', 'row': l:line - 1, 'col': l:col - 1})
endif
endfunction

function! s:close_popup() abort
if s:last_popup_id >= 0
if s:use_vim_popup | call popup_close(s:last_popup_id) | endif
if s:use_nvim_float | call nvim_win_close(s:last_popup_id, 1) | endif

let s:last_popup_id = -1
endif
endfunction

function! lsp#ui#vim#documentation#setup() abort
augroup lsp_documentation_popup
autocmd!
autocmd CompleteChanged * call s:complete_done()
autocmd CompleteDone * call s:close_popup()
augroup end
endfunction
82 changes: 46 additions & 36 deletions autoload/lsp/ui/vim/output.vim
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,8 @@ function! lsp#ui#vim#output#floatingpreview(data) abort
endif

let l:opts = s:get_float_positioning(l:height, l:width)

let s:winid = nvim_open_win(buf, v:true, l:opts)
call nvim_win_set_option(s:winid, 'winhl', 'Normal:Pmenu,NormalNC:Pmenu')
call nvim_win_set_option(s:winid, 'foldenable', v:false)
call nvim_win_set_option(s:winid, 'wrap', v:true)
call nvim_win_set_option(s:winid, 'statusline', '')
call nvim_win_set_option(s:winid, 'number', v:false)
call nvim_win_set_option(s:winid, 'relativenumber', v:false)
call nvim_win_set_option(s:winid, 'cursorline', v:false)
let l:opts['style'] = 'minimal'
let s:winid = nvim_open_win(buf, v:false, l:opts)
" Enable closing the preview with esc, but map only in the scratch buffer
nmap <buffer><silent> <esc> :pclose<cr>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we're not focusing the float, this would apply to the main buffer, I believe.

Copy link
Contributor Author

@clason clason Sep 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you're right. Didn't notice that. How weird...

But even without that line, I get strange styling of the main buffer whenever a float is open (color changes in the status bar).

elseif s:use_vim_popup
Expand All @@ -143,31 +136,42 @@ function! lsp#ui#vim#output#floatingpreview(data) abort
return s:winid
endfunction

function! s:setcontent(lines, ft) abort
if s:use_vim_popup
" vim popup
call setbufline(winbufnr(s:winid), 1, a:lines)
function! lsp#ui#vim#output#setcontent(winid, lines, ft) abort
if s:use_vim_popup || s:use_nvim_float
let l:buf = winbufnr(a:winid)
call setbufline(l:buf, 1, a:lines)
let l:lightline_toggle = v:false
if exists('#lightline') && !has('nvim')
" Lightline does not work in popups but does not recognize it yet.
" It is ugly to have an check for an other plugin here, better fix lightline...
let l:lightline_toggle = v:true
call lightline#disable()
endif
call win_execute(s:winid, 'setlocal filetype=' . a:ft . '.lsp-hover')

let l:filetype = a:ft . '.lsp-hover'
if s:use_vim_popup
call win_execute(a:winid, 'setlocal filetype=' . l:filetype)
elseif s:use_nvim_float
let l:height = len(a:lines)
let l:width = max(map(a:lines, 'strdisplaywidth(v:val)'))
let l:opts = s:get_float_positioning(l:height, l:width)
call nvim_win_set_config(a:winid, l:opts)
call nvim_buf_set_option(l:buf, 'filetype', l:filetype)
endif

if l:lightline_toggle
call lightline#enable()
endif
else
" nvim floating or preview
" preview
call setline(1, a:lines)

setlocal readonly nomodifiable
silent! let &l:filetype = a:ft . '.lsp-hover'
endif
endfunction

function! s:adjust_float_placement(bufferlines, maxwidth) abort
function! lsp#ui#vim#output#adjust_float_placement(bufferlines, maxwidth) abort
if s:use_nvim_float
let l:win_config = {}
let l:height = min([winheight(s:winid), a:bufferlines])
Expand Down Expand Up @@ -284,6 +288,26 @@ function! s:align_preview(options) abort
endif
endfunction

function! lsp#ui#vim#output#get_size_info(lines) abort
" Get size information while still having the buffer active
let l:maxwidth = max(map(a:lines, 'strdisplaywidth(v:val)'))
if g:lsp_preview_max_width > 0
let l:bufferlines = 0
let l:maxwidth = min([g:lsp_preview_max_width, l:maxwidth])

" Determine, for each line, how many "virtual" lines it spans, and add
" these together for all lines in the buffer
for l:line in a:lines
let l:num_lines = str2nr(string(ceil(strdisplaywidth(l:line) * 1.0 / g:lsp_preview_max_width)))
let l:bufferlines += max([l:num_lines, 1])
endfor
else
let l:bufferlines = line('$')
endif

return [l:bufferlines, l:maxwidth]
endfunction

function! lsp#ui#vim#output#preview(server, data, options) abort
if s:winid && type(s:preview_data) == type(a:data)
\ && s:preview_data == a:data
Expand All @@ -303,7 +327,7 @@ function! lsp#ui#vim#output#preview(server, data, options) abort
let s:preview_data = a:data
let l:lines = []
let l:syntax_lines = []
let l:ft = s:append(a:data, l:lines, l:syntax_lines)
let l:ft = lsp#ui#vim#output#append(a:data, l:lines, l:syntax_lines)

if has_key(a:options, 'filetype')
let l:ft = a:options['filetype']
Expand All @@ -318,23 +342,9 @@ function! lsp#ui#vim#output#preview(server, data, options) abort

call setbufvar(winbufnr(s:winid), 'lsp_syntax_highlights', l:syntax_lines)
call setbufvar(winbufnr(s:winid), 'lsp_do_conceal', l:do_conceal)
call s:setcontent(l:lines, l:ft)

" Get size information while still having the buffer active
let l:maxwidth = max(map(getline(1, '$'), 'strdisplaywidth(v:val)'))
if g:lsp_preview_max_width > 0
let l:bufferlines = 0
let l:maxwidth = min([g:lsp_preview_max_width, l:maxwidth])
call lsp#ui#vim#output#setcontent(s:winid, l:lines, l:ft)

" Determine, for each line, how many "virtual" lines it spans, and add
" these together for all lines in the buffer
for l:line in getline(1, '$')
let l:num_lines = str2nr(string(ceil(strdisplaywidth(l:line) * 1.0 / g:lsp_preview_max_width)))
let l:bufferlines += max([l:num_lines, 1])
endfor
else
let l:bufferlines = line('$')
endif
let [l:bufferlines, l:maxwidth] = lsp#ui#vim#output#get_size_info(getline(1, '$'))

if s:use_preview
" Set statusline
Expand All @@ -353,7 +363,7 @@ function! lsp#ui#vim#output#preview(server, data, options) abort
if s:winid && (s:use_vim_popup || s:use_nvim_float)
if s:use_nvim_float
" Neovim floats
call s:adjust_float_placement(l:bufferlines, l:maxwidth)
call lsp#ui#vim#output#adjust_float_placement(l:bufferlines, l:maxwidth)
call s:set_cursor(l:current_window_id, a:options)
call s:add_float_closing_hooks()
elseif s:use_vim_popup
Expand All @@ -371,10 +381,10 @@ function! lsp#ui#vim#output#preview(server, data, options) abort
return ''
endfunction

function! s:append(data, lines, syntax_lines) abort
function! lsp#ui#vim#output#append(data, lines, syntax_lines) abort
if type(a:data) == type([])
for l:entry in a:data
call s:append(entry, a:lines, a:syntax_lines)
call lsp#ui#vim#output#append(entry, a:lines, a:syntax_lines)
endfor

return 'markdown'
Expand Down
127 changes: 127 additions & 0 deletions doc/float-api.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
==============================================================================
LSP-SPECIFIC API *lsp-specific-api*

parse_lsp_response({data})

Parses a LSP response (be it a String, MarkedString, Markdown segment,
MarkupContent, ...; from hereon just {data}) to:

- A |list| of |String|s that represent the "stripped" content. E.g.
markdown `*bold*` strings will be converted to just `bold`, code
blocks will have the markers removed, etc. It's the "plain-text, no
colour" version of the {data}.

- A |list| of |Dicts| that add colour to the plaintext. Each entry
essentially says: highlight this character range using this Vim
syntax group. Can be used for displaying stripped `*bold*` in a bold
font, or to apply syntax highlighting to a region.

Example of an entry:
{
'range': {
'start': {
'line': 5,
'character': 10
},
'end': {
'line': 5,
'character': 15
}
},
'group': 'markdownBold'
}


==============================================================================
TOP-LEVEL API *top-level-api*

All the following functions take the same initial arguments:

- {lines}: A |list| of |String|s that represents the plain-text content of
the popup.
- {syn-ranges}: A |list| of |dict|s, each representing a syntax highlight
to be applied to the popup. See parse_lsp_response for more info.
You can pass an empty |list| `[]` if you don't want any additional
syntax highlighting (of course, the syntax of {filetype} still applies).
- {filetype}: The |'filetype'| of the popup buffer. This determines the
syntax highlighting of the entire buffer, which ftplugin is used, and so
on.


show_cursor_tooltip({lines}, {syn-ranges}, {filetype}, {options})

Shows a tooltip at the current cursor position, either above or below,
depending on where there is enough space.

The preview's content is set depending on {lines}, {syn-ranges} and
{filetype}, see above.

Returns the window ID of the created popup.

{options} is a |dict| that can contain the following keys:
TODO: possibly add some options or maxwidth, close_on_cursor_move,
firstline, cursor_pos, cursor_alignment


show_pum_tooltip({lines}, {syn-ranges}, {filetype}, {options})

Shows a tooltip associated with the currently selected item in the popup
menu. It can be either to the left/right of the item, depending on where
there is enough space. Settings from |'completepopup'| is taken into
account.

The preview's content is set depending on {lines}, {syn-ranges} and
{filetype}, see above.

Returns the window ID of the created popup.

{options} is a |dict| that can contain the following keys:
TODO: might not make sense to have options here, because everything is in
'completepopup' already?


update_pum_tooltip({winid}, {lines}, {syn-ranges}, {filetype}, {options})

Changes the content of the tooltip associated with the currently selected
item in the popup menu. {winid} must be the value returned by
`show_pum_tooltip`.

For the meaning of {options}, see `show_pum_tooltip`.

==============================================================================
INTERMEDIATE-LEVEL API *intermediate-level-api*

These functions will be backend specific (Vim popup/Nvim float).

create_tooltip()

Creates a hidden floating window, with undefined position, and an empty
buffer associated with it.

Returns the created window ID.


set_tooltip_contents({winid}, {lines})

Updates the contents of a given floating window, and unhides it. Also
retriggers size calculation.


set_tooltip_position({winid}, {options})

Sets the position of the given floating window, be it cursor-relative,
pum-relative, etc. Also retriggers size calculation.

TODO: Which {options}?


close_tooltip({winid})

Closes the tooltip with the ID {winid}.

==============================================================================
LOW-LEVEL API *low-level-api*

To be determined.

vim:tw=78:ts=4:ft=help:et