##// END OF EJS Templates
color: support a different color mode when the pager is active...
Gregory Szorc -
r24068:4e02418b default
parent child Browse files
Show More
@@ -1,667 +1,683 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.current = 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
144 On some systems (such as MSYS in Windows), the terminal may support
145 a different color mode than the pager (activated via the "pager"
146 extension). It is possible to define separate modes depending on whether
147 the pager is active::
148
149 [color]
150 mode = auto
151 pagermode = ansi
152
153 If ``pagermode`` is not defined, the ``mode`` will be used.
143 154 '''
144 155
145 156 import os
146 157
147 158 from mercurial import cmdutil, commands, dispatch, extensions, subrepo, util
148 159 from mercurial import ui as uimod
149 160 from mercurial import templater, error
150 161 from mercurial.i18n import _
151 162
152 163 cmdtable = {}
153 164 command = cmdutil.command(cmdtable)
154 165 testedwith = 'internal'
155 166
156 167 # start and stop parameters for effects
157 168 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
158 169 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
159 170 'italic': 3, 'underline': 4, 'inverse': 7, 'dim': 2,
160 171 'black_background': 40, 'red_background': 41,
161 172 'green_background': 42, 'yellow_background': 43,
162 173 'blue_background': 44, 'purple_background': 45,
163 174 'cyan_background': 46, 'white_background': 47}
164 175
165 176 def _terminfosetup(ui, mode):
166 177 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
167 178
168 179 global _terminfo_params
169 180 # If we failed to load curses, we go ahead and return.
170 181 if not _terminfo_params:
171 182 return
172 183 # Otherwise, see what the config file says.
173 184 if mode not in ('auto', 'terminfo'):
174 185 return
175 186
176 187 _terminfo_params.update((key[6:], (False, int(val)))
177 188 for key, val in ui.configitems('color')
178 189 if key.startswith('color.'))
179 190
180 191 try:
181 192 curses.setupterm()
182 193 except curses.error, e:
183 194 _terminfo_params = {}
184 195 return
185 196
186 197 for key, (b, e) in _terminfo_params.items():
187 198 if not b:
188 199 continue
189 200 if not curses.tigetstr(e):
190 201 # Most terminals don't support dim, invis, etc, so don't be
191 202 # noisy and use ui.debug().
192 203 ui.debug("no terminfo entry for %s\n" % e)
193 204 del _terminfo_params[key]
194 205 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
195 206 # Only warn about missing terminfo entries if we explicitly asked for
196 207 # terminfo mode.
197 208 if mode == "terminfo":
198 209 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
199 210 "ECMA-48 color\n"))
200 211 _terminfo_params = {}
201 212
202 213 def _modesetup(ui, coloropt):
203 214 global _terminfo_params
204 215
205 216 if coloropt == 'debug':
206 217 return 'debug'
207 218
208 219 auto = (coloropt == 'auto')
209 220 always = not auto and util.parsebool(coloropt)
210 221 if not always and not auto:
211 222 return None
212 223
213 224 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
214 225
215 226 mode = ui.config('color', 'mode', 'auto')
227
228 # If pager is active, color.pagermode overrides color.mode.
229 if getattr(ui, 'pageractive', False):
230 mode = ui.config('color', 'pagermode', mode)
231
216 232 realmode = mode
217 233 if mode == 'auto':
218 234 if os.name == 'nt':
219 235 term = os.environ.get('TERM')
220 236 # TERM won't be defined in a vanilla cmd.exe environment.
221 237 if not term:
222 238 realmode = 'win32'
223 239
224 240 # UNIX-like environments on Windows such as Cygwin and MSYS will
225 241 # set TERM. They appear to make a best effort attempt at setting it
226 242 # to something appropriate. However, not all environments with TERM
227 243 # defined support ANSI. Since "ansi" could result in terminal
228 244 # gibberish, we error on the side of selecting "win32". However, if
229 245 # w32effects is not defined, we almost certainly don't support
230 246 # "win32", so don't even try.
231 247 if 'xterm' in term or not w32effects:
232 248 realmode = 'ansi'
233 249 else:
234 250 realmode = 'win32'
235 251 else:
236 252 realmode = 'ansi'
237 253
238 254 if realmode == 'win32':
239 255 _terminfo_params = {}
240 256 if not w32effects:
241 257 if mode == 'win32':
242 258 # only warn if color.mode is explicitly set to win32
243 259 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
244 260 return None
245 261 _effects.update(w32effects)
246 262 elif realmode == 'ansi':
247 263 _terminfo_params = {}
248 264 elif realmode == 'terminfo':
249 265 _terminfosetup(ui, mode)
250 266 if not _terminfo_params:
251 267 if mode == 'terminfo':
252 268 ## FIXME Shouldn't we return None in this case too?
253 269 # only warn if color.mode is explicitly set to win32
254 270 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
255 271 realmode = 'ansi'
256 272 else:
257 273 return None
258 274
259 275 if always or (auto and formatted):
260 276 return realmode
261 277 return None
262 278
263 279 try:
264 280 import curses
265 281 # Mapping from effect name to terminfo attribute name or color number.
266 282 # This will also force-load the curses module.
267 283 _terminfo_params = {'none': (True, 'sgr0'),
268 284 'standout': (True, 'smso'),
269 285 'underline': (True, 'smul'),
270 286 'reverse': (True, 'rev'),
271 287 'inverse': (True, 'rev'),
272 288 'blink': (True, 'blink'),
273 289 'dim': (True, 'dim'),
274 290 'bold': (True, 'bold'),
275 291 'invisible': (True, 'invis'),
276 292 'italic': (True, 'sitm'),
277 293 'black': (False, curses.COLOR_BLACK),
278 294 'red': (False, curses.COLOR_RED),
279 295 'green': (False, curses.COLOR_GREEN),
280 296 'yellow': (False, curses.COLOR_YELLOW),
281 297 'blue': (False, curses.COLOR_BLUE),
282 298 'magenta': (False, curses.COLOR_MAGENTA),
283 299 'cyan': (False, curses.COLOR_CYAN),
284 300 'white': (False, curses.COLOR_WHITE)}
285 301 except ImportError:
286 302 _terminfo_params = {}
287 303
288 304 _styles = {'grep.match': 'red bold',
289 305 'grep.linenumber': 'green',
290 306 'grep.rev': 'green',
291 307 'grep.change': 'green',
292 308 'grep.sep': 'cyan',
293 309 'grep.filename': 'magenta',
294 310 'grep.user': 'magenta',
295 311 'grep.date': 'magenta',
296 312 'bookmarks.current': 'green',
297 313 'branches.active': 'none',
298 314 'branches.closed': 'black bold',
299 315 'branches.current': 'green',
300 316 'branches.inactive': 'none',
301 317 'diff.changed': 'white',
302 318 'diff.deleted': 'red',
303 319 'diff.diffline': 'bold',
304 320 'diff.extended': 'cyan bold',
305 321 'diff.file_a': 'red bold',
306 322 'diff.file_b': 'green bold',
307 323 'diff.hunk': 'magenta',
308 324 'diff.inserted': 'green',
309 325 'diff.tab': '',
310 326 'diff.trailingwhitespace': 'bold red_background',
311 327 'changeset.public' : '',
312 328 'changeset.draft' : '',
313 329 'changeset.secret' : '',
314 330 'diffstat.deleted': 'red',
315 331 'diffstat.inserted': 'green',
316 332 'histedit.remaining': 'red bold',
317 333 'ui.prompt': 'yellow',
318 334 'log.changeset': 'yellow',
319 335 'patchbomb.finalsummary': '',
320 336 'patchbomb.from': 'magenta',
321 337 'patchbomb.to': 'cyan',
322 338 'patchbomb.subject': 'green',
323 339 'patchbomb.diffstats': '',
324 340 'rebase.rebased': 'blue',
325 341 'rebase.remaining': 'red bold',
326 342 'resolve.resolved': 'green bold',
327 343 'resolve.unresolved': 'red bold',
328 344 'shelve.age': 'cyan',
329 345 'shelve.newest': 'green bold',
330 346 'shelve.name': 'blue bold',
331 347 'status.added': 'green bold',
332 348 'status.clean': 'none',
333 349 'status.copied': 'none',
334 350 'status.deleted': 'cyan bold underline',
335 351 'status.ignored': 'black bold',
336 352 'status.modified': 'blue bold',
337 353 'status.removed': 'red bold',
338 354 'status.unknown': 'magenta bold underline',
339 355 'tags.normal': 'green',
340 356 'tags.local': 'black bold'}
341 357
342 358
343 359 def _effect_str(effect):
344 360 '''Helper function for render_effects().'''
345 361
346 362 bg = False
347 363 if effect.endswith('_background'):
348 364 bg = True
349 365 effect = effect[:-11]
350 366 attr, val = _terminfo_params[effect]
351 367 if attr:
352 368 return curses.tigetstr(val)
353 369 elif bg:
354 370 return curses.tparm(curses.tigetstr('setab'), val)
355 371 else:
356 372 return curses.tparm(curses.tigetstr('setaf'), val)
357 373
358 374 def render_effects(text, effects):
359 375 'Wrap text in commands to turn on each effect.'
360 376 if not text:
361 377 return text
362 378 if not _terminfo_params:
363 379 start = [str(_effects[e]) for e in ['none'] + effects.split()]
364 380 start = '\033[' + ';'.join(start) + 'm'
365 381 stop = '\033[' + str(_effects['none']) + 'm'
366 382 else:
367 383 start = ''.join(_effect_str(effect)
368 384 for effect in ['none'] + effects.split())
369 385 stop = _effect_str('none')
370 386 return ''.join([start, text, stop])
371 387
372 388 def extstyles():
373 389 for name, ext in extensions.extensions():
374 390 _styles.update(getattr(ext, 'colortable', {}))
375 391
376 392 def valideffect(effect):
377 393 'Determine if the effect is valid or not.'
378 394 good = False
379 395 if not _terminfo_params and effect in _effects:
380 396 good = True
381 397 elif effect in _terminfo_params or effect[:-11] in _terminfo_params:
382 398 good = True
383 399 return good
384 400
385 401 def configstyles(ui):
386 402 for status, cfgeffects in ui.configitems('color'):
387 403 if '.' not in status or status.startswith('color.'):
388 404 continue
389 405 cfgeffects = ui.configlist('color', status)
390 406 if cfgeffects:
391 407 good = []
392 408 for e in cfgeffects:
393 409 if valideffect(e):
394 410 good.append(e)
395 411 else:
396 412 ui.warn(_("ignoring unknown color/effect %r "
397 413 "(configured in color.%s)\n")
398 414 % (e, status))
399 415 _styles[status] = ' '.join(good)
400 416
401 417 class colorui(uimod.ui):
402 418 def popbuffer(self, labeled=False):
403 419 if self._colormode is None:
404 420 return super(colorui, self).popbuffer(labeled)
405 421
406 422 self._bufferstates.pop()
407 423 if labeled:
408 424 return ''.join(self.label(a, label) for a, label
409 425 in self._buffers.pop())
410 426 return ''.join(a for a, label in self._buffers.pop())
411 427
412 428 _colormode = 'ansi'
413 429 def write(self, *args, **opts):
414 430 if self._colormode is None:
415 431 return super(colorui, self).write(*args, **opts)
416 432
417 433 label = opts.get('label', '')
418 434 if self._buffers:
419 435 self._buffers[-1].extend([(str(a), label) for a in args])
420 436 elif self._colormode == 'win32':
421 437 for a in args:
422 438 win32print(a, super(colorui, self).write, **opts)
423 439 else:
424 440 return super(colorui, self).write(
425 441 *[self.label(str(a), label) for a in args], **opts)
426 442
427 443 def write_err(self, *args, **opts):
428 444 if self._colormode is None:
429 445 return super(colorui, self).write_err(*args, **opts)
430 446
431 447 label = opts.get('label', '')
432 448 if self._bufferstates and self._bufferstates[-1]:
433 449 return self.write(*args, **opts)
434 450 if self._colormode == 'win32':
435 451 for a in args:
436 452 win32print(a, super(colorui, self).write_err, **opts)
437 453 else:
438 454 return super(colorui, self).write_err(
439 455 *[self.label(str(a), label) for a in args], **opts)
440 456
441 457 def showlabel(self, msg, label):
442 458 if label and msg:
443 459 if msg[-1] == '\n':
444 460 return "[%s|%s]\n" % (label, msg[:-1])
445 461 else:
446 462 return "[%s|%s]" % (label, msg)
447 463 else:
448 464 return msg
449 465
450 466 def label(self, msg, label):
451 467 if self._colormode is None:
452 468 return super(colorui, self).label(msg, label)
453 469
454 470 if self._colormode == 'debug':
455 471 return self.showlabel(msg, label)
456 472
457 473 effects = []
458 474 for l in label.split():
459 475 s = _styles.get(l, '')
460 476 if s:
461 477 effects.append(s)
462 478 elif valideffect(l):
463 479 effects.append(l)
464 480 effects = ' '.join(effects)
465 481 if effects:
466 482 return '\n'.join([render_effects(s, effects)
467 483 for s in msg.split('\n')])
468 484 return msg
469 485
470 486 def templatelabel(context, mapping, args):
471 487 if len(args) != 2:
472 488 # i18n: "label" is a keyword
473 489 raise error.ParseError(_("label expects two arguments"))
474 490
475 491 # add known effects to the mapping so symbols like 'red', 'bold',
476 492 # etc. don't need to be quoted
477 493 mapping.update(dict([(k, k) for k in _effects]))
478 494
479 495 thing = templater._evalifliteral(args[1], context, mapping)
480 496
481 497 # apparently, repo could be a string that is the favicon?
482 498 repo = mapping.get('repo', '')
483 499 if isinstance(repo, str):
484 500 return thing
485 501
486 502 label = templater._evalifliteral(args[0], context, mapping)
487 503
488 504 thing = templater.stringify(thing)
489 505 label = templater.stringify(label)
490 506
491 507 return repo.ui.label(thing, label)
492 508
493 509 def uisetup(ui):
494 510 if ui.plain():
495 511 return
496 512 if not isinstance(ui, colorui):
497 513 colorui.__bases__ = (ui.__class__,)
498 514 ui.__class__ = colorui
499 515 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
500 516 mode = _modesetup(ui_, opts['color'])
501 517 colorui._colormode = mode
502 518 if mode and mode != 'debug':
503 519 extstyles()
504 520 configstyles(ui_)
505 521 return orig(ui_, opts, cmd, cmdfunc)
506 522 def colorgit(orig, gitsub, commands, env=None, stream=False, cwd=None):
507 523 if gitsub.ui._colormode and len(commands) and commands[0] == "diff":
508 524 # insert the argument in the front,
509 525 # the end of git diff arguments is used for paths
510 526 commands.insert(1, '--color')
511 527 return orig(gitsub, commands, env, stream, cwd)
512 528 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
513 529 extensions.wrapfunction(subrepo.gitsubrepo, '_gitnodir', colorgit)
514 530 templater.funcs['label'] = templatelabel
515 531
516 532 def extsetup(ui):
517 533 commands.globalopts.append(
518 534 ('', 'color', 'auto',
519 535 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
520 536 # and should not be translated
521 537 _("when to colorize (boolean, always, auto, never, or debug)"),
522 538 _('TYPE')))
523 539
524 540 @command('debugcolor', [], 'hg debugcolor')
525 541 def debugcolor(ui, repo, **opts):
526 542 global _styles
527 543 _styles = {}
528 544 for effect in _effects.keys():
529 545 _styles[effect] = effect
530 546 ui.write(('color mode: %s\n') % ui._colormode)
531 547 ui.write(_('available colors:\n'))
532 548 for label, colors in _styles.items():
533 549 ui.write(('%s\n') % colors, label=label)
534 550
535 551 if os.name != 'nt':
536 552 w32effects = None
537 553 else:
538 554 import re, ctypes
539 555
540 556 _kernel32 = ctypes.windll.kernel32
541 557
542 558 _WORD = ctypes.c_ushort
543 559
544 560 _INVALID_HANDLE_VALUE = -1
545 561
546 562 class _COORD(ctypes.Structure):
547 563 _fields_ = [('X', ctypes.c_short),
548 564 ('Y', ctypes.c_short)]
549 565
550 566 class _SMALL_RECT(ctypes.Structure):
551 567 _fields_ = [('Left', ctypes.c_short),
552 568 ('Top', ctypes.c_short),
553 569 ('Right', ctypes.c_short),
554 570 ('Bottom', ctypes.c_short)]
555 571
556 572 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
557 573 _fields_ = [('dwSize', _COORD),
558 574 ('dwCursorPosition', _COORD),
559 575 ('wAttributes', _WORD),
560 576 ('srWindow', _SMALL_RECT),
561 577 ('dwMaximumWindowSize', _COORD)]
562 578
563 579 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
564 580 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
565 581
566 582 _FOREGROUND_BLUE = 0x0001
567 583 _FOREGROUND_GREEN = 0x0002
568 584 _FOREGROUND_RED = 0x0004
569 585 _FOREGROUND_INTENSITY = 0x0008
570 586
571 587 _BACKGROUND_BLUE = 0x0010
572 588 _BACKGROUND_GREEN = 0x0020
573 589 _BACKGROUND_RED = 0x0040
574 590 _BACKGROUND_INTENSITY = 0x0080
575 591
576 592 _COMMON_LVB_REVERSE_VIDEO = 0x4000
577 593 _COMMON_LVB_UNDERSCORE = 0x8000
578 594
579 595 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
580 596 w32effects = {
581 597 'none': -1,
582 598 'black': 0,
583 599 'red': _FOREGROUND_RED,
584 600 'green': _FOREGROUND_GREEN,
585 601 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
586 602 'blue': _FOREGROUND_BLUE,
587 603 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
588 604 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
589 605 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
590 606 'bold': _FOREGROUND_INTENSITY,
591 607 'black_background': 0x100, # unused value > 0x0f
592 608 'red_background': _BACKGROUND_RED,
593 609 'green_background': _BACKGROUND_GREEN,
594 610 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
595 611 'blue_background': _BACKGROUND_BLUE,
596 612 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
597 613 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
598 614 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
599 615 _BACKGROUND_BLUE),
600 616 'bold_background': _BACKGROUND_INTENSITY,
601 617 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
602 618 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
603 619 }
604 620
605 621 passthrough = set([_FOREGROUND_INTENSITY,
606 622 _BACKGROUND_INTENSITY,
607 623 _COMMON_LVB_UNDERSCORE,
608 624 _COMMON_LVB_REVERSE_VIDEO])
609 625
610 626 stdout = _kernel32.GetStdHandle(
611 627 _STD_OUTPUT_HANDLE) # don't close the handle returned
612 628 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
613 629 w32effects = None
614 630 else:
615 631 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
616 632 if not _kernel32.GetConsoleScreenBufferInfo(
617 633 stdout, ctypes.byref(csbi)):
618 634 # stdout may not support GetConsoleScreenBufferInfo()
619 635 # when called from subprocess or redirected
620 636 w32effects = None
621 637 else:
622 638 origattr = csbi.wAttributes
623 639 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
624 640 re.MULTILINE | re.DOTALL)
625 641
626 642 def win32print(text, orig, **opts):
627 643 label = opts.get('label', '')
628 644 attr = origattr
629 645
630 646 def mapcolor(val, attr):
631 647 if val == -1:
632 648 return origattr
633 649 elif val in passthrough:
634 650 return attr | val
635 651 elif val > 0x0f:
636 652 return (val & 0x70) | (attr & 0x8f)
637 653 else:
638 654 return (val & 0x07) | (attr & 0xf8)
639 655
640 656 # determine console attributes based on labels
641 657 for l in label.split():
642 658 style = _styles.get(l, '')
643 659 for effect in style.split():
644 660 try:
645 661 attr = mapcolor(w32effects[effect], attr)
646 662 except KeyError:
647 663 # w32effects could not have certain attributes so we skip
648 664 # them if not found
649 665 pass
650 666 # hack to ensure regexp finds data
651 667 if not text.startswith('\033['):
652 668 text = '\033[m' + text
653 669
654 670 # Look for ANSI-like codes embedded in text
655 671 m = re.match(ansire, text)
656 672
657 673 try:
658 674 while m:
659 675 for sattr in m.group(1).split(';'):
660 676 if sattr:
661 677 attr = mapcolor(int(sattr), attr)
662 678 _kernel32.SetConsoleTextAttribute(stdout, attr)
663 679 orig(m.group(2), **opts)
664 680 m = re.match(ansire, m.group(3))
665 681 finally:
666 682 # Explicitly reset original attributes
667 683 _kernel32.SetConsoleTextAttribute(stdout, origattr)
General Comments 0
You need to be logged in to leave comments. Login now