color.py
665 lines
| 22.8 KiB
| text/x-python
|
PythonLexer
/ hgext / color.py
|
r22771 | # color.py color output for Mercurial commands | ||
|
r5787 | # | ||
# Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> | ||||
# | ||||
|
r15771 | # This software may be used and distributed according to the terms of the | ||
# GNU General Public License version 2 or any later version. | ||||
|
r5787 | |||
|
r8894 | '''colorize output from some commands | ||
|
r5787 | |||
|
r22771 | The color extension colorizes output from several Mercurial commands. | ||
For example, the diff command shows additions in green and deletions | ||||
in red, while the status command shows modified files in magenta. Many | ||||
other commands have analogous colors. It is possible to customize | ||||
these colors. | ||||
|
r7457 | |||
|
r22772 | Effects | ||
------- | ||||
|
r7988 | Other effects in addition to color, like bold and underlined text, are | ||
|
r13987 | also available. By default, the terminfo database is used to find the | ||
terminal codes used to change color and effect. If terminfo is not | ||||
available, then effects are rendered with the ECMA-48 SGR control | ||||
|
r13635 | function (aka ANSI escape codes). | ||
|
r5787 | |||
|
r22772 | The available effects in terminfo mode are 'blink', 'bold', 'dim', | ||
'inverse', 'invisible', 'italic', 'standout', and 'underline'; in | ||||
ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and | ||||
'underline'. How each is rendered depends on the terminal emulator. | ||||
Some may not be available for a given terminal type, and will be | ||||
silently ignored. | ||||
Labels | ||||
------ | ||||
|
r22462 | Text receives color effects depending on the labels that it has. Many | ||
default Mercurial commands emit labelled text. You can also define | ||||
your own labels in templates using the label function, see :hg:`help | ||||
templates`. A single portion of text may have more than one label. In | ||||
that case, effects given to the last label will override any other | ||||
effects. This includes the special "none" effect, which nullifies | ||||
other effects. | ||||
|
r22463 | Labels are normally invisible. In order to see these labels and their | ||
|
r22711 | position in the text, use the global --color=debug option. The same | ||
anchor text may be associated to multiple labels, e.g. | ||||
|
r22463 | |||
|
r22711 | [log.changeset changeset.secret|changeset: 22611:6f0a53c8f587] | ||
|
r22463 | |||
|
r22462 | The following are the default effects for some default labels. Default | ||
effects may be overridden from your configuration file:: | ||||
|
r5787 | |||
|
r9206 | [color] | ||
status.modified = blue bold underline red_background | ||||
status.added = green bold | ||||
status.removed = red bold blue_background | ||||
status.deleted = cyan bold underline | ||||
status.unknown = magenta bold underline | ||||
status.ignored = black bold | ||||
|
r5787 | |||
|
r9206 | # 'none' turns off all effects | ||
status.clean = none | ||||
status.copied = none | ||||
|
r5787 | |||
|
r9206 | qseries.applied = blue bold underline | ||
qseries.unapplied = black bold | ||||
qseries.missing = red bold | ||||
|
r7456 | |||
|
r9206 | diff.diffline = bold | ||
diff.extended = cyan bold | ||||
diff.file_a = red bold | ||||
diff.file_b = green bold | ||||
diff.hunk = magenta | ||||
diff.deleted = red | ||||
diff.inserted = green | ||||
diff.changed = white | ||||
|
r22710 | diff.tab = | ||
|
r9206 | diff.trailingwhitespace = bold red_background | ||
|
r10046 | |||
|
r22465 | # Blank so it inherits the style of the surrounding label | ||
changeset.public = | ||||
changeset.draft = | ||||
changeset.secret = | ||||
|
r10223 | resolve.unresolved = red bold | ||
resolve.resolved = green bold | ||||
|
r25347 | bookmarks.active = green | ||
|
r10870 | |||
|
r11969 | branches.active = none | ||
branches.closed = black bold | ||||
branches.current = green | ||||
branches.inactive = none | ||||
|
r15048 | tags.normal = green | ||
tags.local = black bold | ||||
|
r19214 | rebase.rebased = blue | ||
rebase.remaining = red bold | ||||
|
r19854 | shelve.age = cyan | ||
shelve.newest = green bold | ||||
shelve.name = blue bold | ||||
|
r19215 | histedit.remaining = red bold | ||
|
r22772 | Custom colors | ||
------------- | ||||
|
r14769 | |||
|
r13987 | Because there are only eight standard colors, this module allows you | ||
to define color names for other color slots which might be available | ||||
for your terminal type, assuming terminfo mode. For instance:: | ||||
color.brightblue = 12 | ||||
color.pink = 207 | ||||
color.orange = 202 | ||||
to set 'brightblue' to color slot 12 (useful for 16 color terminals | ||||
that have brighter colors defined in the upper eight) and, 'pink' and | ||||
'orange' to colors in 256-color xterm's default color cube. These | ||||
defined colors may then be used as any of the pre-defined eight, | ||||
including appending '_background' to set the background to that color. | ||||
|
r22772 | Modes | ||
----- | ||||
|
r14769 | By default, the color extension will use ANSI mode (or win32 mode on | ||
Windows) if it detects a terminal. To override auto mode (to enable | ||||
terminfo mode, for example), set the following configuration option:: | ||||
|
r10870 | |||
[color] | ||||
|
r14769 | mode = terminfo | ||
|
r10870 | |||
|
r13987 | Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will | ||
disable color. | ||||
|
r22772 | |||
Note that on some systems, terminfo mode may cause problems when using | ||||
color with the pager extension and less -R. less with the -R option | ||||
will only display ECMA-48 color codes, and terminfo mode may sometimes | ||||
emit codes that less doesn't understand. You can work around this by | ||||
either using ansi mode (or auto mode), or by using less -r (which will | ||||
pass through all terminal control codes, not just color control | ||||
codes). | ||||
|
r24068 | |||
On some systems (such as MSYS in Windows), the terminal may support | ||||
a different color mode than the pager (activated via the "pager" | ||||
extension). It is possible to define separate modes depending on whether | ||||
the pager is active:: | ||||
[color] | ||||
mode = auto | ||||
pagermode = ansi | ||||
If ``pagermode`` is not defined, the ``mode`` will be used. | ||||
|
r5787 | ''' | ||
|
r28968 | from __future__ import absolute_import | ||
|
r5787 | |||
|
r28968 | import os | ||
|
r29205 | |||
from mercurial.i18n import _ | ||||
|
r28968 | from mercurial import ( | ||
cmdutil, | ||||
commands, | ||||
dispatch, | ||||
extensions, | ||||
subrepo, | ||||
ui as uimod, | ||||
util, | ||||
) | ||||
|
r5787 | |||
|
r21249 | cmdtable = {} | ||
command = cmdutil.command(cmdtable) | ||||
|
r29841 | # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for | ||
|
r25186 | # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should | ||
# be specifying the version(s) of Mercurial they are tested with, or | ||||
# leave the attribute unspecified. | ||||
|
r29841 | testedwith = 'ships-with-hg-core' | ||
|
r16743 | |||
|
r5787 | # start and stop parameters for effects | ||
|
r10826 | _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, | ||
'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1, | ||||
|
r23925 | 'italic': 3, 'underline': 4, 'inverse': 7, 'dim': 2, | ||
|
r10826 | 'black_background': 40, 'red_background': 41, | ||
'green_background': 42, 'yellow_background': 43, | ||||
'blue_background': 44, 'purple_background': 45, | ||||
'cyan_background': 46, 'white_background': 47} | ||||
|
r14495 | def _terminfosetup(ui, mode): | ||
|
r13987 | '''Initialize terminfo data and the terminal if we're in terminfo mode.''' | ||
global _terminfo_params | ||||
# If we failed to load curses, we go ahead and return. | ||||
if not _terminfo_params: | ||||
return | ||||
# Otherwise, see what the config file says. | ||||
if mode not in ('auto', 'terminfo'): | ||||
return | ||||
|
r13998 | _terminfo_params.update((key[6:], (False, int(val))) | ||
|
r13987 | for key, val in ui.configitems('color') | ||
|
r13998 | if key.startswith('color.')) | ||
|
r13987 | |||
try: | ||||
curses.setupterm() | ||||
|
r25660 | except curses.error as e: | ||
|
r13987 | _terminfo_params = {} | ||
return | ||||
for key, (b, e) in _terminfo_params.items(): | ||||
if not b: | ||||
continue | ||||
if not curses.tigetstr(e): | ||||
# Most terminals don't support dim, invis, etc, so don't be | ||||
# noisy and use ui.debug(). | ||||
ui.debug("no terminfo entry for %s\n" % e) | ||||
del _terminfo_params[key] | ||||
if not curses.tigetstr('setaf') or not curses.tigetstr('setab'): | ||||
|
r14758 | # Only warn about missing terminfo entries if we explicitly asked for | ||
# terminfo mode. | ||||
if mode == "terminfo": | ||||
ui.warn(_("no terminfo entry for setab/setaf: reverting to " | ||||
"ECMA-48 color\n")) | ||||
|
r13987 | _terminfo_params = {} | ||
|
r19297 | def _modesetup(ui, coloropt): | ||
|
r14495 | global _terminfo_params | ||
|
r22463 | if coloropt == 'debug': | ||
return 'debug' | ||||
|
r21723 | auto = (coloropt == 'auto') | ||
|
r14495 | always = not auto and util.parsebool(coloropt) | ||
if not always and not auto: | ||||
return None | ||||
formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted()) | ||||
mode = ui.config('color', 'mode', 'auto') | ||||
|
r24068 | |||
# If pager is active, color.pagermode overrides color.mode. | ||||
if getattr(ui, 'pageractive', False): | ||||
mode = ui.config('color', 'pagermode', mode) | ||||
|
r14495 | realmode = mode | ||
if mode == 'auto': | ||||
|
r24028 | if os.name == 'nt': | ||
term = os.environ.get('TERM') | ||||
# TERM won't be defined in a vanilla cmd.exe environment. | ||||
# UNIX-like environments on Windows such as Cygwin and MSYS will | ||||
# set TERM. They appear to make a best effort attempt at setting it | ||||
# to something appropriate. However, not all environments with TERM | ||||
# defined support ANSI. Since "ansi" could result in terminal | ||||
# gibberish, we error on the side of selecting "win32". However, if | ||||
# w32effects is not defined, we almost certainly don't support | ||||
# "win32", so don't even try. | ||||
|
r24535 | if (term and 'xterm' in term) or not w32effects: | ||
|
r24028 | realmode = 'ansi' | ||
else: | ||||
realmode = 'win32' | ||||
|
r14769 | else: | ||
|
r14495 | realmode = 'ansi' | ||
|
r24266 | def modewarn(): | ||
# only warn if color.mode was explicitly set and we're in | ||||
# an interactive terminal | ||||
if mode == realmode and ui.interactive(): | ||||
ui.warn(_('warning: failed to set color mode to %s\n') % mode) | ||||
|
r14495 | if realmode == 'win32': | ||
|
r14989 | _terminfo_params = {} | ||
|
r14768 | if not w32effects: | ||
|
r24266 | modewarn() | ||
|
r14495 | return None | ||
_effects.update(w32effects) | ||||
elif realmode == 'ansi': | ||||
_terminfo_params = {} | ||||
elif realmode == 'terminfo': | ||||
_terminfosetup(ui, mode) | ||||
if not _terminfo_params: | ||||
|
r24266 | ## FIXME Shouldn't we return None in this case too? | ||
modewarn() | ||||
|
r14495 | realmode = 'ansi' | ||
else: | ||||
return None | ||||
if always or (auto and formatted): | ||||
return realmode | ||||
return None | ||||
|
r13987 | try: | ||
import curses | ||||
# Mapping from effect name to terminfo attribute name or color number. | ||||
# This will also force-load the curses module. | ||||
_terminfo_params = {'none': (True, 'sgr0'), | ||||
'standout': (True, 'smso'), | ||||
'underline': (True, 'smul'), | ||||
'reverse': (True, 'rev'), | ||||
'inverse': (True, 'rev'), | ||||
'blink': (True, 'blink'), | ||||
'dim': (True, 'dim'), | ||||
'bold': (True, 'bold'), | ||||
'invisible': (True, 'invis'), | ||||
'italic': (True, 'sitm'), | ||||
'black': (False, curses.COLOR_BLACK), | ||||
'red': (False, curses.COLOR_RED), | ||||
'green': (False, curses.COLOR_GREEN), | ||||
'yellow': (False, curses.COLOR_YELLOW), | ||||
'blue': (False, curses.COLOR_BLUE), | ||||
'magenta': (False, curses.COLOR_MAGENTA), | ||||
'cyan': (False, curses.COLOR_CYAN), | ||||
'white': (False, curses.COLOR_WHITE)} | ||||
except ImportError: | ||||
|
r21227 | _terminfo_params = {} | ||
|
r13987 | |||
|
r10826 | _styles = {'grep.match': 'red bold', | ||
|
r17806 | 'grep.linenumber': 'green', | ||
'grep.rev': 'green', | ||||
'grep.change': 'green', | ||||
'grep.sep': 'cyan', | ||||
'grep.filename': 'magenta', | ||||
'grep.user': 'magenta', | ||||
'grep.date': 'magenta', | ||||
|
r25347 | 'bookmarks.active': 'green', | ||
|
r11969 | 'branches.active': 'none', | ||
'branches.closed': 'black bold', | ||||
'branches.current': 'green', | ||||
'branches.inactive': 'none', | ||||
|
r10826 | 'diff.changed': 'white', | ||
'diff.deleted': 'red', | ||||
'diff.diffline': 'bold', | ||||
'diff.extended': 'cyan bold', | ||||
'diff.file_a': 'red bold', | ||||
'diff.file_b': 'green bold', | ||||
'diff.hunk': 'magenta', | ||||
'diff.inserted': 'green', | ||||
|
r22710 | 'diff.tab': '', | ||
|
r10826 | 'diff.trailingwhitespace': 'bold red_background', | ||
|
r22465 | 'changeset.public' : '', | ||
'changeset.draft' : '', | ||||
'changeset.secret' : '', | ||||
|
r10826 | 'diffstat.deleted': 'red', | ||
'diffstat.inserted': 'green', | ||||
|
r19215 | 'histedit.remaining': 'red bold', | ||
|
r13774 | 'ui.prompt': 'yellow', | ||
|
r10826 | 'log.changeset': 'yellow', | ||
|
r23173 | 'patchbomb.finalsummary': '', | ||
'patchbomb.from': 'magenta', | ||||
'patchbomb.to': 'cyan', | ||||
'patchbomb.subject': 'green', | ||||
'patchbomb.diffstats': '', | ||||
|
r19214 | 'rebase.rebased': 'blue', | ||
'rebase.remaining': 'red bold', | ||||
|
r10826 | 'resolve.resolved': 'green bold', | ||
'resolve.unresolved': 'red bold', | ||||
|
r19854 | 'shelve.age': 'cyan', | ||
'shelve.newest': 'green bold', | ||||
'shelve.name': 'blue bold', | ||||
|
r10826 | 'status.added': 'green bold', | ||
'status.clean': 'none', | ||||
'status.copied': 'none', | ||||
'status.deleted': 'cyan bold underline', | ||||
'status.ignored': 'black bold', | ||||
'status.modified': 'blue bold', | ||||
'status.removed': 'red bold', | ||||
|
r15048 | 'status.unknown': 'magenta bold underline', | ||
'tags.normal': 'green', | ||||
'tags.local': 'black bold'} | ||||
|
r10826 | |||
|
r5787 | |||
|
r13987 | def _effect_str(effect): | ||
'''Helper function for render_effects().''' | ||||
bg = False | ||||
if effect.endswith('_background'): | ||||
bg = True | ||||
effect = effect[:-11] | ||||
attr, val = _terminfo_params[effect] | ||||
if attr: | ||||
return curses.tigetstr(val) | ||||
elif bg: | ||||
return curses.tparm(curses.tigetstr('setab'), val) | ||||
else: | ||||
return curses.tparm(curses.tigetstr('setaf'), val) | ||||
|
r8622 | def render_effects(text, effects): | ||
|
r5787 | 'Wrap text in commands to turn on each effect.' | ||
|
r10826 | if not text: | ||
return text | ||||
|
r13987 | if not _terminfo_params: | ||
start = [str(_effects[e]) for e in ['none'] + effects.split()] | ||||
start = '\033[' + ';'.join(start) + 'm' | ||||
stop = '\033[' + str(_effects['none']) + 'm' | ||||
else: | ||||
start = ''.join(_effect_str(effect) | ||||
for effect in ['none'] + effects.split()) | ||||
stop = _effect_str('none') | ||||
|
r7459 | return ''.join([start, text, stop]) | ||
|
r5787 | |||
|
r10826 | def extstyles(): | ||
for name, ext in extensions.extensions(): | ||||
_styles.update(getattr(ext, 'colortable', {})) | ||||
|
r7456 | |||
|
r20992 | def valideffect(effect): | ||
'Determine if the effect is valid or not.' | ||||
good = False | ||||
if not _terminfo_params and effect in _effects: | ||||
good = True | ||||
elif effect in _terminfo_params or effect[:-11] in _terminfo_params: | ||||
good = True | ||||
return good | ||||
|
r10826 | def configstyles(ui): | ||
for status, cfgeffects in ui.configitems('color'): | ||||
|
r13987 | if '.' not in status or status.startswith('color.'): | ||
|
r10826 | continue | ||
cfgeffects = ui.configlist('color', status) | ||||
if cfgeffects: | ||||
|
r8945 | good = [] | ||
|
r10826 | for e in cfgeffects: | ||
|
r20992 | if valideffect(e): | ||
|
r8945 | good.append(e) | ||
else: | ||||
ui.warn(_("ignoring unknown color/effect %r " | ||||
"(configured in color.%s)\n") | ||||
|
r10826 | % (e, status)) | ||
_styles[status] = ' '.join(good) | ||||
|
r11555 | class colorui(uimod.ui): | ||
_colormode = 'ansi' | ||||
def write(self, *args, **opts): | ||||
|
r19072 | if self._colormode is None: | ||
return super(colorui, self).write(*args, **opts) | ||||
|
r11555 | label = opts.get('label', '') | ||
|
r28633 | if self._buffers and not opts.get('prompt', False): | ||
|
r27108 | if self._bufferapplylabels: | ||
|
r27110 | self._buffers[-1].extend(self.label(a, label) for a in args) | ||
|
r27108 | else: | ||
|
r27110 | self._buffers[-1].extend(args) | ||
|
r11555 | elif self._colormode == 'win32': | ||
for a in args: | ||||
|
r11727 | win32print(a, super(colorui, self).write, **opts) | ||
|
r11555 | else: | ||
return super(colorui, self).write( | ||||
|
r27110 | *[self.label(a, label) for a in args], **opts) | ||
|
r10826 | |||
|
r11555 | def write_err(self, *args, **opts): | ||
|
r19072 | if self._colormode is None: | ||
return super(colorui, self).write_err(*args, **opts) | ||||
|
r11555 | label = opts.get('label', '') | ||
|
r24848 | if self._bufferstates and self._bufferstates[-1][0]: | ||
|
r21190 | return self.write(*args, **opts) | ||
|
r11555 | if self._colormode == 'win32': | ||
for a in args: | ||||
|
r11727 | win32print(a, super(colorui, self).write_err, **opts) | ||
|
r11555 | else: | ||
|
r11732 | return super(colorui, self).write_err( | ||
|
r27110 | *[self.label(a, label) for a in args], **opts) | ||
|
r10826 | |||
|
r22463 | def showlabel(self, msg, label): | ||
|
r22763 | if label and msg: | ||
if msg[-1] == '\n': | ||||
|
r22464 | return "[%s|%s]\n" % (label, msg[:-1]) | ||
|
r22463 | else: | ||
|
r22464 | return "[%s|%s]" % (label, msg) | ||
|
r22463 | else: | ||
return msg | ||||
|
r11555 | def label(self, msg, label): | ||
|
r19072 | if self._colormode is None: | ||
return super(colorui, self).label(msg, label) | ||||
|
r22463 | if self._colormode == 'debug': | ||
return self.showlabel(msg, label) | ||||
|
r11555 | effects = [] | ||
for l in label.split(): | ||||
s = _styles.get(l, '') | ||||
if s: | ||||
effects.append(s) | ||||
|
r20993 | elif valideffect(l): | ||
effects.append(l) | ||||
|
r14145 | effects = ' '.join(effects) | ||
|
r11555 | if effects: | ||
return '\n'.join([render_effects(s, effects) | ||||
for s in msg.split('\n')]) | ||||
return msg | ||||
|
r10826 | def uisetup(ui): | ||
|
r10871 | if ui.plain(): | ||
return | ||||
|
r19298 | if not isinstance(ui, colorui): | ||
|
r19063 | colorui.__bases__ = (ui.__class__,) | ||
ui.__class__ = colorui | ||||
|
r10826 | def colorcmd(orig, ui_, opts, cmd, cmdfunc): | ||
|
r19297 | mode = _modesetup(ui_, opts['color']) | ||
|
r19073 | colorui._colormode = mode | ||
|
r22463 | if mode and mode != 'debug': | ||
|
r10826 | extstyles() | ||
|
r11555 | configstyles(ui_) | ||
|
r10826 | return orig(ui_, opts, cmd, cmdfunc) | ||
|
r23780 | def colorgit(orig, gitsub, commands, env=None, stream=False, cwd=None): | ||
if gitsub.ui._colormode and len(commands) and commands[0] == "diff": | ||||
# insert the argument in the front, | ||||
# the end of git diff arguments is used for paths | ||||
commands.insert(1, '--color') | ||||
return orig(gitsub, commands, env, stream, cwd) | ||||
|
r10826 | extensions.wrapfunction(dispatch, '_runcommand', colorcmd) | ||
|
r23780 | extensions.wrapfunction(subrepo.gitsubrepo, '_gitnodir', colorgit) | ||
|
r10826 | |||
|
r12693 | def extsetup(ui): | ||
commands.globalopts.append( | ||||
('', 'color', 'auto', | ||||
|
r22463 | # i18n: 'always', 'auto', 'never', and 'debug' are keywords | ||
# and should not be translated | ||||
_("when to colorize (boolean, always, auto, never, or debug)"), | ||||
|
r12693 | _('TYPE'))) | ||
|
r10870 | |||
|
r21249 | @command('debugcolor', [], 'hg debugcolor') | ||
|
r20455 | def debugcolor(ui, repo, **opts): | ||
global _styles | ||||
_styles = {} | ||||
for effect in _effects.keys(): | ||||
_styles[effect] = effect | ||||
|
r21024 | ui.write(('color mode: %s\n') % ui._colormode) | ||
|
r20455 | ui.write(_('available colors:\n')) | ||
for label, colors in _styles.items(): | ||||
ui.write(('%s\n') % colors, label=label) | ||||
|
r13641 | if os.name != 'nt': | ||
w32effects = None | ||||
else: | ||||
|
r28968 | import ctypes | ||
import re | ||||
|
r13641 | |||
_kernel32 = ctypes.windll.kernel32 | ||||
_WORD = ctypes.c_ushort | ||||
_INVALID_HANDLE_VALUE = -1 | ||||
class _COORD(ctypes.Structure): | ||||
_fields_ = [('X', ctypes.c_short), | ||||
('Y', ctypes.c_short)] | ||||
class _SMALL_RECT(ctypes.Structure): | ||||
_fields_ = [('Left', ctypes.c_short), | ||||
('Top', ctypes.c_short), | ||||
('Right', ctypes.c_short), | ||||
('Bottom', ctypes.c_short)] | ||||
class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure): | ||||
_fields_ = [('dwSize', _COORD), | ||||
('dwCursorPosition', _COORD), | ||||
('wAttributes', _WORD), | ||||
('srWindow', _SMALL_RECT), | ||||
('dwMaximumWindowSize', _COORD)] | ||||
_STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11 | ||||
_STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12 | ||||
_FOREGROUND_BLUE = 0x0001 | ||||
_FOREGROUND_GREEN = 0x0002 | ||||
_FOREGROUND_RED = 0x0004 | ||||
_FOREGROUND_INTENSITY = 0x0008 | ||||
_BACKGROUND_BLUE = 0x0010 | ||||
_BACKGROUND_GREEN = 0x0020 | ||||
_BACKGROUND_RED = 0x0040 | ||||
_BACKGROUND_INTENSITY = 0x0080 | ||||
_COMMON_LVB_REVERSE_VIDEO = 0x4000 | ||||
_COMMON_LVB_UNDERSCORE = 0x8000 | ||||
|
r10870 | |||
# http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx | ||||
w32effects = { | ||||
|
r12277 | 'none': -1, | ||
|
r10870 | 'black': 0, | ||
|
r13641 | 'red': _FOREGROUND_RED, | ||
'green': _FOREGROUND_GREEN, | ||||
'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN, | ||||
'blue': _FOREGROUND_BLUE, | ||||
'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED, | ||||
'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN, | ||||
'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE, | ||||
'bold': _FOREGROUND_INTENSITY, | ||||
|
r12278 | 'black_background': 0x100, # unused value > 0x0f | ||
|
r13641 | 'red_background': _BACKGROUND_RED, | ||
'green_background': _BACKGROUND_GREEN, | ||||
'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN, | ||||
'blue_background': _BACKGROUND_BLUE, | ||||
'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED, | ||||
'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN, | ||||
'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN | | ||||
_BACKGROUND_BLUE), | ||||
'bold_background': _BACKGROUND_INTENSITY, | ||||
'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only | ||||
'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only | ||||
|
r10870 | } | ||
|
r13641 | passthrough = set([_FOREGROUND_INTENSITY, | ||
_BACKGROUND_INTENSITY, | ||||
_COMMON_LVB_UNDERSCORE, | ||||
_COMMON_LVB_REVERSE_VIDEO]) | ||||
|
r12277 | |||
|
r13641 | stdout = _kernel32.GetStdHandle( | ||
_STD_OUTPUT_HANDLE) # don't close the handle returned | ||||
if stdout is None or stdout == _INVALID_HANDLE_VALUE: | ||||
w32effects = None | ||||
else: | ||||
csbi = _CONSOLE_SCREEN_BUFFER_INFO() | ||||
if not _kernel32.GetConsoleScreenBufferInfo( | ||||
stdout, ctypes.byref(csbi)): | ||||
# stdout may not support GetConsoleScreenBufferInfo() | ||||
# when called from subprocess or redirected | ||||
w32effects = None | ||||
else: | ||||
origattr = csbi.wAttributes | ||||
ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)', | ||||
re.MULTILINE | re.DOTALL) | ||||
|
r10870 | |||
def win32print(text, orig, **opts): | ||||
label = opts.get('label', '') | ||||
|
r12277 | attr = origattr | ||
def mapcolor(val, attr): | ||||
if val == -1: | ||||
return origattr | ||||
elif val in passthrough: | ||||
return attr | val | ||||
elif val > 0x0f: | ||||
return (val & 0x70) | (attr & 0x8f) | ||||
else: | ||||
return (val & 0x07) | (attr & 0xf8) | ||||
|
r10870 | |||
# determine console attributes based on labels | ||||
for l in label.split(): | ||||
style = _styles.get(l, '') | ||||
for effect in style.split(): | ||||
|
r21991 | try: | ||
attr = mapcolor(w32effects[effect], attr) | ||||
except KeyError: | ||||
# w32effects could not have certain attributes so we skip | ||||
# them if not found | ||||
pass | ||||
|
r10870 | # hack to ensure regexp finds data | ||
if not text.startswith('\033['): | ||||
text = '\033[m' + text | ||||
# Look for ANSI-like codes embedded in text | ||||
m = re.match(ansire, text) | ||||
|
r13919 | try: | ||
while m: | ||||
for sattr in m.group(1).split(';'): | ||||
if sattr: | ||||
attr = mapcolor(int(sattr), attr) | ||||
_kernel32.SetConsoleTextAttribute(stdout, attr) | ||||
orig(m.group(2), **opts) | ||||
m = re.match(ansire, m.group(3)) | ||||
finally: | ||||
|
r17424 | # Explicitly reset original attributes | ||
|
r13919 | _kernel32.SetConsoleTextAttribute(stdout, origattr) | ||