|
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-2009 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 from pygments.formatter import Formatter |
|
28 |
|
29 |
|
30 __all__ = ['Terminal256Formatter'] |
|
31 |
|
32 |
|
33 class EscapeSequence: |
|
34 def __init__(self, fg=None, bg=None, bold=False, underline=False): |
|
35 self.fg = fg |
|
36 self.bg = bg |
|
37 self.bold = bold |
|
38 self.underline = underline |
|
39 |
|
40 def escape(self, attrs): |
|
41 if len(attrs): |
|
42 return "\x1b[" + ";".join(attrs) + "m" |
|
43 return "" |
|
44 |
|
45 def color_string(self): |
|
46 attrs = [] |
|
47 if self.fg is not None: |
|
48 attrs.extend(("38", "5", "%i" % self.fg)) |
|
49 if self.bg is not None: |
|
50 attrs.extend(("48", "5", "%i" % self.bg)) |
|
51 if self.bold: |
|
52 attrs.append("01") |
|
53 if self.underline: |
|
54 attrs.append("04") |
|
55 return self.escape(attrs) |
|
56 |
|
57 def reset_string(self): |
|
58 attrs = [] |
|
59 if self.fg is not None: |
|
60 attrs.append("39") |
|
61 if self.bg is not None: |
|
62 attrs.append("49") |
|
63 if self.bold or self.underline: |
|
64 attrs.append("00") |
|
65 return self.escape(attrs) |
|
66 |
|
67 class Terminal256Formatter(Formatter): |
|
68 r""" |
|
69 Format tokens with ANSI color sequences, for output in a 256-color |
|
70 terminal or console. Like in `TerminalFormatter` color sequences |
|
71 are terminated at newlines, so that paging the output works correctly. |
|
72 |
|
73 The formatter takes colors from a style defined by the `style` option |
|
74 and converts them to nearest ANSI 256-color escape sequences. Bold and |
|
75 underline attributes from the style are preserved (and displayed). |
|
76 |
|
77 *New in Pygments 0.9.* |
|
78 |
|
79 Options accepted: |
|
80 |
|
81 `style` |
|
82 The style to use, can be a string or a Style subclass (default: |
|
83 ``'default'``). |
|
84 """ |
|
85 name = 'Terminal256' |
|
86 aliases = ['terminal256', 'console256', '256'] |
|
87 filenames = [] |
|
88 |
|
89 def __init__(self, **options): |
|
90 Formatter.__init__(self, **options) |
|
91 |
|
92 self.xterm_colors = [] |
|
93 self.best_match = {} |
|
94 self.style_string = {} |
|
95 |
|
96 self.usebold = 'nobold' not in options |
|
97 self.useunderline = 'nounderline' not in options |
|
98 |
|
99 self._build_color_table() # build an RGB-to-256 color conversion table |
|
100 self._setup_styles() # convert selected style's colors to term. colors |
|
101 |
|
102 def _build_color_table(self): |
|
103 # colors 0..15: 16 basic colors |
|
104 |
|
105 self.xterm_colors.append((0x00, 0x00, 0x00)) # 0 |
|
106 self.xterm_colors.append((0xcd, 0x00, 0x00)) # 1 |
|
107 self.xterm_colors.append((0x00, 0xcd, 0x00)) # 2 |
|
108 self.xterm_colors.append((0xcd, 0xcd, 0x00)) # 3 |
|
109 self.xterm_colors.append((0x00, 0x00, 0xee)) # 4 |
|
110 self.xterm_colors.append((0xcd, 0x00, 0xcd)) # 5 |
|
111 self.xterm_colors.append((0x00, 0xcd, 0xcd)) # 6 |
|
112 self.xterm_colors.append((0xe5, 0xe5, 0xe5)) # 7 |
|
113 self.xterm_colors.append((0x7f, 0x7f, 0x7f)) # 8 |
|
114 self.xterm_colors.append((0xff, 0x00, 0x00)) # 9 |
|
115 self.xterm_colors.append((0x00, 0xff, 0x00)) # 10 |
|
116 self.xterm_colors.append((0xff, 0xff, 0x00)) # 11 |
|
117 self.xterm_colors.append((0x5c, 0x5c, 0xff)) # 12 |
|
118 self.xterm_colors.append((0xff, 0x00, 0xff)) # 13 |
|
119 self.xterm_colors.append((0x00, 0xff, 0xff)) # 14 |
|
120 self.xterm_colors.append((0xff, 0xff, 0xff)) # 15 |
|
121 |
|
122 # colors 16..232: the 6x6x6 color cube |
|
123 |
|
124 valuerange = (0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff) |
|
125 |
|
126 for i in range(217): |
|
127 r = valuerange[(i // 36) % 6] |
|
128 g = valuerange[(i // 6) % 6] |
|
129 b = valuerange[i % 6] |
|
130 self.xterm_colors.append((r, g, b)) |
|
131 |
|
132 # colors 233..253: grayscale |
|
133 |
|
134 for i in range(1, 22): |
|
135 v = 8 + i * 10 |
|
136 self.xterm_colors.append((v, v, v)) |
|
137 |
|
138 def _closest_color(self, r, g, b): |
|
139 distance = 257*257*3 # "infinity" (>distance from #000000 to #ffffff) |
|
140 match = 0 |
|
141 |
|
142 for i in range(0, 254): |
|
143 values = self.xterm_colors[i] |
|
144 |
|
145 rd = r - values[0] |
|
146 gd = g - values[1] |
|
147 bd = b - values[2] |
|
148 d = rd*rd + gd*gd + bd*bd |
|
149 |
|
150 if d < distance: |
|
151 match = i |
|
152 distance = d |
|
153 return match |
|
154 |
|
155 def _color_index(self, color): |
|
156 index = self.best_match.get(color, None) |
|
157 if index is None: |
|
158 try: |
|
159 rgb = int(str(color), 16) |
|
160 except ValueError: |
|
161 rgb = 0 |
|
162 |
|
163 r = (rgb >> 16) & 0xff |
|
164 g = (rgb >> 8) & 0xff |
|
165 b = rgb & 0xff |
|
166 index = self._closest_color(r, g, b) |
|
167 self.best_match[color] = index |
|
168 return index |
|
169 |
|
170 def _setup_styles(self): |
|
171 for ttype, ndef in self.style: |
|
172 escape = EscapeSequence() |
|
173 if ndef['color']: |
|
174 escape.fg = self._color_index(ndef['color']) |
|
175 if ndef['bgcolor']: |
|
176 escape.bg = self._color_index(ndef['bgcolor']) |
|
177 if self.usebold and ndef['bold']: |
|
178 escape.bold = True |
|
179 if self.useunderline and ndef['underline']: |
|
180 escape.underline = True |
|
181 self.style_string[str(ttype)] = (escape.color_string(), |
|
182 escape.reset_string()) |
|
183 |
|
184 def format(self, tokensource, outfile): |
|
185 # hack: if the output is a terminal and has an encoding set, |
|
186 # use that to avoid unicode encode problems |
|
187 if not self.encoding and hasattr(outfile, "encoding") and \ |
|
188 hasattr(outfile, "isatty") and outfile.isatty(): |
|
189 self.encoding = outfile.encoding |
|
190 return Formatter.format(self, tokensource, outfile) |
|
191 |
|
192 def format_unencoded(self, tokensource, outfile): |
|
193 for ttype, value in tokensource: |
|
194 not_found = True |
|
195 while ttype and not_found: |
|
196 try: |
|
197 #outfile.write( "<" + str(ttype) + ">" ) |
|
198 on, off = self.style_string[str(ttype)] |
|
199 |
|
200 # Like TerminalFormatter, add "reset colors" escape sequence |
|
201 # on newline. |
|
202 spl = value.split('\n') |
|
203 for line in spl[:-1]: |
|
204 if line: |
|
205 outfile.write(on + line + off) |
|
206 outfile.write('\n') |
|
207 if spl[-1]: |
|
208 outfile.write(on + spl[-1] + off) |
|
209 |
|
210 not_found = False |
|
211 #outfile.write( '#' + str(ttype) + '#' ) |
|
212 |
|
213 except KeyError: |
|
214 #ottype = ttype |
|
215 ttype = ttype[:-1] |
|
216 #outfile.write( '!' + str(ottype) + '->' + str(ttype) + '!' ) |
|
217 |
|
218 if not_found: |
|
219 outfile.write(value) |