summaryrefslogtreecommitdiff
path: root/autoload/ale/highlight.vim
blob: 7807c8da5f57bce53507cc39530f0f4e864fcdda (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
scriptencoding utf8
" Author: w0rp <devw0rp@gmail.com>
" Description: This module implements error/warning highlighting.

if !hlexists('ALEError')
    highlight link ALEError SpellBad
endif

if !hlexists('ALEStyleError')
    highlight link ALEStyleError ALEError
endif

if !hlexists('ALEWarning')
    highlight link ALEWarning SpellCap
endif

if !hlexists('ALEStyleWarning')
    highlight link ALEStyleWarning ALEWarning
endif

if !hlexists('ALEInfo')
    highlight link ALEInfo ALEWarning
endif

" This map holds highlights to be set when buffers are opened.
" We can only set highlights for whatever the current buffer is, so we will
" wait until the buffer is entered again to show the highlights, unless
" the buffer is in focus when linting completes.
let s:buffer_highlights = {}
let s:buffer_restore_map = {}
" The maximum number of items for the second argument of matchaddpos()
let s:MAX_POS_VALUES = 8
let s:MAX_COL_SIZE = 4294967296

function! ale#highlight#CreatePositions(line, col, end_line, end_col) abort
    if a:line >= a:end_line
        " For single lines, just return the one position.
        return [[[a:line, a:col, a:end_col - a:col + 1]]]
    endif

    " Get positions from the first line at the first column, up to a large
    " integer for highlighting up to the end of the line, followed by
    " the lines in-between, for highlighting entire lines, and
    " a highlight for the last line, up to the end column.
    let l:all_positions =
    \   [[a:line, a:col, s:MAX_COL_SIZE]]
    \   + range(a:line + 1, a:end_line - 1)
    \   + [[a:end_line, 1, a:end_col]]

    return map(
    \   range(0, len(l:all_positions) - 1, s:MAX_POS_VALUES),
    \   'l:all_positions[v:val : v:val + s:MAX_POS_VALUES - 1]',
    \)
endfunction

function! ale#highlight#UnqueueHighlights(buffer) abort
    if has_key(s:buffer_highlights, a:buffer)
        call remove(s:buffer_highlights, a:buffer)
    endif

    if has_key(s:buffer_restore_map, a:buffer)
        call remove(s:buffer_restore_map, a:buffer)
    endif
endfunction

function! s:GetALEMatches() abort
    let l:list = []

    for l:match in getmatches()
        if l:match['group'] ==# 'ALEError' || l:match['group'] ==# 'ALEWarning'
            call add(l:list, l:match)
        endif
    endfor

    return l:list
endfunction

function! s:GetCurrentMatchIDs(loclist) abort
    let l:current_id_map = {}

    for l:item in a:loclist
        for l:id in get(l:item, 'match_id_list', [])
            let l:current_id_map[l:id] = 1
        endfor
    endfor

    return l:current_id_map
endfunction

" Given a loclist for current items to highlight, remove all highlights
" except these which have matching loclist item entries.
function! ale#highlight#RemoveHighlights(loclist) abort
    let l:current_id_map = s:GetCurrentMatchIDs(a:loclist)

    for l:match in s:GetALEMatches()
        if !has_key(l:current_id_map, l:match.id)
            call matchdelete(l:match.id)
        endif
    endfor
endfunction

function! ale#highlight#UpdateHighlights() abort
    let l:buffer = bufnr('%')
    let l:has_new_items = has_key(s:buffer_highlights, l:buffer)
    let l:loclist = l:has_new_items ? remove(s:buffer_highlights, l:buffer) : []

    if l:has_new_items || !g:ale_enabled
        call ale#highlight#RemoveHighlights(l:loclist)
    endif

    " Remove anything with a current match_id
    call filter(l:loclist, '!has_key(v:val, ''match_id_list'')')

    " Restore items from the map of hidden items,
    " if we don't have some new items to set already.
    if empty(l:loclist) && has_key(s:buffer_restore_map, l:buffer)
        let l:loclist = s:buffer_restore_map[l:buffer]
    endif

    if g:ale_enabled
        for l:item in l:loclist
            let l:col = l:item.col

            if l:item.type ==# 'W'
                if get(l:item, 'sub_type', '') ==# 'style'
                    let l:group = 'ALEStyleWarning'
                else
                    let l:group = 'ALEWarning'
                endif
            elseif l:item.type ==# 'I'
                let l:group = 'ALEInfo'
            elseif get(l:item, 'sub_type', '') ==# 'style'
                let l:group = 'ALEStyleError'
            else
                let l:group = 'ALEError'
            endif

            let l:line = l:item.lnum
            let l:size = has_key(l:item, 'end_col') ? l:item.end_col - l:col + 1 : 1

            " Rememeber the match ID for the item.
            " This ID will be used to preserve loclist items which are set
            " many times.
            let l:item.match_id_list = [matchaddpos(l:group, [[l:line, l:col, l:size]])]
        endfor
    endif
endfunction

function! ale#highlight#BufferHidden(buffer) abort
    let l:loclist = get(g:ale_buffer_info, a:buffer, {'loclist': []}).loclist

    " Remember loclist items, so they can be restored later.
    if !empty(l:loclist)
        " Remove match_ids, as they must be re-calculated when buffers are
        " shown again.
        for l:item in l:loclist
            if has_key(l:item, 'match_id_list')
                call remove(l:item, 'match_id_list')
            endif
        endfor

        let s:buffer_restore_map[a:buffer] = l:loclist
        call clearmatches()
    endif
endfunction

augroup ALEHighlightBufferGroup
    autocmd!
    autocmd BufEnter * call ale#highlight#UpdateHighlights()
    autocmd BufHidden * call ale#highlight#BufferHidden(expand('<abuf>'))
augroup END

function! ale#highlight#SetHighlights(buffer, loclist) abort
    " Only set set items for the buffer if ALE is enabled.
    if g:ale_enabled
        " Set a list of items to be set as highlights for a buffer when
        " we next open it.
        "
        " We'll filter the loclist down to items we can set now.
        let s:buffer_highlights[a:buffer] = filter(
        \   copy(a:loclist),
        \   'v:val.bufnr == a:buffer && v:val.col > 0'
        \)

        " Update highlights for the current buffer, which may or may not
        " be the buffer we just set highlights for.
        call ale#highlight#UpdateHighlights()
    endif
endfunction