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