]> git.r.bdr.sh - rbdr/dotfiles/blob - vim/autoload/Pl/Parser.vim
Add DS_Store to gitignore
[rbdr/dotfiles] / vim / autoload / Pl / Parser.vim
1 let g:Pl#Parser#Symbols = {
2 \ 'compatible': {
3 \ 'dividers': [ '', [0x2502], '', [0x2502] ]
4 \ , 'symbols' : {
5 \ 'BRANCH': 'BR:'
6 \ , 'RO' : 'RO'
7 \ , 'FT' : 'FT'
8 \ , 'LINE' : 'LN'
9 \ , 'COL' : 'C'
10 \ }
11 \ },
12 \ 'unicode': {
13 \ 'dividers': [ [0x25b6], [0x276f], [0x25c0], [0x276e] ]
14 \ , 'symbols' : {
15 \ 'BRANCH': [0x26a1]
16 \ , 'RO' : [0x2613]
17 \ , 'FT' : [0x2691]
18 \ , 'LINE' : [0x204b]
19 \ , 'COL' : [0x2551]
20 \ },
21 \ },
22 \ 'fancy': {
23 \ 'dividers': [ [0x2b80], [0x2b81], [0x2b82], [0x2b83] ]
24 \ , 'symbols' : {
25 \ 'BRANCH': [0x2b60]
26 \ , 'RO' : [0x2b64]
27 \ , 'FT' : [0x2b62, 0x2b63]
28 \ , 'LINE' : [0x2b61]
29 \ , 'COL' : [0x2551]
30 \ }
31 \ }
32 \ }
33
34 let s:LEFT_SIDE = 0
35 let s:RIGHT_SIDE = 2
36
37 let s:PADDING = 1
38
39 let s:EMPTY_SEGMENT = { 'type': 'empty' }
40
41 let s:HARD_DIVIDER = 0
42 let s:SOFT_DIVIDER = 1
43
44 function! Pl#Parser#GetStatusline(segments) " {{{
45 let statusline = {
46 \ 'n': ''
47 \ , 'N': ''
48 \ , 'v': ''
49 \ , 'i': ''
50 \ , 'r': ''
51 \ , 's': ''
52 \ }
53
54 " Run through the different modes and create the statuslines
55 for mode in keys(statusline)
56 " Create an empty statusline list
57 let stl = []
58
59 call extend(stl, s:ParseSegments(mode, s:LEFT_SIDE, a:segments))
60
61 let statusline[mode] .= join(stl, '')
62 endfor
63
64 return statusline
65 endfunction " }}}
66 function! Pl#Parser#ParseChars(arg) " {{{
67 " Handles symbol arrays which can be either an array of hex values,
68 " or a string. Will convert the hex array to a string, or return the
69 " string as-is.
70 let arg = a:arg
71
72 if type(arg) == type([])
73 " Hex array
74 call map(arg, 'nr2char(v:val)')
75
76 return join(arg, '')
77 endif
78
79 " Anything else, just return it as it is
80 return arg
81 endfunction " }}}
82 function! s:ParseSegments(mode, side, segments, ...) " {{{
83 let mode = a:mode
84 let side = a:side
85 let segments = a:segments
86
87 let level = exists('a:1') ? a:1 : 0
88 let base_color = exists('a:2') ? a:2 : {}
89
90 let ret = []
91
92 for i in range(0, len(segments) - 1)
93 unlet! seg_prev seg_curr seg_next
94
95 " Prepare some resources (fetch previous, current and next segment)
96 let seg_curr = deepcopy(get(segments, i))
97
98 " Find previous segment
99 let seg_prev = s:EMPTY_SEGMENT
100
101 " If we're currently at i = 0 we have to start on 0 or else we will start on the last segment (list[-1])
102 let range_start = (i == 0 ? i : i - 1)
103 for j in range(range_start, 0, -1)
104 let seg = deepcopy(get(segments, j))
105 if get(seg, 'name') ==# 'TRUNCATE'
106 " Skip truncate segments
107 continue
108 endif
109
110 " Look ahead for another segment that's visible in this mode
111 if index(get(seg, 'modes'), mode) != -1
112 " Use this segment
113 let seg_prev = seg
114
115 break
116 endif
117 endfor
118
119 "" Find next segment
120 let seg_next = s:EMPTY_SEGMENT
121
122 " If we're currently at i = len(segments) - 1 we have to start on i or else we will get an error because the index doesn't exist
123 let range_start = (i == len(segments) - 1 ? i : i + 1)
124 for j in range(range_start, len(segments) - 1, 1)
125 let seg = deepcopy(get(segments, j))
126 if get(seg, 'name') ==# 'TRUNCATE'
127 " Skip truncate segments
128 continue
129 endif
130
131 " Look ahead for another segment that's visible in this mode
132 if index(get(seg, 'modes'), mode) != -1
133 " Use this segment
134 let seg_next = seg
135
136 break
137 endif
138 endfor
139
140 if index(get(seg_curr, 'modes', []), mode) == -1
141 " The segment is invisible in this mode, skip it
142 " FIXME When two segments after each other are hidden, a gap appears where the segments would be, this is probably due to segment padding
143 continue
144 endif
145
146 " Handle the different segment types
147 if seg_curr.type == 'segment'
148 if seg_curr.name ==# 'TRUNCATE'
149 " Truncate statusline
150 call add(ret, '%<')
151 elseif seg_curr.name ==# 'SPLIT'
152 " Split statusline
153
154 " Switch sides
155 let side = s:RIGHT_SIDE
156
157 " Handle highlighting
158 let mode_colors = get(seg_curr.colors, mode, seg_curr.colors['n'])
159 let hl_group = s:HlCreate(mode_colors)
160
161 " Add segment text
162 call add(ret, '%#'. hl_group .'#%=')
163 else
164 " Add segment text
165 let text = seg_curr.text
166
167 " Decide on whether to use the group's colors or the segment's colors
168 let colors = get(seg_curr, 'colors', base_color)
169
170 " Fallback to normal (current) highlighting if we don't have mode-specific highlighting
171 let mode_colors = get(colors, mode, get(colors, 'n', {}))
172
173 if empty(mode_colors)
174 echom 'Segment doesn''t have any colors! NS: "'. seg_curr.ns .'" SEG: "'. seg_curr.name .'"'
175
176 continue
177 endif
178
179 " Check if we're in a group (level > 0)
180 if level > 0
181 " If we're in a group we don't have dividers between segments, so we should only pad one side
182 let padding_right = (side == s:LEFT_SIDE ? repeat(' ', s:PADDING) : '')
183 let padding_left = (side == s:RIGHT_SIDE ? repeat(' ', s:PADDING) : '')
184
185 " Check if we lack a bg/fg color for this segment
186 " If we do, use the bg/fg color from base_color
187 let base_color_mode = ! has_key(base_color, mode) ? base_color['n'] : base_color[mode]
188
189 for col in ['ctermbg', 'ctermfg', 'guibg', 'guifg']
190 if empty(mode_colors[col])
191 let mode_colors[col] = base_color_mode[col]
192 endif
193 endfor
194 else
195 "" If we're outside a group we have dividers and must pad both sides
196 let padding_left = repeat(' ', s:PADDING)
197 let padding_right = repeat(' ', s:PADDING)
198 endif
199
200 " Get main hl group for segment
201 let hl_group = s:HlCreate(mode_colors)
202
203 " Prepare segment text
204 let text = '%(%#'. hl_group .'#'. padding_left . text . padding_right . '%)'
205
206 if level == 0
207 " Add divider to single segments
208 let text = s:AddDivider(text, side, mode, colors, seg_prev, seg_curr, seg_next)
209 endif
210
211 call add(ret, text)
212 endif
213 elseif seg_curr.type == 'segment_group'
214 " Recursively parse segment group
215 let func_params = [mode, side, seg_curr.segments, level + 1]
216
217 if has_key(seg_curr, 'colors')
218 " Pass the base colors on to the child segments
219 call add(func_params, seg_curr.colors)
220 endif
221
222 " Get segment group string
223 let text = join(call('s:ParseSegments', func_params), '')
224
225 " Pad on the opposite side of the divider
226 let padding_right = (side == s:RIGHT_SIDE ? repeat(' ', s:PADDING) : '')
227 let padding_left = (side == s:LEFT_SIDE ? repeat(' ', s:PADDING) : '')
228
229 let text = s:AddDivider(padding_left . text . padding_right, side, mode, seg_curr.colors, seg_prev, seg_curr, seg_next)
230
231 call add(ret, text)
232 endif
233 endfor
234
235 return ret
236 endfunction " }}}
237 function! s:HlCreate(hl) " {{{
238 " Create a short and unique highlighting group name
239 " It uses the hex values of all the color properties and an attribute flag at the end
240 " NONE colors are translated to NN for cterm and NNNNNN for gui colors
241 let hi_group = printf('Pl%s%s%s%s%s'
242 \ , (a:hl['ctermfg'] == 'NONE' ? 'NN' : printf('%02x', a:hl['ctermfg']))
243 \ , (a:hl['guifg'] == 'NONE' ? 'NNNNNN' : printf('%06x', a:hl['guifg'] ))
244 \ , (a:hl['ctermbg'] == 'NONE' ? 'NN' : printf('%02x', a:hl['ctermbg']))
245 \ , (a:hl['guibg'] == 'NONE' ? 'NNNNNN' : printf('%06x', a:hl['guibg'] ))
246 \ , substitute(a:hl['attr'], '\v([a-zA-Z])[a-zA-Z]*,?', '\1', 'g')
247 \ )
248
249 if ! s:HlExists(hi_group)
250 let ctermbg = a:hl['ctermbg'] == 'NONE' ? 'NONE' : printf('%d', a:hl['ctermbg'])
251 if (has('win32') || has('win64')) && !has('gui_running') && ctermbg != 'NONE' && ctermbg > 128
252 let ctermbg -= 128
253 endif
254 let hi_cmd = printf('hi %s ctermfg=%s ctermbg=%s cterm=%s guifg=%s guibg=%s gui=%s'
255 \ , hi_group
256 \ , a:hl['ctermfg'] == 'NONE' ? 'NONE' : printf('%d', a:hl['ctermfg'])
257 \ , ctermbg
258 \ , a:hl['attr']
259 \ , (a:hl['guifg'] == 'NONE' ? 'NONE' : printf('#%06x', a:hl['guifg']))
260 \ , (a:hl['guibg'] == 'NONE' ? 'NONE' : printf('#%06x', a:hl['guibg']))
261 \ , a:hl['attr']
262 \ )
263
264 exec hi_cmd
265
266 " Add command to Pl#HL list for caching
267 call add(g:Pl#HL, hi_cmd)
268 endif
269
270 " Return only the highlighting group name
271 return hi_group
272 endfunction " }}}
273 function! s:HlExists(hl) " {{{
274 if ! hlexists(a:hl)
275 return 0
276 endif
277
278 redir => hlstatus
279 silent exec 'hi' a:hl
280 redir END
281
282 return (hlstatus !~ 'cleared')
283 endfunction " }}}
284 function! s:AddDivider(text, side, mode, colors, prev, curr, next) " {{{
285 let seg_prev = a:prev
286 let seg_curr = a:curr
287 let seg_next = a:next
288
289 " Set default color/type for the divider
290 let div_colors = get(a:colors, a:mode, a:colors['n'])
291 let div_type = s:SOFT_DIVIDER
292
293 " Define segment to compare
294 let cmp_seg = a:side == s:LEFT_SIDE ? seg_next : seg_prev
295
296 let cmp_all_colors = get(cmp_seg, 'colors', {})
297 let cmp_colors = get(cmp_all_colors, a:mode, get(cmp_all_colors, 'n', {}))
298
299 if ! empty(cmp_colors)
300 " Compare the highlighting groups
301 "
302 " If the background color for cterm is equal, use soft divider with the current segment's highlighting
303 " If not, use hard divider with a new highlighting group
304 "
305 " Note that if the previous/next segment is the split, a hard divider is always used
306 if get(div_colors, 'ctermbg') != get(cmp_colors, 'ctermbg') || get(seg_next, 'name') ==# 'SPLIT' || get(seg_prev, 'name') ==# 'SPLIT'
307 let div_type = s:HARD_DIVIDER
308
309 " Create new highlighting group
310 " Use FG = CURRENT BG, BG = CMP BG
311 let div_colors['ctermfg'] = get(div_colors, 'ctermbg')
312 let div_colors['guifg'] = get(div_colors, 'guibg')
313
314 let div_colors['ctermbg'] = get(cmp_colors, 'ctermbg')
315 let div_colors['guibg'] = get(cmp_colors, 'guibg')
316
317 let div_colors['attr'] = 'NONE'
318 endif
319 endif
320
321 " Prepare divider
322 let divider_raw = deepcopy(g:Pl#Parser#Symbols[g:Powerline_symbols].dividers[a:side + div_type])
323 let divider = Pl#Parser#ParseChars(divider_raw)
324
325 " Don't add dividers for segments adjacent to split (unless it's a hard divider)
326 if ((get(seg_next, 'name') ==# 'SPLIT' || get(seg_prev, 'name') ==# 'SPLIT') && div_type != s:HARD_DIVIDER)
327 return ''
328 endif
329
330 if a:side == s:LEFT_SIDE
331 " Left side
332 " Divider to the right
333 return printf('%%(%s%%#%s#%s%%)', a:text, s:HlCreate(div_colors), divider)
334 else
335 " Right side
336 " Divider to the left
337 return printf('%%(%%#%s#%s%s%%)', s:HlCreate(div_colors), divider, a:text)
338 endif
339 endfunction " }}}