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