| 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 " }}} |