]>
Commit | Line | Data |
---|---|---|
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 " }}} |