##// END OF EJS Templates
color: code simplification
Patrick Mezard -
r13998:14c7526f default
parent child Browse files
Show More
@@ -1,480 +1,478 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
103 import os
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(dict((
129 _terminfo_params.update((key[6:], (False, int(val)))
130 (key[6:], (False, int(val)))
131 for key, val in ui.configitems('color')
130 for key, val in ui.configitems('color')
132 if key.startswith('color.')
131 if key.startswith('color.'))
133 )))
134
132
135 try:
133 try:
136 curses.setupterm()
134 curses.setupterm()
137 except curses.error, e:
135 except curses.error, e:
138 _terminfo_params = {}
136 _terminfo_params = {}
139 return
137 return
140
138
141 for key, (b, e) in _terminfo_params.items():
139 for key, (b, e) in _terminfo_params.items():
142 if not b:
140 if not b:
143 continue
141 continue
144 if not curses.tigetstr(e):
142 if not curses.tigetstr(e):
145 # 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
146 # noisy and use ui.debug().
144 # noisy and use ui.debug().
147 ui.debug("no terminfo entry for %s\n" % e)
145 ui.debug("no terminfo entry for %s\n" % e)
148 del _terminfo_params[key]
146 del _terminfo_params[key]
149 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
147 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
150 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
148 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
151 "ECMA-48 color\n"))
149 "ECMA-48 color\n"))
152 _terminfo_params = {}
150 _terminfo_params = {}
153
151
154 try:
152 try:
155 import curses
153 import curses
156 # Mapping from effect name to terminfo attribute name or color number.
154 # Mapping from effect name to terminfo attribute name or color number.
157 # This will also force-load the curses module.
155 # This will also force-load the curses module.
158 _terminfo_params = {'none': (True, 'sgr0'),
156 _terminfo_params = {'none': (True, 'sgr0'),
159 'standout': (True, 'smso'),
157 'standout': (True, 'smso'),
160 'underline': (True, 'smul'),
158 'underline': (True, 'smul'),
161 'reverse': (True, 'rev'),
159 'reverse': (True, 'rev'),
162 'inverse': (True, 'rev'),
160 'inverse': (True, 'rev'),
163 'blink': (True, 'blink'),
161 'blink': (True, 'blink'),
164 'dim': (True, 'dim'),
162 'dim': (True, 'dim'),
165 'bold': (True, 'bold'),
163 'bold': (True, 'bold'),
166 'invisible': (True, 'invis'),
164 'invisible': (True, 'invis'),
167 'italic': (True, 'sitm'),
165 'italic': (True, 'sitm'),
168 'black': (False, curses.COLOR_BLACK),
166 'black': (False, curses.COLOR_BLACK),
169 'red': (False, curses.COLOR_RED),
167 'red': (False, curses.COLOR_RED),
170 'green': (False, curses.COLOR_GREEN),
168 'green': (False, curses.COLOR_GREEN),
171 'yellow': (False, curses.COLOR_YELLOW),
169 'yellow': (False, curses.COLOR_YELLOW),
172 'blue': (False, curses.COLOR_BLUE),
170 'blue': (False, curses.COLOR_BLUE),
173 'magenta': (False, curses.COLOR_MAGENTA),
171 'magenta': (False, curses.COLOR_MAGENTA),
174 'cyan': (False, curses.COLOR_CYAN),
172 'cyan': (False, curses.COLOR_CYAN),
175 'white': (False, curses.COLOR_WHITE)}
173 'white': (False, curses.COLOR_WHITE)}
176 except ImportError:
174 except ImportError:
177 _terminfo_params = False
175 _terminfo_params = False
178
176
179 _styles = {'grep.match': 'red bold',
177 _styles = {'grep.match': 'red bold',
180 'bookmarks.current': 'green',
178 'bookmarks.current': 'green',
181 'branches.active': 'none',
179 'branches.active': 'none',
182 'branches.closed': 'black bold',
180 'branches.closed': 'black bold',
183 'branches.current': 'green',
181 'branches.current': 'green',
184 'branches.inactive': 'none',
182 'branches.inactive': 'none',
185 'diff.changed': 'white',
183 'diff.changed': 'white',
186 'diff.deleted': 'red',
184 'diff.deleted': 'red',
187 'diff.diffline': 'bold',
185 'diff.diffline': 'bold',
188 'diff.extended': 'cyan bold',
186 'diff.extended': 'cyan bold',
189 'diff.file_a': 'red bold',
187 'diff.file_a': 'red bold',
190 'diff.file_b': 'green bold',
188 'diff.file_b': 'green bold',
191 'diff.hunk': 'magenta',
189 'diff.hunk': 'magenta',
192 'diff.inserted': 'green',
190 'diff.inserted': 'green',
193 'diff.trailingwhitespace': 'bold red_background',
191 'diff.trailingwhitespace': 'bold red_background',
194 'diffstat.deleted': 'red',
192 'diffstat.deleted': 'red',
195 'diffstat.inserted': 'green',
193 'diffstat.inserted': 'green',
196 'ui.prompt': 'yellow',
194 'ui.prompt': 'yellow',
197 'log.changeset': 'yellow',
195 'log.changeset': 'yellow',
198 'resolve.resolved': 'green bold',
196 'resolve.resolved': 'green bold',
199 'resolve.unresolved': 'red bold',
197 'resolve.unresolved': 'red bold',
200 'status.added': 'green bold',
198 'status.added': 'green bold',
201 'status.clean': 'none',
199 'status.clean': 'none',
202 'status.copied': 'none',
200 'status.copied': 'none',
203 'status.deleted': 'cyan bold underline',
201 'status.deleted': 'cyan bold underline',
204 'status.ignored': 'black bold',
202 'status.ignored': 'black bold',
205 'status.modified': 'blue bold',
203 'status.modified': 'blue bold',
206 'status.removed': 'red bold',
204 'status.removed': 'red bold',
207 'status.unknown': 'magenta bold underline'}
205 'status.unknown': 'magenta bold underline'}
208
206
209
207
210 def _effect_str(effect):
208 def _effect_str(effect):
211 '''Helper function for render_effects().'''
209 '''Helper function for render_effects().'''
212
210
213 bg = False
211 bg = False
214 if effect.endswith('_background'):
212 if effect.endswith('_background'):
215 bg = True
213 bg = True
216 effect = effect[:-11]
214 effect = effect[:-11]
217 attr, val = _terminfo_params[effect]
215 attr, val = _terminfo_params[effect]
218 if attr:
216 if attr:
219 return curses.tigetstr(val)
217 return curses.tigetstr(val)
220 elif bg:
218 elif bg:
221 return curses.tparm(curses.tigetstr('setab'), val)
219 return curses.tparm(curses.tigetstr('setab'), val)
222 else:
220 else:
223 return curses.tparm(curses.tigetstr('setaf'), val)
221 return curses.tparm(curses.tigetstr('setaf'), val)
224
222
225 def render_effects(text, effects):
223 def render_effects(text, effects):
226 'Wrap text in commands to turn on each effect.'
224 'Wrap text in commands to turn on each effect.'
227 if not text:
225 if not text:
228 return text
226 return text
229 if not _terminfo_params:
227 if not _terminfo_params:
230 start = [str(_effects[e]) for e in ['none'] + effects.split()]
228 start = [str(_effects[e]) for e in ['none'] + effects.split()]
231 start = '\033[' + ';'.join(start) + 'm'
229 start = '\033[' + ';'.join(start) + 'm'
232 stop = '\033[' + str(_effects['none']) + 'm'
230 stop = '\033[' + str(_effects['none']) + 'm'
233 else:
231 else:
234 start = ''.join(_effect_str(effect)
232 start = ''.join(_effect_str(effect)
235 for effect in ['none'] + effects.split())
233 for effect in ['none'] + effects.split())
236 stop = _effect_str('none')
234 stop = _effect_str('none')
237 return ''.join([start, text, stop])
235 return ''.join([start, text, stop])
238
236
239 def extstyles():
237 def extstyles():
240 for name, ext in extensions.extensions():
238 for name, ext in extensions.extensions():
241 _styles.update(getattr(ext, 'colortable', {}))
239 _styles.update(getattr(ext, 'colortable', {}))
242
240
243 def configstyles(ui):
241 def configstyles(ui):
244 for status, cfgeffects in ui.configitems('color'):
242 for status, cfgeffects in ui.configitems('color'):
245 if '.' not in status or status.startswith('color.'):
243 if '.' not in status or status.startswith('color.'):
246 continue
244 continue
247 cfgeffects = ui.configlist('color', status)
245 cfgeffects = ui.configlist('color', status)
248 if cfgeffects:
246 if cfgeffects:
249 good = []
247 good = []
250 for e in cfgeffects:
248 for e in cfgeffects:
251 if not _terminfo_params and e in _effects:
249 if not _terminfo_params and e in _effects:
252 good.append(e)
250 good.append(e)
253 elif e in _terminfo_params or e[:-11] in _terminfo_params:
251 elif e in _terminfo_params or e[:-11] in _terminfo_params:
254 good.append(e)
252 good.append(e)
255 else:
253 else:
256 ui.warn(_("ignoring unknown color/effect %r "
254 ui.warn(_("ignoring unknown color/effect %r "
257 "(configured in color.%s)\n")
255 "(configured in color.%s)\n")
258 % (e, status))
256 % (e, status))
259 _styles[status] = ' '.join(good)
257 _styles[status] = ' '.join(good)
260
258
261 class colorui(uimod.ui):
259 class colorui(uimod.ui):
262 def popbuffer(self, labeled=False):
260 def popbuffer(self, labeled=False):
263 if labeled:
261 if labeled:
264 return ''.join(self.label(a, label) for a, label
262 return ''.join(self.label(a, label) for a, label
265 in self._buffers.pop())
263 in self._buffers.pop())
266 return ''.join(a for a, label in self._buffers.pop())
264 return ''.join(a for a, label in self._buffers.pop())
267
265
268 _colormode = 'ansi'
266 _colormode = 'ansi'
269 def write(self, *args, **opts):
267 def write(self, *args, **opts):
270 label = opts.get('label', '')
268 label = opts.get('label', '')
271 if self._buffers:
269 if self._buffers:
272 self._buffers[-1].extend([(str(a), label) for a in args])
270 self._buffers[-1].extend([(str(a), label) for a in args])
273 elif self._colormode == 'win32':
271 elif self._colormode == 'win32':
274 for a in args:
272 for a in args:
275 win32print(a, super(colorui, self).write, **opts)
273 win32print(a, super(colorui, self).write, **opts)
276 else:
274 else:
277 return super(colorui, self).write(
275 return super(colorui, self).write(
278 *[self.label(str(a), label) for a in args], **opts)
276 *[self.label(str(a), label) for a in args], **opts)
279
277
280 def write_err(self, *args, **opts):
278 def write_err(self, *args, **opts):
281 label = opts.get('label', '')
279 label = opts.get('label', '')
282 if self._colormode == 'win32':
280 if self._colormode == 'win32':
283 for a in args:
281 for a in args:
284 win32print(a, super(colorui, self).write_err, **opts)
282 win32print(a, super(colorui, self).write_err, **opts)
285 else:
283 else:
286 return super(colorui, self).write_err(
284 return super(colorui, self).write_err(
287 *[self.label(str(a), label) for a in args], **opts)
285 *[self.label(str(a), label) for a in args], **opts)
288
286
289 def label(self, msg, label):
287 def label(self, msg, label):
290 effects = []
288 effects = []
291 for l in label.split():
289 for l in label.split():
292 s = _styles.get(l, '')
290 s = _styles.get(l, '')
293 if s:
291 if s:
294 effects.append(s)
292 effects.append(s)
295 effects = ''.join(effects)
293 effects = ''.join(effects)
296 if effects:
294 if effects:
297 return '\n'.join([render_effects(s, effects)
295 return '\n'.join([render_effects(s, effects)
298 for s in msg.split('\n')])
296 for s in msg.split('\n')])
299 return msg
297 return msg
300
298
301
299
302 def uisetup(ui):
300 def uisetup(ui):
303 global _terminfo_params
301 global _terminfo_params
304 if ui.plain():
302 if ui.plain():
305 return
303 return
306 mode = ui.config('color', 'mode', 'auto')
304 mode = ui.config('color', 'mode', 'auto')
307 if mode == 'auto':
305 if mode == 'auto':
308 if os.name == 'nt' and 'TERM' not in os.environ:
306 if os.name == 'nt' and 'TERM' not in os.environ:
309 # looks line a cmd.exe console, use win32 API or nothing
307 # looks line a cmd.exe console, use win32 API or nothing
310 mode = w32effects and 'win32' or 'none'
308 mode = w32effects and 'win32' or 'none'
311 else:
309 else:
312 _terminfosetup(ui)
310 _terminfosetup(ui)
313 if not _terminfo_params:
311 if not _terminfo_params:
314 mode = 'ansi'
312 mode = 'ansi'
315 else:
313 else:
316 mode = 'terminfo'
314 mode = 'terminfo'
317 if mode == 'win32':
315 if mode == 'win32':
318 if w32effects is None:
316 if w32effects is None:
319 # only warn if color.mode is explicitly set to win32
317 # only warn if color.mode is explicitly set to win32
320 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
318 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
321 return
319 return
322 _effects.update(w32effects)
320 _effects.update(w32effects)
323 elif mode == 'ansi':
321 elif mode == 'ansi':
324 _terminfo_params = {}
322 _terminfo_params = {}
325 elif mode == 'terminfo':
323 elif mode == 'terminfo':
326 _terminfosetup(ui)
324 _terminfosetup(ui)
327 elif mode not in ('ansi', 'terminfo'):
325 else:
328 return
326 return
329 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
327 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
330 coloropt = opts['color']
328 coloropt = opts['color']
331 auto = coloropt == 'auto'
329 auto = coloropt == 'auto'
332 always = util.parsebool(coloropt)
330 always = util.parsebool(coloropt)
333 if (always or
331 if (always or
334 (always is None and
332 (always is None and
335 (auto and (os.environ.get('TERM') != 'dumb' and ui_.formatted())))):
333 (auto and (os.environ.get('TERM') != 'dumb' and ui_.formatted())))):
336 colorui._colormode = mode
334 colorui._colormode = mode
337 colorui.__bases__ = (ui_.__class__,)
335 colorui.__bases__ = (ui_.__class__,)
338 ui_.__class__ = colorui
336 ui_.__class__ = colorui
339 extstyles()
337 extstyles()
340 configstyles(ui_)
338 configstyles(ui_)
341 return orig(ui_, opts, cmd, cmdfunc)
339 return orig(ui_, opts, cmd, cmdfunc)
342 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
340 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
343
341
344 def extsetup(ui):
342 def extsetup(ui):
345 commands.globalopts.append(
343 commands.globalopts.append(
346 ('', 'color', 'auto',
344 ('', 'color', 'auto',
347 # i18n: 'always', 'auto', and 'never' are keywords and should
345 # i18n: 'always', 'auto', and 'never' are keywords and should
348 # not be translated
346 # not be translated
349 _("when to colorize (boolean, always, auto, or never)"),
347 _("when to colorize (boolean, always, auto, or never)"),
350 _('TYPE')))
348 _('TYPE')))
351
349
352 if os.name != 'nt':
350 if os.name != 'nt':
353 w32effects = None
351 w32effects = None
354 else:
352 else:
355 import re, ctypes
353 import re, ctypes
356
354
357 _kernel32 = ctypes.windll.kernel32
355 _kernel32 = ctypes.windll.kernel32
358
356
359 _WORD = ctypes.c_ushort
357 _WORD = ctypes.c_ushort
360
358
361 _INVALID_HANDLE_VALUE = -1
359 _INVALID_HANDLE_VALUE = -1
362
360
363 class _COORD(ctypes.Structure):
361 class _COORD(ctypes.Structure):
364 _fields_ = [('X', ctypes.c_short),
362 _fields_ = [('X', ctypes.c_short),
365 ('Y', ctypes.c_short)]
363 ('Y', ctypes.c_short)]
366
364
367 class _SMALL_RECT(ctypes.Structure):
365 class _SMALL_RECT(ctypes.Structure):
368 _fields_ = [('Left', ctypes.c_short),
366 _fields_ = [('Left', ctypes.c_short),
369 ('Top', ctypes.c_short),
367 ('Top', ctypes.c_short),
370 ('Right', ctypes.c_short),
368 ('Right', ctypes.c_short),
371 ('Bottom', ctypes.c_short)]
369 ('Bottom', ctypes.c_short)]
372
370
373 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
371 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
374 _fields_ = [('dwSize', _COORD),
372 _fields_ = [('dwSize', _COORD),
375 ('dwCursorPosition', _COORD),
373 ('dwCursorPosition', _COORD),
376 ('wAttributes', _WORD),
374 ('wAttributes', _WORD),
377 ('srWindow', _SMALL_RECT),
375 ('srWindow', _SMALL_RECT),
378 ('dwMaximumWindowSize', _COORD)]
376 ('dwMaximumWindowSize', _COORD)]
379
377
380 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
378 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
381 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
379 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
382
380
383 _FOREGROUND_BLUE = 0x0001
381 _FOREGROUND_BLUE = 0x0001
384 _FOREGROUND_GREEN = 0x0002
382 _FOREGROUND_GREEN = 0x0002
385 _FOREGROUND_RED = 0x0004
383 _FOREGROUND_RED = 0x0004
386 _FOREGROUND_INTENSITY = 0x0008
384 _FOREGROUND_INTENSITY = 0x0008
387
385
388 _BACKGROUND_BLUE = 0x0010
386 _BACKGROUND_BLUE = 0x0010
389 _BACKGROUND_GREEN = 0x0020
387 _BACKGROUND_GREEN = 0x0020
390 _BACKGROUND_RED = 0x0040
388 _BACKGROUND_RED = 0x0040
391 _BACKGROUND_INTENSITY = 0x0080
389 _BACKGROUND_INTENSITY = 0x0080
392
390
393 _COMMON_LVB_REVERSE_VIDEO = 0x4000
391 _COMMON_LVB_REVERSE_VIDEO = 0x4000
394 _COMMON_LVB_UNDERSCORE = 0x8000
392 _COMMON_LVB_UNDERSCORE = 0x8000
395
393
396 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
394 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
397 w32effects = {
395 w32effects = {
398 'none': -1,
396 'none': -1,
399 'black': 0,
397 'black': 0,
400 'red': _FOREGROUND_RED,
398 'red': _FOREGROUND_RED,
401 'green': _FOREGROUND_GREEN,
399 'green': _FOREGROUND_GREEN,
402 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
400 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
403 'blue': _FOREGROUND_BLUE,
401 'blue': _FOREGROUND_BLUE,
404 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
402 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
405 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
403 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
406 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
404 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
407 'bold': _FOREGROUND_INTENSITY,
405 'bold': _FOREGROUND_INTENSITY,
408 'black_background': 0x100, # unused value > 0x0f
406 'black_background': 0x100, # unused value > 0x0f
409 'red_background': _BACKGROUND_RED,
407 'red_background': _BACKGROUND_RED,
410 'green_background': _BACKGROUND_GREEN,
408 'green_background': _BACKGROUND_GREEN,
411 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
409 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
412 'blue_background': _BACKGROUND_BLUE,
410 'blue_background': _BACKGROUND_BLUE,
413 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
411 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
414 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
412 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
415 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
413 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
416 _BACKGROUND_BLUE),
414 _BACKGROUND_BLUE),
417 'bold_background': _BACKGROUND_INTENSITY,
415 'bold_background': _BACKGROUND_INTENSITY,
418 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
416 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
419 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
417 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
420 }
418 }
421
419
422 passthrough = set([_FOREGROUND_INTENSITY,
420 passthrough = set([_FOREGROUND_INTENSITY,
423 _BACKGROUND_INTENSITY,
421 _BACKGROUND_INTENSITY,
424 _COMMON_LVB_UNDERSCORE,
422 _COMMON_LVB_UNDERSCORE,
425 _COMMON_LVB_REVERSE_VIDEO])
423 _COMMON_LVB_REVERSE_VIDEO])
426
424
427 stdout = _kernel32.GetStdHandle(
425 stdout = _kernel32.GetStdHandle(
428 _STD_OUTPUT_HANDLE) # don't close the handle returned
426 _STD_OUTPUT_HANDLE) # don't close the handle returned
429 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
427 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
430 w32effects = None
428 w32effects = None
431 else:
429 else:
432 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
430 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
433 if not _kernel32.GetConsoleScreenBufferInfo(
431 if not _kernel32.GetConsoleScreenBufferInfo(
434 stdout, ctypes.byref(csbi)):
432 stdout, ctypes.byref(csbi)):
435 # stdout may not support GetConsoleScreenBufferInfo()
433 # stdout may not support GetConsoleScreenBufferInfo()
436 # when called from subprocess or redirected
434 # when called from subprocess or redirected
437 w32effects = None
435 w32effects = None
438 else:
436 else:
439 origattr = csbi.wAttributes
437 origattr = csbi.wAttributes
440 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
438 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
441 re.MULTILINE | re.DOTALL)
439 re.MULTILINE | re.DOTALL)
442
440
443 def win32print(text, orig, **opts):
441 def win32print(text, orig, **opts):
444 label = opts.get('label', '')
442 label = opts.get('label', '')
445 attr = origattr
443 attr = origattr
446
444
447 def mapcolor(val, attr):
445 def mapcolor(val, attr):
448 if val == -1:
446 if val == -1:
449 return origattr
447 return origattr
450 elif val in passthrough:
448 elif val in passthrough:
451 return attr | val
449 return attr | val
452 elif val > 0x0f:
450 elif val > 0x0f:
453 return (val & 0x70) | (attr & 0x8f)
451 return (val & 0x70) | (attr & 0x8f)
454 else:
452 else:
455 return (val & 0x07) | (attr & 0xf8)
453 return (val & 0x07) | (attr & 0xf8)
456
454
457 # determine console attributes based on labels
455 # determine console attributes based on labels
458 for l in label.split():
456 for l in label.split():
459 style = _styles.get(l, '')
457 style = _styles.get(l, '')
460 for effect in style.split():
458 for effect in style.split():
461 attr = mapcolor(w32effects[effect], attr)
459 attr = mapcolor(w32effects[effect], attr)
462
460
463 # hack to ensure regexp finds data
461 # hack to ensure regexp finds data
464 if not text.startswith('\033['):
462 if not text.startswith('\033['):
465 text = '\033[m' + text
463 text = '\033[m' + text
466
464
467 # Look for ANSI-like codes embedded in text
465 # Look for ANSI-like codes embedded in text
468 m = re.match(ansire, text)
466 m = re.match(ansire, text)
469
467
470 try:
468 try:
471 while m:
469 while m:
472 for sattr in m.group(1).split(';'):
470 for sattr in m.group(1).split(';'):
473 if sattr:
471 if sattr:
474 attr = mapcolor(int(sattr), attr)
472 attr = mapcolor(int(sattr), attr)
475 _kernel32.SetConsoleTextAttribute(stdout, attr)
473 _kernel32.SetConsoleTextAttribute(stdout, attr)
476 orig(m.group(2), **opts)
474 orig(m.group(2), **opts)
477 m = re.match(ansire, m.group(3))
475 m = re.match(ansire, m.group(3))
478 finally:
476 finally:
479 # Explicity reset original attributes
477 # Explicity reset original attributes
480 _kernel32.SetConsoleTextAttribute(stdout, origattr)
478 _kernel32.SetConsoleTextAttribute(stdout, origattr)
General Comments 0
You need to be logged in to leave comments. Login now