1# vim:fileencoding=utf-8:noet 2from __future__ import (unicode_literals, division, absolute_import, print_function) 3 4from powerline.renderer import Renderer 5from powerline.theme import Theme 6from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE 7 8 9def int_to_rgb(num): 10 r = (num >> 16) & 0xff 11 g = (num >> 8) & 0xff 12 b = num & 0xff 13 return r, g, b 14 15 16class PromptRenderer(Renderer): 17 '''Powerline generic prompt segment renderer''' 18 19 def __init__(self, old_widths=None, **kwargs): 20 super(PromptRenderer, self).__init__(**kwargs) 21 self.old_widths = old_widths if old_widths is not None else {} 22 23 def get_client_id(self, segment_info): 24 '''Get client ID given segment info 25 26 This is used by daemon to correctly cache widths for different clients 27 using a single renderer instance. 28 29 :param dict segment_info: 30 :ref:`Segment info dictionary <dev-segments-info>`. Out of it only 31 ``client_id`` key is used. It is OK for this dictionary to not 32 contain this key. 33 34 :return: Any hashable value or ``None``. 35 ''' 36 return segment_info.get('client_id') if isinstance(segment_info, dict) else None 37 38 def do_render(self, output_width, segment_info, side, theme, width=None, **kwargs): 39 client_id = self.get_client_id(segment_info) 40 if client_id is not None: 41 local_key = (client_id, side, None if theme is self.theme else id(theme)) 42 key = (client_id, side, None) 43 did_width = False 44 if local_key[-1] != key[-1] and side == 'left': 45 try: 46 width = self.old_widths[key] 47 except KeyError: 48 pass 49 else: 50 did_width = True 51 if not did_width and width is not None: 52 if theme.cursor_space_multiplier is not None: 53 width = int(width * theme.cursor_space_multiplier) 54 elif theme.cursor_columns: 55 width -= theme.cursor_columns 56 57 if side == 'right': 58 try: 59 width -= self.old_widths[(client_id, 'left', local_key[-1])] 60 except KeyError: 61 pass 62 res = super(PromptRenderer, self).do_render( 63 output_width=True, 64 width=width, 65 theme=theme, 66 segment_info=segment_info, 67 side=side, 68 **kwargs 69 ) 70 if client_id is not None: 71 self.old_widths[local_key] = res[-1] 72 ret = res if output_width else res[:-1] 73 if len(ret) == 1: 74 return ret[0] 75 else: 76 return ret 77 78 79class ShellRenderer(PromptRenderer): 80 '''Powerline shell segment renderer.''' 81 escape_hl_start = '' 82 escape_hl_end = '' 83 term_truecolor = False 84 term_escape_style = 'auto' 85 tmux_escape = False 86 screen_escape = False 87 88 character_translations = Renderer.character_translations.copy() 89 90 def render(self, segment_info, **kwargs): 91 local_theme = segment_info.get('local_theme') 92 return super(ShellRenderer, self).render( 93 matcher_info=local_theme, 94 segment_info=segment_info, 95 **kwargs 96 ) 97 98 def do_render(self, segment_info, **kwargs): 99 if self.term_escape_style == 'auto': 100 if segment_info['environ'].get('TERM') == 'fbterm': 101 self.used_term_escape_style = 'fbterm' 102 else: 103 self.used_term_escape_style = 'xterm' 104 else: 105 self.used_term_escape_style = self.term_escape_style 106 return super(ShellRenderer, self).do_render(segment_info=segment_info, **kwargs) 107 108 def hlstyle(self, fg=None, bg=None, attrs=None, escape=True, **kwargs): 109 '''Highlight a segment. 110 111 If an argument is None, the argument is ignored. If an argument is 112 False, the argument is reset to the terminal defaults. If an argument 113 is a valid color or attribute, it’s added to the ANSI escape code. 114 ''' 115 ansi = [0] 116 is_fbterm = self.used_term_escape_style == 'fbterm' 117 term_truecolor = not is_fbterm and self.term_truecolor 118 if fg is not None: 119 if fg is False or fg[0] is False: 120 ansi += [39] 121 else: 122 if term_truecolor: 123 ansi += [38, 2] + list(int_to_rgb(fg[1])) 124 else: 125 ansi += [38, 5, fg[0]] 126 if bg is not None: 127 if bg is False or bg[0] is False: 128 ansi += [49] 129 else: 130 if term_truecolor: 131 ansi += [48, 2] + list(int_to_rgb(bg[1])) 132 else: 133 ansi += [48, 5, bg[0]] 134 if attrs is not None: 135 if attrs is False: 136 ansi += [22] 137 else: 138 if attrs & ATTR_BOLD: 139 ansi += [1] 140 elif attrs & ATTR_ITALIC: 141 # Note: is likely not to work or even be inverse in place of 142 # italic. Omit using this in colorschemes. 143 ansi += [3] 144 elif attrs & ATTR_UNDERLINE: 145 ansi += [4] 146 if is_fbterm: 147 r = [] 148 while ansi: 149 cur_ansi = ansi.pop(0) 150 if cur_ansi == 38: 151 ansi.pop(0) 152 r.append('\033[1;{0}}}'.format(ansi.pop(0))) 153 elif cur_ansi == 48: 154 ansi.pop(0) 155 r.append('\033[2;{0}}}'.format(ansi.pop(0))) 156 else: 157 r.append('\033[{0}m'.format(cur_ansi)) 158 r = ''.join(r) 159 else: 160 r = '\033[{0}m'.format(';'.join(str(attr) for attr in ansi)) 161 if self.tmux_escape: 162 r = '\033Ptmux;' + r.replace('\033', '\033\033') + '\033\\' 163 elif self.screen_escape: 164 r = '\033P' + r.replace('\033', '\033\033') + '\033\\' 165 return self.escape_hl_start + r + self.escape_hl_end if escape else r 166 167 def get_theme(self, matcher_info): 168 if not matcher_info: 169 return self.theme 170 match = self.local_themes[matcher_info] 171 try: 172 return match['theme'] 173 except KeyError: 174 match['theme'] = Theme( 175 theme_config=match['config'], 176 main_theme_config=self.theme_config, 177 **self.theme_kwargs 178 ) 179 return match['theme'] 180 181 182renderer = ShellRenderer 183