]> git.madduck.net Git - etc/vim.git/blob - autoload/ale/sign.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

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.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Squashed '.vim/bundle/ale/' content from commit 22185c4c
[etc/vim.git] / autoload / ale / sign.vim
1 scriptencoding utf8
2 " Author: w0rp <devw0rp@gmail.com>
3 " Description: Draws error and warning signs into signcolumn
4
5 " This flag can be set to some integer to control the maximum number of signs
6 " that ALE will set.
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
9 " there are errors.
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)
25
26 let s:supports_sign_groups = has('nvim-0.4.2') || has('patch-8.1.614')
27
28 if !hlexists('ALEErrorSign')
29     highlight link ALEErrorSign error
30 endif
31
32 if !hlexists('ALEStyleErrorSign')
33     highlight link ALEStyleErrorSign ALEErrorSign
34 endif
35
36 if !hlexists('ALEWarningSign')
37     highlight link ALEWarningSign todo
38 endif
39
40 if !hlexists('ALEStyleWarningSign')
41     highlight link ALEStyleWarningSign ALEWarningSign
42 endif
43
44 if !hlexists('ALEInfoSign')
45     highlight link ALEInfoSign ALEWarningSign
46 endif
47
48 if !hlexists('ALESignColumnWithErrors')
49     highlight link ALESignColumnWithErrors error
50 endif
51
52 function! ale#sign#SetUpDefaultColumnWithoutErrorsHighlight() abort
53     let l:verbose = &verbose
54     set verbose=0
55     let l:output = execute('highlight SignColumn', 'silent')
56     let &verbose = l:verbose
57
58     let l:highlight_syntax = join(split(l:output)[2:])
59     let l:match = matchlist(l:highlight_syntax, '\vlinks to (.+)$')
60
61     if !empty(l:match)
62         execute 'highlight link ALESignColumnWithoutErrors ' . l:match[1]
63     elseif l:highlight_syntax isnot# 'cleared'
64         execute 'highlight ALESignColumnWithoutErrors ' . l:highlight_syntax
65     endif
66 endfunction
67
68 if !hlexists('ALESignColumnWithoutErrors')
69     call ale#sign#SetUpDefaultColumnWithoutErrorsHighlight()
70 endif
71
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')
75 endfunction
76
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
89
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
93     endif
94
95     if !hlexists('ALEStyleErrorSignLineNr')
96         highlight link ALEStyleErrorSignLineNr CursorLineNr
97     endif
98
99     if !hlexists('ALEWarningSignLineNr')
100         highlight link ALEWarningSignLineNr CursorLineNr
101     endif
102
103     if !hlexists('ALEStyleWarningSignLineNr')
104         highlight link ALEStyleWarningSignLineNr CursorLineNr
105     endif
106
107     if !hlexists('ALEInfoSignLineNr')
108         highlight link ALEInfoSignLineNr CursorLineNr
109     endif
110
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
116 endif
117
118 function! ale#sign#GetSignName(sublist) abort
119     let l:priority = g:ale#util#style_warning_priority
120
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)
124
125         if l:item_priority > l:priority
126             let l:priority = l:item_priority
127         endif
128     endfor
129
130     if l:priority is# g:ale#util#error_priority
131         return 'ALEErrorSign'
132     endif
133
134     if l:priority is# g:ale#util#warning_priority
135         return 'ALEWarningSign'
136     endif
137
138     if l:priority is# g:ale#util#style_error_priority
139         return 'ALEStyleErrorSign'
140     endif
141
142     if l:priority is# g:ale#util#style_warning_priority
143         return 'ALEStyleWarningSign'
144     endif
145
146     if l:priority is# g:ale#util#info_priority
147         return 'ALEInfoSign'
148     endif
149
150     " Use the error sign for invalid severities.
151     return 'ALEErrorSign'
152 endfunction
153
154 function! s:PriorityCmd() abort
155     if s:supports_sign_groups
156         return ' priority=' . g:ale_sign_priority . ' '
157     else
158         return ''
159     endif
160 endfunction
161
162 function! s:GroupCmd() abort
163     if s:supports_sign_groups
164         return ' group=ale_signs '
165     else
166         return ' '
167     endif
168 endfunction
169
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
175     \ )
176
177     return split(l:output, "\n")
178 endfunction
179
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)'
190     else
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)'
199     endif
200
201     return l:pattern
202 endfunction
203
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
207     let l:result = []
208     let l:is_dummy_sign_set = 0
209
210     for l:sign in l:signs
211         if l:sign['name'] is# 'ALEDummySign'
212             let l:is_dummy_sign_set = 1
213         else
214             call add(l:result, [
215             \   str2nr(l:sign['lnum']),
216             \   str2nr(l:sign['id']),
217             \   l:sign['name'],
218             \])
219         endif
220     endfor
221
222     return [l:is_dummy_sign_set, l:result]
223 endfunction
224
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()
228     let l:result = []
229     let l:is_dummy_sign_set = 0
230
231     for l:line in a:line_list
232         let l:match = matchlist(l:line, l:pattern)
233
234         if len(l:match) > 0
235             if l:match[3] is# 'ALEDummySign'
236                 let l:is_dummy_sign_set = 1
237             else
238                 call add(l:result, [
239                 \   str2nr(l:match[1]),
240                 \   str2nr(l:match[2]),
241                 \   l:match[3],
242                 \])
243             endif
244         endif
245     endfor
246
247     return [l:is_dummy_sign_set, l:result]
248 endfunction
249
250 function! ale#sign#FindCurrentSigns(buffer) abort
251     if exists('*sign_getplaced')
252         return ale#sign#ParseSignsWithGetPlaced(a:buffer)
253     else
254         let l:line_list = ale#sign#ReadSigns(a:buffer)
255
256         return ale#sign#ParseSigns(l:line_list)
257     endif
258 endfunction
259
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 = []
263     let l:last_lnum = -1
264
265     for l:obj in a:loclist
266         if l:obj.bufnr != a:buffer
267             continue
268         endif
269
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, [])
273         endif
274
275         call add(l:grouped_items[-1], l:obj)
276         let l:last_lnum = l:obj.lnum
277     endfor
278
279     return l:grouped_items
280 endfunction
281
282 function! s:UpdateLineNumbers(buffer, current_sign_list, loclist) abort
283     let l:line_map = {}
284     let l:line_numbers_changed = 0
285
286     for [l:line, l:sign_id, l:name] in a:current_sign_list
287         let l:line_map[l:sign_id] = l:line
288     endfor
289
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)
293
294             if l:lnum && l:item.lnum != l:lnum
295                 let l:item.lnum = l:lnum
296                 let l:line_numbers_changed = 1
297             endif
298         endif
299     endfor
300
301     " When the line numbers change, sort the list again
302     if l:line_numbers_changed
303         call sort(a:loclist, 'ale#util#LocItemCompare')
304     endif
305 endfunction
306
307 function! s:BuildSignMap(buffer, current_sign_list, grouped_items) abort
308     let l:max_signs = ale#Var(a:buffer, 'max_signs')
309
310     if l:max_signs is 0
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]
314     else
315         let l:selected_grouped_items = a:grouped_items
316     endif
317
318     let l:sign_map = {}
319     let l:sign_offset = g:ale_sign_offset
320
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': [],
325         \   'new_id': 0,
326         \   'new_name': '',
327         \   'items': [],
328         \})
329
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
333         endif
334
335         " Remember the sign names and IDs in separate Lists, so they are easy
336         " to work with.
337         call add(l:sign_info.current_id_list, l:sign_id)
338         call add(l:sign_info.current_name_list, l:name)
339
340         let l:sign_map[l:line] = l:sign_info
341     endfor
342
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': [],
348         \   'new_id': 0,
349         \   'new_name': '',
350         \   'items': [],
351         \})
352
353         let l:sign_info.new_name = ale#sign#GetSignName(l:group)
354         let l:sign_info.items = l:group
355
356         let l:index = index(
357         \   l:sign_info.current_name_list,
358         \   l:sign_info.new_name
359         \)
360
361         if l:index >= 0
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]
364         else
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
368         endif
369
370         let l:sign_map[l:line] = l:sign_info
371     endfor
372
373     return l:sign_map
374 endfunction
375
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
379
380     " Set the dummy sign if we need to.
381     " The dummy sign is needed to keep the sign column open while we add
382     " and remove signs.
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
386         \   . s:GroupCmd()
387         \   . s:PriorityCmd()
388         \   . ' line=1 name=ALEDummySign '
389         \   . ' buffer=' . a:buffer
390         \)
391         let l:is_dummy_sign_set = 1
392     endif
393
394     " Place new items first.
395     for [l:line_str, l:info] in items(a:sign_map)
396         if l:info.new_id
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
401             endfor
402
403             if index(l:info.current_id_list, l:info.new_id) < 0
404                 call add(l:command_list, 'sign place '
405                 \   . (l:info.new_id)
406                 \   . s:GroupCmd()
407                 \   . s:PriorityCmd()
408                 \   . ' line=' . l:line_str
409                 \   . ' name=' . (l:info.new_name)
410                 \   . ' buffer=' . a:buffer
411                 \)
412             endif
413         endif
414     endfor
415
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 '
421                 \   . l:current_id
422                 \   . s:GroupCmd()
423                 \   . ' buffer=' . a:buffer
424                 \)
425             endif
426         endfor
427     endfor
428
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
433         \   . s:GroupCmd()
434         \   . ' buffer=' . a:buffer
435         \)
436     endif
437
438     return l:command_list
439 endfunction
440
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
445         " does not exist.
446         return
447     endif
448
449     " Find the current markers
450     let [l:is_dummy_sign_set, l:current_sign_list] =
451     \   ale#sign#FindCurrentSigns(a:buffer)
452
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)
455
456     " Group items after updating the line numbers.
457     let l:grouped_items = s:GroupLoclistItems(a:buffer, a:loclist)
458
459     " Build a map of current and new signs, with the lines as the keys.
460     let l:sign_map = s:BuildSignMap(
461     \   a:buffer,
462     \   l:current_sign_list,
463     \   l:grouped_items,
464     \)
465
466     let l:command_list = ale#sign#GetSignCommands(
467     \   a:buffer,
468     \   l:is_dummy_sign_set,
469     \   l:sign_map,
470     \)
471
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
476     endif
477
478     for l:command in l:command_list
479         silent! execute l:command
480     endfor
481
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
486     endif
487 endfunction
488
489 " Remove all signs.
490 function! ale#sign#Clear() abort
491     if s:supports_sign_groups
492         sign unplace group=ale_signs *
493     else
494         sign unplace *
495     endif
496 endfunction