]> git.madduck.net Git - etc/vim.git/blob - autoload/ale/virtualtext.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 / virtualtext.vim
1 scriptencoding utf-8
2 " Author: w0rp <devw0rp@gmail.com>
3 " Author: Luan Santos <cfcluan@gmail.com>
4 " Description: Shows lint message for the current line as virtualtext, if any
5
6 if !hlexists('ALEVirtualTextError')
7     highlight link ALEVirtualTextError Comment
8 endif
9
10 if !hlexists('ALEVirtualTextStyleError')
11     highlight link ALEVirtualTextStyleError ALEVirtualTextError
12 endif
13
14 if !hlexists('ALEVirtualTextWarning')
15     highlight link ALEVirtualTextWarning Comment
16 endif
17
18 if !hlexists('ALEVirtualTextStyleWarning')
19     highlight link ALEVirtualTextStyleWarning ALEVirtualTextWarning
20 endif
21
22 if !hlexists('ALEVirtualTextInfo')
23     highlight link ALEVirtualTextInfo ALEVirtualTextWarning
24 endif
25
26 let g:ale_virtualtext_prefix =
27 \   get(g:, 'ale_virtualtext_prefix', '%comment% %type%: ')
28 " Controls the milliseconds delay before showing a message.
29 let g:ale_virtualtext_delay = get(g:, 'ale_virtualtext_delay', 10)
30
31 " Controls the positioning of virtualtext
32 let g:ale_virtualtext_column = get(g:, 'ale_virtualtext_column', 0)
33 let g:ale_virtualtext_maxcolumn = get(g:, 'ale_virtualtext_maxcolumn', 0)
34 " If 1, only show the first problem with virtualtext.
35 let g:ale_virtualtext_single = get(g:, 'ale_virtualtext_single', v:true)
36
37 let s:cursor_timer = get(s:, 'cursor_timer', -1)
38 let s:last_pos = get(s:, 'last_pos', [0, 0, 0])
39 let s:hl_list = get(s:, 'hl_list', [])
40 let s:last_message = ''
41
42 if !has_key(s:, 'has_virt_text')
43     let s:has_virt_text = 0
44     let s:emulate_virt = 0
45     let s:last_virt = -1
46
47     if has('nvim-0.3.2')
48         let s:ns_id = nvim_create_namespace('ale')
49         let s:has_virt_text = 1
50     elseif has('textprop') && has('popupwin')
51         let s:has_virt_text = 1
52         let s:emulate_virt = !has('patch-9.0.0297')
53
54         if s:emulate_virt
55             call prop_type_add('ale', {})
56         endif
57     endif
58 endif
59
60 function! s:StopCursorTimer() abort
61     if s:cursor_timer != -1
62         call timer_stop(s:cursor_timer)
63         let s:cursor_timer = -1
64     endif
65 endfunction
66
67 function! ale#virtualtext#ResetDataForTests() abort
68     let s:last_pos = [0, 0, 0]
69     let s:last_message = ''
70 endfunction
71
72 function! ale#virtualtext#GetLastMessageForTests() abort
73     return s:last_message
74 endfunction
75
76 function! ale#virtualtext#GetComment(buffer) abort
77     let l:filetype = getbufvar(a:buffer, '&filetype')
78     let l:split = split(getbufvar(a:buffer, '&commentstring'), '%s')
79
80     return !empty(l:split) ? trim(l:split[0]) : '#'
81 endfunction
82
83 function! ale#virtualtext#Clear(buffer) abort
84     if !s:has_virt_text || !bufexists(str2nr(a:buffer))
85         return
86     endif
87
88     if has('nvim')
89         call nvim_buf_clear_namespace(a:buffer, s:ns_id, 0, -1)
90     else
91         if s:emulate_virt && s:last_virt != -1
92             call prop_remove({'type': 'ale'})
93             call popup_close(s:last_virt)
94             let s:last_virt = -1
95         elseif !empty(s:hl_list)
96             call prop_remove({
97             \   'types': s:hl_list,
98             \   'all': 1,
99             \   'bufnr': a:buffer,
100             \})
101         endif
102     endif
103 endfunction
104
105 function! ale#virtualtext#GetGroup(item) abort
106     let l:type = get(a:item, 'type', 'E')
107     let l:sub_type = get(a:item, 'sub_type', '')
108
109     if l:type is# 'E'
110         if l:sub_type is# 'style'
111             return 'ALEVirtualTextStyleError'
112         endif
113
114         return 'ALEVirtualTextError'
115     endif
116
117     if l:type is# 'W'
118         if l:sub_type is# 'style'
119             return 'ALEVirtualTextStyleWarning'
120         endif
121
122         return 'ALEVirtualTextWarning'
123     endif
124
125     return 'ALEVirtualTextInfo'
126 endfunction
127
128 function! ale#virtualtext#GetColumnPadding(buffer, line) abort
129     let l:mincol = ale#Var(a:buffer, 'virtualtext_column')
130     let l:maxcol = ale#Var(a:buffer, 'virtualtext_maxcolumn')
131     let l:win = bufwinnr(a:buffer)
132
133     if l:mincol[len(l:mincol)-1] is# '%'
134         let l:mincol = (winwidth(l:win) * l:mincol) / 100
135     endif
136
137     if l:maxcol[len(l:maxcol)-1] is# '%'
138         let l:maxcol = (winwidth(l:win) * l:maxcol) / 100
139     endif
140
141     " Calculate padding for virtualtext alignment
142     if l:mincol > 0 || l:maxcol > 0
143         let l:line_width = strdisplaywidth(getline(a:line))
144
145         if l:line_width < l:mincol
146             return l:mincol - l:line_width
147         elseif l:maxcol > 0 && l:line_width >= l:maxcol
148             " Stop processing if virtualtext would start beyond maxcol
149             return -1
150         endif
151     endif
152
153     " no padding.
154     return 0
155 endfunction
156
157 function! ale#virtualtext#ShowMessage(buffer, item) abort
158     if !s:has_virt_text || !bufexists(str2nr(a:buffer))
159         return
160     endif
161
162     let l:line = max([1, a:item.lnum])
163     let l:hl_group = ale#virtualtext#GetGroup(a:item)
164
165     " Get a language-appropriate comment character, or default to '#'.
166     let l:comment = ale#virtualtext#GetComment(a:buffer)
167     let l:prefix = ale#Var(a:buffer, 'virtualtext_prefix')
168     let l:prefix = ale#GetLocItemMessage(a:item, l:prefix)
169     let l:prefix = substitute(l:prefix, '\V%comment%', '\=l:comment', 'g')
170     let l:msg = l:prefix . substitute(a:item.text, '\n', ' ', 'g')
171     let l:col_pad = ale#virtualtext#GetColumnPadding(a:buffer, l:line)
172
173     " Store the last message we're going to set so we can read it in tests.
174     let s:last_message = l:msg
175
176     " Discard virtualtext if padding is negative.
177     if l:col_pad < 0
178         return
179     endif
180
181     if has('nvim')
182         call nvim_buf_set_virtual_text(
183         \   a:buffer,
184         \   s:ns_id, l:line - 1,
185         \   [[l:msg, l:hl_group]],
186         \   {}
187         \)
188     elseif s:emulate_virt
189         let l:left_pad = col('$')
190         call prop_add(l:line, l:left_pad, {'type': 'ale'})
191         let s:last_virt = popup_create(l:msg, {
192         \   'line': -1,
193         \   'padding': [0, 0, 0, 1],
194         \   'mask': [[1, 1, 1, 1]],
195         \   'textprop': 'ale',
196         \   'highlight': l:hl_group,
197         \   'fixed': 1,
198         \   'wrap': 0,
199         \   'zindex': 2
200         \})
201     else
202         let l:type = prop_type_get(l:hl_group)
203
204         if l:type == {}
205             call prop_type_add(l:hl_group, {'highlight': l:hl_group})
206         endif
207
208         " Add highlight groups to the list so we can clear them later.
209         if index(s:hl_list, l:hl_group) == -1
210             call add(s:hl_list, l:hl_group)
211         endif
212
213         " We ignore all errors from prop_add.
214         silent! call prop_add(l:line, 0, {
215         \   'type': l:hl_group,
216         \   'text': ' ' . l:msg,
217         \   'bufnr': a:buffer,
218         \   'text_padding_left': l:col_pad,
219         \})
220     endif
221 endfunction
222
223 function! ale#virtualtext#ShowCursorWarning(...) abort
224     if g:ale_virtualtext_cursor isnot# 'current'
225     \&& g:ale_virtualtext_cursor != 1
226         return
227     endif
228
229     let l:buffer = bufnr('')
230
231     if mode(1) isnot# 'n'
232     \|| g:ale_use_neovim_diagnostics_api
233     \|| ale#ShouldDoNothing(l:buffer)
234         return
235     endif
236
237     let [l:info, l:item] = ale#util#FindItemAtCursor(l:buffer)
238     call ale#virtualtext#Clear(l:buffer)
239
240     if !empty(l:item)
241         call ale#virtualtext#ShowMessage(l:buffer, l:item)
242     endif
243 endfunction
244
245 function! ale#virtualtext#ShowCursorWarningWithDelay() abort
246     let l:buffer = bufnr('')
247
248     if g:ale_virtualtext_cursor isnot# 'current'
249     \&& g:ale_virtualtext_cursor != 1
250         return
251     endif
252
253     call s:StopCursorTimer()
254
255     if mode(1) isnot# 'n'
256     \|| g:ale_use_neovim_diagnostics_api
257         return
258     endif
259
260     let l:pos = getpos('.')[0:2]
261
262     " Check the current buffer, line, and column number against the last
263     " recorded position. If the position has actually changed, *then*
264     " we should show something. Otherwise we can end up doing processing
265     " the show message far too frequently.
266     if l:pos != s:last_pos
267         let l:delay = ale#Var(l:buffer, 'virtualtext_delay')
268
269         let s:last_pos = l:pos
270         let s:cursor_timer = timer_start(
271         \   l:delay,
272         \   function('ale#virtualtext#ShowCursorWarning')
273         \)
274     endif
275 endfunction
276
277 function! ale#virtualtext#CompareSeverityPerLine(left, right) abort
278     " Compare lines
279     if a:left.lnum < a:right.lnum
280         return -1
281     endif
282
283     if a:left.lnum > a:right.lnum
284         return 1
285     endif
286
287     let l:left_priority = ale#util#GetItemPriority(a:left)
288     let l:right_priority = ale#util#GetItemPriority(a:right)
289
290     " Put highest priority items first.
291     if l:left_priority > l:right_priority
292         return -1
293     endif
294
295     if l:left_priority < l:right_priority
296         return 1
297     endif
298
299     " Put the first seen problem first.
300     return a:left.col - a:right.col
301 endfunction
302
303 function! ale#virtualtext#SetTexts(buffer, loclist) abort
304     if !has('nvim') && s:emulate_virt
305         return
306     endif
307
308     call ale#virtualtext#Clear(a:buffer)
309
310     let l:buffer_list = filter(copy(a:loclist), 'v:val.bufnr == a:buffer')
311
312     if ale#Var(a:buffer,'virtualtext_single')
313         " If we want a single problem per line, sort items on each line by
314         " highest severity and then lowest column position, then de-duplicate
315         " the items by line.
316         call uniq(
317         \   sort(l:buffer_list, function('ale#virtualtext#CompareSeverityPerLine')),
318         \   {a, b -> a.lnum - b.lnum}
319         \)
320     endif
321
322     for l:item in l:buffer_list
323         call ale#virtualtext#ShowMessage(a:buffer, l:item)
324     endfor
325 endfunction