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