Files
ale/autoload/ale/sign.vim
Horacio Sanson bafe1c0fd6 3560 add vim 8.2 and nvim 0.4 to ci tests (#3561)
* Add vim82 and neovim04 to CI tests.

* Fix test_sign_column_hightlighting test.

In vim82 with verbose=1 the output of highlight command changes breaking
the ale#sign#SetUpDefaultColumnWithoutErrorsHighlight(). This commit
forces verbose=0 when the method starts and restores the previous value
before exiting.

* No return values in vim82 returns a numeric value instead of a empty string.

* Fix test_reek_handler test

The FuzzyJSONDecode() method catches E474 when it fails to parse the
input as JSON but Vim8.2 throws E491 instead. This commit modifies the
function to catch both E474 or E491.

* Fix perl6 handler test.

Perl6 handler catches json parse errors using the E474 error but in
Vim82 it changed to E491. This commit modifies the handler so both
errors are considered.

* Fix list opening tests.

In Vim 8.2 the call `range(1, bufnr('$'))` always returns quickfix
buffers no matter if they are closed or not. Using `ls` does not show
them but the above range will always include them.

This new behavior breaks the ale#list#IsQuickfixOpen() method that in
turn breaks many other things. This commit fixes this by using the
getqflist() and getloclist() methods instead.

* Fix test updates loclist test.

For some reason in Vim 8.2 the sign offset seems to not reset between
tests causing the sign_id to not match in the Assert. When the test is
run individually it passes but when run as part of the whole suite the
sign_id is off by one.

Forcing the offset in the test setup seems to fix the issue.

* Fix omnifunc completion test.

For unknown reasons the SetCompletionResponse tests fail in Neovim 0.2
and 0.4. Unfortunatelly the only solution I found is to disable them
for neovim.

* Fix linter warnings

* Fix smoker test.

Add vim 8.2 to the list of versions that need some retires due to
randomly failing tests.

* Add docker image build job.

Trying some clever trick to build the docker image if not available
locally or in Docker hub. It uses the Dockerfile md5 checksum as tag so
only when changes on that file occur will the image be downloaded or
build.

* Add labels to Docker image

* Remove tests for middle versions 8.1 and 0.3.5

* Use same vader commit as appveyor

* Implement image push to Docker Hub

Co-authored-by: Horacio Sanson <horacio@allm.inc>
2021-01-27 19:52:24 +00:00

474 lines
15 KiB
VimL

scriptencoding utf8
" Author: w0rp <devw0rp@gmail.com>
" Description: Draws error and warning signs into signcolumn
" This flag can be set to some integer to control the maximum number of signs
" that ALE will set.
let g:ale_max_signs = get(g:, 'ale_max_signs', -1)
" This flag can be set to 1 to enable changing the sign column colors when
" there are errors.
let g:ale_change_sign_column_color = get(g:, 'ale_change_sign_column_color', 0)
" These variables dictate what signs are used to indicate errors and warnings.
let g:ale_sign_error = get(g:, 'ale_sign_error', '>>')
let g:ale_sign_style_error = get(g:, 'ale_sign_style_error', g:ale_sign_error)
let g:ale_sign_warning = get(g:, 'ale_sign_warning', '--')
let g:ale_sign_style_warning = get(g:, 'ale_sign_style_warning', g:ale_sign_warning)
let g:ale_sign_info = get(g:, 'ale_sign_info', g:ale_sign_warning)
let g:ale_sign_priority = get(g:, 'ale_sign_priority', 30)
" This variable sets an offset which can be set for sign IDs.
" This ID can be changed depending on what IDs are set for other plugins.
" The dummy sign will use the ID exactly equal to the offset.
let g:ale_sign_offset = get(g:, 'ale_sign_offset', 1000000)
" This flag can be set to 1 to keep sign gutter always open
let g:ale_sign_column_always = get(g:, 'ale_sign_column_always', 0)
let g:ale_sign_highlight_linenrs = get(g:, 'ale_sign_highlight_linenrs', 0)
let s:supports_sign_groups = has('nvim-0.4.2') || has('patch-8.1.614')
if !hlexists('ALEErrorSign')
highlight link ALEErrorSign error
endif
if !hlexists('ALEStyleErrorSign')
highlight link ALEStyleErrorSign ALEErrorSign
endif
if !hlexists('ALEWarningSign')
highlight link ALEWarningSign todo
endif
if !hlexists('ALEStyleWarningSign')
highlight link ALEStyleWarningSign ALEWarningSign
endif
if !hlexists('ALEInfoSign')
highlight link ALEInfoSign ALEWarningSign
endif
if !hlexists('ALESignColumnWithErrors')
highlight link ALESignColumnWithErrors error
endif
function! ale#sign#SetUpDefaultColumnWithoutErrorsHighlight() abort
let l:verbose = &verbose
set verbose=0
redir => l:output
0verbose silent highlight SignColumn
redir end
let &verbose = l:verbose
let l:highlight_syntax = join(split(l:output)[2:])
let l:match = matchlist(l:highlight_syntax, '\vlinks to (.+)$')
if !empty(l:match)
execute 'highlight link ALESignColumnWithoutErrors ' . l:match[1]
elseif l:highlight_syntax isnot# 'cleared'
execute 'highlight ALESignColumnWithoutErrors ' . l:highlight_syntax
endif
endfunction
if !hlexists('ALESignColumnWithoutErrors')
call ale#sign#SetUpDefaultColumnWithoutErrorsHighlight()
endif
" Spaces and backslashes need to be escaped for signs.
function! s:EscapeSignText(sign_text) abort
return substitute(substitute(a:sign_text, ' *$', '', ''), '\\\| ', '\\\0', 'g')
endfunction
" Signs show up on the left for error markers.
execute 'sign define ALEErrorSign text=' . s:EscapeSignText(g:ale_sign_error)
\ . ' texthl=ALEErrorSign linehl=ALEErrorLine'
execute 'sign define ALEStyleErrorSign text=' . s:EscapeSignText(g:ale_sign_style_error)
\ . ' texthl=ALEStyleErrorSign linehl=ALEErrorLine'
execute 'sign define ALEWarningSign text=' . s:EscapeSignText(g:ale_sign_warning)
\ . ' texthl=ALEWarningSign linehl=ALEWarningLine'
execute 'sign define ALEStyleWarningSign text=' . s:EscapeSignText(g:ale_sign_style_warning)
\ . ' texthl=ALEStyleWarningSign linehl=ALEWarningLine'
execute 'sign define ALEInfoSign text=' . s:EscapeSignText(g:ale_sign_info)
\ . ' texthl=ALEInfoSign linehl=ALEInfoLine'
sign define ALEDummySign
if g:ale_sign_highlight_linenrs && has('nvim-0.3.2')
if !hlexists('ALEErrorSignLineNr')
highlight link ALEErrorSignLineNr CursorLineNr
endif
if !hlexists('ALEStyleErrorSignLineNr')
highlight link ALEStyleErrorSignLineNr CursorLineNr
endif
if !hlexists('ALEWarningSignLineNr')
highlight link ALEWarningSignLineNr CursorLineNr
endif
if !hlexists('ALEStyleWarningSignLineNr')
highlight link ALEStyleWarningSignLineNr CursorLineNr
endif
if !hlexists('ALEInfoSignLineNr')
highlight link ALEInfoSignLineNr CursorLineNr
endif
sign define ALEErrorSign numhl=ALEErrorSignLineNr
sign define ALEStyleErrorSign numhl=ALEStyleErrorSignLineNr
sign define ALEWarningSign numhl=ALEWarningSignLineNr
sign define ALEStyleWarningSign numhl=ALEStyleWarningSignLineNr
sign define ALEInfoSign numhl=ALEInfoSignLineNr
endif
function! ale#sign#GetSignName(sublist) abort
let l:priority = g:ale#util#style_warning_priority
" Determine the highest priority item for the line.
for l:item in a:sublist
let l:item_priority = ale#util#GetItemPriority(l:item)
if l:item_priority > l:priority
let l:priority = l:item_priority
endif
endfor
if l:priority is# g:ale#util#error_priority
return 'ALEErrorSign'
endif
if l:priority is# g:ale#util#warning_priority
return 'ALEWarningSign'
endif
if l:priority is# g:ale#util#style_error_priority
return 'ALEStyleErrorSign'
endif
if l:priority is# g:ale#util#style_warning_priority
return 'ALEStyleWarningSign'
endif
if l:priority is# g:ale#util#info_priority
return 'ALEInfoSign'
endif
" Use the error sign for invalid severities.
return 'ALEErrorSign'
endfunction
function! s:PriorityCmd() abort
if s:supports_sign_groups
return ' priority=' . g:ale_sign_priority . ' '
else
return ''
endif
endfunction
function! s:GroupCmd() abort
if s:supports_sign_groups
return ' group=ale '
else
return ' '
endif
endfunction
" Read sign data for a buffer to a list of lines.
function! ale#sign#ReadSigns(buffer) abort
redir => l:output
silent execute 'sign place ' . s:GroupCmd() . s:PriorityCmd()
\ . ' buffer=' . a:buffer
redir end
return split(l:output, "\n")
endfunction
function! ale#sign#ParsePattern() abort
if s:supports_sign_groups
" Matches output like :
" line=4 id=1 group=ale name=ALEErrorSign
" строка=1 id=1000001 группа=ale имя=ALEErrorSign
" 行=1 識別子=1000001 グループ=ale 名前=ALEWarningSign
" línea=12 id=1000001 grupo=ale nombre=ALEWarningSign
" riga=1 id=1000001 gruppo=ale nome=ALEWarningSign
" Zeile=235 id=1000001 Gruppe=ale Name=ALEErrorSign
let l:pattern = '\v^.*\=(\d+).*\=(\d+).*\=ale>.*\=(ALE[a-zA-Z]+Sign)'
else
" Matches output like :
" line=4 id=1 name=ALEErrorSign
" строка=1 id=1000001 имя=ALEErrorSign
" 行=1 識別子=1000001 名前=ALEWarningSign
" línea=12 id=1000001 nombre=ALEWarningSign
" riga=1 id=1000001 nome=ALEWarningSign
" Zeile=235 id=1000001 Name=ALEErrorSign
let l:pattern = '\v^.*\=(\d+).*\=(\d+).*\=(ALE[a-zA-Z]+Sign)'
endif
return l:pattern
endfunction
" Given a list of lines for sign output, return a List of [line, id, group]
function! ale#sign#ParseSigns(line_list) abort
let l:pattern =ale#sign#ParsePattern()
let l:result = []
let l:is_dummy_sign_set = 0
for l:line in a:line_list
let l:match = matchlist(l:line, l:pattern)
if len(l:match) > 0
if l:match[3] is# 'ALEDummySign'
let l:is_dummy_sign_set = 1
else
call add(l:result, [
\ str2nr(l:match[1]),
\ str2nr(l:match[2]),
\ l:match[3],
\])
endif
endif
endfor
return [l:is_dummy_sign_set, l:result]
endfunction
function! ale#sign#FindCurrentSigns(buffer) abort
let l:line_list = ale#sign#ReadSigns(a:buffer)
return ale#sign#ParseSigns(l:line_list)
endfunction
" Given a loclist, group the List into with one List per line.
function! s:GroupLoclistItems(buffer, loclist) abort
let l:grouped_items = []
let l:last_lnum = -1
for l:obj in a:loclist
if l:obj.bufnr != a:buffer
continue
endif
" Create a new sub-List when we hit a new line.
if l:obj.lnum != l:last_lnum
call add(l:grouped_items, [])
endif
call add(l:grouped_items[-1], l:obj)
let l:last_lnum = l:obj.lnum
endfor
return l:grouped_items
endfunction
function! s:UpdateLineNumbers(buffer, current_sign_list, loclist) abort
let l:line_map = {}
let l:line_numbers_changed = 0
for [l:line, l:sign_id, l:name] in a:current_sign_list
let l:line_map[l:sign_id] = l:line
endfor
for l:item in a:loclist
if l:item.bufnr == a:buffer
let l:lnum = get(l:line_map, get(l:item, 'sign_id', 0), 0)
if l:lnum && l:item.lnum != l:lnum
let l:item.lnum = l:lnum
let l:line_numbers_changed = 1
endif
endif
endfor
" When the line numbers change, sort the list again
if l:line_numbers_changed
call sort(a:loclist, 'ale#util#LocItemCompare')
endif
endfunction
function! s:BuildSignMap(buffer, current_sign_list, grouped_items) abort
let l:max_signs = ale#Var(a:buffer, 'max_signs')
if l:max_signs is 0
let l:selected_grouped_items = []
elseif type(l:max_signs) is v:t_number && l:max_signs > 0
let l:selected_grouped_items = a:grouped_items[:l:max_signs - 1]
else
let l:selected_grouped_items = a:grouped_items
endif
let l:sign_map = {}
let l:sign_offset = g:ale_sign_offset
for [l:line, l:sign_id, l:name] in a:current_sign_list
let l:sign_info = get(l:sign_map, l:line, {
\ 'current_id_list': [],
\ 'current_name_list': [],
\ 'new_id': 0,
\ 'new_name': '',
\ 'items': [],
\})
" Increment the sign offset for new signs, by the maximum sign ID.
if l:sign_id > l:sign_offset
let l:sign_offset = l:sign_id
endif
" Remember the sign names and IDs in separate Lists, so they are easy
" to work with.
call add(l:sign_info.current_id_list, l:sign_id)
call add(l:sign_info.current_name_list, l:name)
let l:sign_map[l:line] = l:sign_info
endfor
for l:group in l:selected_grouped_items
let l:line = l:group[0].lnum
let l:sign_info = get(l:sign_map, l:line, {
\ 'current_id_list': [],
\ 'current_name_list': [],
\ 'new_id': 0,
\ 'new_name': '',
\ 'items': [],
\})
let l:sign_info.new_name = ale#sign#GetSignName(l:group)
let l:sign_info.items = l:group
let l:index = index(
\ l:sign_info.current_name_list,
\ l:sign_info.new_name
\)
if l:index >= 0
" We have a sign with this name already, so use the same ID.
let l:sign_info.new_id = l:sign_info.current_id_list[l:index]
else
" This sign name replaces the previous name, so use a new ID.
let l:sign_info.new_id = l:sign_offset + 1
let l:sign_offset += 1
endif
let l:sign_map[l:line] = l:sign_info
endfor
return l:sign_map
endfunction
function! ale#sign#GetSignCommands(buffer, was_sign_set, sign_map) abort
let l:command_list = []
let l:is_dummy_sign_set = a:was_sign_set
" Set the dummy sign if we need to.
" The dummy sign is needed to keep the sign column open while we add
" and remove signs.
if !l:is_dummy_sign_set && (!empty(a:sign_map) || g:ale_sign_column_always)
call add(l:command_list, 'sign place '
\ . g:ale_sign_offset
\ . s:GroupCmd()
\ . s:PriorityCmd()
\ . ' line=1 name=ALEDummySign '
\ . ' buffer=' . a:buffer
\)
let l:is_dummy_sign_set = 1
endif
" Place new items first.
for [l:line_str, l:info] in items(a:sign_map)
if l:info.new_id
" Save the sign IDs we are setting back on our loclist objects.
" These IDs will be used to preserve items which are set many times.
for l:item in l:info.items
let l:item.sign_id = l:info.new_id
endfor
if index(l:info.current_id_list, l:info.new_id) < 0
call add(l:command_list, 'sign place '
\ . (l:info.new_id)
\ . s:GroupCmd()
\ . s:PriorityCmd()
\ . ' line=' . l:line_str
\ . ' name=' . (l:info.new_name)
\ . ' buffer=' . a:buffer
\)
endif
endif
endfor
" Remove signs without new IDs.
for l:info in values(a:sign_map)
for l:current_id in l:info.current_id_list
if l:current_id isnot l:info.new_id
call add(l:command_list, 'sign unplace '
\ . l:current_id
\ . s:GroupCmd()
\ . ' buffer=' . a:buffer
\)
endif
endfor
endfor
" Remove the dummy sign to close the sign column if we need to.
if l:is_dummy_sign_set && !g:ale_sign_column_always
call add(l:command_list, 'sign unplace '
\ . g:ale_sign_offset
\ . s:GroupCmd()
\ . ' buffer=' . a:buffer
\)
endif
return l:command_list
endfunction
" This function will set the signs which show up on the left.
function! ale#sign#SetSigns(buffer, loclist) abort
if !bufexists(str2nr(a:buffer))
" Stop immediately when attempting to set signs for a buffer which
" does not exist.
return
endif
" Find the current markers
let [l:is_dummy_sign_set, l:current_sign_list] =
\ ale#sign#FindCurrentSigns(a:buffer)
" Update the line numbers for items from before which may have moved.
call s:UpdateLineNumbers(a:buffer, l:current_sign_list, a:loclist)
" Group items after updating the line numbers.
let l:grouped_items = s:GroupLoclistItems(a:buffer, a:loclist)
" Build a map of current and new signs, with the lines as the keys.
let l:sign_map = s:BuildSignMap(
\ a:buffer,
\ l:current_sign_list,
\ l:grouped_items,
\)
let l:command_list = ale#sign#GetSignCommands(
\ a:buffer,
\ l:is_dummy_sign_set,
\ l:sign_map,
\)
" Change the sign column color if the option is on.
if g:ale_change_sign_column_color && !empty(a:loclist)
highlight clear SignColumn
highlight link SignColumn ALESignColumnWithErrors
endif
for l:command in l:command_list
silent! execute l:command
endfor
" Reset the sign column color when there are no more errors.
if g:ale_change_sign_column_color && empty(a:loclist)
highlight clear SignColumn
highlight link SignColumn ALESignColumnWithoutErrors
endif
endfunction
" Remove all signs.
function! ale#sign#Clear() abort
if s:supports_sign_groups
sign unplace group=ale *
else
sign unplace *
endif
endfunction