##// END OF EJS Templates
color: move the 'colorlabel' function in the core module...
Pierre-Yves David -
r31086:e6082078 default
parent child Browse files
Show More
@@ -1,430 +1,408
1 # color.py color output for Mercurial commands
1 # color.py color output for Mercurial 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 software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''colorize output from some commands
8 '''colorize output from some commands
9
9
10 The color extension colorizes output from several Mercurial commands.
10 The color extension colorizes output from several Mercurial commands.
11 For example, the diff command shows additions in green and deletions
11 For example, the diff command shows additions in green and deletions
12 in red, while the status command shows modified files in magenta. Many
12 in red, while the status command shows modified files in magenta. Many
13 other commands have analogous colors. It is possible to customize
13 other commands have analogous colors. It is possible to customize
14 these colors.
14 these colors.
15
15
16 Effects
16 Effects
17 -------
17 -------
18
18
19 Other effects in addition to color, like bold and underlined text, are
19 Other effects in addition to color, like bold and underlined text, are
20 also available. By default, the terminfo database is used to find the
20 also available. By default, the terminfo database is used to find the
21 terminal codes used to change color and effect. If terminfo is not
21 terminal codes used to change color and effect. If terminfo is not
22 available, then effects are rendered with the ECMA-48 SGR control
22 available, then effects are rendered with the ECMA-48 SGR control
23 function (aka ANSI escape codes).
23 function (aka ANSI escape codes).
24
24
25 The available effects in terminfo mode are 'blink', 'bold', 'dim',
25 The available effects in terminfo mode are 'blink', 'bold', 'dim',
26 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
26 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
27 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
27 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
28 'underline'. How each is rendered depends on the terminal emulator.
28 'underline'. How each is rendered depends on the terminal emulator.
29 Some may not be available for a given terminal type, and will be
29 Some may not be available for a given terminal type, and will be
30 silently ignored.
30 silently ignored.
31
31
32 If the terminfo entry for your terminal is missing codes for an effect
32 If the terminfo entry for your terminal is missing codes for an effect
33 or has the wrong codes, you can add or override those codes in your
33 or has the wrong codes, you can add or override those codes in your
34 configuration::
34 configuration::
35
35
36 [color]
36 [color]
37 terminfo.dim = \E[2m
37 terminfo.dim = \E[2m
38
38
39 where '\E' is substituted with an escape character.
39 where '\E' is substituted with an escape character.
40
40
41 Labels
41 Labels
42 ------
42 ------
43
43
44 Text receives color effects depending on the labels that it has. Many
44 Text receives color effects depending on the labels that it has. Many
45 default Mercurial commands emit labelled text. You can also define
45 default Mercurial commands emit labelled text. You can also define
46 your own labels in templates using the label function, see :hg:`help
46 your own labels in templates using the label function, see :hg:`help
47 templates`. A single portion of text may have more than one label. In
47 templates`. A single portion of text may have more than one label. In
48 that case, effects given to the last label will override any other
48 that case, effects given to the last label will override any other
49 effects. This includes the special "none" effect, which nullifies
49 effects. This includes the special "none" effect, which nullifies
50 other effects.
50 other effects.
51
51
52 Labels are normally invisible. In order to see these labels and their
52 Labels are normally invisible. In order to see these labels and their
53 position in the text, use the global --color=debug option. The same
53 position in the text, use the global --color=debug option. The same
54 anchor text may be associated to multiple labels, e.g.
54 anchor text may be associated to multiple labels, e.g.
55
55
56 [log.changeset changeset.secret|changeset: 22611:6f0a53c8f587]
56 [log.changeset changeset.secret|changeset: 22611:6f0a53c8f587]
57
57
58 The following are the default effects for some default labels. Default
58 The following are the default effects for some default labels. Default
59 effects may be overridden from your configuration file::
59 effects may be overridden from your configuration file::
60
60
61 [color]
61 [color]
62 status.modified = blue bold underline red_background
62 status.modified = blue bold underline red_background
63 status.added = green bold
63 status.added = green bold
64 status.removed = red bold blue_background
64 status.removed = red bold blue_background
65 status.deleted = cyan bold underline
65 status.deleted = cyan bold underline
66 status.unknown = magenta bold underline
66 status.unknown = magenta bold underline
67 status.ignored = black bold
67 status.ignored = black bold
68
68
69 # 'none' turns off all effects
69 # 'none' turns off all effects
70 status.clean = none
70 status.clean = none
71 status.copied = none
71 status.copied = none
72
72
73 qseries.applied = blue bold underline
73 qseries.applied = blue bold underline
74 qseries.unapplied = black bold
74 qseries.unapplied = black bold
75 qseries.missing = red bold
75 qseries.missing = red bold
76
76
77 diff.diffline = bold
77 diff.diffline = bold
78 diff.extended = cyan bold
78 diff.extended = cyan bold
79 diff.file_a = red bold
79 diff.file_a = red bold
80 diff.file_b = green bold
80 diff.file_b = green bold
81 diff.hunk = magenta
81 diff.hunk = magenta
82 diff.deleted = red
82 diff.deleted = red
83 diff.inserted = green
83 diff.inserted = green
84 diff.changed = white
84 diff.changed = white
85 diff.tab =
85 diff.tab =
86 diff.trailingwhitespace = bold red_background
86 diff.trailingwhitespace = bold red_background
87
87
88 # Blank so it inherits the style of the surrounding label
88 # Blank so it inherits the style of the surrounding label
89 changeset.public =
89 changeset.public =
90 changeset.draft =
90 changeset.draft =
91 changeset.secret =
91 changeset.secret =
92
92
93 resolve.unresolved = red bold
93 resolve.unresolved = red bold
94 resolve.resolved = green bold
94 resolve.resolved = green bold
95
95
96 bookmarks.active = green
96 bookmarks.active = green
97
97
98 branches.active = none
98 branches.active = none
99 branches.closed = black bold
99 branches.closed = black bold
100 branches.current = green
100 branches.current = green
101 branches.inactive = none
101 branches.inactive = none
102
102
103 tags.normal = green
103 tags.normal = green
104 tags.local = black bold
104 tags.local = black bold
105
105
106 rebase.rebased = blue
106 rebase.rebased = blue
107 rebase.remaining = red bold
107 rebase.remaining = red bold
108
108
109 shelve.age = cyan
109 shelve.age = cyan
110 shelve.newest = green bold
110 shelve.newest = green bold
111 shelve.name = blue bold
111 shelve.name = blue bold
112
112
113 histedit.remaining = red bold
113 histedit.remaining = red bold
114
114
115 Custom colors
115 Custom colors
116 -------------
116 -------------
117
117
118 Because there are only eight standard colors, this module allows you
118 Because there are only eight standard colors, this module allows you
119 to define color names for other color slots which might be available
119 to define color names for other color slots which might be available
120 for your terminal type, assuming terminfo mode. For instance::
120 for your terminal type, assuming terminfo mode. For instance::
121
121
122 color.brightblue = 12
122 color.brightblue = 12
123 color.pink = 207
123 color.pink = 207
124 color.orange = 202
124 color.orange = 202
125
125
126 to set 'brightblue' to color slot 12 (useful for 16 color terminals
126 to set 'brightblue' to color slot 12 (useful for 16 color terminals
127 that have brighter colors defined in the upper eight) and, 'pink' and
127 that have brighter colors defined in the upper eight) and, 'pink' and
128 'orange' to colors in 256-color xterm's default color cube. These
128 'orange' to colors in 256-color xterm's default color cube. These
129 defined colors may then be used as any of the pre-defined eight,
129 defined colors may then be used as any of the pre-defined eight,
130 including appending '_background' to set the background to that color.
130 including appending '_background' to set the background to that color.
131
131
132 Modes
132 Modes
133 -----
133 -----
134
134
135 By default, the color extension will use ANSI mode (or win32 mode on
135 By default, the color extension will use ANSI mode (or win32 mode on
136 Windows) if it detects a terminal. To override auto mode (to enable
136 Windows) if it detects a terminal. To override auto mode (to enable
137 terminfo mode, for example), set the following configuration option::
137 terminfo mode, for example), set the following configuration option::
138
138
139 [color]
139 [color]
140 mode = terminfo
140 mode = terminfo
141
141
142 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
142 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
143 disable color.
143 disable color.
144
144
145 Note that on some systems, terminfo mode may cause problems when using
145 Note that on some systems, terminfo mode may cause problems when using
146 color with the pager extension and less -R. less with the -R option
146 color with the pager extension and less -R. less with the -R option
147 will only display ECMA-48 color codes, and terminfo mode may sometimes
147 will only display ECMA-48 color codes, and terminfo mode may sometimes
148 emit codes that less doesn't understand. You can work around this by
148 emit codes that less doesn't understand. You can work around this by
149 either using ansi mode (or auto mode), or by using less -r (which will
149 either using ansi mode (or auto mode), or by using less -r (which will
150 pass through all terminal control codes, not just color control
150 pass through all terminal control codes, not just color control
151 codes).
151 codes).
152
152
153 On some systems (such as MSYS in Windows), the terminal may support
153 On some systems (such as MSYS in Windows), the terminal may support
154 a different color mode than the pager (activated via the "pager"
154 a different color mode than the pager (activated via the "pager"
155 extension). It is possible to define separate modes depending on whether
155 extension). It is possible to define separate modes depending on whether
156 the pager is active::
156 the pager is active::
157
157
158 [color]
158 [color]
159 mode = auto
159 mode = auto
160 pagermode = ansi
160 pagermode = ansi
161
161
162 If ``pagermode`` is not defined, the ``mode`` will be used.
162 If ``pagermode`` is not defined, the ``mode`` will be used.
163 '''
163 '''
164
164
165 from __future__ import absolute_import
165 from __future__ import absolute_import
166
166
167 try:
167 try:
168 import curses
168 import curses
169 curses.COLOR_BLACK # force import
169 curses.COLOR_BLACK # force import
170 except ImportError:
170 except ImportError:
171 curses = None
171 curses = None
172
172
173 from mercurial.i18n import _
173 from mercurial.i18n import _
174 from mercurial import (
174 from mercurial import (
175 cmdutil,
175 cmdutil,
176 color,
176 color,
177 commands,
177 commands,
178 dispatch,
178 dispatch,
179 encoding,
179 encoding,
180 extensions,
180 extensions,
181 pycompat,
181 pycompat,
182 subrepo,
182 subrepo,
183 ui as uimod,
183 ui as uimod,
184 util,
184 util,
185 )
185 )
186
186
187 cmdtable = {}
187 cmdtable = {}
188 command = cmdutil.command(cmdtable)
188 command = cmdutil.command(cmdtable)
189 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
189 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
190 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
190 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
191 # be specifying the version(s) of Mercurial they are tested with, or
191 # be specifying the version(s) of Mercurial they are tested with, or
192 # leave the attribute unspecified.
192 # leave the attribute unspecified.
193 testedwith = 'ships-with-hg-core'
193 testedwith = 'ships-with-hg-core'
194
194
195 def _terminfosetup(ui, mode):
195 def _terminfosetup(ui, mode):
196 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
196 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
197
197
198 # If we failed to load curses, we go ahead and return.
198 # If we failed to load curses, we go ahead and return.
199 if curses is None:
199 if curses is None:
200 return
200 return
201 # Otherwise, see what the config file says.
201 # Otherwise, see what the config file says.
202 if mode not in ('auto', 'terminfo'):
202 if mode not in ('auto', 'terminfo'):
203 return
203 return
204
204
205 for key, val in ui.configitems('color'):
205 for key, val in ui.configitems('color'):
206 if key.startswith('color.'):
206 if key.startswith('color.'):
207 newval = (False, int(val), '')
207 newval = (False, int(val), '')
208 color._terminfo_params[key[6:]] = newval
208 color._terminfo_params[key[6:]] = newval
209 elif key.startswith('terminfo.'):
209 elif key.startswith('terminfo.'):
210 newval = (True, '', val.replace('\\E', '\x1b'))
210 newval = (True, '', val.replace('\\E', '\x1b'))
211 color._terminfo_params[key[9:]] = newval
211 color._terminfo_params[key[9:]] = newval
212 try:
212 try:
213 curses.setupterm()
213 curses.setupterm()
214 except curses.error as e:
214 except curses.error as e:
215 color._terminfo_params.clear()
215 color._terminfo_params.clear()
216 return
216 return
217
217
218 for key, (b, e, c) in color._terminfo_params.items():
218 for key, (b, e, c) in color._terminfo_params.items():
219 if not b:
219 if not b:
220 continue
220 continue
221 if not c and not curses.tigetstr(e):
221 if not c and not curses.tigetstr(e):
222 # Most terminals don't support dim, invis, etc, so don't be
222 # Most terminals don't support dim, invis, etc, so don't be
223 # noisy and use ui.debug().
223 # noisy and use ui.debug().
224 ui.debug("no terminfo entry for %s\n" % e)
224 ui.debug("no terminfo entry for %s\n" % e)
225 del color._terminfo_params[key]
225 del color._terminfo_params[key]
226 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
226 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
227 # Only warn about missing terminfo entries if we explicitly asked for
227 # Only warn about missing terminfo entries if we explicitly asked for
228 # terminfo mode.
228 # terminfo mode.
229 if mode == "terminfo":
229 if mode == "terminfo":
230 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
230 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
231 "ECMA-48 color\n"))
231 "ECMA-48 color\n"))
232 color._terminfo_params.clear()
232 color._terminfo_params.clear()
233
233
234 def _modesetup(ui, coloropt):
234 def _modesetup(ui, coloropt):
235 if coloropt == 'debug':
235 if coloropt == 'debug':
236 return 'debug'
236 return 'debug'
237
237
238 auto = (coloropt == 'auto')
238 auto = (coloropt == 'auto')
239 always = not auto and util.parsebool(coloropt)
239 always = not auto and util.parsebool(coloropt)
240 if not always and not auto:
240 if not always and not auto:
241 return None
241 return None
242
242
243 formatted = (always or (encoding.environ.get('TERM') != 'dumb'
243 formatted = (always or (encoding.environ.get('TERM') != 'dumb'
244 and ui.formatted()))
244 and ui.formatted()))
245
245
246 mode = ui.config('color', 'mode', 'auto')
246 mode = ui.config('color', 'mode', 'auto')
247
247
248 # If pager is active, color.pagermode overrides color.mode.
248 # If pager is active, color.pagermode overrides color.mode.
249 if getattr(ui, 'pageractive', False):
249 if getattr(ui, 'pageractive', False):
250 mode = ui.config('color', 'pagermode', mode)
250 mode = ui.config('color', 'pagermode', mode)
251
251
252 realmode = mode
252 realmode = mode
253 if mode == 'auto':
253 if mode == 'auto':
254 if pycompat.osname == 'nt':
254 if pycompat.osname == 'nt':
255 term = encoding.environ.get('TERM')
255 term = encoding.environ.get('TERM')
256 # TERM won't be defined in a vanilla cmd.exe environment.
256 # TERM won't be defined in a vanilla cmd.exe environment.
257
257
258 # UNIX-like environments on Windows such as Cygwin and MSYS will
258 # UNIX-like environments on Windows such as Cygwin and MSYS will
259 # set TERM. They appear to make a best effort attempt at setting it
259 # set TERM. They appear to make a best effort attempt at setting it
260 # to something appropriate. However, not all environments with TERM
260 # to something appropriate. However, not all environments with TERM
261 # defined support ANSI. Since "ansi" could result in terminal
261 # defined support ANSI. Since "ansi" could result in terminal
262 # gibberish, we error on the side of selecting "win32". However, if
262 # gibberish, we error on the side of selecting "win32". However, if
263 # w32effects is not defined, we almost certainly don't support
263 # w32effects is not defined, we almost certainly don't support
264 # "win32", so don't even try.
264 # "win32", so don't even try.
265 if (term and 'xterm' in term) or not color.w32effects:
265 if (term and 'xterm' in term) or not color.w32effects:
266 realmode = 'ansi'
266 realmode = 'ansi'
267 else:
267 else:
268 realmode = 'win32'
268 realmode = 'win32'
269 else:
269 else:
270 realmode = 'ansi'
270 realmode = 'ansi'
271
271
272 def modewarn():
272 def modewarn():
273 # only warn if color.mode was explicitly set and we're in
273 # only warn if color.mode was explicitly set and we're in
274 # a formatted terminal
274 # a formatted terminal
275 if mode == realmode and ui.formatted():
275 if mode == realmode and ui.formatted():
276 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
276 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
277
277
278 if realmode == 'win32':
278 if realmode == 'win32':
279 color._terminfo_params.clear()
279 color._terminfo_params.clear()
280 if not color.w32effects:
280 if not color.w32effects:
281 modewarn()
281 modewarn()
282 return None
282 return None
283 color._effects.update(color.w32effects)
283 color._effects.update(color.w32effects)
284 elif realmode == 'ansi':
284 elif realmode == 'ansi':
285 color._terminfo_params.clear()
285 color._terminfo_params.clear()
286 elif realmode == 'terminfo':
286 elif realmode == 'terminfo':
287 _terminfosetup(ui, mode)
287 _terminfosetup(ui, mode)
288 if not color._terminfo_params:
288 if not color._terminfo_params:
289 ## FIXME Shouldn't we return None in this case too?
289 ## FIXME Shouldn't we return None in this case too?
290 modewarn()
290 modewarn()
291 realmode = 'ansi'
291 realmode = 'ansi'
292 else:
292 else:
293 return None
293 return None
294
294
295 if always or (auto and formatted):
295 if always or (auto and formatted):
296 return realmode
296 return realmode
297 return None
297 return None
298
298
299 class colorui(uimod.ui):
299 class colorui(uimod.ui):
300 def write(self, *args, **opts):
300 def write(self, *args, **opts):
301 if self._colormode is None:
301 if self._colormode is None:
302 return super(colorui, self).write(*args, **opts)
302 return super(colorui, self).write(*args, **opts)
303
303
304 label = opts.get('label', '')
304 label = opts.get('label', '')
305 if self._buffers and not opts.get('prompt', False):
305 if self._buffers and not opts.get('prompt', False):
306 if self._bufferapplylabels:
306 if self._bufferapplylabels:
307 self._buffers[-1].extend(self.label(a, label) for a in args)
307 self._buffers[-1].extend(self.label(a, label) for a in args)
308 else:
308 else:
309 self._buffers[-1].extend(args)
309 self._buffers[-1].extend(args)
310 elif self._colormode == 'win32':
310 elif self._colormode == 'win32':
311 for a in args:
311 for a in args:
312 color.win32print(a, super(colorui, self).write, **opts)
312 color.win32print(a, super(colorui, self).write, **opts)
313 else:
313 else:
314 return super(colorui, self).write(
314 return super(colorui, self).write(
315 *[self.label(a, label) for a in args], **opts)
315 *[self.label(a, label) for a in args], **opts)
316
316
317 def write_err(self, *args, **opts):
317 def write_err(self, *args, **opts):
318 if self._colormode is None:
318 if self._colormode is None:
319 return super(colorui, self).write_err(*args, **opts)
319 return super(colorui, self).write_err(*args, **opts)
320
320
321 label = opts.get('label', '')
321 label = opts.get('label', '')
322 if self._bufferstates and self._bufferstates[-1][0]:
322 if self._bufferstates and self._bufferstates[-1][0]:
323 return self.write(*args, **opts)
323 return self.write(*args, **opts)
324 if self._colormode == 'win32':
324 if self._colormode == 'win32':
325 for a in args:
325 for a in args:
326 color.win32print(a, super(colorui, self).write_err, **opts)
326 color.win32print(a, super(colorui, self).write_err, **opts)
327 else:
327 else:
328 return super(colorui, self).write_err(
328 return super(colorui, self).write_err(
329 *[self.label(a, label) for a in args], **opts)
329 *[self.label(a, label) for a in args], **opts)
330
330
331 def label(self, msg, label):
331 def label(self, msg, label):
332 if self._colormode is None:
332 if self._colormode is None:
333 return super(colorui, self).label(msg, label)
333 return super(colorui, self).label(msg, label)
334 return colorlabel(self, msg, label)
334 return color.colorlabel(self, msg, label)
335
336 def colorlabel(ui, msg, label):
337 """add color control code according to the mode"""
338 if ui._colormode == 'debug':
339 if label and msg:
340 if msg[-1] == '\n':
341 msg = "[%s|%s]\n" % (label, msg[:-1])
342 else:
343 msg = "[%s|%s]" % (label, msg)
344 elif ui._colormode is not None:
345 effects = []
346 for l in label.split():
347 s = color._styles.get(l, '')
348 if s:
349 effects.append(s)
350 elif color.valideffect(l):
351 effects.append(l)
352 effects = ' '.join(effects)
353 if effects:
354 msg = '\n'.join([color._render_effects(line, effects)
355 for line in msg.split('\n')])
356 return msg
357
335
358 def uisetup(ui):
336 def uisetup(ui):
359 if ui.plain():
337 if ui.plain():
360 return
338 return
361 if not isinstance(ui, colorui):
339 if not isinstance(ui, colorui):
362 colorui.__bases__ = (ui.__class__,)
340 colorui.__bases__ = (ui.__class__,)
363 ui.__class__ = colorui
341 ui.__class__ = colorui
364 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
342 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
365 mode = _modesetup(ui_, opts['color'])
343 mode = _modesetup(ui_, opts['color'])
366 colorui._colormode = mode
344 colorui._colormode = mode
367 if mode and mode != 'debug':
345 if mode and mode != 'debug':
368 color.configstyles(ui_)
346 color.configstyles(ui_)
369 return orig(ui_, opts, cmd, cmdfunc)
347 return orig(ui_, opts, cmd, cmdfunc)
370 def colorgit(orig, gitsub, commands, env=None, stream=False, cwd=None):
348 def colorgit(orig, gitsub, commands, env=None, stream=False, cwd=None):
371 if gitsub.ui._colormode and len(commands) and commands[0] == "diff":
349 if gitsub.ui._colormode and len(commands) and commands[0] == "diff":
372 # insert the argument in the front,
350 # insert the argument in the front,
373 # the end of git diff arguments is used for paths
351 # the end of git diff arguments is used for paths
374 commands.insert(1, '--color')
352 commands.insert(1, '--color')
375 return orig(gitsub, commands, env, stream, cwd)
353 return orig(gitsub, commands, env, stream, cwd)
376 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
354 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
377 extensions.wrapfunction(subrepo.gitsubrepo, '_gitnodir', colorgit)
355 extensions.wrapfunction(subrepo.gitsubrepo, '_gitnodir', colorgit)
378
356
379 def extsetup(ui):
357 def extsetup(ui):
380 commands.globalopts.append(
358 commands.globalopts.append(
381 ('', 'color', 'auto',
359 ('', 'color', 'auto',
382 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
360 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
383 # and should not be translated
361 # and should not be translated
384 _("when to colorize (boolean, always, auto, never, or debug)"),
362 _("when to colorize (boolean, always, auto, never, or debug)"),
385 _('TYPE')))
363 _('TYPE')))
386
364
387 @command('debugcolor',
365 @command('debugcolor',
388 [('', 'style', None, _('show all configured styles'))],
366 [('', 'style', None, _('show all configured styles'))],
389 'hg debugcolor')
367 'hg debugcolor')
390 def debugcolor(ui, repo, **opts):
368 def debugcolor(ui, repo, **opts):
391 """show available color, effects or style"""
369 """show available color, effects or style"""
392 ui.write(('color mode: %s\n') % ui._colormode)
370 ui.write(('color mode: %s\n') % ui._colormode)
393 if opts.get('style'):
371 if opts.get('style'):
394 return _debugdisplaystyle(ui)
372 return _debugdisplaystyle(ui)
395 else:
373 else:
396 return _debugdisplaycolor(ui)
374 return _debugdisplaycolor(ui)
397
375
398 def _debugdisplaycolor(ui):
376 def _debugdisplaycolor(ui):
399 oldstyle = color._styles.copy()
377 oldstyle = color._styles.copy()
400 try:
378 try:
401 color._styles.clear()
379 color._styles.clear()
402 for effect in color._effects.keys():
380 for effect in color._effects.keys():
403 color._styles[effect] = effect
381 color._styles[effect] = effect
404 if color._terminfo_params:
382 if color._terminfo_params:
405 for k, v in ui.configitems('color'):
383 for k, v in ui.configitems('color'):
406 if k.startswith('color.'):
384 if k.startswith('color.'):
407 color._styles[k] = k[6:]
385 color._styles[k] = k[6:]
408 elif k.startswith('terminfo.'):
386 elif k.startswith('terminfo.'):
409 color._styles[k] = k[9:]
387 color._styles[k] = k[9:]
410 ui.write(_('available colors:\n'))
388 ui.write(_('available colors:\n'))
411 # sort label with a '_' after the other to group '_background' entry.
389 # sort label with a '_' after the other to group '_background' entry.
412 items = sorted(color._styles.items(),
390 items = sorted(color._styles.items(),
413 key=lambda i: ('_' in i[0], i[0], i[1]))
391 key=lambda i: ('_' in i[0], i[0], i[1]))
414 for colorname, label in items:
392 for colorname, label in items:
415 ui.write(('%s\n') % colorname, label=label)
393 ui.write(('%s\n') % colorname, label=label)
416 finally:
394 finally:
417 color._styles.clear()
395 color._styles.clear()
418 color._styles.update(oldstyle)
396 color._styles.update(oldstyle)
419
397
420 def _debugdisplaystyle(ui):
398 def _debugdisplaystyle(ui):
421 ui.write(_('available style:\n'))
399 ui.write(_('available style:\n'))
422 width = max(len(s) for s in color._styles)
400 width = max(len(s) for s in color._styles)
423 for label, effects in sorted(color._styles.items()):
401 for label, effects in sorted(color._styles.items()):
424 ui.write('%s' % label, label=label)
402 ui.write('%s' % label, label=label)
425 if effects:
403 if effects:
426 # 50
404 # 50
427 ui.write(': ')
405 ui.write(': ')
428 ui.write(' ' * (max(0, width - len(label))))
406 ui.write(' ' * (max(0, width - len(label))))
429 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
407 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
430 ui.write('\n')
408 ui.write('\n')
@@ -1,310 +1,332
1 # utility for color output for Mercurial commands
1 # utility for color output for Mercurial commands
2 #
2 #
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> and other
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> and other
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from .i18n import _
10 from .i18n import _
11
11
12 from . import pycompat
12 from . import pycompat
13
13
14 try:
14 try:
15 import curses
15 import curses
16 # Mapping from effect name to terminfo attribute name (or raw code) or
16 # Mapping from effect name to terminfo attribute name (or raw code) or
17 # color number. This will also force-load the curses module.
17 # color number. This will also force-load the curses module.
18 _terminfo_params = {'none': (True, 'sgr0', ''),
18 _terminfo_params = {'none': (True, 'sgr0', ''),
19 'standout': (True, 'smso', ''),
19 'standout': (True, 'smso', ''),
20 'underline': (True, 'smul', ''),
20 'underline': (True, 'smul', ''),
21 'reverse': (True, 'rev', ''),
21 'reverse': (True, 'rev', ''),
22 'inverse': (True, 'rev', ''),
22 'inverse': (True, 'rev', ''),
23 'blink': (True, 'blink', ''),
23 'blink': (True, 'blink', ''),
24 'dim': (True, 'dim', ''),
24 'dim': (True, 'dim', ''),
25 'bold': (True, 'bold', ''),
25 'bold': (True, 'bold', ''),
26 'invisible': (True, 'invis', ''),
26 'invisible': (True, 'invis', ''),
27 'italic': (True, 'sitm', ''),
27 'italic': (True, 'sitm', ''),
28 'black': (False, curses.COLOR_BLACK, ''),
28 'black': (False, curses.COLOR_BLACK, ''),
29 'red': (False, curses.COLOR_RED, ''),
29 'red': (False, curses.COLOR_RED, ''),
30 'green': (False, curses.COLOR_GREEN, ''),
30 'green': (False, curses.COLOR_GREEN, ''),
31 'yellow': (False, curses.COLOR_YELLOW, ''),
31 'yellow': (False, curses.COLOR_YELLOW, ''),
32 'blue': (False, curses.COLOR_BLUE, ''),
32 'blue': (False, curses.COLOR_BLUE, ''),
33 'magenta': (False, curses.COLOR_MAGENTA, ''),
33 'magenta': (False, curses.COLOR_MAGENTA, ''),
34 'cyan': (False, curses.COLOR_CYAN, ''),
34 'cyan': (False, curses.COLOR_CYAN, ''),
35 'white': (False, curses.COLOR_WHITE, '')}
35 'white': (False, curses.COLOR_WHITE, '')}
36 except ImportError:
36 except ImportError:
37 curses = None
37 curses = None
38 _terminfo_params = {}
38 _terminfo_params = {}
39
39
40 # start and stop parameters for effects
40 # start and stop parameters for effects
41 _effects = {'none': 0,
41 _effects = {'none': 0,
42 'black': 30,
42 'black': 30,
43 'red': 31,
43 'red': 31,
44 'green': 32,
44 'green': 32,
45 'yellow': 33,
45 'yellow': 33,
46 'blue': 34,
46 'blue': 34,
47 'magenta': 35,
47 'magenta': 35,
48 'cyan': 36,
48 'cyan': 36,
49 'white': 37,
49 'white': 37,
50 'bold': 1,
50 'bold': 1,
51 'italic': 3,
51 'italic': 3,
52 'underline': 4,
52 'underline': 4,
53 'inverse': 7,
53 'inverse': 7,
54 'dim': 2,
54 'dim': 2,
55 'black_background': 40,
55 'black_background': 40,
56 'red_background': 41,
56 'red_background': 41,
57 'green_background': 42,
57 'green_background': 42,
58 'yellow_background': 43,
58 'yellow_background': 43,
59 'blue_background': 44,
59 'blue_background': 44,
60 'purple_background': 45,
60 'purple_background': 45,
61 'cyan_background': 46,
61 'cyan_background': 46,
62 'white_background': 47}
62 'white_background': 47}
63
63
64 _styles = {'grep.match': 'red bold',
64 _styles = {'grep.match': 'red bold',
65 'grep.linenumber': 'green',
65 'grep.linenumber': 'green',
66 'grep.rev': 'green',
66 'grep.rev': 'green',
67 'grep.change': 'green',
67 'grep.change': 'green',
68 'grep.sep': 'cyan',
68 'grep.sep': 'cyan',
69 'grep.filename': 'magenta',
69 'grep.filename': 'magenta',
70 'grep.user': 'magenta',
70 'grep.user': 'magenta',
71 'grep.date': 'magenta',
71 'grep.date': 'magenta',
72 'bookmarks.active': 'green',
72 'bookmarks.active': 'green',
73 'branches.active': 'none',
73 'branches.active': 'none',
74 'branches.closed': 'black bold',
74 'branches.closed': 'black bold',
75 'branches.current': 'green',
75 'branches.current': 'green',
76 'branches.inactive': 'none',
76 'branches.inactive': 'none',
77 'diff.changed': 'white',
77 'diff.changed': 'white',
78 'diff.deleted': 'red',
78 'diff.deleted': 'red',
79 'diff.diffline': 'bold',
79 'diff.diffline': 'bold',
80 'diff.extended': 'cyan bold',
80 'diff.extended': 'cyan bold',
81 'diff.file_a': 'red bold',
81 'diff.file_a': 'red bold',
82 'diff.file_b': 'green bold',
82 'diff.file_b': 'green bold',
83 'diff.hunk': 'magenta',
83 'diff.hunk': 'magenta',
84 'diff.inserted': 'green',
84 'diff.inserted': 'green',
85 'diff.tab': '',
85 'diff.tab': '',
86 'diff.trailingwhitespace': 'bold red_background',
86 'diff.trailingwhitespace': 'bold red_background',
87 'changeset.public' : '',
87 'changeset.public' : '',
88 'changeset.draft' : '',
88 'changeset.draft' : '',
89 'changeset.secret' : '',
89 'changeset.secret' : '',
90 'diffstat.deleted': 'red',
90 'diffstat.deleted': 'red',
91 'diffstat.inserted': 'green',
91 'diffstat.inserted': 'green',
92 'histedit.remaining': 'red bold',
92 'histedit.remaining': 'red bold',
93 'ui.prompt': 'yellow',
93 'ui.prompt': 'yellow',
94 'log.changeset': 'yellow',
94 'log.changeset': 'yellow',
95 'patchbomb.finalsummary': '',
95 'patchbomb.finalsummary': '',
96 'patchbomb.from': 'magenta',
96 'patchbomb.from': 'magenta',
97 'patchbomb.to': 'cyan',
97 'patchbomb.to': 'cyan',
98 'patchbomb.subject': 'green',
98 'patchbomb.subject': 'green',
99 'patchbomb.diffstats': '',
99 'patchbomb.diffstats': '',
100 'rebase.rebased': 'blue',
100 'rebase.rebased': 'blue',
101 'rebase.remaining': 'red bold',
101 'rebase.remaining': 'red bold',
102 'resolve.resolved': 'green bold',
102 'resolve.resolved': 'green bold',
103 'resolve.unresolved': 'red bold',
103 'resolve.unresolved': 'red bold',
104 'shelve.age': 'cyan',
104 'shelve.age': 'cyan',
105 'shelve.newest': 'green bold',
105 'shelve.newest': 'green bold',
106 'shelve.name': 'blue bold',
106 'shelve.name': 'blue bold',
107 'status.added': 'green bold',
107 'status.added': 'green bold',
108 'status.clean': 'none',
108 'status.clean': 'none',
109 'status.copied': 'none',
109 'status.copied': 'none',
110 'status.deleted': 'cyan bold underline',
110 'status.deleted': 'cyan bold underline',
111 'status.ignored': 'black bold',
111 'status.ignored': 'black bold',
112 'status.modified': 'blue bold',
112 'status.modified': 'blue bold',
113 'status.removed': 'red bold',
113 'status.removed': 'red bold',
114 'status.unknown': 'magenta bold underline',
114 'status.unknown': 'magenta bold underline',
115 'tags.normal': 'green',
115 'tags.normal': 'green',
116 'tags.local': 'black bold'}
116 'tags.local': 'black bold'}
117
117
118 def loadcolortable(ui, extname, colortable):
118 def loadcolortable(ui, extname, colortable):
119 _styles.update(colortable)
119 _styles.update(colortable)
120
120
121 def configstyles(ui):
121 def configstyles(ui):
122 for status, cfgeffects in ui.configitems('color'):
122 for status, cfgeffects in ui.configitems('color'):
123 if '.' not in status or status.startswith(('color.', 'terminfo.')):
123 if '.' not in status or status.startswith(('color.', 'terminfo.')):
124 continue
124 continue
125 cfgeffects = ui.configlist('color', status)
125 cfgeffects = ui.configlist('color', status)
126 if cfgeffects:
126 if cfgeffects:
127 good = []
127 good = []
128 for e in cfgeffects:
128 for e in cfgeffects:
129 if valideffect(e):
129 if valideffect(e):
130 good.append(e)
130 good.append(e)
131 else:
131 else:
132 ui.warn(_("ignoring unknown color/effect %r "
132 ui.warn(_("ignoring unknown color/effect %r "
133 "(configured in color.%s)\n")
133 "(configured in color.%s)\n")
134 % (e, status))
134 % (e, status))
135 _styles[status] = ' '.join(good)
135 _styles[status] = ' '.join(good)
136
136
137 def valideffect(effect):
137 def valideffect(effect):
138 'Determine if the effect is valid or not.'
138 'Determine if the effect is valid or not.'
139 return ((not _terminfo_params and effect in _effects)
139 return ((not _terminfo_params and effect in _effects)
140 or (effect in _terminfo_params
140 or (effect in _terminfo_params
141 or effect[:-11] in _terminfo_params))
141 or effect[:-11] in _terminfo_params))
142
142
143 def _effect_str(effect):
143 def _effect_str(effect):
144 '''Helper function for render_effects().'''
144 '''Helper function for render_effects().'''
145
145
146 bg = False
146 bg = False
147 if effect.endswith('_background'):
147 if effect.endswith('_background'):
148 bg = True
148 bg = True
149 effect = effect[:-11]
149 effect = effect[:-11]
150 try:
150 try:
151 attr, val, termcode = _terminfo_params[effect]
151 attr, val, termcode = _terminfo_params[effect]
152 except KeyError:
152 except KeyError:
153 return ''
153 return ''
154 if attr:
154 if attr:
155 if termcode:
155 if termcode:
156 return termcode
156 return termcode
157 else:
157 else:
158 return curses.tigetstr(val)
158 return curses.tigetstr(val)
159 elif bg:
159 elif bg:
160 return curses.tparm(curses.tigetstr('setab'), val)
160 return curses.tparm(curses.tigetstr('setab'), val)
161 else:
161 else:
162 return curses.tparm(curses.tigetstr('setaf'), val)
162 return curses.tparm(curses.tigetstr('setaf'), val)
163
163
164 def _render_effects(text, effects):
164 def _render_effects(text, effects):
165 'Wrap text in commands to turn on each effect.'
165 'Wrap text in commands to turn on each effect.'
166 if not text:
166 if not text:
167 return text
167 return text
168 if _terminfo_params:
168 if _terminfo_params:
169 start = ''.join(_effect_str(effect)
169 start = ''.join(_effect_str(effect)
170 for effect in ['none'] + effects.split())
170 for effect in ['none'] + effects.split())
171 stop = _effect_str('none')
171 stop = _effect_str('none')
172 else:
172 else:
173 start = [str(_effects[e]) for e in ['none'] + effects.split()]
173 start = [str(_effects[e]) for e in ['none'] + effects.split()]
174 start = '\033[' + ';'.join(start) + 'm'
174 start = '\033[' + ';'.join(start) + 'm'
175 stop = '\033[' + str(_effects['none']) + 'm'
175 stop = '\033[' + str(_effects['none']) + 'm'
176 return ''.join([start, text, stop])
176 return ''.join([start, text, stop])
177
177
178 def colorlabel(ui, msg, label):
179 """add color control code according to the mode"""
180 if ui._colormode == 'debug':
181 if label and msg:
182 if msg[-1] == '\n':
183 msg = "[%s|%s]\n" % (label, msg[:-1])
184 else:
185 msg = "[%s|%s]" % (label, msg)
186 elif ui._colormode is not None:
187 effects = []
188 for l in label.split():
189 s = _styles.get(l, '')
190 if s:
191 effects.append(s)
192 elif valideffect(l):
193 effects.append(l)
194 effects = ' '.join(effects)
195 if effects:
196 msg = '\n'.join([_render_effects(line, effects)
197 for line in msg.split('\n')])
198 return msg
199
178 w32effects = None
200 w32effects = None
179 if pycompat.osname == 'nt':
201 if pycompat.osname == 'nt':
180 import ctypes
202 import ctypes
181 import re
203 import re
182
204
183 _kernel32 = ctypes.windll.kernel32
205 _kernel32 = ctypes.windll.kernel32
184
206
185 _WORD = ctypes.c_ushort
207 _WORD = ctypes.c_ushort
186
208
187 _INVALID_HANDLE_VALUE = -1
209 _INVALID_HANDLE_VALUE = -1
188
210
189 class _COORD(ctypes.Structure):
211 class _COORD(ctypes.Structure):
190 _fields_ = [('X', ctypes.c_short),
212 _fields_ = [('X', ctypes.c_short),
191 ('Y', ctypes.c_short)]
213 ('Y', ctypes.c_short)]
192
214
193 class _SMALL_RECT(ctypes.Structure):
215 class _SMALL_RECT(ctypes.Structure):
194 _fields_ = [('Left', ctypes.c_short),
216 _fields_ = [('Left', ctypes.c_short),
195 ('Top', ctypes.c_short),
217 ('Top', ctypes.c_short),
196 ('Right', ctypes.c_short),
218 ('Right', ctypes.c_short),
197 ('Bottom', ctypes.c_short)]
219 ('Bottom', ctypes.c_short)]
198
220
199 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
221 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
200 _fields_ = [('dwSize', _COORD),
222 _fields_ = [('dwSize', _COORD),
201 ('dwCursorPosition', _COORD),
223 ('dwCursorPosition', _COORD),
202 ('wAttributes', _WORD),
224 ('wAttributes', _WORD),
203 ('srWindow', _SMALL_RECT),
225 ('srWindow', _SMALL_RECT),
204 ('dwMaximumWindowSize', _COORD)]
226 ('dwMaximumWindowSize', _COORD)]
205
227
206 _STD_OUTPUT_HANDLE = 0xfffffff5 # (DWORD)-11
228 _STD_OUTPUT_HANDLE = 0xfffffff5 # (DWORD)-11
207 _STD_ERROR_HANDLE = 0xfffffff4 # (DWORD)-12
229 _STD_ERROR_HANDLE = 0xfffffff4 # (DWORD)-12
208
230
209 _FOREGROUND_BLUE = 0x0001
231 _FOREGROUND_BLUE = 0x0001
210 _FOREGROUND_GREEN = 0x0002
232 _FOREGROUND_GREEN = 0x0002
211 _FOREGROUND_RED = 0x0004
233 _FOREGROUND_RED = 0x0004
212 _FOREGROUND_INTENSITY = 0x0008
234 _FOREGROUND_INTENSITY = 0x0008
213
235
214 _BACKGROUND_BLUE = 0x0010
236 _BACKGROUND_BLUE = 0x0010
215 _BACKGROUND_GREEN = 0x0020
237 _BACKGROUND_GREEN = 0x0020
216 _BACKGROUND_RED = 0x0040
238 _BACKGROUND_RED = 0x0040
217 _BACKGROUND_INTENSITY = 0x0080
239 _BACKGROUND_INTENSITY = 0x0080
218
240
219 _COMMON_LVB_REVERSE_VIDEO = 0x4000
241 _COMMON_LVB_REVERSE_VIDEO = 0x4000
220 _COMMON_LVB_UNDERSCORE = 0x8000
242 _COMMON_LVB_UNDERSCORE = 0x8000
221
243
222 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
244 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
223 w32effects = {
245 w32effects = {
224 'none': -1,
246 'none': -1,
225 'black': 0,
247 'black': 0,
226 'red': _FOREGROUND_RED,
248 'red': _FOREGROUND_RED,
227 'green': _FOREGROUND_GREEN,
249 'green': _FOREGROUND_GREEN,
228 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
250 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
229 'blue': _FOREGROUND_BLUE,
251 'blue': _FOREGROUND_BLUE,
230 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
252 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
231 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
253 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
232 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
254 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
233 'bold': _FOREGROUND_INTENSITY,
255 'bold': _FOREGROUND_INTENSITY,
234 'black_background': 0x100, # unused value > 0x0f
256 'black_background': 0x100, # unused value > 0x0f
235 'red_background': _BACKGROUND_RED,
257 'red_background': _BACKGROUND_RED,
236 'green_background': _BACKGROUND_GREEN,
258 'green_background': _BACKGROUND_GREEN,
237 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
259 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
238 'blue_background': _BACKGROUND_BLUE,
260 'blue_background': _BACKGROUND_BLUE,
239 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
261 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
240 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
262 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
241 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
263 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
242 _BACKGROUND_BLUE),
264 _BACKGROUND_BLUE),
243 'bold_background': _BACKGROUND_INTENSITY,
265 'bold_background': _BACKGROUND_INTENSITY,
244 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
266 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
245 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
267 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
246 }
268 }
247
269
248 passthrough = set([_FOREGROUND_INTENSITY,
270 passthrough = set([_FOREGROUND_INTENSITY,
249 _BACKGROUND_INTENSITY,
271 _BACKGROUND_INTENSITY,
250 _COMMON_LVB_UNDERSCORE,
272 _COMMON_LVB_UNDERSCORE,
251 _COMMON_LVB_REVERSE_VIDEO])
273 _COMMON_LVB_REVERSE_VIDEO])
252
274
253 stdout = _kernel32.GetStdHandle(
275 stdout = _kernel32.GetStdHandle(
254 _STD_OUTPUT_HANDLE) # don't close the handle returned
276 _STD_OUTPUT_HANDLE) # don't close the handle returned
255 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
277 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
256 w32effects = None
278 w32effects = None
257 else:
279 else:
258 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
280 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
259 if not _kernel32.GetConsoleScreenBufferInfo(
281 if not _kernel32.GetConsoleScreenBufferInfo(
260 stdout, ctypes.byref(csbi)):
282 stdout, ctypes.byref(csbi)):
261 # stdout may not support GetConsoleScreenBufferInfo()
283 # stdout may not support GetConsoleScreenBufferInfo()
262 # when called from subprocess or redirected
284 # when called from subprocess or redirected
263 w32effects = None
285 w32effects = None
264 else:
286 else:
265 origattr = csbi.wAttributes
287 origattr = csbi.wAttributes
266 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
288 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
267 re.MULTILINE | re.DOTALL)
289 re.MULTILINE | re.DOTALL)
268
290
269 def win32print(text, orig, **opts):
291 def win32print(text, orig, **opts):
270 label = opts.get('label', '')
292 label = opts.get('label', '')
271 attr = origattr
293 attr = origattr
272
294
273 def mapcolor(val, attr):
295 def mapcolor(val, attr):
274 if val == -1:
296 if val == -1:
275 return origattr
297 return origattr
276 elif val in passthrough:
298 elif val in passthrough:
277 return attr | val
299 return attr | val
278 elif val > 0x0f:
300 elif val > 0x0f:
279 return (val & 0x70) | (attr & 0x8f)
301 return (val & 0x70) | (attr & 0x8f)
280 else:
302 else:
281 return (val & 0x07) | (attr & 0xf8)
303 return (val & 0x07) | (attr & 0xf8)
282
304
283 # determine console attributes based on labels
305 # determine console attributes based on labels
284 for l in label.split():
306 for l in label.split():
285 style = _styles.get(l, '')
307 style = _styles.get(l, '')
286 for effect in style.split():
308 for effect in style.split():
287 try:
309 try:
288 attr = mapcolor(w32effects[effect], attr)
310 attr = mapcolor(w32effects[effect], attr)
289 except KeyError:
311 except KeyError:
290 # w32effects could not have certain attributes so we skip
312 # w32effects could not have certain attributes so we skip
291 # them if not found
313 # them if not found
292 pass
314 pass
293 # hack to ensure regexp finds data
315 # hack to ensure regexp finds data
294 if not text.startswith('\033['):
316 if not text.startswith('\033['):
295 text = '\033[m' + text
317 text = '\033[m' + text
296
318
297 # Look for ANSI-like codes embedded in text
319 # Look for ANSI-like codes embedded in text
298 m = re.match(ansire, text)
320 m = re.match(ansire, text)
299
321
300 try:
322 try:
301 while m:
323 while m:
302 for sattr in m.group(1).split(';'):
324 for sattr in m.group(1).split(';'):
303 if sattr:
325 if sattr:
304 attr = mapcolor(int(sattr), attr)
326 attr = mapcolor(int(sattr), attr)
305 _kernel32.SetConsoleTextAttribute(stdout, attr)
327 _kernel32.SetConsoleTextAttribute(stdout, attr)
306 orig(m.group(2), **opts)
328 orig(m.group(2), **opts)
307 m = re.match(ansire, m.group(3))
329 m = re.match(ansire, m.group(3))
308 finally:
330 finally:
309 # Explicitly reset original attributes
331 # Explicitly reset original attributes
310 _kernel32.SetConsoleTextAttribute(stdout, origattr)
332 _kernel32.SetConsoleTextAttribute(stdout, origattr)
General Comments 0
You need to be logged in to leave comments. Login now