summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrey Popp <8mayday@gmail.com>2019-05-01 21:35:15 +0300
committerw0rp <w0rp@users.noreply.github.com>2019-05-01 19:35:15 +0100
commit114198e0825d4fa33ad1fe62a41a69fd9b4d52c1 (patch)
treea2615dc0aeeb643bb5ef1e5bdc896343be65952f
parent2f3bce5a1d6e4e1f01ae392d141ab1ab9477ddae (diff)
downloadale-114198e0825d4fa33ad1fe62a41a69fd9b4d52c1.zip
Optionally use neovim's api-highlights (#2169)
-rw-r--r--autoload/ale/highlight.vim140
-rw-r--r--test/test_nvim_api_highlight.vader160
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