diff options
-rw-r--r-- | autoload/ale/highlight.vim | 140 | ||||
-rw-r--r-- | test/test_nvim_api_highlight.vader | 160 |
2 files changed, 285 insertions, 15 deletions
diff --git a/autoload/ale/highlight.vim b/autoload/ale/highlight.vim index ae1f3e7d..3e09f26d 100644 --- a/autoload/ale/highlight.vim +++ b/autoload/ale/highlight.vim @@ -26,6 +26,41 @@ endif let s:MAX_POS_VALUES = 8 let s:MAX_COL_SIZE = 1073741824 " pow(2, 30) +" Check if we have neovim's buffer highlight API +" +" Below we define some functions' implementation conditionally if this API +" exists or not. +" +" The API itself is more ergonomic and neovim performs highlights positions +" rebases during edits so we see less stalled highlights. +let s:nvim_api = exists('*nvim_buf_add_highlight') && exists('*nvim_buf_clear_namespace') + +function! ale#highlight#HasNeovimApi() abort + return s:nvim_api +endfunction + +function! ale#highlight#nvim_buf_clear_namespace(...) abort + return call('nvim_buf_clear_namespace', a:000) +endfunction + +function! ale#highlight#nvim_buf_add_highlight(...) abort + return call('nvim_buf_add_highlight', a:000) +endfunction + +function! s:ale_nvim_highlight_id(bufnr) abort + let l:id = getbufvar(a:bufnr, 'ale_nvim_highlight_id', -1) + + if l:id is -1 + " NOTE: This will highlight nothing but will allocate new id + let l:id = ale#highlight#nvim_buf_add_highlight( + \ a:bufnr, 0, '', 0, 0, -1 + \) + call setbufvar(a:bufnr, 'ale_nvim_highlight_id', l:id) + endif + + return l:id +endfunction + function! ale#highlight#CreatePositions(line, col, end_line, end_col) abort if a:line >= a:end_line " For single lines, just return the one position. @@ -49,12 +84,90 @@ endfunction " Given a loclist for current items to highlight, remove all highlights " except these which have matching loclist item entries. + function! ale#highlight#RemoveHighlights() abort - for l:match in getmatches() - if l:match.group =~# '^ALE' - call matchdelete(l:match.id) + if ale#highlight#HasNeovimApi() + if get(b:, 'ale_nvim_highlight_id', 0) + let l:bufnr = bufnr('%') + " NOTE: 0, -1 means from 0 line till the end of buffer + call ale#highlight#nvim_buf_clear_namespace( + \ l:bufnr, + \ b:ale_nvim_highlight_id, + \ 0, -1 + \) endif - endfor + else + for l:match in getmatches() + if l:match.group =~# '^ALE' + call matchdelete(l:match.id) + endif + endfor + endif +endfunction + +function! s:highlight_line(bufnr, lnum, group) abort + if ale#highlight#HasNeovimApi() + let l:highlight_id = s:ale_nvim_highlight_id(a:bufnr) + call ale#highlight#nvim_buf_add_highlight( + \ a:bufnr, l:highlight_id, a:group, + \ a:lnum, 0, -1 + \) + else + call matchaddpos(a:group, [a:lnum]) + endif +endfunction + +function! s:highlight_range(bufnr, range, group) abort + if ale#highlight#HasNeovimApi() + let l:highlight_id = s:ale_nvim_highlight_id(a:bufnr) + " NOTE: lines and columns indicies are 0-based in nvim_buf_* API. + let l:lnum = a:range.lnum - 1 + let l:end_lnum = a:range.end_lnum - 1 + let l:col = a:range.col - 1 + let l:end_col = a:range.end_col + + if l:lnum >= l:end_lnum + " For single lines, just return the one position. + call ale#highlight#nvim_buf_add_highlight( + \ a:bufnr, l:highlight_id, a:group, + \ l:lnum, l:col, l:end_col + \) + else + " highlight first line from start till the line end + call ale#highlight#nvim_buf_add_highlight( + \ a:bufnr, l:highlight_id, a:group, + \ l:lnum, l:col, -1 + \) + + " highlight all lines between the first and last entirely + let l:cur = l:lnum + 1 + + while l:cur < l:end_lnum + call ale#highlight#nvim_buf_add_highlight( + \ a:bufnr, l:highlight_id, a:group, + \ l:cur, 0, -1 + \ ) + let l:cur += 1 + endwhile + + call ale#highlight#nvim_buf_add_highlight( + \ a:bufnr, l:highlight_id, a:group, + \ l:end_lnum, 0, l:end_col + \) + endif + else + " Set all of the positions, which are chunked into Lists which + " are as large as will be accepted by matchaddpos. + call map( + \ ale#highlight#CreatePositions( + \ a:range.lnum, + \ a:range.col, + \ a:range.end_lnum, + \ a:range.end_col + \ ), + \ 'matchaddpos(a:group, v:val)' + \) + endif endfunction function! ale#highlight#UpdateHighlights() abort @@ -79,17 +192,14 @@ function! ale#highlight#UpdateHighlights() abort let l:group = 'ALEError' endif - let l:line = l:item.lnum - let l:col = l:item.col - let l:end_line = get(l:item, 'end_lnum', l:line) - let l:end_col = get(l:item, 'end_col', l:col) + let l:range = { + \ 'lnum': l:item.lnum, + \ 'col': l:item.col, + \ 'end_lnum': get(l:item, 'end_lnum', l:item.lnum), + \ 'end_col': get(l:item, 'end_col', l:item.col) + \} - " Set all of the positions, which are chunked into Lists which - " are as large as will be accepted by matchaddpos. - call map( - \ ale#highlight#CreatePositions(l:line, l:col, l:end_line, l:end_col), - \ 'matchaddpos(l:group, v:val)' - \) + call s:highlight_range(l:item.bufnr, l:range, l:group) endfor " If highlights are enabled and signs are not enabled, we should still @@ -111,7 +221,7 @@ function! ale#highlight#UpdateHighlights() abort endif if l:available_groups[l:group] - call matchaddpos(l:group, [l:item.lnum]) + call s:highlight_line(l:item.bufnr, l:item.lnum, l:group) endif endfor endif diff --git a/test/test_nvim_api_highlight.vader b/test/test_nvim_api_highlight.vader new file mode 100644 index 00000000..fd535914 --- /dev/null +++ b/test/test_nvim_api_highlight.vader @@ -0,0 +1,160 @@ +Before: + Save g:ale_enabled + Save g:ale_set_signs + + let g:nvim_buf_clear_namespace_calls = [] + let g:nvim_buf_add_highlight_calls = [] + + call ale#test#SetDirectory('/testplugin/test/highlight') + call ale#test#SetFilename('dummy.txt') + + runtime autoload/ale/highlight.vim + + let g:ale_set_signs = 1 + let g:ale_enabled = 1 + let b:ale_nvim_highlight_id = 42 + let b:ale_highlight_items = [] + + function! ale#highlight#nvim_buf_clear_namespace(...) abort + call add(g:nvim_buf_clear_namespace_calls, a:000) + endfunction + + function! ale#highlight#nvim_buf_add_highlight(...) abort + call add(g:nvim_buf_add_highlight_calls, a:000) + return 42 " returns namespace id + endfunction + +After: + Restore + + unlet! b:ale_enabled + unlet! b:ale_nvim_highlight_id + unlet! b:ale_highlight_items + + unlet! g:nvim_buf_clear_namespace_calls + unlet! g:nvim_buf_add_highlight_calls + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + runtime autoload/ale/highlight.vim + +Given foobar (Some imaginary filetype): + <contents> + +Execute(Check usage of nvim_buf_clear_namespace): + if ale#highlight#HasNeovimApi() + call ale#highlight#SetHighlights(bufnr(''), []) + + AssertEqual 1, len(g:nvim_buf_clear_namespace_calls) + AssertEqual + \ [[bufnr(''), 42, 0, -1]], + \ g:nvim_buf_clear_namespace_calls + endif + +Execute(Check usage of nvim_buf_add_highlight / single char): + if ale#highlight#HasNeovimApi() + call ale#highlight#SetHighlights(bufnr(''), [ + \ { + \ 'bufnr': bufnr(''), + \ 'type': 'E', + \ 'lnum': 2, + \ 'col': 4, + \ } + \]) + + " Highlights are cleared on update + AssertEqual 1, len(g:nvim_buf_clear_namespace_calls) + AssertEqual [[bufnr(''), 42, 0, -1]], g:nvim_buf_clear_namespace_calls + + " Should highlight a single char by lnum, col + AssertEqual 1, len(g:nvim_buf_add_highlight_calls) + AssertEqual + \ [[bufnr(''), 42, 'ALEError', 1, 3, 4]], + \ g:nvim_buf_add_highlight_calls + endif + +Execute(Check usage of nvim_buf_add_highlight / single line span): + if ale#highlight#HasNeovimApi() + call ale#highlight#SetHighlights(bufnr(''), [ + \ { + \ 'bufnr': bufnr(''), + \ 'type': 'E', + \ 'lnum': 2, + \ 'col': 4, + \ 'end_lnum': 2, + \ 'end_col': 10, + \ } + \]) + + " Highlights are cleared on update + AssertEqual 1, len(g:nvim_buf_clear_namespace_calls) + AssertEqual [[bufnr(''), 42, 0, -1]], g:nvim_buf_clear_namespace_calls + + " Should highlight a span between col and end_col on lnum + AssertEqual 1, len(g:nvim_buf_add_highlight_calls) + AssertEqual + \ [[bufnr(''), 42, 'ALEError', 1, 3, 10]], + \ g:nvim_buf_add_highlight_calls + endif + +Execute(Check usage of nvim_buf_add_highlight / multiple lines span): + if ale#highlight#HasNeovimApi() + call ale#highlight#SetHighlights(bufnr(''), [ + \ { + \ 'bufnr': bufnr(''), + \ 'type': 'E', + \ 'lnum': 2, + \ 'col': 4, + \ 'end_lnum': 5, + \ 'end_col': 10, + \ } + \]) + + " Highlights are cleared on update + AssertEqual 1, len(g:nvim_buf_clear_namespace_calls) + AssertEqual [[bufnr(''), 42, 0, -1]], g:nvim_buf_clear_namespace_calls + + " Should highlight all lines from lnum till end_lnum + AssertEqual 4, len(g:nvim_buf_add_highlight_calls) + AssertEqual + \ [ + \ [bufnr(''), 42, 'ALEError', 1, 3, -1], + \ [bufnr(''), 42, 'ALEError', 2, 0, -1], + \ [bufnr(''), 42, 'ALEError', 3, 0, -1], + \ [bufnr(''), 42, 'ALEError', 4, 0, 10] + \ ], + \ g:nvim_buf_add_highlight_calls + endif + +Execute(Check usage of nvim_buf_add_highlight / line highights): + let g:ale_set_signs = 0 + + if ale#highlight#HasNeovimApi() + call ale#highlight#SetHighlights(bufnr(''), [ + \ { + \ 'bufnr': bufnr(''), + \ 'type': 'E', + \ 'lnum': 2, + \ 'col': 4, + \ 'end_lnum': 5, + \ 'end_col': 10, + \ } + \]) + + " Highlights are cleared on update + AssertEqual 1, len(g:nvim_buf_clear_namespace_calls) + AssertEqual [[bufnr(''), 42, 0, -1]], g:nvim_buf_clear_namespace_calls + + " Now the last highlight should be put on the entire line + AssertEqual 5, len(g:nvim_buf_add_highlight_calls) + AssertEqual + \ [ + \ [bufnr(''), 42, 'ALEError', 1, 3, -1], + \ [bufnr(''), 42, 'ALEError', 2, 0, -1], + \ [bufnr(''), 42, 'ALEError', 3, 0, -1], + \ [bufnr(''), 42, 'ALEError', 4, 0, 10], + \ [bufnr(''), 42, 'ALEErrorLine', 2, 0, -1] + \ ], + \ g:nvim_buf_add_highlight_calls + endif |