|
1 # -*- coding: utf-8 -*- |
|
2 """ |
|
3 pygments.formatters.terminal256 |
|
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
5 |
|
6 Formatter for 256-color terminal output with ANSI sequences. |
|
7 |
|
8 RGB-to-XTERM color conversion routines adapted from xterm256-conv |
|
9 tool (http://frexx.de/xterm-256-notes/data/xterm256-conv2.tar.bz2) |
|
10 by Wolfgang Frisch. |
|
11 |
|
12 Formatter version 1. |
|
13 |
|
14 :copyright: Copyright 2006-2017 by the Pygments team, see AUTHORS. |
|
15 :license: BSD, see LICENSE for details. |
|
16 """ |
|
17 |
|
18 # TODO: |
|
19 # - Options to map style's bold/underline/italic/border attributes |
|
20 # to some ANSI attrbutes (something like 'italic=underline') |
|
21 # - An option to output "style RGB to xterm RGB/index" conversion table |
|
22 # - An option to indicate that we are running in "reverse background" |
|
23 # xterm. This means that default colors are white-on-black, not |
|
24 # black-on-while, so colors like "white background" need to be converted |
|
25 # to "white background, black foreground", etc... |
|
26 |
|
27 import sys |
|
28 |
|
29 from pygments.formatter import Formatter |
|
30 from pygments.console import codes |
|
31 from pygments.style import ansicolors |
|
32 |
|
33 |
|
34 __all__ = ['Terminal256Formatter', 'TerminalTrueColorFormatter'] |
|
35 |
|
36 |
|
37 class EscapeSequence: |
|
38 def __init__(self, fg=None, bg=None, bold=False, underline=False): |
|
39 self.fg = fg |
|
40 self.bg = bg |
|
41 self.bold = bold |
|
42 self.underline = underline |
|
43 |
|
44 def escape(self, attrs): |
|
45 if len(attrs): |
|
46 return "\x1b[" + ";".join(attrs) + "m" |
|
47 return "" |
|
48 |
|
49 def color_string(self): |
|
50 attrs = [] |
|
51 if self.fg is not None: |
|
52 if self.fg in ansicolors: |
|
53 esc = codes[self.fg[5:]] |
|
54 if ';01m' in esc: |
|
55 self.bold = True |
|
56 # extract fg color code. |
|
57 attrs.append(esc[2:4]) |
|
58 else: |
|
59 attrs.extend(("38", "5", "%i" % self.fg)) |
|
60 if self.bg is not None: |
|
61 if self.bg in ansicolors: |
|
62 esc = codes[self.bg[5:]] |
|
63 # extract fg color code, add 10 for bg. |
|
64 attrs.append(str(int(esc[2:4])+10)) |
|
65 else: |
|
66 attrs.extend(("48", "5", "%i" % self.bg)) |
|
67 if self.bold: |
|
68 attrs.append("01") |
|
69 if self.underline: |
|
70 attrs.append("04") |
|
71 return self.escape(attrs) |
|
72 |
|
73 def true_color_string(self): |
|
74 attrs = [] |
|
75 if self.fg: |
|
76 attrs.extend(("38", "2", str(self.fg[0]), str(self.fg[1]), str(self.fg[2]))) |
|
77 if self.bg: |
|
78 attrs.extend(("48", "2", str(self.bg[0]), str(self.bg[1]), str(self.bg[2]))) |
|
79 if self.bold: |
|
80 attrs.append("01") |
|
81 if self.underline: |
|
82 attrs.append("04") |
|
83 return self.escape(attrs) |
|
84 |
|
85 def reset_string(self): |
|
86 attrs = [] |
|
87 if self.fg is not None: |
|
88 attrs.append("39") |
|
89 if self.bg is not None: |
|
90 attrs.append("49") |
|
91 if self.bold or self.underline: |
|
92 attrs.append("00") |
|
93 return self.escape(attrs) |
|
94 |
|
95 |
|
96 class Terminal256Formatter(Formatter): |
|
97 """ |
|
98 Format tokens with ANSI color sequences, for output in a 256-color |
|
99 terminal or console. Like in `TerminalFormatter` color sequences |
|
100 are terminated at newlines, so that paging the output works correctly. |
|
101 |
|
102 The formatter takes colors from a style defined by the `style` option |
|
103 and converts them to nearest ANSI 256-color escape sequences. Bold and |
|
104 underline attributes from the style are preserved (and displayed). |
|
105 |
|
106 .. versionadded:: 0.9 |
|
107 |
|
108 .. versionchanged:: 2.2 |
|
109 If the used style defines foreground colors in the form ``#ansi*``, then |
|
110 `Terminal256Formatter` will map these to non extended foreground color. |
|
111 See :ref:`AnsiTerminalStyle` for more information. |
|
112 |
|
113 Options accepted: |
|
114 |
|
115 `style` |
|
116 The style to use, can be a string or a Style subclass (default: |
|
117 ``'default'``). |
|
118 """ |
|
119 name = 'Terminal256' |
|
120 aliases = ['terminal256', 'console256', '256'] |
|
121 filenames = [] |
|
122 |
|
123 def __init__(self, **options): |
|
124 Formatter.__init__(self, **options) |
|
125 |
|
126 self.xterm_colors = [] |
|
127 self.best_match = {} |
|
128 self.style_string = {} |
|
129 |
|
130 self.usebold = 'nobold' not in options |
|
131 self.useunderline = 'nounderline' not in options |
|
132 |
|
133 self._build_color_table() # build an RGB-to-256 color conversion table |
|
134 self._setup_styles() # convert selected style's colors to term. colors |
|
135 |
|
136 def _build_color_table(self): |
|
137 # colors 0..15: 16 basic colors |
|
138 |
|
139 self.xterm_colors.append((0x00, 0x00, 0x00)) # 0 |
|
140 self.xterm_colors.append((0xcd, 0x00, 0x00)) # 1 |
|
141 self.xterm_colors.append((0x00, 0xcd, 0x00)) # 2 |
|
142 self.xterm_colors.append((0xcd, 0xcd, 0x00)) # 3 |
|
143 self.xterm_colors.append((0x00, 0x00, 0xee)) # 4 |
|
144 self.xterm_colors.append((0xcd, 0x00, 0xcd)) # 5 |
|
145 self.xterm_colors.append((0x00, 0xcd, 0xcd)) # 6 |
|
146 self.xterm_colors.append((0xe5, 0xe5, 0xe5)) # 7 |
|
147 self.xterm_colors.append((0x7f, 0x7f, 0x7f)) # 8 |
|
148 self.xterm_colors.append((0xff, 0x00, 0x00)) # 9 |
|
149 self.xterm_colors.append((0x00, 0xff, 0x00)) # 10 |
|
150 self.xterm_colors.append((0xff, 0xff, 0x00)) # 11 |
|
151 self.xterm_colors.append((0x5c, 0x5c, 0xff)) # 12 |
|
152 self.xterm_colors.append((0xff, 0x00, 0xff)) # 13 |
|
153 self.xterm_colors.append((0x00, 0xff, 0xff)) # 14 |
|
154 self.xterm_colors.append((0xff, 0xff, 0xff)) # 15 |
|
155 |
|
156 # colors 16..232: the 6x6x6 color cube |
|
157 |
|
158 valuerange = (0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff) |
|
159 |
|
160 for i in range(217): |
|
161 r = valuerange[(i // 36) % 6] |
|
162 g = valuerange[(i // 6) % 6] |
|
163 b = valuerange[i % 6] |
|
164 self.xterm_colors.append((r, g, b)) |
|
165 |
|
166 # colors 233..253: grayscale |
|
167 |
|
168 for i in range(1, 22): |
|
169 v = 8 + i * 10 |
|
170 self.xterm_colors.append((v, v, v)) |
|
171 |
|
172 def _closest_color(self, r, g, b): |
|
173 distance = 257*257*3 # "infinity" (>distance from #000000 to #ffffff) |
|
174 match = 0 |
|
175 |
|
176 for i in range(0, 254): |
|
177 values = self.xterm_colors[i] |
|
178 |
|
179 rd = r - values[0] |
|
180 gd = g - values[1] |
|
181 bd = b - values[2] |
|
182 d = rd*rd + gd*gd + bd*bd |
|
183 |
|
184 if d < distance: |
|
185 match = i |
|
186 distance = d |
|
187 return match |
|
188 |
|
189 def _color_index(self, color): |
|
190 index = self.best_match.get(color, None) |
|
191 if color in ansicolors: |
|
192 # strip the `#ansi` part and look up code |
|
193 index = color |
|
194 self.best_match[color] = index |
|
195 if index is None: |
|
196 try: |
|
197 rgb = int(str(color), 16) |
|
198 except ValueError: |
|
199 rgb = 0 |
|
200 |
|
201 r = (rgb >> 16) & 0xff |
|
202 g = (rgb >> 8) & 0xff |
|
203 b = rgb & 0xff |
|
204 index = self._closest_color(r, g, b) |
|
205 self.best_match[color] = index |
|
206 return index |
|
207 |
|
208 def _setup_styles(self): |
|
209 for ttype, ndef in self.style: |
|
210 escape = EscapeSequence() |
|
211 # get foreground from ansicolor if set |
|
212 if ndef['ansicolor']: |
|
213 escape.fg = self._color_index(ndef['ansicolor']) |
|
214 elif ndef['color']: |
|
215 escape.fg = self._color_index(ndef['color']) |
|
216 if ndef['bgansicolor']: |
|
217 escape.bg = self._color_index(ndef['bgansicolor']) |
|
218 elif ndef['bgcolor']: |
|
219 escape.bg = self._color_index(ndef['bgcolor']) |
|
220 if self.usebold and ndef['bold']: |
|
221 escape.bold = True |
|
222 if self.useunderline and ndef['underline']: |
|
223 escape.underline = True |
|
224 self.style_string[str(ttype)] = (escape.color_string(), |
|
225 escape.reset_string()) |
|
226 |
|
227 def format(self, tokensource, outfile): |
|
228 # hack: if the output is a terminal and has an encoding set, |
|
229 # use that to avoid unicode encode problems |
|
230 if not self.encoding and hasattr(outfile, "encoding") and \ |
|
231 hasattr(outfile, "isatty") and outfile.isatty() and \ |
|
232 sys.version_info < (3,): |
|
233 self.encoding = outfile.encoding |
|
234 return Formatter.format(self, tokensource, outfile) |
|
235 |
|
236 def format_unencoded(self, tokensource, outfile): |
|
237 for ttype, value in tokensource: |
|
238 not_found = True |
|
239 while ttype and not_found: |
|
240 try: |
|
241 # outfile.write( "<" + str(ttype) + ">" ) |
|
242 on, off = self.style_string[str(ttype)] |
|
243 |
|
244 # Like TerminalFormatter, add "reset colors" escape sequence |
|
245 # on newline. |
|
246 spl = value.split('\n') |
|
247 for line in spl[:-1]: |
|
248 if line: |
|
249 outfile.write(on + line + off) |
|
250 outfile.write('\n') |
|
251 if spl[-1]: |
|
252 outfile.write(on + spl[-1] + off) |
|
253 |
|
254 not_found = False |
|
255 # outfile.write( '#' + str(ttype) + '#' ) |
|
256 |
|
257 except KeyError: |
|
258 # ottype = ttype |
|
259 ttype = ttype[:-1] |
|
260 # outfile.write( '!' + str(ottype) + '->' + str(ttype) + '!' ) |
|
261 |
|
262 if not_found: |
|
263 outfile.write(value) |
|
264 |
|
265 |
|
266 class TerminalTrueColorFormatter(Terminal256Formatter): |
|
267 r""" |
|
268 Format tokens with ANSI color sequences, for output in a true-color |
|
269 terminal or console. Like in `TerminalFormatter` color sequences |
|
270 are terminated at newlines, so that paging the output works correctly. |
|
271 |
|
272 .. versionadded:: 2.1 |
|
273 |
|
274 Options accepted: |
|
275 |
|
276 `style` |
|
277 The style to use, can be a string or a Style subclass (default: |
|
278 ``'default'``). |
|
279 """ |
|
280 name = 'TerminalTrueColor' |
|
281 aliases = ['terminal16m', 'console16m', '16m'] |
|
282 filenames = [] |
|
283 |
|
284 def _build_color_table(self): |
|
285 pass |
|
286 |
|
287 def _color_tuple(self, color): |
|
288 try: |
|
289 rgb = int(str(color), 16) |
|
290 except ValueError: |
|
291 return None |
|
292 r = (rgb >> 16) & 0xff |
|
293 g = (rgb >> 8) & 0xff |
|
294 b = rgb & 0xff |
|
295 return (r, g, b) |
|
296 |
|
297 def _setup_styles(self): |
|
298 for ttype, ndef in self.style: |
|
299 escape = EscapeSequence() |
|
300 if ndef['color']: |
|
301 escape.fg = self._color_tuple(ndef['color']) |
|
302 if ndef['bgcolor']: |
|
303 escape.bg = self._color_tuple(ndef['bgcolor']) |
|
304 if self.usebold and ndef['bold']: |
|
305 escape.bold = True |
|
306 if self.useunderline and ndef['underline']: |
|
307 escape.underline = True |
|
308 self.style_string[str(ttype)] = (escape.true_color_string(), |
|
309 escape.reset_string()) |