Revamp diagnostics virtual text (#990)

This commit is contained in:
Prabir Shrestha
2020-12-30 14:49:44 -08:00
committed by GitHub
parent 58e81e6fca
commit 1489cf8295
11 changed files with 235 additions and 173 deletions

View File

@@ -159,13 +159,6 @@ let g:lsp_signs_warning = {'text': '‼', 'icon': '/path/to/some/icon'} " icons
let g:lsp_signs_hint = {'icon': '/path/to/some/other/icon'} " icons require GUI
```
Also three highlight groups for every sign group are defined (for example for LspError these are LspErrorText, LspErrorVirtual, and LspErrorHighlight). By default, LspError text is highlighted using Error group, LspWarning is highlighted as Todo, others use Normal group. Virtual text will by default use the Text highlight group, for example for LspErrorVirtual will default to LspErrorText. Line highlighting is not set by default. If your colorscheme of choice does not provide any of these, it is possible to clear them or link to some other group, like so:
```viml
highlight link LspErrorText GruvboxRedSign " requires gruvbox
highlight clear LspWarningLine
```
#### Highlights
Highlighting diagnostics requires either NeoVim 0.3+ or Vim with patch 8.1.0579.
@@ -179,19 +172,6 @@ let g:lsp_textprop_enabled = 0
Can be customized by setting or linking `LspErrorHighlight`, `LspWarningHighlight`,
`LspInformationHighlight` and `LspHintHighlight` highlight groups.
#### Virtual text
In NeoVim 0.3 or newer you can use virtual text feature (enabled by default).
You can disable it by adding
```viml
let g:lsp_virtual_text_enabled = 0
```
to your configuration.
Virtual text will use the same highlight groups as signs feature.
### Highlight references
Highlight references to the symbol under the cursor (enabled by default).

View File

@@ -56,7 +56,6 @@ function! lsp#enable() abort
let s:enabled = 1
if g:lsp_diagnostics_enabled
if g:lsp_signs_enabled | call lsp#ui#vim#signs#enable() | endif
if g:lsp_virtual_text_enabled | call lsp#ui#vim#virtual#enable() | endif
if g:lsp_highlights_enabled | call lsp#ui#vim#highlights#enable() | endif
if g:lsp_textprop_enabled | call lsp#ui#vim#diagnostics#textprop#enable() | endif
endif
@@ -75,7 +74,6 @@ function! lsp#disable() abort
return
endif
call lsp#ui#vim#signs#disable()
call lsp#ui#vim#virtual#disable()
call lsp#ui#vim#highlights#disable()
call lsp#ui#vim#diagnostics#textprop#disable()
call lsp#ui#vim#signature_help#_disable()
@@ -239,6 +237,7 @@ function! s:on_text_document_did_open(...) abort
" This diagnostics was stored `autoload/lsp/ui/vim/diagnostics.vim` but not highlighted.
" So we should refresh highlights when buffer opened.
call lsp#ui#vim#diagnostics#force_refresh(l:buf)
call lsp#internal#diagnostics#state#_force_notify_buffer(l:buf)
for l:server_name in lsp#get_allowed_servers(l:buf)
call s:ensure_flush(l:buf, l:server_name, function('s:fire_lsp_buffer_enabled', [l:server_name, l:buf]))

View File

@@ -5,10 +5,12 @@ function! lsp#internal#diagnostics#_enable() abort
call lsp#internal#diagnostics#state#_enable() " Needs to be the first one to register
call lsp#internal#diagnostics#echo#_enable()
call lsp#internal#diagnostics#float#_enable()
call lsp#internal#diagnostics#virtual_text#_enable()
endfunction
function! lsp#internal#diagnostics#_disable() abort
call lsp#internal#diagnostics#echo#_disable()
call lsp#internal#diagnostics#float#_disable()
call lsp#internal#diagnostics#virtual_text#_disable()
call lsp#internal#diagnostics#state#_disable() " Needs to be the last one to unregister
endfunction

View File

@@ -77,7 +77,8 @@ function! lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_server_
return get(s:diagnostics_state, lsp#utils#normalize_uri(a:uri), {})
endfunction
" callers should always treat the return value as immutable
" callers should always treat the return value as immutable.
" callers should treat uri as normalized via lsp#utils#normalize_uri
" @return {
" 'normalized_uri': {
" 'servername': response
@@ -94,6 +95,7 @@ function! s:on_text_documentation_publish_diagnostics(server, response) abort
let s:diagnostics_state[l:normalized_uri] = {}
endif
let s:diagnostics_state[l:normalized_uri][a:server] = a:response
call s:notify_diagnostics_update(a:server, l:normalized_uri)
endfunction
function! s:on_exit(response) abort
@@ -105,11 +107,24 @@ function! s:on_exit(response) abort
call remove(l:value, l:server)
endif
endfor
if l:notify | call s:notify_diagnostics_update() | endif
if l:notify | call s:notify_diagnostics_update(l:server) | endif
endfunction
function! s:notify_diagnostics_update() abort
" TODO: Notify diagnostics update when all diagnostics move to relying on state
function! lsp#internal#diagnostics#state#_force_notify_buffer(buffer) abort
" TODO: optimize buffer only
call s:notify_diagnostics_update()
endfunction
" call s:notify_diagnostics_update()
" call s:notify_diagnostics_update('server')
" call s:notify_diagnostics_update('server', 'uri')
function! s:notify_diagnostics_update(...) abort
let l:data = { 'server': '$vimlsp', 'response': { 'method': '$/vimlsp/lsp_diagnostics_updated', 'params': {} } }
" if a:0 > 0 | let l:data['response']['params']['server'] = a:1 | endif
" if a:0 > 1 | let l:data['response']['params']['uri'] = a:2 | endif
call lsp#stream(1, l:data)
" TODO: uncomment doautocmd when all diagnostics moves to using callbag
" doautocmd <nomodeline> User lsp_diagnostics_updated
endfunction
function! lsp#internal#diagnostics#state#_enable_for_buffer(bufnr) abort

View File

@@ -0,0 +1,131 @@
let s:enabled = 0
let s:namespace_id = '' " will be set when enabled
let s:severity_sign_names_mapping = {
\ 1: 'LspError',
\ 2: 'LspWarning',
\ 3: 'LspInformation',
\ 4: 'LspHint',
\ }
if !hlexists('LspErrorVirtualText')
if !hlexists('LspErrorText')
highlight link LspErrorVirtualText Error
else
highlight link LspErrorVirtualText LspErrorText
endif
endif
if !hlexists('LspWarningVirtualText')
if !hlexists('LspWarningText')
highlight link LspWarningVirtualText Todo
else
highlight link LspWarningVirtualText LspWarningText
endif
endif
if !hlexists('LspInformationVirtualText')
if !hlexists('LspInformationText')
highlight link LspInformationVirtualText Normal
else
highlight link LspInformationVirtualText LspInformationText
endif
endif
if !hlexists('LspHintVirtualText')
if !hlexists('LspHintText')
highlight link LspHintVirtualText Normal
else
highlight link LspHintVirtualText LspHintText
endif
endif
function! lsp#internal#diagnostics#virtual_text#_enable() abort
" don't even bother registering if the feature is disabled
if !lsp#utils#_has_virtual_text() | return | endif
if !g:lsp_diagnostics_virtual_text_enabled | return | endif
if s:enabled | return | endif
let s:enabled = 1
if empty(s:namespace_id)
let s:namespace_id = nvim_create_namespace('vim_lsp_diagnotics_virtual_text')
endif
let s:Dispose = lsp#callbag#pipe(
\ lsp#callbag#merge(
\ lsp#callbag#pipe(
\ lsp#stream(),
\ lsp#callbag#filter({x->has_key(x, 'server') && has_key(x, 'response')
\ && has_key(x['response'], 'method') && x['response']['method'] ==# '$/vimlsp/lsp_diagnostics_updated'
\ && !lsp#client#is_error(x['response'])}),
\ lsp#callbag#map({x->x['response']['params']}),
\ ),
\ lsp#callbag#pipe(
\ lsp#callbag#fromEvent(['InsertEnter', 'InsertLeave']),
\ lsp#callbag#filter({_->!g:lsp_diagnostics_virtual_text_insert_mode_enabled}),
\ lsp#callbag#map({_->{ 'uri': lsp#utils#get_buffer_uri() }}),
\ ),
\ ),
\ lsp#callbag#filter({_->g:lsp_diagnostics_virtual_text_enabled}),
\ lsp#callbag#debounceTime(g:lsp_diagnostics_virtual_text_delay),
\ lsp#callbag#tap({x->s:clear_virtual_text(x)}),
\ lsp#callbag#tap({x->s:set_virtual_text(x)}),
\ lsp#callbag#subscribe(),
\ )
endfunction
function! lsp#internal#diagnostics#virtual_text#_disable() abort
if exists('s:Dispose')
call s:Dispose()
unlet s:Dispose
endif
call s:clear_all_virtual_text()
let s:enabled = 0
endfunction
function! s:clear_all_virtual_text() abort
for l:bufnr in nvim_list_bufs()
if bufexists(l:bufnr) && bufloaded(l:bufnr)
call nvim_buf_clear_namespace(l:bufnr, s:namespace_id, 0, -1)
endif
endfor
endfunction
" params => {
" server: '' " optional
" uri: '' " optional
" }
function! s:clear_virtual_text(params) abort
" TODO: optimize by looking at params
call s:clear_all_virtual_text()
endfunction
" params => {
" server: '' " optional
" uri: '' " optional
" }
function! s:set_virtual_text(params) abort
" TODO: optimize by looking at params
if !g:lsp_diagnostics_virtual_text_insert_mode_enabled
if mode()[0] ==# 'i' | return | endif
endif
for l:bufnr in nvim_list_bufs()
let l:uri = lsp#utils#get_buffer_uri(l:bufnr)
if lsp#internal#diagnostics#state#_is_enabled_for_buffer(l:uri) && bufexists(l:bufnr) && bufloaded(l:bufnr)
for [l:server, l:diagnostics_response] in items(lsp#internal#diagnostics#state#_get_all_diagnostics_grouped_by_server_for_uri(l:uri))
call s:place_virtual_text(l:server, l:diagnostics_response, l:bufnr)
endfor
endif
endfor
endfunction
function! s:place_virtual_text(server, diagnostics_response, bufnr) abort
for l:item in a:diagnostics_response['params']['diagnostics']
let l:line = lsp#utils#position#lsp_line_to_vim(a:bufnr, l:item['range']['start']) - 1
let l:name = get(s:severity_sign_names_mapping, get(l:item, 'severity', 3), 'LspError')
let l:hl_name = l:name . 'VirtualText'
call nvim_buf_set_virtual_text(a:bufnr, s:namespace_id, l:line,
\ [[g:lsp_diagnostics_virtual_text_prefix . l:item['message'], l:hl_name]], {})
endfor
endfunction

View File

@@ -12,7 +12,6 @@ function! lsp#ui#vim#diagnostics#handle_text_document_publish_diagnostics(server
endif
let s:diagnostics[l:uri][a:server_name] = a:data
call lsp#ui#vim#virtual#set(a:server_name, a:data)
call lsp#ui#vim#highlights#set(a:server_name, a:data)
call lsp#ui#vim#diagnostics#textprop#set(a:server_name, a:data)
call lsp#ui#vim#signs#set(a:server_name, a:data)
@@ -24,7 +23,6 @@ function! lsp#ui#vim#diagnostics#force_refresh(bufnr) abort
let l:data = lsp#ui#vim#diagnostics#get_document_diagnostics(a:bufnr)
if !empty(l:data)
for [l:server_name, l:response] in items(l:data)
call lsp#ui#vim#virtual#set(l:server_name, l:response)
call lsp#ui#vim#highlights#set(l:server_name, l:response)
call lsp#ui#vim#diagnostics#textprop#set(l:server_name, l:response)
call lsp#ui#vim#signs#set(l:server_name, l:response)

View File

@@ -1,124 +0,0 @@
let s:supports_vt = exists('*nvim_buf_set_virtual_text')
let s:enabled = 0
let s:severity_sign_names_mapping = {
\ 1: 'LspError',
\ 2: 'LspWarning',
\ 3: 'LspInformation',
\ 4: 'LspHint',
\ }
if !hlexists('LspErrorVirtual')
if !hlexists('LspErrorText')
highlight link LspErrorVirtual Error
else
highlight link LspErrorVirtual LspErrorText
endif
endif
if !hlexists('LspWarningVirtual')
if !hlexists('LspWarningText')
highlight link LspWarningVirtual Todo
else
highlight link LspWarningVirtual LspWarningText
endif
endif
if !hlexists('LspInformationVirtual')
if !hlexists('LspInformationText')
highlight link LspInformationVirtual Normal
else
highlight link LspInformationVirtual LspInformationText
endif
endif
if !hlexists('LspHintVirtual')
if !hlexists('LspHintText')
highlight link LspHintVirtual Normal
else
highlight link LspHintVirtual LspHintText
endif
endif
function! lsp#ui#vim#virtual#enable() abort
if !s:supports_vt
call lsp#log('vim-lsp virtual text requires neovim')
return
endif
if !s:enabled
let s:enabled = 1
call lsp#log('vim-lsp virtual text enabled')
endif
endfunction
function! lsp#ui#vim#virtual#disable() abort
if s:enabled
for l:ns in keys(nvim_get_namespaces())
call s:clear_all_virtual(l:ns)
endfor
let s:enabled = 0
call lsp#log('vim-lsp virtual text disabled')
endif
endfunction
function! s:get_virtual_group(name) abort
return nvim_create_namespace('vim_lsp_'.a:name)
endfunction
function! s:clear_all_virtual(ns) abort
if a:ns =~# '^vim_lsp_'
let l:ns = s:get_virtual_group(a:ns)
for l:bufnr in nvim_list_bufs()
call nvim_buf_clear_namespace(l:bufnr, l:ns, 0, -1)
endfor
endif
endfunction
function! s:clear_virtual(server_name, path) abort
if !s:supports_vt | return | endif
if !s:enabled | return | endif
let l:ns = s:get_virtual_group(a:server_name)
let l:bufnr = bufnr(a:path)
if bufnr(a:path) >= 0
call nvim_buf_clear_namespace(l:bufnr, l:ns, 0, -1)
endif
endfunction
function! s:place_virtual(server_name, path, diagnostics) abort
if !s:supports_vt | return | endif
if !s:enabled | return | endif
let l:ns = s:get_virtual_group(a:server_name)
let l:bufnr = bufnr(a:path)
if !empty(a:diagnostics) && bufnr(a:path) >= 0
for l:item in a:diagnostics
let l:line = l:item['range']['start']['line']
let l:name = get(s:severity_sign_names_mapping, get(l:item, 'severity', 3), 'LspError')
let l:hl_name = l:name . 'Virtual'
call nvim_buf_set_virtual_text(l:bufnr, l:ns, l:line,
\ [[g:lsp_virtual_text_prefix . l:item['message'], l:hl_name]], {})
endfor
endif
endfunction
function! lsp#ui#vim#virtual#set(server_name, data) abort
if !s:supports_vt | return | endif
if !s:enabled | return | endif
if lsp#client#is_error(a:data['response'])
return
endif
let l:uri = a:data['response']['params']['uri']
let l:diagnostics = a:data['response']['params']['diagnostics']
let l:path = lsp#utils#uri_to_path(l:uri)
" will always replace existing set
call s:clear_virtual(a:server_name, l:path)
call s:place_virtual(a:server_name, l:path, l:diagnostics)
endfunction

View File

@@ -1,3 +1,8 @@
let s:has_virtual_text = exists('*nvim_buf_set_virtual_text') && exists('*nvim_create_namespace')
function! lsp#utils#_has_virtual_text() abort
return s:has_virtual_text
endfunction
function! lsp#utils#is_file_uri(uri) abort
return stridx(a:uri, 'file:///') == 0
endfunction

View File

@@ -43,12 +43,35 @@ endfunction
" col
" ]
function! lsp#utils#position#lsp_to_vim(expr, position) abort
let l:line = a:position['line'] + 1
let l:char = a:position['character']
let l:col = s:to_col(a:expr, l:line, l:char)
let l:line = lsp#utils#position#lsp_line_to_vim(a:expr, a:position)
let l:col = lsp#utils#position#lsp_character_to_vim(a:expr, a:position)
return [l:line, l:col]
endfunction
" @param expr = see :help bufname()
" @param position = {
" 'line': 1,
" 'character': 1
" }
" @returns
" line
function! lsp#utils#position#lsp_line_to_vim(expr, position) abort
return a:position['line'] + 1
endfunction
" @param expr = see :help bufname()
" @param position = {
" 'line': 1,
" 'character': 1
" }
" @returns
" line
function! lsp#utils#position#lsp_character_to_vim(expr, position) abort
let l:line = a:position['line'] + 1 " optimize function overhead by not calling lsp_line_to_vim
let l:char = a:position['character']
return s:to_col(a:expr, l:line, l:char)
endfunction
" @param expr = :help bufname()
" @param pos = [lnum, col]
" @returns {

View File

@@ -29,11 +29,18 @@ CONTENTS *vim-lsp-contents*
g:lsp_diagnostics_echo_delay |g:lsp_diagnostics_echo_delay|
g:lsp_diagnostics_float_cursor |g:lsp_diagnostics_float_cursor|
g:lsp_diagnostics_float_delay |g:lsp_diagnostics_float_delay|
g:lsp_diagnostics_virtual_text_enabled
|g:lsp_diagnostics_virtual_text_enabled|
g:lsp_diagnostics_virtual_text_insert_mode_enabled
|g:lsp_diagnostics_virtual_text_insert_mode_enabled|
g:lsp_diagnostics_virtual_text_delay
|g:lsp_diagnostics_virtual_text_delay|
g:lsp_diagnostics_virtual_text_prefix
|g:lsp_diagnostics_virtual_text_prefix|
g:lsp_format_sync_timeout |g:lsp_format_sync_timeout|
g:lsp_signs_enabled |g:lsp_signs_enabled|
g:lsp_signs_priority |g:lsp_signs_priority|
g:lsp_signs_priority_map |g:lsp_signs_priority_map|
g:lsp_virtual_text_enabled |g:lsp_virtual_text_enabled|
g:lsp_highlights_enabled |g:lsp_highlights_enabled|
g:lsp_textprop_enabled |g:lsp_textprop_enabled|
g:lsp_use_event_queue |g:lsp_use_event_queue|
@@ -528,34 +535,56 @@ g:lsp_signs_priority_map *g:lsp_signs_priority_map*
\'clangd_LspInformation': 11
\}
g:lsp_virtual_text_enabled *g:lsp_virtual_text_enabled*
g:lsp_diagnostics_virtual_text_enabled
*g:lsp_diagnostics_virtual_text_enabled*
Type: |Number|
Default: `1` for neovim 0.3+
Enables virtual text to be shown next to diagnostic errors. Requires
NeoVim with version 0.3 or newer. Virtual text uses the same highlight
groups used for signs (eg LspErrorText), but can be uniquely defined if
you want to have different highlight groups for signs and virtual text.
To set unique virtual text highlighting, you can set or link
`LspErrorVirtual`, `LspWarningVirtual`, `LspInformationVirtual` and
`LspHintVirtual` highlight groups.
NeoVim with version 0.3 or newer and |g:lsp_diagnostics_enabled| set to `1`.
Virtual text uses the same highlight groups used for signs (eg LspErrorText),
but can be uniquely defined if you want to have different highlight groups
for signs and virtual text. To set unique virtual text highlighting, you
can set or link `LspErrorVirtualText`, `LspWarningVirtualText`,
`LspInformationVirtualText` and `LspHintVirtualText` highlight groups.
Example: >
let g:lsp_virtual_text_enabled = 1
let g:lsp_virtual_text_enabled = 0
let g:lsp_diagnostics_virtual_text_enabled = 1
let g:lsp_diagnostics_virtual_text_enabled = 0
g:lsp_diagnostics_virtual_text_insert_mode_enabled
*g:lsp_diagnostics_virtual_text_insert_mode_enabled*
Type: |Number|
Default: `0`
g:lsp_virtual_text_prefix *g:lsp_virtual_text_prefix*
Indicates whether to enable diagnostics virtual text when in |insertmode|.
Requires |g:lsp_diagnostics_virtual_text_enabled|.
Example: >
let g:lsp_diagnostics_virtual_text_insert_mode_enabled = 1
let g:lsp_diagnostics_virtual_text_insert_mode_enabled = 0
g:lsp_diagnostics_virtual_text_delay *g:lsp_diagnostics_virtual_text_delay*
Type: |Number|
Default: `500`
Delay milliseconds to update diagnostics virtual text. Requires
|g:lsp_diagnostics_virtual_text_enabled|.
Example: >
let g:lsp_diagnostics_virtual_text_delay = 200
let g:lsp_diagnostics_virtual_text_delay = 1000
g:lsp_diagnostics_virtual_text_prefix *g:lsp_diagnostics_virtual_text_prefix*
Type: |String|
Default: `""` for neovim 0.3+
Default: `""`
Adds the prefix to the diagnostics to be shown as virtual text. Requires
NeoVim with version 0.3 or newer.
|g:lsp_diagnostics_virtual_text_enabled|.
Example: >
let g:lsp_virtual_text_prefix = "> "
let g:lsp_virtual_text_prefix = " ‣ "
let g:lsp_diagnostics_virtual_text_prefix = "> "
let g:lsp_diagnostics_virtual_text_prefix = " ‣ "
g:lsp_highlights_enabled *g:lsp_highlights_enabled*
Type: |Number|

View File

@@ -11,8 +11,6 @@ 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_signs_enabled = get(g:, 'lsp_signs_enabled', exists('*sign_define') && (has('nvim') || has('patch-8.1.0772')))
let g:lsp_virtual_text_enabled = get(g:, 'lsp_virtual_text_enabled', exists('*nvim_buf_set_virtual_text'))
let g:lsp_virtual_text_prefix = get(g:, 'lsp_virtual_text_prefix', '')
let g:lsp_highlights_enabled = get(g:, 'lsp_highlights_enabled', exists('*nvim_buf_add_highlight'))
let g:lsp_textprop_enabled = get(g:, 'lsp_textprop_enabled', exists('*prop_add') && !g:lsp_highlights_enabled)
let g:lsp_signs_error = get(g:, 'lsp_signs_error', {})
@@ -25,11 +23,17 @@ 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_diagnostics_enabled = get(g:, 'lsp_diagnostics_enabled', 1)
let g:lsp_diagnostics_echo_cursor = get(g:, 'lsp_diagnostics_echo_cursor', 0)
let g:lsp_diagnostics_echo_delay = get(g:, 'lsp_diagnostics_echo_delay', 500)
let g:lsp_diagnostics_float_cursor = get(g:, 'lsp_diagnostics_float_cursor', 0)
let g:lsp_diagnostics_float_delay = get(g:, 'lsp_diagnostics_float_delay', 500)
let g:lsp_diagnostics_virtual_text_enabled = get(g:, 'lsp_diagnostics_virtual_text_enabled', lsp#utils#_has_virtual_text())
let g:lsp_diagnostics_virtual_text_insert_mode_enabled = get(g:, 'lsp_diagnostics_virtual_text_insert_mode_enabled', 0)
let g:lsp_diagnostics_virtual_text_delay = get(g:, 'lsp_diagnostics_virtual_text_delay', 500)
let g:lsp_diagnostics_virtual_text_prefix = get(g:, 'lsp_diagnostics_virtual_text_prefix', '')
let g:lsp_next_sign_id = get(g:, 'lsp_next_sign_id', 6999)
let g:lsp_preview_keep_focus = get(g:, 'lsp_preview_keep_focus', 1)
let g:lsp_use_event_queue = get(g:, 'lsp_use_event_queue', has('nvim') || has('patch-8.1.0889'))