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