Skip to content

Commit

Permalink
Revamp diagnostics highlights (prabirshrestha#995)
Browse files Browse the repository at this point in the history
  • Loading branch information
prabirshrestha authored Jan 1, 2021
1 parent b4f710f commit efd07d8
Show file tree
Hide file tree
Showing 13 changed files with 266 additions and 340 deletions.
13 changes: 0 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,19 +144,6 @@ preferred to turn them off and use other plugins instead (like
let g:lsp_diagnostics_enabled = 0 " disable diagnostics support
```

#### Highlights

Highlighting diagnostics requires either NeoVim 0.3+ or Vim with patch 8.1.0579.
They are enabled by default when supported, but can be turned off respectively by

```viml
let g:lsp_highlights_enabled = 0
let g:lsp_textprop_enabled = 0
```

Can be customized by setting or linking `LspErrorHighlight`, `LspWarningHighlight`,
`LspInformationHighlight` and `LspHintHighlight` highlight groups.

### Highlight references

Highlight references to the symbol under the cursor (enabled by default).
Expand Down
7 changes: 0 additions & 7 deletions autoload/lsp.vim
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@ function! lsp#enable() abort
let s:already_setup = 1
endif
let s:enabled = 1
if g:lsp_diagnostics_enabled
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
if g:lsp_signature_help_enabled
call lsp#ui#vim#signature_help#setup()
endif
Expand All @@ -74,8 +70,6 @@ function! lsp#disable() abort
if !s:enabled
return
endif
call lsp#ui#vim#highlights#disable()
call lsp#ui#vim#diagnostics#textprop#disable()
call lsp#ui#vim#signature_help#_disable()
call lsp#ui#vim#completion#_disable()
call lsp#internal#document_highlight#_disable()
Expand Down Expand Up @@ -237,7 +231,6 @@ function! s:on_text_document_did_open(...) abort
" Some language server notify diagnostics to the buffer that has not been loaded yet.
" 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)
Expand Down
2 changes: 2 additions & 0 deletions autoload/lsp/internal/diagnostics.vim
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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#highlights#_enable()
call lsp#internal#diagnostics#float#_enable()
call lsp#internal#diagnostics#signs#_enable()
call lsp#internal#diagnostics#virtual_text#_enable()
Expand All @@ -12,6 +13,7 @@ endfunction
function! lsp#internal#diagnostics#_disable() abort
call lsp#internal#diagnostics#echo#_disable()
call lsp#internal#diagnostics#float#_disable()
call lsp#internal#diagnostics#highlights#_disable()
call lsp#internal#diagnostics#virtual_text#_disable()
call lsp#internal#diagnostics#signs#_disable()
call lsp#internal#diagnostics#state#_disable() " Needs to be the last one to unregister
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
" }
function! lsp#internal#diagnostics#document_diagnostics_command#do(options) abort
if !g:lsp_diagnostics_enabled
call lsp#utils#error(':LspDocumentDiagnostics', 'g:lsp_diagnostics_enabled must be enabled')
call lsp#utils#error(':LspDocumentDiagnostics g:lsp_diagnostics_enabled must be enabled')
return
endif

Expand Down
180 changes: 180 additions & 0 deletions autoload/lsp/internal/diagnostics/highlights.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
" internal state for whether it is enabled or not to avoid multiple subscriptions
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('LspErrorHighlight')
highlight link LspErrorHighlight Error
endif

if !hlexists('LspWarningHighlight')
highlight link LspWarningHighlight Todo
endif

if !hlexists('LspInformationHighlight')
highlight link LspInformationHighlight Normal
endif

if !hlexists('LspHintHighlight')
highlight link LspHintHighlight Normal
endif

function! lsp#internal#diagnostics#highlights#_enable() abort
" don't even bother registering if the feature is disabled
if !lsp#utils#_has_highlights() | return | endif
if !g:lsp_diagnostics_highlights_enabled | return | endif

if s:enabled | return | endif
let s:enabled = 1

if empty(s:namespace_id)
if has('nvim')
let s:namespace_id = nvim_create_namespace('vim_lsp_diagnostics_highlights')
else
let s:namespace_id = 'vim_lsp_diagnostics_highlights'
for l:severity in keys(s:severity_sign_names_mapping)
let l:hl_group = s:severity_sign_names_mapping[l:severity] . 'Highlight'
call prop_type_add(s:get_prop_type_name(l:severity),
\ {'highlight': l:hl_group, 'combine': v:true })
endfor
endif
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_highlights_insert_mode_enabled}),
\ lsp#callbag#map({_->{ 'uri': lsp#utils#get_buffer_uri() }}),
\ ),
\ ),
\ lsp#callbag#filter({_->g:lsp_diagnostics_highlights_enabled}),
\ lsp#callbag#debounceTime(g:lsp_diagnostics_highlights_delay),
\ lsp#callbag#tap({x->s:clear_highlights(x)}),
\ lsp#callbag#tap({x->s:set_highlights(x)}),
\ lsp#callbag#subscribe(),
\ )
endfunction

function! lsp#internal#diagnostics#highlights#_disable() abort
if !s:enabled | return | endif
if exists('s:Dispose')
call s:Dispose()
unlet s:Dispose
endif
call s:clear_all_highlights()
let s:enabled = 0
endfunction

function! s:get_prop_type_name(severity) abort
return s:namespace_id . '_' . get(s:severity_sign_names_mapping, a:severity, 'LspError')
endfunction

function! s:clear_all_highlights() abort
for l:bufnr in range(1, bufnr('$'))
if bufexists(l:bufnr) && bufloaded(l:bufnr)
if has('nvim')
call nvim_buf_clear_namespace(l:bufnr, s:namespace_id, 0, -1)
else
for l:severity in keys(s:severity_sign_names_mapping)
try
" TODO: need to check for valid range before calling prop_add
" See https://github.com/prabirshrestha/vim-lsp/pull/721
silent! call prop_remove({
\ 'type': s:get_prop_type_name(l:severity),
\ 'bufnr': l:bufnr,
\ 'all': v:true })
catch
call lsp#log('diagnostics', 'clear_all_highlights', 'prop_remove', v:exception, v:throwpoint)
endtry
endfor
endif
endif
endfor
endfunction

function! s:clear_highlights(params) abort
" TODO: optimize by looking at params
call s:clear_all_highlights()
endfunction

function! s:set_highlights(params) abort
" TODO: optimize by looking at params
if !g:lsp_diagnostics_highlights_insert_mode_enabled
if mode()[0] ==# 'i' | return | endif
endif

for l:bufnr in range(1, bufnr('$'))
if lsp#internal#diagnostics#state#_is_enabled_for_buffer(l:bufnr) && bufexists(l:bufnr) && bufloaded(l:bufnr)
let l:uri = lsp#utils#get_buffer_uri(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_highlights(l:server, l:diagnostics_response, l:bufnr)
endfor
endif
endfor
endfunction

function! s:place_highlights(server, diagnostics_response, bufnr) abort
" TODO: make diagnostics highlights same across vim and neovim
for l:item in a:diagnostics_response['params']['diagnostics']
let [l:start_line, l:start_col] = lsp#utils#position#lsp_to_vim(a:bufnr, l:item['range']['start'])
let [l:end_line, l:end_col] = lsp#utils#position#lsp_to_vim(a:bufnr, l:item['range']['end'])
let l:severity = get(l:item, 'severity', 3)
let l:hl_group = get(s:severity_sign_names_mapping, l:severity, 'LspError') . 'Highlight'
if has('nvim')
for l:line in range(l:start_line, l:end_line)
if l:line == l:start_line
let l:highlight_start_col = l:start_col
else
let l:highlight_start_col = 1
endif

if l:line == l:end_line
let l:highlight_end_col = l:end_col
else
" neovim treats -1 as end of line, special handle it later
" when calling nvim_buf_add_higlight
let l:highlight_end_col = -1
endif

if l:start_line == l:end_line && l:highlight_start_col == l:highlight_end_col
" higlighting same start col and end col on same line
" doesn't work so use -1 for start col
let l:highlight_start_col -= 1
if l:highlight_start_col <= 0
let l:highlight_start_col = 1
endif
endif

call nvim_buf_add_highlight(a:bufnr, s:namespace_id, l:hl_group,
\ l:line - 1, l:highlight_start_col - 1, l:highlight_end_col == -1 ? -1 : l:highlight_end_col)
endfor
else
try
" TODO: need to check for valid range before calling prop_add
" See https://github.com/prabirshrestha/vim-lsp/pull/721
silent! call prop_add(l:start_line, l:start_col, {
\ 'end_lnum': l:end_line,
\ 'end_col': l:end_col,
\ 'bufnr': a:bufnr,
\ 'type': s:get_prop_type_name(l:severity),
\ })
catch
call lsp#log('diagnostics', 'place_highlights', 'prop_add', v:exception, v:throwpoint)
endtry
endif
endfor
endfunction
20 changes: 13 additions & 7 deletions autoload/lsp/internal/document_highlight.vim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
let s:use_vim_textprops = has('textprop') && !has('nvim')
let s:use_vim_textprops = lsp#utils#_has_textprops() && !has('nvim')
let s:prop_id = 11

function! lsp#internal#document_highlight#_enable() abort
Expand Down Expand Up @@ -108,12 +108,18 @@ function! s:set_highlights(data) abort
call s:init_reference_highlight(l:bufnr)
if s:use_vim_textprops
for l:position in l:position_list
call prop_add(l:position[0], l:position[1],
\ {'id': s:prop_id,
\ 'bufnr': l:bufnr,
\ 'length': l:position[2],
\ 'type': 'vim-lsp-reference-highlight'})
call add(b:lsp_reference_matches, l:position[0])
try
" TODO: need to check for valid range before calling prop_add
" See https://github.com/prabirshrestha/vim-lsp/pull/721
silent! call prop_add(l:position[0], l:position[1], {
\ 'id': s:prop_id,
\ 'bufnr': l:bufnr,
\ 'length': l:position[2],
\ 'type': 'vim-lsp-reference-highlight'})
call add(b:lsp_reference_matches, l:position[0])
catch
call lsp#log('document_highlight', 'set_highlights', v:exception, v:throwpoint)
endtry
endfor
else
for l:position in l:position_list
Expand Down
13 changes: 0 additions & 13 deletions autoload/lsp/ui/vim/diagnostics.vim
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,9 @@ 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#highlights#set(a:server_name, a:data)
call lsp#ui#vim#diagnostics#textprop#set(a:server_name, a:data)

doautocmd <nomodeline> User lsp_diagnostics_updated
endfunction

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#highlights#set(l:server_name, l:response)
call lsp#ui#vim#diagnostics#textprop#set(l:server_name, l:response)
endfor
endif
endfunction

function! lsp#ui#vim#diagnostics#get_document_diagnostics(bufnr) abort
return get(s:diagnostics, lsp#utils#get_buffer_uri(a:bufnr), {})
endfunction
Expand Down
Loading

0 comments on commit efd07d8

Please sign in to comment.