diff --git a/examples/terminal/powerline.py b/examples/terminal/powerline.py index 0e6ab9fc..d16cdb4f 100755 --- a/examples/terminal/powerline.py +++ b/examples/terminal/powerline.py @@ -5,7 +5,7 @@ import os import sys -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) from lib.core import Powerline, Segment from lib.renderers import TerminalSegmentRenderer diff --git a/examples/vim/powerline.py b/examples/vim/powerline.py index 9da9973e..dda44632 100644 --- a/examples/vim/powerline.py +++ b/examples/vim/powerline.py @@ -2,9 +2,8 @@ import vim import os -import re -from lib.core import Powerline, Segment +from lib.core import Powerline, mksegment from lib.renderers import VimSegmentRenderer modes = { @@ -28,6 +27,9 @@ modes = { '!': 'SHELL', } +# We need to replace this private use glyph with a double-percent later +percent_placeholder = u'' + if hasattr(vim, 'bindeval'): # This branch is used to avoid invoking vim parser as much as possible @@ -70,103 +72,119 @@ vim_funcs = { 'col': get_vim_func('col', rettype=int), 'expand': get_vim_func('expand'), 'tbcurtag': get_vim_func('tagbar#currenttag'), - 'sstlflag': get_vim_func('SyntasticStatuslineFlag'), 'hlexists': get_vim_func('hlexists', rettype=int), } +getwinvar = get_vim_func('getwinvar') +setwinvar = get_vim_func('setwinvar') -def statusline(): - winwidth = vim_funcs['winwidth'](0) - # Prepare segment contents +def statusline(winnr): + winwidth = vim_funcs['winwidth'](winnr) + + current = getwinvar(winnr, 'current') + windata = getwinvar(winnr, 'powerline') + + if current: + # Recreate segment data for each redraw if we're in the current window + line_current = vim_funcs['line']('.') + line_end = vim_funcs['line']('$') + line_percent = line_current * 100 // line_end + + try: + branch = vim_funcs['fghead'](5) + except vim.error: + vim_funcs['fghead'] = None + branch = '' + except TypeError: + branch = '' + if branch: + branch = u'⭠ ' + branch + + # Fun gradient colored percent segment + line_percent_gradient = [160, 166, 172, 178, 184, 190] + line_percent_color = line_percent_gradient[int((len(line_percent_gradient) - 1) * line_percent / 100)] + + col_current = vim_funcs['col']('.') + + filepath, filename = os.path.split(vim_funcs['expand']('%:~:.')) + filename_color = 231 + if filepath: + filepath += os.sep + + if not filename: + filename = '[No Name]' + filename_color = 250 + + readonly = vim.eval('&ro ? "⭤ " : ""') + modified = vim.eval('&mod ? " +" : ""') + + try: + currenttag = vim_funcs['tbcurtag']('%s', '') + except vim.error: + vim_funcs['tbcurtag'] = None + currenttag = '' + except TypeError: + currenttag = '' + + windata = { + 'paste': vim.eval('&paste ? "PASTE" : ""'), + 'branch': branch, + 'readonly': readonly, + 'filepath': filepath, + 'filename': filename, + 'filename_color': filename_color, + 'modified': modified, + 'currenttag': currenttag, + 'fileformat': vim.eval('&ff'), + 'fileencoding': vim.eval('&fenc'), + 'filetype': vim.eval('&ft'), + 'line_percent': str(line_percent).rjust(3) + percent_placeholder, + 'line_percent_color': line_percent_color, + 'linecurrent': str(line_current).rjust(3), + 'colcurrent': ':' + str(col_current).ljust(2), + } + + setwinvar(winnr, 'powerline', windata) + mode = modes[vim_funcs['mode']()] - try: - branch = vim_funcs['fghead'](5) - except vim.error: - vim_funcs['fghead'] = None - branch = '' - except TypeError: - branch = '' - if branch: - branch = '⭠ ' + branch - - line_current = vim_funcs['line']('.') - line_end = vim_funcs['line']('$') - line_percent = line_current * 100 // line_end - - # Fun gradient colored percent segment - line_percent_gradient = [160, 166, 172, 178, 184, 190] - line_percent_color = line_percent_gradient[int((len(line_percent_gradient) - 1) * line_percent / 100)] - - col_current = vim_funcs['col']('.') - - filepath, filename = os.path.split(vim_funcs['expand']('%:~:.')) - filename_color = 231 - if filepath: - filepath += os.sep - - if not filename: - filename = '[No Name]' - filename_color = 250 - - readonly = vim.eval('&ro ? "⭤ " : ""') - modified = vim.eval('&mod ? " +" : ""') - - try: - currenttag = vim_funcs['tbcurtag']('%s', '') - except vim.error: - vim_funcs['tbcurtag'] = None - currenttag = '' - except TypeError: - currenttag = '' - - # The Syntastic segment is center aligned (filler segment on each side) to show off how the filler segments work - # Not necessarily how it's going to look in the final theme - set_global_var('syntastic_stl_format', '⮃ %E{ ERRORS (%e) ⭡ %fe }%W{ WARNINGS (%w) ⭡ %fw } ⮁') - try: - syntastic = vim_funcs['sstlflag']() - except vim.error: - vim_funcs['sstlflag'] = None - syntastic = '' - except TypeError: - syntastic = '' + if not current: + mode = None powerline = Powerline([ - Segment(mode, 22, 148, attr=Segment.ATTR_BOLD), - Segment(vim.eval('&paste ? "PASTE" : ""'), 231, 166, attr=Segment.ATTR_BOLD), - Segment(branch, 250, 240, priority=10), - Segment(readonly, 196, 240, draw_divider=False), - Segment(filepath, 250, 240, draw_divider=False, priority=5), - Segment(filename, filename_color, 240, attr=Segment.ATTR_BOLD, draw_divider=not len(modified)), - Segment(modified, 220, 240, attr=Segment.ATTR_BOLD), - Segment(currenttag, 246, 236, draw_divider=False, priority=100), - Segment(filler=True, fg=236, bg=236), - Segment(syntastic, 214, 236, attr=Segment.ATTR_BOLD, draw_divider=False, priority=100), - Segment(filler=True, fg=236, bg=236), - Segment(vim.eval('&ff'), 247, 236, side='r', priority=50), - Segment(vim.eval('&fenc'), 247, 236, side='r', priority=50), - Segment(vim.eval('&ft'), 247, 236, side='r', priority=50), - Segment(str(line_percent).rjust(3) + '%', line_percent_color, 240, side='r', priority=30), - Segment('⭡ ', 239, 252, side='r'), - Segment(str(line_current).rjust(3), 235, 252, attr=Segment.ATTR_BOLD, side='r', draw_divider=False), - Segment(':' + str(col_current).ljust(2), 244, 252, side='r', priority=30, draw_divider=False), + mksegment(mode, 22, 148, attr=Powerline.ATTR_BOLD), + mksegment(windata['paste'], 231, 166, attr=Powerline.ATTR_BOLD), + mksegment(windata['branch'], 250, 240, priority=60), + mksegment(windata['readonly'], 196, 240, draw_divider=False), + mksegment(windata['filepath'], 250, 240, draw_divider=False, priority=40), + mksegment(windata['filename'], windata['filename_color'], 240, attr=Powerline.ATTR_BOLD, draw_divider=not len(windata['modified'])), + mksegment(windata['modified'], 220, 240, attr=Powerline.ATTR_BOLD), + mksegment(windata['currenttag'], 246, 236, draw_divider=False, priority=100), + mksegment(filler=True, cterm_fg=236, cterm_bg=236), + mksegment(windata['fileformat'], 247, 236, side='r', priority=50), + mksegment(windata['fileencoding'], 247, 236, side='r', priority=50), + mksegment(windata['filetype'], 247, 236, side='r', priority=50), + mksegment(windata['line_percent'], windata['line_percent_color'], 240, side='r', priority=30), + mksegment(u'⭡ ', 239, 252, side='r'), + mksegment(windata['linecurrent'], 235, 252, attr=Powerline.ATTR_BOLD, side='r', draw_divider=False), + mksegment(windata['colcurrent'], 244, 252, side='r', priority=30, draw_divider=False), ]) renderer = VimSegmentRenderer() stl = powerline.render(renderer, winwidth) - # Escape percent chars in the statusline, but only if they aren't part of any stl escape sequence - stl = re.sub('(\w+)\%(?![-{()<=#*%])', '\\1%%', stl) + # Replace percent placeholders + stl = stl.replace(percent_placeholder, '%%') # Create highlighting groups - for group, hl in renderer.hl_groups.items(): - if vim_funcs['hlexists'](group): + for idx, hl in renderer.hl_groups.items(): + if vim_funcs['hlexists'](hl['name']): # Only create hl group if it doesn't already exist continue vim.command('hi {group} ctermfg={ctermfg} guifg={guifg} guibg={guibg} ctermbg={ctermbg} cterm={attr} gui={attr}'.format( - group=group, + group=hl['name'], ctermfg=hl['ctermfg'], guifg='#{0:06x}'.format(hl['guifg']) if hl['guifg'] != 'NONE' else 'NONE', ctermbg=hl['ctermbg'], @@ -176,6 +194,4 @@ def statusline(): return stl -statusline() - # vim: ft=python ts=4 sts=4 sw=4 noet diff --git a/examples/vim/powerline.vim b/examples/vim/powerline.vim index 84cdefd5..4babdcb8 100644 --- a/examples/vim/powerline.vim +++ b/examples/vim/powerline.vim @@ -6,7 +6,7 @@ python sys.path.append(vim.eval('expand(":h:h:h")')) python from examples.vim.powerline import statusline if exists('*pyeval') - let s:pyeval=function('pyeval') + let s:pyeval = function('pyeval') else python import json function! s:pyeval(e) @@ -14,8 +14,20 @@ else endfunction endif -function! DynStl() - return s:pyeval('statusline()') +function! Powerline(winnr) + return s:pyeval('statusline('. a:winnr .')') endfunction -set stl=%!DynStl() +function! s:WinDoPowerline() + if ! exists('w:powerline') + let w:powerline = {} + endif + + let &l:stl = '%!Powerline('. winnr() .')' +endfunction + +augroup Powerline + autocmd! + autocmd BufEnter,BufWinEnter,WinEnter * let w:current = 1 | let currwin = winnr() | windo call s:WinDoPowerline() | exec currwin . 'wincmd w' + autocmd BufLeave,BufWinLeave,WinLeave * let w:current = 0 +augroup END diff --git a/lib/colors.py b/lib/colors.py index fb128762..c0a34e45 100644 --- a/lib/colors.py +++ b/lib/colors.py @@ -1,54 +1,42 @@ -def cterm_to_hex(cterm_color): - '''Translate a cterm color index into the corresponding hex/RGB color. - ''' - color_dict = { - 16: 0x000000, 17: 0x00005f, 18: 0x000087, 19: 0x0000af, 20: 0x0000d7, 21: 0x0000ff, - 22: 0x005f00, 23: 0x005f5f, 24: 0x005f87, 25: 0x005faf, 26: 0x005fd7, 27: 0x005fff, - 28: 0x008700, 29: 0x00875f, 30: 0x008787, 31: 0x0087af, 32: 0x0087d7, 33: 0x0087ff, - 34: 0x00af00, 35: 0x00af5f, 36: 0x00af87, 37: 0x00afaf, 38: 0x00afd7, 39: 0x00afff, - 40: 0x00d700, 41: 0x00d75f, 42: 0x00d787, 43: 0x00d7af, 44: 0x00d7d7, 45: 0x00d7ff, - 46: 0x00ff00, 47: 0x00ff5f, 48: 0x00ff87, 49: 0x00ffaf, 50: 0x00ffd7, 51: 0x00ffff, - 52: 0x5f0000, 53: 0x5f005f, 54: 0x5f0087, 55: 0x5f00af, 56: 0x5f00d7, 57: 0x5f00ff, - 58: 0x5f5f00, 59: 0x5f5f5f, 60: 0x5f5f87, 61: 0x5f5faf, 62: 0x5f5fd7, 63: 0x5f5fff, - 64: 0x5f8700, 65: 0x5f875f, 66: 0x5f8787, 67: 0x5f87af, 68: 0x5f87d7, 69: 0x5f87ff, - 70: 0x5faf00, 71: 0x5faf5f, 72: 0x5faf87, 73: 0x5fafaf, 74: 0x5fafd7, 75: 0x5fafff, - 76: 0x5fd700, 77: 0x5fd75f, 78: 0x5fd787, 79: 0x5fd7af, 80: 0x5fd7d7, 81: 0x5fd7ff, - 82: 0x5fff00, 83: 0x5fff5f, 84: 0x5fff87, 85: 0x5fffaf, 86: 0x5fffd7, 87: 0x5fffff, - 88: 0x870000, 89: 0x87005f, 90: 0x870087, 91: 0x8700af, 92: 0x8700d7, 93: 0x8700ff, - 94: 0x875f00, 95: 0x875f5f, 96: 0x875f87, 97: 0x875faf, 98: 0x875fd7, 99: 0x875fff, - 100: 0x878700, 101: 0x87875f, 102: 0x878787, 103: 0x8787af, 104: 0x8787d7, 105: 0x8787ff, - 106: 0x87af00, 107: 0x87af5f, 108: 0x87af87, 109: 0x87afaf, 110: 0x87afd7, 111: 0x87afff, - 112: 0x87d700, 113: 0x87d75f, 114: 0x87d787, 115: 0x87d7af, 116: 0x87d7d7, 117: 0x87d7ff, - 118: 0x87ff00, 119: 0x87ff5f, 120: 0x87ff87, 121: 0x87ffaf, 122: 0x87ffd7, 123: 0x87ffff, - 124: 0xaf0000, 125: 0xaf005f, 126: 0xaf0087, 127: 0xaf00af, 128: 0xaf00d7, 129: 0xaf00ff, - 130: 0xaf5f00, 131: 0xaf5f5f, 132: 0xaf5f87, 133: 0xaf5faf, 134: 0xaf5fd7, 135: 0xaf5fff, - 136: 0xaf8700, 137: 0xaf875f, 138: 0xaf8787, 139: 0xaf87af, 140: 0xaf87d7, 141: 0xaf87ff, - 142: 0xafaf00, 143: 0xafaf5f, 144: 0xafaf87, 145: 0xafafaf, 146: 0xafafd7, 147: 0xafafff, - 148: 0xafd700, 149: 0xafd75f, 150: 0xafd787, 151: 0xafd7af, 152: 0xafd7d7, 153: 0xafd7ff, - 154: 0xafff00, 155: 0xafff5f, 156: 0xafff87, 157: 0xafffaf, 158: 0xafffd7, 159: 0xafffff, - 160: 0xd70000, 161: 0xd7005f, 162: 0xd70087, 163: 0xd700af, 164: 0xd700d7, 165: 0xd700ff, - 166: 0xd75f00, 167: 0xd75f5f, 168: 0xd75f87, 169: 0xd75faf, 170: 0xd75fd7, 171: 0xd75fff, - 172: 0xd78700, 173: 0xd7875f, 174: 0xd78787, 175: 0xd787af, 176: 0xd787d7, 177: 0xd787ff, - 178: 0xd7af00, 179: 0xd7af5f, 180: 0xd7af87, 181: 0xd7afaf, 182: 0xd7afd7, 183: 0xd7afff, - 184: 0xd7d700, 185: 0xd7d75f, 186: 0xd7d787, 187: 0xd7d7af, 188: 0xd7d7d7, 189: 0xd7d7ff, - 190: 0xd7ff00, 191: 0xd7ff5f, 192: 0xd7ff87, 193: 0xd7ffaf, 194: 0xd7ffd7, 195: 0xd7ffff, - 196: 0xff0000, 197: 0xff005f, 198: 0xff0087, 199: 0xff00af, 200: 0xff00d7, 201: 0xff00ff, - 202: 0xff5f00, 203: 0xff5f5f, 204: 0xff5f87, 205: 0xff5faf, 206: 0xff5fd7, 207: 0xff5fff, - 208: 0xff8700, 209: 0xff875f, 210: 0xff8787, 211: 0xff87af, 212: 0xff87d7, 213: 0xff87ff, - 214: 0xffaf00, 215: 0xffaf5f, 216: 0xffaf87, 217: 0xffafaf, 218: 0xffafd7, 219: 0xffafff, - 220: 0xffd700, 221: 0xffd75f, 222: 0xffd787, 223: 0xffd7af, 224: 0xffd7d7, 225: 0xffd7ff, - 226: 0xffff00, 227: 0xffff5f, 228: 0xffff87, 229: 0xffffaf, 230: 0xffffd7, 231: 0xffffff, - 232: 0x080808, 233: 0x121212, 234: 0x1c1c1c, 235: 0x262626, 236: 0x303030, 237: 0x3a3a3a, - 238: 0x444444, 239: 0x4e4e4e, 240: 0x585858, 241: 0x626262, 242: 0x6c6c6c, 243: 0x767676, - 244: 0x808080, 245: 0x8a8a8a, 246: 0x949494, 247: 0x9e9e9e, 248: 0xa8a8a8, 249: 0xb2b2b2, - 250: 0xbcbcbc, 251: 0xc6c6c6, 252: 0xd0d0d0, 253: 0xdadada, 254: 0xe4e4e4, 255: 0xeeeeee, - } - if not cterm_color: - return None - - try: - return color_dict[cterm_color] - except KeyError: - import sys - sys.stderr.write('Invalid cterm color index: {0}\n'.format(cterm_color)) - return None +cterm_to_hex = { + 16: 0x000000, 17: 0x00005f, 18: 0x000087, 19: 0x0000af, 20: 0x0000d7, 21: 0x0000ff, + 22: 0x005f00, 23: 0x005f5f, 24: 0x005f87, 25: 0x005faf, 26: 0x005fd7, 27: 0x005fff, + 28: 0x008700, 29: 0x00875f, 30: 0x008787, 31: 0x0087af, 32: 0x0087d7, 33: 0x0087ff, + 34: 0x00af00, 35: 0x00af5f, 36: 0x00af87, 37: 0x00afaf, 38: 0x00afd7, 39: 0x00afff, + 40: 0x00d700, 41: 0x00d75f, 42: 0x00d787, 43: 0x00d7af, 44: 0x00d7d7, 45: 0x00d7ff, + 46: 0x00ff00, 47: 0x00ff5f, 48: 0x00ff87, 49: 0x00ffaf, 50: 0x00ffd7, 51: 0x00ffff, + 52: 0x5f0000, 53: 0x5f005f, 54: 0x5f0087, 55: 0x5f00af, 56: 0x5f00d7, 57: 0x5f00ff, + 58: 0x5f5f00, 59: 0x5f5f5f, 60: 0x5f5f87, 61: 0x5f5faf, 62: 0x5f5fd7, 63: 0x5f5fff, + 64: 0x5f8700, 65: 0x5f875f, 66: 0x5f8787, 67: 0x5f87af, 68: 0x5f87d7, 69: 0x5f87ff, + 70: 0x5faf00, 71: 0x5faf5f, 72: 0x5faf87, 73: 0x5fafaf, 74: 0x5fafd7, 75: 0x5fafff, + 76: 0x5fd700, 77: 0x5fd75f, 78: 0x5fd787, 79: 0x5fd7af, 80: 0x5fd7d7, 81: 0x5fd7ff, + 82: 0x5fff00, 83: 0x5fff5f, 84: 0x5fff87, 85: 0x5fffaf, 86: 0x5fffd7, 87: 0x5fffff, + 88: 0x870000, 89: 0x87005f, 90: 0x870087, 91: 0x8700af, 92: 0x8700d7, 93: 0x8700ff, + 94: 0x875f00, 95: 0x875f5f, 96: 0x875f87, 97: 0x875faf, 98: 0x875fd7, 99: 0x875fff, + 100: 0x878700, 101: 0x87875f, 102: 0x878787, 103: 0x8787af, 104: 0x8787d7, 105: 0x8787ff, + 106: 0x87af00, 107: 0x87af5f, 108: 0x87af87, 109: 0x87afaf, 110: 0x87afd7, 111: 0x87afff, + 112: 0x87d700, 113: 0x87d75f, 114: 0x87d787, 115: 0x87d7af, 116: 0x87d7d7, 117: 0x87d7ff, + 118: 0x87ff00, 119: 0x87ff5f, 120: 0x87ff87, 121: 0x87ffaf, 122: 0x87ffd7, 123: 0x87ffff, + 124: 0xaf0000, 125: 0xaf005f, 126: 0xaf0087, 127: 0xaf00af, 128: 0xaf00d7, 129: 0xaf00ff, + 130: 0xaf5f00, 131: 0xaf5f5f, 132: 0xaf5f87, 133: 0xaf5faf, 134: 0xaf5fd7, 135: 0xaf5fff, + 136: 0xaf8700, 137: 0xaf875f, 138: 0xaf8787, 139: 0xaf87af, 140: 0xaf87d7, 141: 0xaf87ff, + 142: 0xafaf00, 143: 0xafaf5f, 144: 0xafaf87, 145: 0xafafaf, 146: 0xafafd7, 147: 0xafafff, + 148: 0xafd700, 149: 0xafd75f, 150: 0xafd787, 151: 0xafd7af, 152: 0xafd7d7, 153: 0xafd7ff, + 154: 0xafff00, 155: 0xafff5f, 156: 0xafff87, 157: 0xafffaf, 158: 0xafffd7, 159: 0xafffff, + 160: 0xd70000, 161: 0xd7005f, 162: 0xd70087, 163: 0xd700af, 164: 0xd700d7, 165: 0xd700ff, + 166: 0xd75f00, 167: 0xd75f5f, 168: 0xd75f87, 169: 0xd75faf, 170: 0xd75fd7, 171: 0xd75fff, + 172: 0xd78700, 173: 0xd7875f, 174: 0xd78787, 175: 0xd787af, 176: 0xd787d7, 177: 0xd787ff, + 178: 0xd7af00, 179: 0xd7af5f, 180: 0xd7af87, 181: 0xd7afaf, 182: 0xd7afd7, 183: 0xd7afff, + 184: 0xd7d700, 185: 0xd7d75f, 186: 0xd7d787, 187: 0xd7d7af, 188: 0xd7d7d7, 189: 0xd7d7ff, + 190: 0xd7ff00, 191: 0xd7ff5f, 192: 0xd7ff87, 193: 0xd7ffaf, 194: 0xd7ffd7, 195: 0xd7ffff, + 196: 0xff0000, 197: 0xff005f, 198: 0xff0087, 199: 0xff00af, 200: 0xff00d7, 201: 0xff00ff, + 202: 0xff5f00, 203: 0xff5f5f, 204: 0xff5f87, 205: 0xff5faf, 206: 0xff5fd7, 207: 0xff5fff, + 208: 0xff8700, 209: 0xff875f, 210: 0xff8787, 211: 0xff87af, 212: 0xff87d7, 213: 0xff87ff, + 214: 0xffaf00, 215: 0xffaf5f, 216: 0xffaf87, 217: 0xffafaf, 218: 0xffafd7, 219: 0xffafff, + 220: 0xffd700, 221: 0xffd75f, 222: 0xffd787, 223: 0xffd7af, 224: 0xffd7d7, 225: 0xffd7ff, + 226: 0xffff00, 227: 0xffff5f, 228: 0xffff87, 229: 0xffffaf, 230: 0xffffd7, 231: 0xffffff, + 232: 0x080808, 233: 0x121212, 234: 0x1c1c1c, 235: 0x262626, 236: 0x303030, 237: 0x3a3a3a, + 238: 0x444444, 239: 0x4e4e4e, 240: 0x585858, 241: 0x626262, 242: 0x6c6c6c, 243: 0x767676, + 244: 0x808080, 245: 0x8a8a8a, 246: 0x949494, 247: 0x9e9e9e, 248: 0xa8a8a8, 249: 0xb2b2b2, + 250: 0xbcbcbc, 251: 0xc6c6c6, 252: 0xd0d0d0, 253: 0xdadada, 254: 0xe4e4e4, 255: 0xeeeeee, +} diff --git a/lib/core.py b/lib/core.py index 427ad172..3d3e344d 100644 --- a/lib/core.py +++ b/lib/core.py @@ -1,15 +1,21 @@ # -*- coding: utf-8 -*- +from lib.colors import cterm_to_hex + + +class Powerline(object): + ATTR_BOLD = 1 + ATTR_ITALIC = 2 + ATTR_UNDERLINE = 4 -class Powerline: dividers = { 'l': { - 'hard': '⮀', - 'soft': '⮁', + 'hard': u'⮀', + 'soft': u'⮁', }, 'r': { - 'hard': '⮂', - 'soft': '⮃', + 'hard': u'⮂', + 'soft': u'⮃', }, } @@ -19,7 +25,8 @@ class Powerline: Segments that have empty contents and aren't filler segments are dropped from the segment array. ''' - self.segments = [segment for segment in segments if segment.contents or segment.filler] + self.segments = [segment for segment in segments if segment['contents'] or segment['filler']] + self._hl = {} def render(self, renderer, width=None): '''Render all the segments with the specified renderer. @@ -34,7 +41,7 @@ class Powerline: provided they will fill the remaining space until the desired width is reached. ''' - def render_segments(segments, render_raw=True, render_highlighted=True): + def render_segments(segments, render_highlighted=True): '''Render a segment array. By default this function renders both raw (un-highlighted segments @@ -42,125 +49,114 @@ class Powerline: rendering is used for calculating the total width for dropping low-priority segments. ''' - rendered_raw = '' - rendered_highlighted = '' + rendered_highlighted = u'' + segments_len = len(segments) + empty_segment = mksegment() for idx, segment in enumerate(segments): - prev = segments[idx - 1] if idx > 0 else Segment() - next = segments[idx + 1] if idx < len(segments) - 1 else Segment() + prev = segments[idx - 1] if idx > 0 else empty_segment + next = segments[idx + 1] if idx < segments_len - 1 else empty_segment - compare_segment = next if segment.side == 'l' else prev - divider_type = 'soft' if compare_segment.bg == segment.bg else 'hard' - divider = self.dividers[segment.side][divider_type] + segment['rendered_raw'] = u'' + compare = next if segment['side'] == 'l' else prev + outer_padding = ' ' if idx == 0 or idx == segments_len - 1 else '' + divider_type = 'soft' if compare['bg'] == segment['bg'] else 'hard' + divider = self.dividers[segment['side']][divider_type] + divider_hl = '' + segment_hl = '' - if segment.filler: + if render_highlighted: + # Generate and cache renderer highlighting + if divider_type == 'hard': + hl_key = (segment['bg'], compare['bg']) + if not hl_key in self._hl: + self._hl[hl_key] = renderer.hl(*hl_key) + divider_hl = self._hl[hl_key] + + hl_key = (segment['fg'], segment['bg'], segment['attr']) + if not hl_key in self._hl: + self._hl[hl_key] = renderer.hl(*hl_key) + segment_hl = self._hl[hl_key] + + if segment['filler']: # Filler segments shouldn't be padded - segment_format = '{contents}' - elif segment.draw_divider and (divider_type == 'hard' or segment.side == compare_segment.side): + rendered_highlighted += segment['contents'] + elif segment['draw_divider'] and (divider_type == 'hard' or segment['side'] == compare['side']): # Draw divider if specified, and if the next segment is on # the opposite side only draw the divider if it's a hard # divider - if segment.side == 'l': - segment_format = '{segment_hl}{outer_padding}{contents} {divider_hl}{divider} ' + if segment['side'] == 'l': + segment['rendered_raw'] += outer_padding + segment['contents'] + ' ' + divider + ' ' + rendered_highlighted += segment_hl + outer_padding + segment['contents'] + ' ' + divider_hl + divider + ' ' else: - segment_format = ' {divider_hl}{divider}{segment_hl} {contents}{outer_padding}' - elif segment.contents: + segment['rendered_raw'] += ' ' + divider + ' ' + segment['contents'] + outer_padding + rendered_highlighted += ' ' + divider_hl + divider + segment_hl + ' ' + segment['contents'] + outer_padding + elif segment['contents']: # Segments without divider - segment_format = '{segment_hl}{contents}{outer_padding}' + if segment['side'] == 'l': + segment['rendered_raw'] += outer_padding + segment['contents'] + rendered_highlighted += segment_hl + outer_padding + segment['contents'] + else: + segment['rendered_raw'] += segment['contents'] + outer_padding + rendered_highlighted += segment_hl + segment['contents'] + outer_padding else: # Unknown segment type, skip it continue - if render_raw is True and segment.filler is False: - # Filler segments must be empty when used e.g. in vim (the - # %=%< segment which disappears), so they will be skipped - # when calculating the width using the raw rendering - rendered_raw += segment_format.format( - divider=divider, - contents=segment.contents, - divider_hl='', - segment_hl='', - outer_padding=' ' if idx == 0 or idx == len(segments) - 1 else '', - ) + return rendered_highlighted - if render_highlighted is True: - rendered_highlighted += segment_format.format( - divider=divider, - contents=segment.contents, - divider_hl='' if divider_type == 'soft' else renderer.hl(segment.bg, compare_segment.bg), - segment_hl=renderer.hl(segment.fg, segment.bg, segment.attr), - outer_padding=' ' if idx == 0 or idx == len(segments) - 1 else '', - ) - - return { - 'highlighted': rendered_highlighted.decode('utf-8'), - 'raw': rendered_raw.decode('utf-8'), - } - - rendered = render_segments(self.segments) + rendered_highlighted = render_segments(self.segments) if not width: # No width specified, so we don't need to crop or pad anything - return rendered['highlighted'] + return rendered_highlighted # Create an ordered list of segments that can be dropped - segments_priority = [segment for segment in sorted(self.segments, key=lambda segment: segment.priority, reverse=True) if segment.priority > 0] + segments_priority = [segment for segment in sorted(self.segments, key=lambda segment: segment['priority'], reverse=True) if segment['priority'] > 0] - while len(rendered['raw']) > width and len(segments_priority): + while self._total_len() > width and len(segments_priority): self.segments.remove(segments_priority[0]) segments_priority.pop(0) - rendered = render_segments(self.segments, render_highlighted=False) + # Do another render pass so we can calculate the correct amount of filler space + render_segments(self.segments) # Distribute the remaining space on the filler segments - segments_fillers = [segment for segment in self.segments if segment.filler is True] + segments_fillers = [segment for segment in self.segments if segment['filler'] is True] if segments_fillers: - segments_fillers_len, segments_fillers_remainder = divmod((width - len(rendered['raw'])), len(segments_fillers)) + segments_fillers_len, segments_fillers_remainder = divmod((width - self._total_len()), len(segments_fillers)) segments_fillers_contents = ' ' * segments_fillers_len for segment in segments_fillers: - segment.contents = segments_fillers_contents + segment['contents'] = segments_fillers_contents # Add remainder whitespace to the first filler segment - segments_fillers[0].contents += ' ' * segments_fillers_remainder + segments_fillers[0]['contents'] += ' ' * segments_fillers_remainder - # Do a final render now that we have handled the cropping and padding - rendered = render_segments(self.segments, render_raw=False) + return render_segments(self.segments) - return rendered['highlighted'] + def _total_len(self): + '''Return total/rendered length of all segments. - -class Segment: - ATTR_BOLD = 1 - ATTR_ITALIC = 2 - ATTR_UNDERLINE = 4 - - def __init__(self, contents=None, fg=False, bg=False, attr=False, side='l', draw_divider=True, priority=-1, filler=False): - '''Create a new Powerline segment. + This method uses the rendered_raw property of the segments and requires + that the segments have been rendered using the render() method first. ''' - self.contents = str(contents or '') - self.fg = fg - self.bg = bg - self.attr = attr - self.side = side - self.draw_divider = draw_divider - self.priority = priority - self.filler = filler + return len(''.join([segment['rendered_raw'] for segment in self.segments])) - if self.filler: - # Filler segments should never have any dividers - self.draw_divider = False - try: - if len(self.fg) != 2: - raise TypeError - except TypeError: - # Only the terminal color is defined, so we need to get the hex color - from lib.colors import cterm_to_hex - self.fg = [self.fg, cterm_to_hex(self.fg)] +def mksegment(contents=None, cterm_fg=False, cterm_bg=False, attr=False, hex_fg=False, hex_bg=False, side='l', draw_divider=True, priority=-1, filler=False): + '''Convenience wrapper for segment generation. + ''' + try: + contents = unicode(contents or u'') + except UnicodeDecodeError: + contents = contents.decode('utf-8') or u'' - try: - if len(self.bg) != 2: - raise TypeError - except TypeError: - # Only the terminal color is defined, so we need to get the hex color - from lib.colors import cterm_to_hex - self.bg = [self.bg, cterm_to_hex(self.bg)] + return { + 'contents': contents, + 'fg': (cterm_fg, hex_fg or cterm_to_hex.get(cterm_fg, 0xffffff)), + 'bg': (cterm_bg, hex_bg or cterm_to_hex.get(cterm_bg, 0x000000)), + 'attr': attr, + 'side': side, + 'draw_divider': False if filler else draw_divider, + 'priority': priority, + 'filler': filler, + } diff --git a/lib/renderers/terminal.py b/lib/renderers/terminal.py index b80c37e1..ff55dff2 100644 --- a/lib/renderers/terminal.py +++ b/lib/renderers/terminal.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from lib.core import Segment +from lib.core import Powerline from lib.renderers import SegmentRenderer @@ -32,7 +32,7 @@ class TerminalSegmentRenderer(SegmentRenderer): if attr is False: ansi += [22] else: - if attr & Segment.ATTR_BOLD: + if attr & Powerline.ATTR_BOLD: ansi += [1] return '[{0}m'.format(';'.join(str(attr) for attr in ansi)) diff --git a/lib/renderers/vim.py b/lib/renderers/vim.py index 15f1ef9c..07a4dbb2 100644 --- a/lib/renderers/vim.py +++ b/lib/renderers/vim.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from lib.core import Segment +from lib.core import Powerline from lib.renderers import SegmentRenderer @@ -17,43 +17,44 @@ class VimSegmentRenderer(SegmentRenderer): False, the argument is reset to the terminal defaults. If an argument is a valid color or attribute, it's added to the vim highlight group. ''' - hl_group = { - 'ctermfg': 'NONE', - 'guifg': 'NONE', - 'ctermbg': 'NONE', - 'guibg': 'NONE', - 'attr': ['NONE'], - } - # We don't need to explicitly reset attributes in vim, so skip those calls if not attr and not bg and not fg: return '' - if fg is not None and fg is not False: - hl_group['ctermfg'] = fg[0] - hl_group['guifg'] = fg[1] + if not (fg, bg, attr) in self.hl_groups: + hl_group = { + 'ctermfg': 'NONE', + 'guifg': 'NONE', + 'ctermbg': 'NONE', + 'guibg': 'NONE', + 'attr': ['NONE'], + 'name': '', + } - if bg is not None and bg is not False: - hl_group['ctermbg'] = bg[0] - hl_group['guibg'] = bg[1] + if fg is not None and fg is not False: + hl_group['ctermfg'] = fg[0] + hl_group['guifg'] = fg[1] - if attr is not None and attr is not False and attr != 0: - hl_group['attr'] = [] - if attr & Segment.ATTR_BOLD: - hl_group['attr'].append('bold') - if attr & Segment.ATTR_ITALIC: - hl_group['attr'].append('italic') - if attr & Segment.ATTR_UNDERLINE: - hl_group['attr'].append('underline') + if bg is not None and bg is not False: + hl_group['ctermbg'] = bg[0] + hl_group['guibg'] = bg[1] - hl_group_name = 'Pl_{ctermfg}_{guifg}_{ctermbg}_{guibg}_{attr}'.format( - ctermfg=hl_group['ctermfg'], - guifg=hl_group['guifg'], - ctermbg=hl_group['ctermbg'], - guibg=hl_group['guibg'], - attr=''.join(attr[0] for attr in hl_group['attr']), - ) + if attr: + hl_group['attr'] = [] + if attr & Powerline.ATTR_BOLD: + hl_group['attr'].append('bold') + if attr & Powerline.ATTR_ITALIC: + hl_group['attr'].append('italic') + if attr & Powerline.ATTR_UNDERLINE: + hl_group['attr'].append('underline') - self.hl_groups[hl_group_name] = hl_group + hl_group['name'] = 'Pl_' + \ + str(hl_group['ctermfg']) + '_' + \ + str(hl_group['guifg']) + '_' + \ + str(hl_group['ctermbg']) + '_' + \ + str(hl_group['guibg']) + '_' + \ + ''.join(hl_group['attr']) - return '%#{0}#'.format(hl_group_name) + self.hl_groups[(fg, bg, attr)] = hl_group + + return '%#' + self.hl_groups[(fg, bg, attr)]['name'] + '#'