Implement buffer-wide virtual text support (#4289)

* Remove virtual text via types-filter

This is more robust and has the additional sideeffect that it will make
it easier to implement showing virtual text for all warnings
simultaneously.

We definitely do not want to do a call to prop_remove() for every
virtual text as that will cause noticeable lag when many warnings are
present, thus we can use this to remove all virtual text lines with one
call in the future.

Fixes #4294

refs: https://github.com/vim/vim/pull/10945

* Allow virtual text to appear for all warnings of the buffer

This can be enabled with:

let g:ale_virtualtext_cursor = 2

It is implemented both for neovim and vim 9.0.0297.

Note that sometimes it may appear like some warnings are displayed
multiple times. This is not a bug in the virtual text implementation,
but a sideeffect of multiple linters returning similar results.

For example for Rust, the 'cargo' and 'rls' linters appear to be
activated at the same time, but they sometimes return identical errors.
This causes the virtual text to show the same warning twice.

In the future we can mitigate this problem by removing duplicate errors
from our internal location list.

However users can also achieve cleaner warnings simply by activating
only one linter for each language (or multiple unambiguous linters).

For example for Rust, the problem could be solved with:

let g:ale_linters = {'rust': ['analyzer']}

Fixes #2962
Fixes #3666
This commit is contained in:
Magnus Groß
2022-09-07 12:38:01 +02:00
committed by GitHub
parent 4943b7d39f
commit 9feba1148c
5 changed files with 68 additions and 42 deletions

View File

@@ -203,6 +203,10 @@ function! ale#engine#SetResults(buffer, loclist) abort
call ale#highlight#SetHighlights(a:buffer, a:loclist)
endif
if g:ale_virtualtext_cursor == 2
call ale#virtualtext#SetTexts(a:buffer, a:loclist)
endif
if l:linting_is_done
if g:ale_echo_cursor
" Try and echo the warning now.
@@ -210,7 +214,7 @@ function! ale#engine#SetResults(buffer, loclist) abort
call ale#cursor#EchoCursorWarning()
endif
if g:ale_virtualtext_cursor
if g:ale_virtualtext_cursor == 1
" Try and show the warning now.
" This will only do something meaningful if we're in normal mode.
call ale#virtualtext#ShowCursorWarning()

View File

@@ -139,7 +139,7 @@ function! ale#events#Init() abort
autocmd InsertLeave * if exists('*ale#engine#Cleanup') | call ale#cursor#EchoCursorWarning() | endif
endif
if g:ale_virtualtext_cursor
if g:ale_virtualtext_cursor == 1
autocmd CursorMoved,CursorHold * if exists('*ale#engine#Cleanup') | call ale#virtualtext#ShowCursorWarningWithDelay() | endif
" Look for a warning to echo as soon as we leave Insert mode.
" The script's position variable used when moving the cursor will

View File

@@ -14,8 +14,8 @@ function! s:DisablePostamble() abort
call ale#highlight#UpdateHighlights()
endif
if g:ale_virtualtext_cursor
call ale#virtualtext#Clear()
if g:ale_virtualtext_cursor == 1
call ale#virtualtext#Clear(bufnr(''))
endif
endfunction

View File

@@ -15,52 +15,49 @@ if has('nvim-0.3.2')
let s:has_virt_text = 1
elseif has('textprop') && has('popupwin')
let s:has_virt_text = 1
let s:emulate_virt = !has('patch-9.0.0214')
let s:emulate_virt = !has('patch-9.0.0297')
let s:hl_list = []
if s:emulate_virt
call prop_type_add('ale', {})
let s:last_virt = -1
else
let s:last_virt = 1
endif
endif
function! ale#virtualtext#Clear() abort
function! ale#virtualtext#Clear(buf) abort
if !s:has_virt_text
return
endif
let l:buffer = bufnr('')
if has('nvim')
call nvim_buf_clear_highlight(l:buffer, s:ns_id, 0, -1)
call nvim_buf_clear_namespace(a:buf, s:ns_id, 0, -1)
else
if s:emulate_virt && s:last_virt != -1
call prop_remove({'type': 'ale'})
call popup_close(s:last_virt)
let s:last_virt = -1
elseif !s:emulate_virt && s:last_virt != 1
call prop_remove({'id': s:last_virt})
let s:last_virt = 1
elseif !empty(s:hl_list)
call prop_remove({
\ 'types': s:hl_list,
\ 'all': 1,
\ 'bufnr': a:buf})
endif
endif
endfunction
function! ale#virtualtext#ShowMessage(message, hl_group) abort
if !s:has_virt_text
function! ale#virtualtext#ShowMessage(message, hl_group, buf, line) abort
if !s:has_virt_text || !bufexists(str2nr(a:buf))
return
endif
let l:line = line('.')
let l:buffer = bufnr('')
let l:prefix = get(g:, 'ale_virtualtext_prefix', '> ')
let l:msg = l:prefix.trim(substitute(a:message, '\n', ' ', 'g'))
if has('nvim')
call nvim_buf_set_virtual_text(l:buffer, s:ns_id, l:line-1, [[l:msg, a:hl_group]], {})
call nvim_buf_set_virtual_text(a:buf, s:ns_id, a:line-1, [[l:msg, a:hl_group]], {})
elseif s:emulate_virt
let l:left_pad = col('$')
call prop_add(l:line, l:left_pad, {
call prop_add(a:line, l:left_pad, {
\ 'type': 'ale',
\})
let s:last_virt = popup_create(l:msg, {
@@ -77,12 +74,14 @@ function! ale#virtualtext#ShowMessage(message, hl_group) abort
let type = prop_type_get(a:hl_group)
if type == {}
call add(s:hl_list, a:hl_group)
call prop_type_add(a:hl_group, {'highlight': a:hl_group})
endif
let s:last_virt = prop_add(l:line, 0, {
call prop_add(a:line, 0, {
\ 'type': a:hl_group,
\ 'text': ' ' . l:msg
\ 'text': ' ' . l:msg,
\ 'bufnr': a:buf
\})
endif
endfunction
@@ -94,8 +93,26 @@ function! s:StopCursorTimer() abort
endif
endfunction
function! ale#virtualtext#GetHlGroup(type, style) abort
if a:type is# 'E'
if a:style is# 'style'
return 'ALEVirtualTextStyleError'
else
return 'ALEVirtualTextError'
endif
elseif a:type is# 'W'
if a:style is# 'style'
return 'ALEVirtualTextStyleWarning'
else
return 'ALEVirtualTextWarning'
endif
else
return 'ALEVirtualTextInfo'
endif
endfunction
function! ale#virtualtext#ShowCursorWarning(...) abort
if !g:ale_virtualtext_cursor
if g:ale_virtualtext_cursor != 1
return
endif
@@ -111,35 +128,21 @@ function! ale#virtualtext#ShowCursorWarning(...) abort
let [l:info, l:loc] = ale#util#FindItemAtCursor(l:buffer)
call ale#virtualtext#Clear()
call ale#virtualtext#Clear(l:buffer)
if !empty(l:loc)
let l:msg = l:loc.text
let l:hl_group = 'ALEVirtualTextInfo'
let l:type = get(l:loc, 'type', 'E')
if l:type is# 'E'
if get(l:loc, 'sub_type', '') is# 'style'
let l:hl_group = 'ALEVirtualTextStyleError'
else
let l:hl_group = 'ALEVirtualTextError'
endif
elseif l:type is# 'W'
if get(l:loc, 'sub_type', '') is# 'style'
let l:hl_group = 'ALEVirtualTextStyleWarning'
else
let l:hl_group = 'ALEVirtualTextWarning'
endif
endif
call ale#virtualtext#ShowMessage(l:msg, l:hl_group)
let l:style = get(l:loc, 'sub_type', '')
let l:hl_group = ale#virtualtext#GetHlGroup(l:type, l:style)
call ale#virtualtext#ShowMessage(l:msg, l:hl_group, l:buffer, line('.'))
endif
endfunction
function! ale#virtualtext#ShowCursorWarningWithDelay() abort
let l:buffer = bufnr('')
if !g:ale_virtualtext_cursor
if g:ale_virtualtext_cursor != 1
return
endif
@@ -166,3 +169,19 @@ function! ale#virtualtext#ShowCursorWarningWithDelay() abort
endif
endfunction
function! ale#virtualtext#SetTexts(buf, loclist) abort
if !has('nvim') && s:emulate_virt
return
endif
call ale#virtualtext#Clear(a:buf)
for l in a:loclist
if l['bufnr'] != a:buf
continue
endif
let hl = ale#virtualtext#GetHlGroup(l['type'], get(l, 'sub_type', ''))
call ale#virtualtext#ShowMessage(l['text'], hl, a:buf, l['lnum'])
endfor
endfunction

View File

@@ -2297,6 +2297,9 @@ g:ale_virtualtext_cursor *g:ale_virtualtext_cursor*
column nearest to the cursor when the cursor is resting on a line which
contains a warning or error. This option can be set to `0` to disable this
behavior.
When this option is set to `2`, then all warnings will be shown for the
whole buffer, regardless of if the cursor is currently positioned in that
line.
Messages are only displayed after a short delay. See |g:ale_virtualtext_delay|.