##// END OF EJS Templates
color: use ui.formatted() to test TTYness, not sys.stdout.isatty()...
Brodie Rao -
r14103:a36e8c99 default
parent child Browse files
Show More
@@ -1,481 +1,482 b''
1 # color.py color output for the status and qseries commands
1 # color.py color output for the status and qseries commands
2 #
2 #
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
4 #
4 #
5 # This program is free software; you can redistribute it and/or modify it
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation; either version 2 of the License, or (at your
7 # Free Software Foundation; either version 2 of the License, or (at your
8 # option) any later version.
8 # option) any later version.
9 #
9 #
10 # This program is distributed in the hope that it will be useful, but
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 # Public License for more details.
13 # Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License along
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
18
19 '''colorize output from some commands
19 '''colorize output from some commands
20
20
21 This extension modifies the status and resolve commands to add color
21 This extension modifies the status and resolve commands to add color
22 to their output to reflect file status, the qseries command to add
22 to their output to reflect file status, the qseries command to add
23 color to reflect patch status (applied, unapplied, missing), and to
23 color to reflect patch status (applied, unapplied, missing), and to
24 diff-related commands to highlight additions, removals, diff headers,
24 diff-related commands to highlight additions, removals, diff headers,
25 and trailing whitespace.
25 and trailing whitespace.
26
26
27 Other effects in addition to color, like bold and underlined text, are
27 Other effects in addition to color, like bold and underlined text, are
28 also available. By default, the terminfo database is used to find the
28 also available. By default, the terminfo database is used to find the
29 terminal codes used to change color and effect. If terminfo is not
29 terminal codes used to change color and effect. If terminfo is not
30 available, then effects are rendered with the ECMA-48 SGR control
30 available, then effects are rendered with the ECMA-48 SGR control
31 function (aka ANSI escape codes).
31 function (aka ANSI escape codes).
32
32
33 Default effects may be overridden from your configuration file::
33 Default effects may be overridden from your configuration file::
34
34
35 [color]
35 [color]
36 status.modified = blue bold underline red_background
36 status.modified = blue bold underline red_background
37 status.added = green bold
37 status.added = green bold
38 status.removed = red bold blue_background
38 status.removed = red bold blue_background
39 status.deleted = cyan bold underline
39 status.deleted = cyan bold underline
40 status.unknown = magenta bold underline
40 status.unknown = magenta bold underline
41 status.ignored = black bold
41 status.ignored = black bold
42
42
43 # 'none' turns off all effects
43 # 'none' turns off all effects
44 status.clean = none
44 status.clean = none
45 status.copied = none
45 status.copied = none
46
46
47 qseries.applied = blue bold underline
47 qseries.applied = blue bold underline
48 qseries.unapplied = black bold
48 qseries.unapplied = black bold
49 qseries.missing = red bold
49 qseries.missing = red bold
50
50
51 diff.diffline = bold
51 diff.diffline = bold
52 diff.extended = cyan bold
52 diff.extended = cyan bold
53 diff.file_a = red bold
53 diff.file_a = red bold
54 diff.file_b = green bold
54 diff.file_b = green bold
55 diff.hunk = magenta
55 diff.hunk = magenta
56 diff.deleted = red
56 diff.deleted = red
57 diff.inserted = green
57 diff.inserted = green
58 diff.changed = white
58 diff.changed = white
59 diff.trailingwhitespace = bold red_background
59 diff.trailingwhitespace = bold red_background
60
60
61 resolve.unresolved = red bold
61 resolve.unresolved = red bold
62 resolve.resolved = green bold
62 resolve.resolved = green bold
63
63
64 bookmarks.current = green
64 bookmarks.current = green
65
65
66 branches.active = none
66 branches.active = none
67 branches.closed = black bold
67 branches.closed = black bold
68 branches.current = green
68 branches.current = green
69 branches.inactive = none
69 branches.inactive = none
70
70
71 The available effects in terminfo mode are 'blink', 'bold', 'dim',
71 The available effects in terminfo mode are 'blink', 'bold', 'dim',
72 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
72 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
73 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
73 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
74 'underline'. How each is rendered depends on the terminal emulator.
74 'underline'. How each is rendered depends on the terminal emulator.
75 Some may not be available for a given terminal type, and will be
75 Some may not be available for a given terminal type, and will be
76 silently ignored.
76 silently ignored.
77
77
78 Because there are only eight standard colors, this module allows you
78 Because there are only eight standard colors, this module allows you
79 to define color names for other color slots which might be available
79 to define color names for other color slots which might be available
80 for your terminal type, assuming terminfo mode. For instance::
80 for your terminal type, assuming terminfo mode. For instance::
81
81
82 color.brightblue = 12
82 color.brightblue = 12
83 color.pink = 207
83 color.pink = 207
84 color.orange = 202
84 color.orange = 202
85
85
86 to set 'brightblue' to color slot 12 (useful for 16 color terminals
86 to set 'brightblue' to color slot 12 (useful for 16 color terminals
87 that have brighter colors defined in the upper eight) and, 'pink' and
87 that have brighter colors defined in the upper eight) and, 'pink' and
88 'orange' to colors in 256-color xterm's default color cube. These
88 'orange' to colors in 256-color xterm's default color cube. These
89 defined colors may then be used as any of the pre-defined eight,
89 defined colors may then be used as any of the pre-defined eight,
90 including appending '_background' to set the background to that color.
90 including appending '_background' to set the background to that color.
91
91
92 The color extension will try to detect whether to use terminfo, ANSI
92 The color extension will try to detect whether to use terminfo, ANSI
93 codes or Win32 console APIs, unless it is made explicit; e.g.::
93 codes or Win32 console APIs, unless it is made explicit; e.g.::
94
94
95 [color]
95 [color]
96 mode = ansi
96 mode = ansi
97
97
98 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
98 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
99 disable color.
99 disable color.
100
100
101 '''
101 '''
102
102
103 import os, sys
103 import os, sys
104
104
105 from mercurial import commands, dispatch, extensions, ui as uimod, util
105 from mercurial import commands, dispatch, extensions, ui as uimod, util
106 from mercurial.i18n import _
106 from mercurial.i18n import _
107
107
108 # start and stop parameters for effects
108 # start and stop parameters for effects
109 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
109 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
110 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
110 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
111 'italic': 3, 'underline': 4, 'inverse': 7,
111 'italic': 3, 'underline': 4, 'inverse': 7,
112 'black_background': 40, 'red_background': 41,
112 'black_background': 40, 'red_background': 41,
113 'green_background': 42, 'yellow_background': 43,
113 'green_background': 42, 'yellow_background': 43,
114 'blue_background': 44, 'purple_background': 45,
114 'blue_background': 44, 'purple_background': 45,
115 'cyan_background': 46, 'white_background': 47}
115 'cyan_background': 46, 'white_background': 47}
116
116
117 def _terminfosetup(ui):
117 def _terminfosetup(ui):
118 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
118 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
119
119
120 global _terminfo_params
120 global _terminfo_params
121 # If we failed to load curses, we go ahead and return.
121 # If we failed to load curses, we go ahead and return.
122 if not _terminfo_params:
122 if not _terminfo_params:
123 return
123 return
124 # Otherwise, see what the config file says.
124 # Otherwise, see what the config file says.
125 mode = ui.config('color', 'mode', 'auto')
125 mode = ui.config('color', 'mode', 'auto')
126 if mode not in ('auto', 'terminfo'):
126 if mode not in ('auto', 'terminfo'):
127 return
127 return
128
128
129 _terminfo_params.update((key[6:], (False, int(val)))
129 _terminfo_params.update((key[6:], (False, int(val)))
130 for key, val in ui.configitems('color')
130 for key, val in ui.configitems('color')
131 if key.startswith('color.'))
131 if key.startswith('color.'))
132
132
133 try:
133 try:
134 curses.setupterm()
134 curses.setupterm()
135 except curses.error, e:
135 except curses.error, e:
136 _terminfo_params = {}
136 _terminfo_params = {}
137 return
137 return
138
138
139 for key, (b, e) in _terminfo_params.items():
139 for key, (b, e) in _terminfo_params.items():
140 if not b:
140 if not b:
141 continue
141 continue
142 if not curses.tigetstr(e):
142 if not curses.tigetstr(e):
143 # Most terminals don't support dim, invis, etc, so don't be
143 # Most terminals don't support dim, invis, etc, so don't be
144 # noisy and use ui.debug().
144 # noisy and use ui.debug().
145 ui.debug("no terminfo entry for %s\n" % e)
145 ui.debug("no terminfo entry for %s\n" % e)
146 del _terminfo_params[key]
146 del _terminfo_params[key]
147 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
147 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
148 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
148 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
149 "ECMA-48 color\n"))
149 "ECMA-48 color\n"))
150 _terminfo_params = {}
150 _terminfo_params = {}
151
151
152 try:
152 try:
153 import curses
153 import curses
154 # Mapping from effect name to terminfo attribute name or color number.
154 # Mapping from effect name to terminfo attribute name or color number.
155 # This will also force-load the curses module.
155 # This will also force-load the curses module.
156 _terminfo_params = {'none': (True, 'sgr0'),
156 _terminfo_params = {'none': (True, 'sgr0'),
157 'standout': (True, 'smso'),
157 'standout': (True, 'smso'),
158 'underline': (True, 'smul'),
158 'underline': (True, 'smul'),
159 'reverse': (True, 'rev'),
159 'reverse': (True, 'rev'),
160 'inverse': (True, 'rev'),
160 'inverse': (True, 'rev'),
161 'blink': (True, 'blink'),
161 'blink': (True, 'blink'),
162 'dim': (True, 'dim'),
162 'dim': (True, 'dim'),
163 'bold': (True, 'bold'),
163 'bold': (True, 'bold'),
164 'invisible': (True, 'invis'),
164 'invisible': (True, 'invis'),
165 'italic': (True, 'sitm'),
165 'italic': (True, 'sitm'),
166 'black': (False, curses.COLOR_BLACK),
166 'black': (False, curses.COLOR_BLACK),
167 'red': (False, curses.COLOR_RED),
167 'red': (False, curses.COLOR_RED),
168 'green': (False, curses.COLOR_GREEN),
168 'green': (False, curses.COLOR_GREEN),
169 'yellow': (False, curses.COLOR_YELLOW),
169 'yellow': (False, curses.COLOR_YELLOW),
170 'blue': (False, curses.COLOR_BLUE),
170 'blue': (False, curses.COLOR_BLUE),
171 'magenta': (False, curses.COLOR_MAGENTA),
171 'magenta': (False, curses.COLOR_MAGENTA),
172 'cyan': (False, curses.COLOR_CYAN),
172 'cyan': (False, curses.COLOR_CYAN),
173 'white': (False, curses.COLOR_WHITE)}
173 'white': (False, curses.COLOR_WHITE)}
174 except ImportError:
174 except ImportError:
175 _terminfo_params = False
175 _terminfo_params = False
176
176
177 _styles = {'grep.match': 'red bold',
177 _styles = {'grep.match': 'red bold',
178 'bookmarks.current': 'green',
178 'bookmarks.current': 'green',
179 'branches.active': 'none',
179 'branches.active': 'none',
180 'branches.closed': 'black bold',
180 'branches.closed': 'black bold',
181 'branches.current': 'green',
181 'branches.current': 'green',
182 'branches.inactive': 'none',
182 'branches.inactive': 'none',
183 'diff.changed': 'white',
183 'diff.changed': 'white',
184 'diff.deleted': 'red',
184 'diff.deleted': 'red',
185 'diff.diffline': 'bold',
185 'diff.diffline': 'bold',
186 'diff.extended': 'cyan bold',
186 'diff.extended': 'cyan bold',
187 'diff.file_a': 'red bold',
187 'diff.file_a': 'red bold',
188 'diff.file_b': 'green bold',
188 'diff.file_b': 'green bold',
189 'diff.hunk': 'magenta',
189 'diff.hunk': 'magenta',
190 'diff.inserted': 'green',
190 'diff.inserted': 'green',
191 'diff.trailingwhitespace': 'bold red_background',
191 'diff.trailingwhitespace': 'bold red_background',
192 'diffstat.deleted': 'red',
192 'diffstat.deleted': 'red',
193 'diffstat.inserted': 'green',
193 'diffstat.inserted': 'green',
194 'ui.prompt': 'yellow',
194 'ui.prompt': 'yellow',
195 'log.changeset': 'yellow',
195 'log.changeset': 'yellow',
196 'resolve.resolved': 'green bold',
196 'resolve.resolved': 'green bold',
197 'resolve.unresolved': 'red bold',
197 'resolve.unresolved': 'red bold',
198 'status.added': 'green bold',
198 'status.added': 'green bold',
199 'status.clean': 'none',
199 'status.clean': 'none',
200 'status.copied': 'none',
200 'status.copied': 'none',
201 'status.deleted': 'cyan bold underline',
201 'status.deleted': 'cyan bold underline',
202 'status.ignored': 'black bold',
202 'status.ignored': 'black bold',
203 'status.modified': 'blue bold',
203 'status.modified': 'blue bold',
204 'status.removed': 'red bold',
204 'status.removed': 'red bold',
205 'status.unknown': 'magenta bold underline'}
205 'status.unknown': 'magenta bold underline'}
206
206
207
207
208 def _effect_str(effect):
208 def _effect_str(effect):
209 '''Helper function for render_effects().'''
209 '''Helper function for render_effects().'''
210
210
211 bg = False
211 bg = False
212 if effect.endswith('_background'):
212 if effect.endswith('_background'):
213 bg = True
213 bg = True
214 effect = effect[:-11]
214 effect = effect[:-11]
215 attr, val = _terminfo_params[effect]
215 attr, val = _terminfo_params[effect]
216 if attr:
216 if attr:
217 return curses.tigetstr(val)
217 return curses.tigetstr(val)
218 elif bg:
218 elif bg:
219 return curses.tparm(curses.tigetstr('setab'), val)
219 return curses.tparm(curses.tigetstr('setab'), val)
220 else:
220 else:
221 return curses.tparm(curses.tigetstr('setaf'), val)
221 return curses.tparm(curses.tigetstr('setaf'), val)
222
222
223 def render_effects(text, effects):
223 def render_effects(text, effects):
224 'Wrap text in commands to turn on each effect.'
224 'Wrap text in commands to turn on each effect.'
225 if not text:
225 if not text:
226 return text
226 return text
227 if not _terminfo_params:
227 if not _terminfo_params:
228 start = [str(_effects[e]) for e in ['none'] + effects.split()]
228 start = [str(_effects[e]) for e in ['none'] + effects.split()]
229 start = '\033[' + ';'.join(start) + 'm'
229 start = '\033[' + ';'.join(start) + 'm'
230 stop = '\033[' + str(_effects['none']) + 'm'
230 stop = '\033[' + str(_effects['none']) + 'm'
231 else:
231 else:
232 start = ''.join(_effect_str(effect)
232 start = ''.join(_effect_str(effect)
233 for effect in ['none'] + effects.split())
233 for effect in ['none'] + effects.split())
234 stop = _effect_str('none')
234 stop = _effect_str('none')
235 return ''.join([start, text, stop])
235 return ''.join([start, text, stop])
236
236
237 def extstyles():
237 def extstyles():
238 for name, ext in extensions.extensions():
238 for name, ext in extensions.extensions():
239 _styles.update(getattr(ext, 'colortable', {}))
239 _styles.update(getattr(ext, 'colortable', {}))
240
240
241 def configstyles(ui):
241 def configstyles(ui):
242 for status, cfgeffects in ui.configitems('color'):
242 for status, cfgeffects in ui.configitems('color'):
243 if '.' not in status or status.startswith('color.'):
243 if '.' not in status or status.startswith('color.'):
244 continue
244 continue
245 cfgeffects = ui.configlist('color', status)
245 cfgeffects = ui.configlist('color', status)
246 if cfgeffects:
246 if cfgeffects:
247 good = []
247 good = []
248 for e in cfgeffects:
248 for e in cfgeffects:
249 if not _terminfo_params and e in _effects:
249 if not _terminfo_params and e in _effects:
250 good.append(e)
250 good.append(e)
251 elif e in _terminfo_params or e[:-11] in _terminfo_params:
251 elif e in _terminfo_params or e[:-11] in _terminfo_params:
252 good.append(e)
252 good.append(e)
253 else:
253 else:
254 ui.warn(_("ignoring unknown color/effect %r "
254 ui.warn(_("ignoring unknown color/effect %r "
255 "(configured in color.%s)\n")
255 "(configured in color.%s)\n")
256 % (e, status))
256 % (e, status))
257 _styles[status] = ' '.join(good)
257 _styles[status] = ' '.join(good)
258
258
259 class colorui(uimod.ui):
259 class colorui(uimod.ui):
260 def popbuffer(self, labeled=False):
260 def popbuffer(self, labeled=False):
261 if labeled:
261 if labeled:
262 return ''.join(self.label(a, label) for a, label
262 return ''.join(self.label(a, label) for a, label
263 in self._buffers.pop())
263 in self._buffers.pop())
264 return ''.join(a for a, label in self._buffers.pop())
264 return ''.join(a for a, label in self._buffers.pop())
265
265
266 _colormode = 'ansi'
266 _colormode = 'ansi'
267 def write(self, *args, **opts):
267 def write(self, *args, **opts):
268 label = opts.get('label', '')
268 label = opts.get('label', '')
269 if self._buffers:
269 if self._buffers:
270 self._buffers[-1].extend([(str(a), label) for a in args])
270 self._buffers[-1].extend([(str(a), label) for a in args])
271 elif self._colormode == 'win32':
271 elif self._colormode == 'win32':
272 for a in args:
272 for a in args:
273 win32print(a, super(colorui, self).write, **opts)
273 win32print(a, super(colorui, self).write, **opts)
274 else:
274 else:
275 return super(colorui, self).write(
275 return super(colorui, self).write(
276 *[self.label(str(a), label) for a in args], **opts)
276 *[self.label(str(a), label) for a in args], **opts)
277
277
278 def write_err(self, *args, **opts):
278 def write_err(self, *args, **opts):
279 label = opts.get('label', '')
279 label = opts.get('label', '')
280 if self._colormode == 'win32':
280 if self._colormode == 'win32':
281 for a in args:
281 for a in args:
282 win32print(a, super(colorui, self).write_err, **opts)
282 win32print(a, super(colorui, self).write_err, **opts)
283 else:
283 else:
284 return super(colorui, self).write_err(
284 return super(colorui, self).write_err(
285 *[self.label(str(a), label) for a in args], **opts)
285 *[self.label(str(a), label) for a in args], **opts)
286
286
287 def label(self, msg, label):
287 def label(self, msg, label):
288 effects = []
288 effects = []
289 for l in label.split():
289 for l in label.split():
290 s = _styles.get(l, '')
290 s = _styles.get(l, '')
291 if s:
291 if s:
292 effects.append(s)
292 effects.append(s)
293 effects = ''.join(effects)
293 effects = ''.join(effects)
294 if effects:
294 if effects:
295 return '\n'.join([render_effects(s, effects)
295 return '\n'.join([render_effects(s, effects)
296 for s in msg.split('\n')])
296 for s in msg.split('\n')])
297 return msg
297 return msg
298
298
299
299
300 def uisetup(ui):
300 def uisetup(ui):
301 global _terminfo_params
301 global _terminfo_params
302 if ui.plain():
302 if ui.plain():
303 return
303 return
304
305 formatted = (os.environ.get('TERM') != 'dumb' and ui.formatted())
304 mode = ui.config('color', 'mode', 'auto')
306 mode = ui.config('color', 'mode', 'auto')
305 if mode == 'auto':
307 if mode == 'auto':
306 if os.name == 'nt' and 'TERM' not in os.environ:
308 if os.name == 'nt' and 'TERM' not in os.environ:
307 # looks line a cmd.exe console, use win32 API or nothing
309 # looks line a cmd.exe console, use win32 API or nothing
308 mode = w32effects and 'win32' or 'none'
310 mode = w32effects and 'win32' or 'none'
309 else:
311 else:
310 if getattr(sys.stdout, 'isatty', None) and sys.stdout.isatty():
312 if not formatted:
311 _terminfo_params = False
313 _terminfo_params = False
312 else:
314 else:
313 _terminfosetup(ui)
315 _terminfosetup(ui)
314 if not _terminfo_params:
316 if not _terminfo_params:
315 mode = 'ansi'
317 mode = 'ansi'
316 else:
318 else:
317 mode = 'terminfo'
319 mode = 'terminfo'
318 if mode == 'win32':
320 if mode == 'win32':
319 if w32effects is None:
321 if w32effects is None:
320 # only warn if color.mode is explicitly set to win32
322 # only warn if color.mode is explicitly set to win32
321 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
323 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
322 return
324 return
323 _effects.update(w32effects)
325 _effects.update(w32effects)
324 elif mode == 'ansi':
326 elif mode == 'ansi':
325 _terminfo_params = {}
327 _terminfo_params = {}
326 elif mode == 'terminfo':
328 elif mode == 'terminfo':
327 _terminfosetup(ui)
329 _terminfosetup(ui)
328 else:
330 else:
329 return
331 return
330 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
332 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
331 coloropt = opts['color']
333 coloropt = opts['color']
332 auto = coloropt == 'auto'
334 auto = coloropt == 'auto'
333 always = util.parsebool(coloropt)
335 always = util.parsebool(coloropt)
334 if (always or
336 if (always or
335 (always is None and
337 (always is None and auto and formatted)):
336 (auto and (os.environ.get('TERM') != 'dumb' and ui_.formatted())))):
337 colorui._colormode = mode
338 colorui._colormode = mode
338 colorui.__bases__ = (ui_.__class__,)
339 colorui.__bases__ = (ui_.__class__,)
339 ui_.__class__ = colorui
340 ui_.__class__ = colorui
340 extstyles()
341 extstyles()
341 configstyles(ui_)
342 configstyles(ui_)
342 return orig(ui_, opts, cmd, cmdfunc)
343 return orig(ui_, opts, cmd, cmdfunc)
343 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
344 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
344
345
345 def extsetup(ui):
346 def extsetup(ui):
346 commands.globalopts.append(
347 commands.globalopts.append(
347 ('', 'color', 'auto',
348 ('', 'color', 'auto',
348 # i18n: 'always', 'auto', and 'never' are keywords and should
349 # i18n: 'always', 'auto', and 'never' are keywords and should
349 # not be translated
350 # not be translated
350 _("when to colorize (boolean, always, auto, or never)"),
351 _("when to colorize (boolean, always, auto, or never)"),
351 _('TYPE')))
352 _('TYPE')))
352
353
353 if os.name != 'nt':
354 if os.name != 'nt':
354 w32effects = None
355 w32effects = None
355 else:
356 else:
356 import re, ctypes
357 import re, ctypes
357
358
358 _kernel32 = ctypes.windll.kernel32
359 _kernel32 = ctypes.windll.kernel32
359
360
360 _WORD = ctypes.c_ushort
361 _WORD = ctypes.c_ushort
361
362
362 _INVALID_HANDLE_VALUE = -1
363 _INVALID_HANDLE_VALUE = -1
363
364
364 class _COORD(ctypes.Structure):
365 class _COORD(ctypes.Structure):
365 _fields_ = [('X', ctypes.c_short),
366 _fields_ = [('X', ctypes.c_short),
366 ('Y', ctypes.c_short)]
367 ('Y', ctypes.c_short)]
367
368
368 class _SMALL_RECT(ctypes.Structure):
369 class _SMALL_RECT(ctypes.Structure):
369 _fields_ = [('Left', ctypes.c_short),
370 _fields_ = [('Left', ctypes.c_short),
370 ('Top', ctypes.c_short),
371 ('Top', ctypes.c_short),
371 ('Right', ctypes.c_short),
372 ('Right', ctypes.c_short),
372 ('Bottom', ctypes.c_short)]
373 ('Bottom', ctypes.c_short)]
373
374
374 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
375 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
375 _fields_ = [('dwSize', _COORD),
376 _fields_ = [('dwSize', _COORD),
376 ('dwCursorPosition', _COORD),
377 ('dwCursorPosition', _COORD),
377 ('wAttributes', _WORD),
378 ('wAttributes', _WORD),
378 ('srWindow', _SMALL_RECT),
379 ('srWindow', _SMALL_RECT),
379 ('dwMaximumWindowSize', _COORD)]
380 ('dwMaximumWindowSize', _COORD)]
380
381
381 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
382 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
382 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
383 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
383
384
384 _FOREGROUND_BLUE = 0x0001
385 _FOREGROUND_BLUE = 0x0001
385 _FOREGROUND_GREEN = 0x0002
386 _FOREGROUND_GREEN = 0x0002
386 _FOREGROUND_RED = 0x0004
387 _FOREGROUND_RED = 0x0004
387 _FOREGROUND_INTENSITY = 0x0008
388 _FOREGROUND_INTENSITY = 0x0008
388
389
389 _BACKGROUND_BLUE = 0x0010
390 _BACKGROUND_BLUE = 0x0010
390 _BACKGROUND_GREEN = 0x0020
391 _BACKGROUND_GREEN = 0x0020
391 _BACKGROUND_RED = 0x0040
392 _BACKGROUND_RED = 0x0040
392 _BACKGROUND_INTENSITY = 0x0080
393 _BACKGROUND_INTENSITY = 0x0080
393
394
394 _COMMON_LVB_REVERSE_VIDEO = 0x4000
395 _COMMON_LVB_REVERSE_VIDEO = 0x4000
395 _COMMON_LVB_UNDERSCORE = 0x8000
396 _COMMON_LVB_UNDERSCORE = 0x8000
396
397
397 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
398 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
398 w32effects = {
399 w32effects = {
399 'none': -1,
400 'none': -1,
400 'black': 0,
401 'black': 0,
401 'red': _FOREGROUND_RED,
402 'red': _FOREGROUND_RED,
402 'green': _FOREGROUND_GREEN,
403 'green': _FOREGROUND_GREEN,
403 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
404 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
404 'blue': _FOREGROUND_BLUE,
405 'blue': _FOREGROUND_BLUE,
405 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
406 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
406 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
407 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
407 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
408 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
408 'bold': _FOREGROUND_INTENSITY,
409 'bold': _FOREGROUND_INTENSITY,
409 'black_background': 0x100, # unused value > 0x0f
410 'black_background': 0x100, # unused value > 0x0f
410 'red_background': _BACKGROUND_RED,
411 'red_background': _BACKGROUND_RED,
411 'green_background': _BACKGROUND_GREEN,
412 'green_background': _BACKGROUND_GREEN,
412 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
413 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
413 'blue_background': _BACKGROUND_BLUE,
414 'blue_background': _BACKGROUND_BLUE,
414 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
415 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
415 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
416 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
416 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
417 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
417 _BACKGROUND_BLUE),
418 _BACKGROUND_BLUE),
418 'bold_background': _BACKGROUND_INTENSITY,
419 'bold_background': _BACKGROUND_INTENSITY,
419 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
420 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
420 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
421 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
421 }
422 }
422
423
423 passthrough = set([_FOREGROUND_INTENSITY,
424 passthrough = set([_FOREGROUND_INTENSITY,
424 _BACKGROUND_INTENSITY,
425 _BACKGROUND_INTENSITY,
425 _COMMON_LVB_UNDERSCORE,
426 _COMMON_LVB_UNDERSCORE,
426 _COMMON_LVB_REVERSE_VIDEO])
427 _COMMON_LVB_REVERSE_VIDEO])
427
428
428 stdout = _kernel32.GetStdHandle(
429 stdout = _kernel32.GetStdHandle(
429 _STD_OUTPUT_HANDLE) # don't close the handle returned
430 _STD_OUTPUT_HANDLE) # don't close the handle returned
430 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
431 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
431 w32effects = None
432 w32effects = None
432 else:
433 else:
433 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
434 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
434 if not _kernel32.GetConsoleScreenBufferInfo(
435 if not _kernel32.GetConsoleScreenBufferInfo(
435 stdout, ctypes.byref(csbi)):
436 stdout, ctypes.byref(csbi)):
436 # stdout may not support GetConsoleScreenBufferInfo()
437 # stdout may not support GetConsoleScreenBufferInfo()
437 # when called from subprocess or redirected
438 # when called from subprocess or redirected
438 w32effects = None
439 w32effects = None
439 else:
440 else:
440 origattr = csbi.wAttributes
441 origattr = csbi.wAttributes
441 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
442 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
442 re.MULTILINE | re.DOTALL)
443 re.MULTILINE | re.DOTALL)
443
444
444 def win32print(text, orig, **opts):
445 def win32print(text, orig, **opts):
445 label = opts.get('label', '')
446 label = opts.get('label', '')
446 attr = origattr
447 attr = origattr
447
448
448 def mapcolor(val, attr):
449 def mapcolor(val, attr):
449 if val == -1:
450 if val == -1:
450 return origattr
451 return origattr
451 elif val in passthrough:
452 elif val in passthrough:
452 return attr | val
453 return attr | val
453 elif val > 0x0f:
454 elif val > 0x0f:
454 return (val & 0x70) | (attr & 0x8f)
455 return (val & 0x70) | (attr & 0x8f)
455 else:
456 else:
456 return (val & 0x07) | (attr & 0xf8)
457 return (val & 0x07) | (attr & 0xf8)
457
458
458 # determine console attributes based on labels
459 # determine console attributes based on labels
459 for l in label.split():
460 for l in label.split():
460 style = _styles.get(l, '')
461 style = _styles.get(l, '')
461 for effect in style.split():
462 for effect in style.split():
462 attr = mapcolor(w32effects[effect], attr)
463 attr = mapcolor(w32effects[effect], attr)
463
464
464 # hack to ensure regexp finds data
465 # hack to ensure regexp finds data
465 if not text.startswith('\033['):
466 if not text.startswith('\033['):
466 text = '\033[m' + text
467 text = '\033[m' + text
467
468
468 # Look for ANSI-like codes embedded in text
469 # Look for ANSI-like codes embedded in text
469 m = re.match(ansire, text)
470 m = re.match(ansire, text)
470
471
471 try:
472 try:
472 while m:
473 while m:
473 for sattr in m.group(1).split(';'):
474 for sattr in m.group(1).split(';'):
474 if sattr:
475 if sattr:
475 attr = mapcolor(int(sattr), attr)
476 attr = mapcolor(int(sattr), attr)
476 _kernel32.SetConsoleTextAttribute(stdout, attr)
477 _kernel32.SetConsoleTextAttribute(stdout, attr)
477 orig(m.group(2), **opts)
478 orig(m.group(2), **opts)
478 m = re.match(ansire, m.group(3))
479 m = re.match(ansire, m.group(3))
479 finally:
480 finally:
480 # Explicity reset original attributes
481 # Explicity reset original attributes
481 _kernel32.SetConsoleTextAttribute(stdout, origattr)
482 _kernel32.SetConsoleTextAttribute(stdout, origattr)
General Comments 0
You need to be logged in to leave comments. Login now