All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
2 " Author: w0rp <devw0rp@gmail.com>
3 " Description: Draws error and warning signs into signcolumn
5 " This flag can be set to some integer to control the maximum number of signs
7 let g:ale_max_signs = get(g:, 'ale_max_signs', -1)
8 " This flag can be set to 1 to enable changing the sign column colors when
10 let g:ale_change_sign_column_color = get(g:, 'ale_change_sign_column_color', v:false)
11 " These variables dictate what signs are used to indicate errors and warnings.
12 let g:ale_sign_error = get(g:, 'ale_sign_error', 'E')
13 let g:ale_sign_style_error = get(g:, 'ale_sign_style_error', g:ale_sign_error)
14 let g:ale_sign_warning = get(g:, 'ale_sign_warning', 'W')
15 let g:ale_sign_style_warning = get(g:, 'ale_sign_style_warning', g:ale_sign_warning)
16 let g:ale_sign_info = get(g:, 'ale_sign_info', 'I')
17 let g:ale_sign_priority = get(g:, 'ale_sign_priority', 30)
18 " This variable sets an offset which can be set for sign IDs.
19 " This ID can be changed depending on what IDs are set for other plugins.
20 " The dummy sign will use the ID exactly equal to the offset.
21 let g:ale_sign_offset = get(g:, 'ale_sign_offset', 1000000)
22 " This flag can be set to 1 to keep sign gutter always open
23 let g:ale_sign_column_always = get(g:, 'ale_sign_column_always', v:false)
24 let g:ale_sign_highlight_linenrs = get(g:, 'ale_sign_highlight_linenrs', v:false)
26 let s:supports_sign_groups = has('nvim-0.4.2') || has('patch-8.1.614')
28 if !hlexists('ALEErrorSign')
29 highlight link ALEErrorSign error
32 if !hlexists('ALEStyleErrorSign')
33 highlight link ALEStyleErrorSign ALEErrorSign
36 if !hlexists('ALEWarningSign')
37 highlight link ALEWarningSign todo
40 if !hlexists('ALEStyleWarningSign')
41 highlight link ALEStyleWarningSign ALEWarningSign
44 if !hlexists('ALEInfoSign')
45 highlight link ALEInfoSign ALEWarningSign
48 if !hlexists('ALESignColumnWithErrors')
49 highlight link ALESignColumnWithErrors error
52 function! ale#sign#SetUpDefaultColumnWithoutErrorsHighlight() abort
53 let l:verbose = &verbose
55 let l:output = execute('highlight SignColumn', 'silent')
56 let &verbose = l:verbose
58 let l:highlight_syntax = join(split(l:output)[2:])
59 let l:match = matchlist(l:highlight_syntax, '\vlinks to (.+)$')
62 execute 'highlight link ALESignColumnWithoutErrors ' . l:match[1]
63 elseif l:highlight_syntax isnot# 'cleared'
64 execute 'highlight ALESignColumnWithoutErrors ' . l:highlight_syntax
68 if !hlexists('ALESignColumnWithoutErrors')
69 call ale#sign#SetUpDefaultColumnWithoutErrorsHighlight()
72 " Spaces and backslashes need to be escaped for signs.
73 function! s:EscapeSignText(sign_text) abort
74 return substitute(substitute(a:sign_text, ' *$', '', ''), '\\\| ', '\\\0', 'g')
77 " Signs show up on the left for error markers.
78 execute 'sign define ALEErrorSign text=' . s:EscapeSignText(g:ale_sign_error)
79 \ . ' texthl=ALEErrorSign linehl=ALEErrorLine'
80 execute 'sign define ALEStyleErrorSign text=' . s:EscapeSignText(g:ale_sign_style_error)
81 \ . ' texthl=ALEStyleErrorSign linehl=ALEErrorLine'
82 execute 'sign define ALEWarningSign text=' . s:EscapeSignText(g:ale_sign_warning)
83 \ . ' texthl=ALEWarningSign linehl=ALEWarningLine'
84 execute 'sign define ALEStyleWarningSign text=' . s:EscapeSignText(g:ale_sign_style_warning)
85 \ . ' texthl=ALEStyleWarningSign linehl=ALEWarningLine'
86 execute 'sign define ALEInfoSign text=' . s:EscapeSignText(g:ale_sign_info)
87 \ . ' texthl=ALEInfoSign linehl=ALEInfoLine'
88 sign define ALEDummySign text=\ texthl=SignColumn
90 if g:ale_sign_highlight_linenrs && (has('nvim-0.3.2') || has('patch-8.2.3874'))
91 if !hlexists('ALEErrorSignLineNr')
92 highlight link ALEErrorSignLineNr CursorLineNr
95 if !hlexists('ALEStyleErrorSignLineNr')
96 highlight link ALEStyleErrorSignLineNr CursorLineNr
99 if !hlexists('ALEWarningSignLineNr')
100 highlight link ALEWarningSignLineNr CursorLineNr
103 if !hlexists('ALEStyleWarningSignLineNr')
104 highlight link ALEStyleWarningSignLineNr CursorLineNr
107 if !hlexists('ALEInfoSignLineNr')
108 highlight link ALEInfoSignLineNr CursorLineNr
111 sign define ALEErrorSign numhl=ALEErrorSignLineNr
112 sign define ALEStyleErrorSign numhl=ALEStyleErrorSignLineNr
113 sign define ALEWarningSign numhl=ALEWarningSignLineNr
114 sign define ALEStyleWarningSign numhl=ALEStyleWarningSignLineNr
115 sign define ALEInfoSign numhl=ALEInfoSignLineNr
118 function! ale#sign#GetSignName(sublist) abort
119 let l:priority = g:ale#util#style_warning_priority
121 " Determine the highest priority item for the line.
122 for l:item in a:sublist
123 let l:item_priority = ale#util#GetItemPriority(l:item)
125 if l:item_priority > l:priority
126 let l:priority = l:item_priority
130 if l:priority is# g:ale#util#error_priority
131 return 'ALEErrorSign'
134 if l:priority is# g:ale#util#warning_priority
135 return 'ALEWarningSign'
138 if l:priority is# g:ale#util#style_error_priority
139 return 'ALEStyleErrorSign'
142 if l:priority is# g:ale#util#style_warning_priority
143 return 'ALEStyleWarningSign'
146 if l:priority is# g:ale#util#info_priority
150 " Use the error sign for invalid severities.
151 return 'ALEErrorSign'
154 function! s:PriorityCmd() abort
155 if s:supports_sign_groups
156 return ' priority=' . g:ale_sign_priority . ' '
162 function! s:GroupCmd() abort
163 if s:supports_sign_groups
164 return ' group=ale_signs '
170 " Read sign data for a buffer to a list of lines.
171 function! ale#sign#ReadSigns(buffer) abort
172 let l:output = execute(
173 \ 'sign place ' . s:GroupCmd() . s:PriorityCmd()
174 \ . ' buffer=' . a:buffer
177 return split(l:output, "\n")
180 function! ale#sign#ParsePattern() abort
181 if s:supports_sign_groups
182 " Matches output like :
183 " line=4 id=1 group=ale_signs name=ALEErrorSign
184 " строка=1 id=1000001 группа=ale_signs имя=ALEErrorSign
185 " 行=1 識別子=1000001 グループ=ale_signs 名前=ALEWarningSign
186 " línea=12 id=1000001 grupo=ale_signs nombre=ALEWarningSign
187 " riga=1 id=1000001 gruppo=ale_signs nome=ALEWarningSign
188 " Zeile=235 id=1000001 Gruppe=ale_signs Name=ALEErrorSign
189 let l:pattern = '\v^.*\=(\d+).*\=(\d+).*\=ale_signs>.*\=(ALE[a-zA-Z]+Sign)'
191 " Matches output like :
192 " line=4 id=1 name=ALEErrorSign
193 " строка=1 id=1000001 имя=ALEErrorSign
194 " 行=1 識別子=1000001 名前=ALEWarningSign
195 " línea=12 id=1000001 nombre=ALEWarningSign
196 " riga=1 id=1000001 nome=ALEWarningSign
197 " Zeile=235 id=1000001 Name=ALEErrorSign
198 let l:pattern = '\v^.*\=(\d+).*\=(\d+).*\=(ALE[a-zA-Z]+Sign)'
204 " Given a buffer number, return a List of placed signs [line, id, group]
205 function! ale#sign#ParseSignsWithGetPlaced(buffer) abort
206 let l:signs = sign_getplaced(a:buffer, { 'group': s:supports_sign_groups ? 'ale_signs' : '' })[0].signs
208 let l:is_dummy_sign_set = 0
210 for l:sign in l:signs
211 if l:sign['name'] is# 'ALEDummySign'
212 let l:is_dummy_sign_set = 1
215 \ str2nr(l:sign['lnum']),
216 \ str2nr(l:sign['id']),
222 return [l:is_dummy_sign_set, l:result]
225 " Given a list of lines for sign output, return a List of [line, id, group]
226 function! ale#sign#ParseSigns(line_list) abort
227 let l:pattern =ale#sign#ParsePattern()
229 let l:is_dummy_sign_set = 0
231 for l:line in a:line_list
232 let l:match = matchlist(l:line, l:pattern)
235 if l:match[3] is# 'ALEDummySign'
236 let l:is_dummy_sign_set = 1
239 \ str2nr(l:match[1]),
240 \ str2nr(l:match[2]),
247 return [l:is_dummy_sign_set, l:result]
250 function! ale#sign#FindCurrentSigns(buffer) abort
251 if exists('*sign_getplaced')
252 return ale#sign#ParseSignsWithGetPlaced(a:buffer)
254 let l:line_list = ale#sign#ReadSigns(a:buffer)
256 return ale#sign#ParseSigns(l:line_list)
260 " Given a loclist, group the List into with one List per line.
261 function! s:GroupLoclistItems(buffer, loclist) abort
262 let l:grouped_items = []
265 for l:obj in a:loclist
266 if l:obj.bufnr != a:buffer
270 " Create a new sub-List when we hit a new line.
271 if l:obj.lnum != l:last_lnum
272 call add(l:grouped_items, [])
275 call add(l:grouped_items[-1], l:obj)
276 let l:last_lnum = l:obj.lnum
279 return l:grouped_items
282 function! s:UpdateLineNumbers(buffer, current_sign_list, loclist) abort
284 let l:line_numbers_changed = 0
286 for [l:line, l:sign_id, l:name] in a:current_sign_list
287 let l:line_map[l:sign_id] = l:line
290 for l:item in a:loclist
291 if l:item.bufnr == a:buffer
292 let l:lnum = get(l:line_map, get(l:item, 'sign_id', 0), 0)
294 if l:lnum && l:item.lnum != l:lnum
295 let l:item.lnum = l:lnum
296 let l:line_numbers_changed = 1
301 " When the line numbers change, sort the list again
302 if l:line_numbers_changed
303 call sort(a:loclist, 'ale#util#LocItemCompare')
307 function! s:BuildSignMap(buffer, current_sign_list, grouped_items) abort
308 let l:max_signs = ale#Var(a:buffer, 'max_signs')
311 let l:selected_grouped_items = []
312 elseif type(l:max_signs) is v:t_number && l:max_signs > 0
313 let l:selected_grouped_items = a:grouped_items[:l:max_signs - 1]
315 let l:selected_grouped_items = a:grouped_items
319 let l:sign_offset = g:ale_sign_offset
321 for [l:line, l:sign_id, l:name] in a:current_sign_list
322 let l:sign_info = get(l:sign_map, l:line, {
323 \ 'current_id_list': [],
324 \ 'current_name_list': [],
330 " Increment the sign offset for new signs, by the maximum sign ID.
331 if l:sign_id > l:sign_offset
332 let l:sign_offset = l:sign_id
335 " Remember the sign names and IDs in separate Lists, so they are easy
337 call add(l:sign_info.current_id_list, l:sign_id)
338 call add(l:sign_info.current_name_list, l:name)
340 let l:sign_map[l:line] = l:sign_info
343 for l:group in l:selected_grouped_items
344 let l:line = l:group[0].lnum
345 let l:sign_info = get(l:sign_map, l:line, {
346 \ 'current_id_list': [],
347 \ 'current_name_list': [],
353 let l:sign_info.new_name = ale#sign#GetSignName(l:group)
354 let l:sign_info.items = l:group
357 \ l:sign_info.current_name_list,
358 \ l:sign_info.new_name
362 " We have a sign with this name already, so use the same ID.
363 let l:sign_info.new_id = l:sign_info.current_id_list[l:index]
365 " This sign name replaces the previous name, so use a new ID.
366 let l:sign_info.new_id = l:sign_offset + 1
367 let l:sign_offset += 1
370 let l:sign_map[l:line] = l:sign_info
376 function! ale#sign#GetSignCommands(buffer, was_sign_set, sign_map) abort
377 let l:command_list = []
378 let l:is_dummy_sign_set = a:was_sign_set
380 " Set the dummy sign if we need to.
381 " The dummy sign is needed to keep the sign column open while we add
383 if !l:is_dummy_sign_set && (!empty(a:sign_map) || g:ale_sign_column_always)
384 call add(l:command_list, 'sign place '
385 \ . g:ale_sign_offset
388 \ . ' line=1 name=ALEDummySign '
389 \ . ' buffer=' . a:buffer
391 let l:is_dummy_sign_set = 1
394 " Place new items first.
395 for [l:line_str, l:info] in items(a:sign_map)
397 " Save the sign IDs we are setting back on our loclist objects.
398 " These IDs will be used to preserve items which are set many times.
399 for l:item in l:info.items
400 let l:item.sign_id = l:info.new_id
403 if index(l:info.current_id_list, l:info.new_id) < 0
404 call add(l:command_list, 'sign place '
408 \ . ' line=' . l:line_str
409 \ . ' name=' . (l:info.new_name)
410 \ . ' buffer=' . a:buffer
416 " Remove signs without new IDs.
417 for l:info in values(a:sign_map)
418 for l:current_id in l:info.current_id_list
419 if l:current_id isnot l:info.new_id
420 call add(l:command_list, 'sign unplace '
423 \ . ' buffer=' . a:buffer
429 " Remove the dummy sign to close the sign column if we need to.
430 if l:is_dummy_sign_set && !g:ale_sign_column_always
431 call add(l:command_list, 'sign unplace '
432 \ . g:ale_sign_offset
434 \ . ' buffer=' . a:buffer
438 return l:command_list
441 " This function will set the signs which show up on the left.
442 function! ale#sign#SetSigns(buffer, loclist) abort
443 if !bufexists(str2nr(a:buffer))
444 " Stop immediately when attempting to set signs for a buffer which
449 " Find the current markers
450 let [l:is_dummy_sign_set, l:current_sign_list] =
451 \ ale#sign#FindCurrentSigns(a:buffer)
453 " Update the line numbers for items from before which may have moved.
454 call s:UpdateLineNumbers(a:buffer, l:current_sign_list, a:loclist)
456 " Group items after updating the line numbers.
457 let l:grouped_items = s:GroupLoclistItems(a:buffer, a:loclist)
459 " Build a map of current and new signs, with the lines as the keys.
460 let l:sign_map = s:BuildSignMap(
462 \ l:current_sign_list,
466 let l:command_list = ale#sign#GetSignCommands(
468 \ l:is_dummy_sign_set,
472 " Change the sign column color if the option is on.
473 if g:ale_change_sign_column_color && !empty(a:loclist)
474 highlight clear SignColumn
475 highlight link SignColumn ALESignColumnWithErrors
478 for l:command in l:command_list
479 silent! execute l:command
482 " Reset the sign column color when there are no more errors.
483 if g:ale_change_sign_column_color && empty(a:loclist)
484 highlight clear SignColumn
485 highlight link SignColumn ALESignColumnWithoutErrors
490 function! ale#sign#Clear() abort
491 if s:supports_sign_groups
492 sign unplace group=ale_signs *