You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

354 lines
10 KiB
VimL

scriptencoding utf8
" Author: w0rp <devw0rp@gmail.com>
" Description: Draws error and warning signs into signcolumn
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
if !hlexists('ALESignColumnWithoutErrors')
function! s:SetSignColumnWithoutErrorsHighlight() abort
redir => l:output
silent highlight SignColumn
redir end
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
call s:SetSignColumnWithoutErrorsHighlight()
delfunction s:SetSignColumnWithoutErrorsHighlight
endif
" Signs show up on the left for error markers.
execute 'sign define ALEErrorSign text=' . g:ale_sign_error
\ . ' texthl=ALEErrorSign linehl=ALEErrorLine'
execute 'sign define ALEStyleErrorSign text=' . g:ale_sign_style_error
\ . ' texthl=ALEStyleErrorSign linehl=ALEErrorLine'
execute 'sign define ALEWarningSign text=' . g:ale_sign_warning
\ . ' texthl=ALEWarningSign linehl=ALEWarningLine'
execute 'sign define ALEStyleWarningSign text=' . g:ale_sign_style_warning
\ . ' texthl=ALEStyleWarningSign linehl=ALEWarningLine'
execute 'sign define ALEInfoSign text=' . g:ale_sign_info
\ . ' texthl=ALEInfoSign linehl=ALEInfoLine'
sign define ALEDummySign
let s:error_priority = 1
let s:warning_priority = 2
let s:info_priority = 3
let s:style_error_priority = 4
let s:style_warning_priority = 5
function! ale#sign#GetSignName(sublist) abort
let l:priority = s:style_warning_priority
" Determine the highest priority item for the line.
for l:item in a:sublist
if l:item.type is# 'I'
let l:item_priority = s:info_priority
elseif l:item.type is# 'W'
if get(l:item, 'sub_type', '') is# 'style'
let l:item_priority = s:style_warning_priority
else
let l:item_priority = s:warning_priority
endif
else
if get(l:item, 'sub_type', '') is# 'style'
let l:item_priority = s:style_error_priority
else
let l:item_priority = s:error_priority
endif
endif
if l:item_priority < l:priority
let l:priority = l:item_priority
endif
endfor
if l:priority is# s:error_priority
return 'ALEErrorSign'
endif
if l:priority is# s:warning_priority
return 'ALEWarningSign'
endif
if l:priority is# s:style_error_priority
return 'ALEStyleErrorSign'
endif
if l:priority is# s:style_warning_priority
return 'ALEStyleWarningSign'
endif
if l:priority is# s:info_priority
return 'ALEInfoSign'
endif
" Use the error sign for invalid severities.
return 'ALEErrorSign'
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 buffer=' . a:buffer
redir end
return split(l:output, "\n")
endfunction
" Given a list of lines for sign output, return a List of [line, id, group]
function! ale#sign#ParseSigns(line_list) abort
" 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
let l:pattern = '\v^.*\=(\d+).*\=(\d+).*\=(ALE[a-zA-Z]+Sign)'
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! ale#sign#SetSignColumnHighlight(has_problems) abort
highlight clear SignColumn
if a:has_problems
highlight link SignColumn ALESignColumnWithErrors
else
highlight link SignColumn ALESignColumnWithoutErrors
endif
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(current_sign_list, grouped_items) abort
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_map[l:line] = {
\ 'current_id': l:sign_id,
\ 'current_name': l:name,
\ 'new_id': 0,
\ 'new_name': '',
\ 'items': [],
\}
if l:sign_id > l:sign_offset
let l:sign_offset = l:sign_id
endif
endfor
for l:group in a:grouped_items
let l:line = l:group[0].lnum
let l:sign_info = get(l:sign_map, l:line, {
\ 'current_id': 0,
\ 'current_name': '',
\ 'new_id': 0,
\ 'new_name': '',
\ 'items': [],
\})
let l:sign_info.new_name = ale#sign#GetSignName(l:group)
let l:sign_info.items = l:group
if l:sign_info.current_name isnot# l:sign_info.new_name
let l:sign_info.new_id = l:sign_offset + 1
let l:sign_offset += 1
else
let l:sign_info.new_id = l:sign_info.current_id
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
\ . ' 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 l:info.new_id isnot l:info.current_id
call add(l:command_list, 'sign place '
\ . (l:info.new_id)
\ . ' 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)
if l:info.current_id && l:info.current_id isnot l:info.new_id
call add(l:command_list, 'sign unplace '
\ . (l:info.current_id)
\ . ' buffer=' . a:buffer
\)
endif
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
\ . ' 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(l:current_sign_list, l:grouped_items)
let l:command_list = ale#sign#GetSignCommands(
\ a:buffer,
\ l:is_dummy_sign_set,
\ l:sign_map,
\)
for l:command in l:command_list
silent! execute l:command
endfor
endfunction