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