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 " Author: Luan Santos <cfcluan@gmail.com>
4 " Description: Shows lint message for the current line as virtualtext, if any
6 if !hlexists('ALEVirtualTextError')
7 highlight link ALEVirtualTextError Comment
10 if !hlexists('ALEVirtualTextStyleError')
11 highlight link ALEVirtualTextStyleError ALEVirtualTextError
14 if !hlexists('ALEVirtualTextWarning')
15 highlight link ALEVirtualTextWarning Comment
18 if !hlexists('ALEVirtualTextStyleWarning')
19 highlight link ALEVirtualTextStyleWarning ALEVirtualTextWarning
22 if !hlexists('ALEVirtualTextInfo')
23 highlight link ALEVirtualTextInfo ALEVirtualTextWarning
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)
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)
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 = ''
42 if !has_key(s:, 'has_virt_text')
43 let s:has_virt_text = 0
44 let s:emulate_virt = 0
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')
55 call prop_type_add('ale', {})
60 function! s:StopCursorTimer() abort
61 if s:cursor_timer != -1
62 call timer_stop(s:cursor_timer)
63 let s:cursor_timer = -1
67 function! ale#virtualtext#ResetDataForTests() abort
68 let s:last_pos = [0, 0, 0]
69 let s:last_message = ''
72 function! ale#virtualtext#GetLastMessageForTests() abort
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')
80 return !empty(l:split) ? trim(l:split[0]) : '#'
83 function! ale#virtualtext#Clear(buffer) abort
84 if !s:has_virt_text || !bufexists(str2nr(a:buffer))
89 call nvim_buf_clear_namespace(a:buffer, s:ns_id, 0, -1)
91 if s:emulate_virt && s:last_virt != -1
92 call prop_remove({'type': 'ale'})
93 call popup_close(s:last_virt)
95 elseif !empty(s:hl_list)
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', '')
110 if l:sub_type is# 'style'
111 return 'ALEVirtualTextStyleError'
114 return 'ALEVirtualTextError'
118 if l:sub_type is# 'style'
119 return 'ALEVirtualTextStyleWarning'
122 return 'ALEVirtualTextWarning'
125 return 'ALEVirtualTextInfo'
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)
133 if l:mincol[len(l:mincol)-1] is# '%'
134 let l:mincol = (winwidth(l:win) * l:mincol) / 100
137 if l:maxcol[len(l:maxcol)-1] is# '%'
138 let l:maxcol = (winwidth(l:win) * l:maxcol) / 100
141 " Calculate padding for virtualtext alignment
142 if l:mincol > 0 || l:maxcol > 0
143 let l:line_width = strdisplaywidth(getline(a:line))
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
157 function! ale#virtualtext#ShowMessage(buffer, item) abort
158 if !s:has_virt_text || !bufexists(str2nr(a:buffer))
162 let l:line = max([1, a:item.lnum])
163 let l:hl_group = ale#virtualtext#GetGroup(a:item)
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)
173 " Store the last message we're going to set so we can read it in tests.
174 let s:last_message = l:msg
176 " Discard virtualtext if padding is negative.
182 call nvim_buf_set_virtual_text(
184 \ s:ns_id, l:line - 1,
185 \ [[l:msg, l:hl_group]],
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, {
193 \ 'padding': [0, 0, 0, 1],
194 \ 'mask': [[1, 1, 1, 1]],
196 \ 'highlight': l:hl_group,
202 let l:type = prop_type_get(l:hl_group)
205 call prop_type_add(l:hl_group, {'highlight': l:hl_group})
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)
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,
218 \ 'text_padding_left': l:col_pad,
223 function! ale#virtualtext#ShowCursorWarning(...) abort
224 if g:ale_virtualtext_cursor isnot# 'current'
225 \&& g:ale_virtualtext_cursor != 1
229 let l:buffer = bufnr('')
231 if mode(1) isnot# 'n'
232 \|| g:ale_use_neovim_diagnostics_api
233 \|| ale#ShouldDoNothing(l:buffer)
237 let [l:info, l:item] = ale#util#FindItemAtCursor(l:buffer)
238 call ale#virtualtext#Clear(l:buffer)
241 call ale#virtualtext#ShowMessage(l:buffer, l:item)
245 function! ale#virtualtext#ShowCursorWarningWithDelay() abort
246 let l:buffer = bufnr('')
248 if g:ale_virtualtext_cursor isnot# 'current'
249 \&& g:ale_virtualtext_cursor != 1
253 call s:StopCursorTimer()
255 if mode(1) isnot# 'n'
256 \|| g:ale_use_neovim_diagnostics_api
260 let l:pos = getpos('.')[0:2]
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')
269 let s:last_pos = l:pos
270 let s:cursor_timer = timer_start(
272 \ function('ale#virtualtext#ShowCursorWarning')
277 function! ale#virtualtext#CompareSeverityPerLine(left, right) abort
279 if a:left.lnum < a:right.lnum
283 if a:left.lnum > a:right.lnum
287 let l:left_priority = ale#util#GetItemPriority(a:left)
288 let l:right_priority = ale#util#GetItemPriority(a:right)
290 " Put highest priority items first.
291 if l:left_priority > l:right_priority
295 if l:left_priority < l:right_priority
299 " Put the first seen problem first.
300 return a:left.col - a:right.col
303 function! ale#virtualtext#SetTexts(buffer, loclist) abort
304 if !has('nvim') && s:emulate_virt
308 call ale#virtualtext#Clear(a:buffer)
310 let l:buffer_list = filter(copy(a:loclist), 'v:val.bufnr == a:buffer')
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
317 \ sort(l:buffer_list, function('ale#virtualtext#CompareSeverityPerLine')),
318 \ {a, b -> a.lnum - b.lnum}
322 for l:item in l:buffer_list
323 call ale#virtualtext#ShowMessage(a:buffer, l:item)