summaryrefslogtreecommitdiffstats
path: root/powerline/renderers/shell/__init__.py
blob: d7dbf96b1be92ff68a85b7f42cfc0f7f8e9fba24 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)

from powerline.renderer import Renderer
from powerline.theme import Theme
from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE


def int_to_rgb(num):
	r = (num >> 16) & 0xff
	g = (num >> 8) & 0xff
	b = num & 0xff
	return r, g, b


class PromptRenderer(Renderer):
	'''Powerline generic prompt segment renderer'''

	def __init__(self, old_widths=None, **kwargs):
		super(PromptRenderer, self).__init__(**kwargs)
		self.old_widths = old_widths if old_widths is not None else {}

	def get_client_id(self, segment_info):
		'''Get client ID given segment info

		This is used by daemon to correctly cache widths for different clients 
		using a single renderer instance.

		:param dict segment_info:
			:ref:`Segment info dictionary <dev-segments-info>`. Out of it only 
			``client_id`` key is used. It is OK for this dictionary to not 
			contain this key.

		:return: Any hashable value or ``None``.
		'''
		return segment_info.get('client_id') if isinstance(segment_info, dict) else None

	def do_render(self, output_width, segment_info, side, theme, width=None, **kwargs):
		client_id = self.get_client_id(segment_info)
		if client_id is not None:
			local_key = (client_id, side, None if theme is self.theme else id(theme))
			key = (client_id, side, None)
			did_width = False
			if local_key[-1] != key[-1] and side == 'left':
				try:
					width = self.old_widths[key]
				except KeyError:
					pass
				else:
					did_width = True
			if not did_width and width is not None:
				if theme.cursor_space_multiplier is not None:
					width = int(width * theme.cursor_space_multiplier)
				elif theme.cursor_columns:
					width -= theme.cursor_columns

				if side == 'right':
					try:
						width -= self.old_widths[(client_id, 'left', local_key[-1])]
					except KeyError:
						pass
		res = super(PromptRenderer, self).do_render(
			output_width=True,
			width=width,
			theme=theme,
			segment_info=segment_info,
			side=side,
			**kwargs
		)
		if client_id is not None:
			self.old_widths[local_key] = res[-1]
		ret = res if output_width else res[:-1]
		if len(ret) == 1:
			return ret[0]
		else:
			return ret


class ShellRenderer(PromptRenderer):
	'''Powerline shell segment renderer.'''
	escape_hl_start = ''
	escape_hl_end = ''
	term_truecolor = False
	term_escape_style = 'auto'
	tmux_escape = False
	screen_escape = False

	character_translations = Renderer.character_translations.copy()

	def render(self, segment_info, **kwargs):
		local_theme = segment_info.get('local_theme')
		return super(ShellRenderer, self).render(
			matcher_info=local_theme,
			segment_info=segment_info,
			**kwargs
		)

	def do_render(self, segment_info, **kwargs):
		if self.term_escape_style == 'auto':
			if segment_info['environ'].get('TERM') == 'fbterm':
				self.used_term_escape_style = 'fbterm'
			else:
				self.used_term_escape_style = 'xterm'
		else:
			self.used_term_escape_style = self.term_escape_style
		return super(ShellRenderer, self).do_render(segment_info=segment_info, **kwargs)

	def hlstyle(self, fg=None, bg=None, attrs=None, escape=True, **kwargs):
		'''Highlight a segment.

		If an argument is None, the argument is ignored. If an argument is
		False, the argument is reset to the terminal defaults. If an argument
		is a valid color or attribute, it’s added to the ANSI escape code.
		'''
		ansi = [0]
		is_fbterm = self.used_term_escape_style == 'fbterm'
		term_truecolor = not is_fbterm and self.term_truecolor
		if fg is not None:
			if fg is False or fg[0] is False:
				ansi += [39]
			else:
				if term_truecolor:
					ansi += [38, 2] + list(int_to_rgb(fg[1]))
				else:
					ansi += [38, 5, fg[0]]
		if bg is not None:
			if bg is False or bg[0] is False:
				ansi += [49]
			else:
				if term_truecolor:
					ansi += [48, 2] + list(int_to_rgb(bg[1]))
				else:
					ansi += [48, 5, bg[0]]
		if attrs is not None:
			if attrs is False:
				ansi += [22]
			else:
				if attrs & ATTR_BOLD:
					ansi += [1]
				elif attrs & ATTR_ITALIC:
					# Note: is likely not to work or even be inverse in place of
					# italic. Omit using this in colorschemes.
					ansi += [3]
				elif attrs & ATTR_UNDERLINE:
					ansi += [4]
		if is_fbterm:
			r = []
			while ansi:
				cur_ansi = ansi.pop(0)
				if cur_ansi == 38:
					ansi.pop(0)
					r.append('\033[1;{0}}}'.format(ansi.pop(0)))
				elif cur_ansi == 48:
					ansi.pop(0)
					r.append('\033[2;{0}}}'.format(ansi.pop(0)))
				else:
					r.append('\033[{0}m'.format(cur_ansi))
			r = ''.join(r)
		else:
			r = '\033[{0}m'.format(';'.join(str(attr) for attr in ansi))
		if self.tmux_escape:
			r = '\033Ptmux;' + r.replace('\033', '\033\033') + '\033\\'
		elif self.screen_escape:
			r = '\033P' + r.replace('\033', '\033\033') + '\033\\'
		return self.escape_hl_start + r + self.escape_hl_end if escape else r

	def get_theme(self, matcher_info):
		if not matcher_info:
			return self.theme
		match = self.local_themes[matcher_info]
		try:
			return match['theme']
		except KeyError:
			match['theme'] = Theme(
				theme_config=match['config'],
				main_theme_config=self.theme_config,
				**self.theme_kwargs
			)
			return match['theme']


renderer = ShellRenderer