diff --git a/examples/vim/powerline.py b/examples/vim/powerline.py index f4951744..435392ee 100644 --- a/examples/vim/powerline.py +++ b/examples/vim/powerline.py @@ -176,13 +176,13 @@ def statusline(winnr): stl = re.sub('(\w+)\%(?![-{()<=#*%])', '\\1%%', stl) # 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'], 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..25b57a91 100644 --- a/lib/core.py +++ b/lib/core.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +from lib.colors import cterm_to_hex + class Powerline: dividers = { @@ -20,6 +22,7 @@ class Powerline: dropped from the segment array. ''' 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 +37,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,90 +45,94 @@ class Powerline: rendering is used for calculating the total width for dropping low-priority segments. ''' - rendered_raw = '' rendered_highlighted = '' + segments_len = len(segments) + empty_segment = Segment() 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' + 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 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} ' + 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}' + 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.decode('utf-8') - 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] - while len(rendered['raw']) > width and len(segments_priority): + while self._total_len() > width and len(segments_priority): + # FIXME The remove method is quite expensive and we should find another way of removing low-priority segments self.segments.remove(segments_priority[0]) segments_priority.pop(0) - rendered = render_segments(self.segments, render_highlighted=False) - # Distribute the remaining space on the filler segments 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 # Add remainder whitespace to the first filler segment 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. + + This method uses the rendered_raw property of the segments and requires + that the segments have been rendered using the render() method first. + ''' + return len(''.join([segment.rendered_raw for segment in self.segments]).decode('utf-8')) class Segment: @@ -144,23 +151,20 @@ class Segment: self.draw_divider = draw_divider self.priority = priority self.filler = filler + self.rendered_raw = '' if self.filler: # Filler segments should never have any dividers self.draw_divider = False try: - if len(self.fg) != 2: - raise TypeError + self.fg = (fg[0], fg[1]) 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)] + self.fg = (self.fg, cterm_to_hex.get(self.fg, 0xffffff)) try: - if len(self.bg) != 2: - raise TypeError + self.bg = (bg[0], bg[1]) 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)] + self.bg = (self.bg, cterm_to_hex.get(self.bg, 0x000000)) diff --git a/lib/renderers/vim.py b/lib/renderers/vim.py index 15f1ef9c..b91fd96c 100644 --- a/lib/renderers/vim.py +++ b/lib/renderers/vim.py @@ -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 & 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') - 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'] + '#'