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