color.py
577 lines
| 18.4 KiB
| text/x-python
|
PythonLexer
/ mercurial / color.py
|
r30652 | # utility for color output for Mercurial commands | ||
# | ||||
# Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> and other | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
from __future__ import absolute_import | ||||
|
r31521 | import re | ||
|
r30971 | from .i18n import _ | ||
|
r43359 | from .pycompat import getattr | ||
|
r30971 | |||
|
r31101 | from . import ( | ||
encoding, | ||||
pycompat, | ||||
|
r37102 | ) | ||
|
r43346 | from .utils import stringutil | ||
|
r31067 | |||
|
r30968 | try: | ||
import curses | ||||
|
r43346 | |||
|
r30968 | # Mapping from effect name to terminfo attribute name (or raw code) or | ||
# color number. This will also force-load the curses module. | ||||
|
r31113 | _baseterminfoparams = { | ||
|
r43347 | b'none': (True, b'sgr0', b''), | ||
b'standout': (True, b'smso', b''), | ||||
b'underline': (True, b'smul', b''), | ||||
b'reverse': (True, b'rev', b''), | ||||
b'inverse': (True, b'rev', b''), | ||||
b'blink': (True, b'blink', b''), | ||||
b'dim': (True, b'dim', b''), | ||||
b'bold': (True, b'bold', b''), | ||||
b'invisible': (True, b'invis', b''), | ||||
b'italic': (True, b'sitm', b''), | ||||
b'black': (False, curses.COLOR_BLACK, b''), | ||||
b'red': (False, curses.COLOR_RED, b''), | ||||
b'green': (False, curses.COLOR_GREEN, b''), | ||||
b'yellow': (False, curses.COLOR_YELLOW, b''), | ||||
b'blue': (False, curses.COLOR_BLUE, b''), | ||||
b'magenta': (False, curses.COLOR_MAGENTA, b''), | ||||
b'cyan': (False, curses.COLOR_CYAN, b''), | ||||
b'white': (False, curses.COLOR_WHITE, b''), | ||||
|
r31109 | } | ||
|
r30968 | except ImportError: | ||
curses = None | ||||
|
r31113 | _baseterminfoparams = {} | ||
|
r30968 | |||
|
r30967 | # start and stop parameters for effects | ||
|
r31109 | _effects = { | ||
|
r43347 | b'none': 0, | ||
b'black': 30, | ||||
b'red': 31, | ||||
b'green': 32, | ||||
b'yellow': 33, | ||||
b'blue': 34, | ||||
b'magenta': 35, | ||||
b'cyan': 36, | ||||
b'white': 37, | ||||
b'bold': 1, | ||||
b'italic': 3, | ||||
b'underline': 4, | ||||
b'inverse': 7, | ||||
b'dim': 2, | ||||
b'black_background': 40, | ||||
b'red_background': 41, | ||||
b'green_background': 42, | ||||
b'yellow_background': 43, | ||||
b'blue_background': 44, | ||||
b'purple_background': 45, | ||||
b'cyan_background': 46, | ||||
b'white_background': 47, | ||||
|
r43346 | } | ||
|
r30967 | |||
|
r31116 | _defaultstyles = { | ||
|
r43347 | b'grep.match': b'red bold', | ||
b'grep.linenumber': b'green', | ||||
b'grep.rev': b'blue', | ||||
b'grep.sep': b'cyan', | ||||
b'grep.filename': b'magenta', | ||||
b'grep.user': b'magenta', | ||||
b'grep.date': b'magenta', | ||||
b'grep.inserted': b'green bold', | ||||
b'grep.deleted': b'red bold', | ||||
b'bookmarks.active': b'green', | ||||
b'branches.active': b'none', | ||||
b'branches.closed': b'black bold', | ||||
b'branches.current': b'green', | ||||
b'branches.inactive': b'none', | ||||
b'diff.changed': b'white', | ||||
b'diff.deleted': b'red', | ||||
b'diff.deleted.changed': b'red bold underline', | ||||
b'diff.deleted.unchanged': b'red', | ||||
b'diff.diffline': b'bold', | ||||
b'diff.extended': b'cyan bold', | ||||
b'diff.file_a': b'red bold', | ||||
b'diff.file_b': b'green bold', | ||||
b'diff.hunk': b'magenta', | ||||
b'diff.inserted': b'green', | ||||
b'diff.inserted.changed': b'green bold underline', | ||||
b'diff.inserted.unchanged': b'green', | ||||
b'diff.tab': b'', | ||||
b'diff.trailingwhitespace': b'bold red_background', | ||||
b'changeset.public': b'', | ||||
b'changeset.draft': b'', | ||||
b'changeset.secret': b'', | ||||
b'diffstat.deleted': b'red', | ||||
b'diffstat.inserted': b'green', | ||||
b'formatvariant.name.mismatchconfig': b'red', | ||||
b'formatvariant.name.mismatchdefault': b'yellow', | ||||
b'formatvariant.name.uptodate': b'green', | ||||
b'formatvariant.repo.mismatchconfig': b'red', | ||||
b'formatvariant.repo.mismatchdefault': b'yellow', | ||||
b'formatvariant.repo.uptodate': b'green', | ||||
b'formatvariant.config.special': b'yellow', | ||||
b'formatvariant.config.default': b'green', | ||||
b'formatvariant.default': b'', | ||||
b'histedit.remaining': b'red bold', | ||||
b'ui.addremove.added': b'green', | ||||
b'ui.addremove.removed': b'red', | ||||
b'ui.error': b'red', | ||||
b'ui.prompt': b'yellow', | ||||
b'log.changeset': b'yellow', | ||||
b'patchbomb.finalsummary': b'', | ||||
b'patchbomb.from': b'magenta', | ||||
b'patchbomb.to': b'cyan', | ||||
b'patchbomb.subject': b'green', | ||||
b'patchbomb.diffstats': b'', | ||||
b'rebase.rebased': b'blue', | ||||
b'rebase.remaining': b'red bold', | ||||
b'resolve.resolved': b'green bold', | ||||
b'resolve.unresolved': b'red bold', | ||||
b'shelve.age': b'cyan', | ||||
b'shelve.newest': b'green bold', | ||||
b'shelve.name': b'blue bold', | ||||
b'status.added': b'green bold', | ||||
b'status.clean': b'none', | ||||
b'status.copied': b'none', | ||||
b'status.deleted': b'cyan bold underline', | ||||
b'status.ignored': b'black bold', | ||||
b'status.modified': b'blue bold', | ||||
b'status.removed': b'red bold', | ||||
b'status.unknown': b'magenta bold underline', | ||||
b'tags.normal': b'green', | ||||
b'tags.local': b'black bold', | ||||
r44257 | b'upgrade-repo.requirement.preserved': b'cyan', | |||
b'upgrade-repo.requirement.added': b'green', | ||||
b'upgrade-repo.requirement.removed': b'red', | ||||
|
r31109 | } | ||
|
r30653 | |||
|
r43346 | |||
|
r30653 | def loadcolortable(ui, extname, colortable): | ||
|
r31116 | _defaultstyles.update(colortable) | ||
|
r30969 | |||
|
r43346 | |||
|
r33643 | def _terminfosetup(ui, mode, formatted): | ||
|
r31100 | '''Initialize terminfo data and the terminal if we're in terminfo mode.''' | ||
# If we failed to load curses, we go ahead and return. | ||||
if curses is None: | ||||
return | ||||
# Otherwise, see what the config file says. | ||||
|
r43347 | if mode not in (b'auto', b'terminfo'): | ||
|
r31100 | return | ||
|
r31113 | ui._terminfoparams.update(_baseterminfoparams) | ||
|
r31100 | |||
|
r43347 | for key, val in ui.configitems(b'color'): | ||
if key.startswith(b'color.'): | ||||
newval = (False, int(val), b'') | ||||
|
r31113 | ui._terminfoparams[key[6:]] = newval | ||
|
r43347 | elif key.startswith(b'terminfo.'): | ||
newval = (True, b'', val.replace(b'\\E', b'\x1b')) | ||||
|
r31113 | ui._terminfoparams[key[9:]] = newval | ||
|
r31100 | try: | ||
curses.setupterm() | ||||
|
r41401 | except curses.error: | ||
|
r31113 | ui._terminfoparams.clear() | ||
|
r31100 | return | ||
|
r37680 | for key, (b, e, c) in ui._terminfoparams.copy().items(): | ||
|
r31100 | if not b: | ||
continue | ||||
|
r37682 | if not c and not curses.tigetstr(pycompat.sysstr(e)): | ||
|
r31100 | # Most terminals don't support dim, invis, etc, so don't be | ||
# noisy and use ui.debug(). | ||||
|
r43347 | ui.debug(b"no terminfo entry for %s\n" % e) | ||
|
r31113 | del ui._terminfoparams[key] | ||
|
r43906 | if not curses.tigetstr('setaf') or not curses.tigetstr('setab'): | ||
|
r31100 | # Only warn about missing terminfo entries if we explicitly asked for | ||
|
r33643 | # terminfo mode and we're in a formatted terminal. | ||
|
r43347 | if mode == b"terminfo" and formatted: | ||
|
r43346 | ui.warn( | ||
_( | ||||
|
r43347 | b"no terminfo entry for setab/setaf: reverting to " | ||
b"ECMA-48 color\n" | ||||
|
r43346 | ) | ||
) | ||||
|
r31113 | ui._terminfoparams.clear() | ||
|
r31100 | |||
|
r43346 | |||
|
r31110 | def setup(ui): | ||
|
r31105 | """configure color on a ui | ||
|
r31110 | That function both set the colormode for the ui object and read | ||
|
r31105 | the configuration looking for custom colors and effect definitions.""" | ||
|
r31110 | mode = _modesetup(ui) | ||
|
r31106 | ui._colormode = mode | ||
|
r43347 | if mode and mode != b'debug': | ||
|
r31105 | configstyles(ui) | ||
|
r43346 | |||
|
r31110 | def _modesetup(ui): | ||
|
r43347 | if ui.plain(b'color'): | ||
|
r31103 | return None | ||
|
r43347 | config = ui.config(b'ui', b'color') | ||
if config == b'debug': | ||||
return b'debug' | ||||
|
r31101 | |||
|
r43347 | auto = config == b'auto' | ||
|
r32103 | always = False | ||
|
r37102 | if not auto and stringutil.parsebool(config): | ||
|
r32104 | # We want the config to behave like a boolean, "on" is actually auto, | ||
# but "always" value is treated as a special case to reduce confusion. | ||||
|
r43347 | if ( | ||
ui.configsource(b'ui', b'color') == b'--color' | ||||
or config == b'always' | ||||
): | ||||
|
r32103 | always = True | ||
else: | ||||
auto = True | ||||
|
r31101 | if not always and not auto: | ||
return None | ||||
|
r43346 | formatted = always or ( | ||
|
r43347 | encoding.environ.get(b'TERM') != b'dumb' and ui.formatted() | ||
|
r43346 | ) | ||
|
r31101 | |||
|
r43347 | mode = ui.config(b'color', b'mode') | ||
|
r31101 | |||
# If pager is active, color.pagermode overrides color.mode. | ||||
if getattr(ui, 'pageractive', False): | ||||
|
r43347 | mode = ui.config(b'color', b'pagermode', mode) | ||
|
r31101 | |||
realmode = mode | ||||
|
r34646 | if pycompat.iswindows: | ||
|
r32665 | from . import win32 | ||
|
r43347 | term = encoding.environ.get(b'TERM') | ||
|
r32665 | # TERM won't be defined in a vanilla cmd.exe environment. | ||
|
r31101 | |||
|
r32665 | # 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. | ||||
|
r43347 | ansienviron = term and b'xterm' in term | ||
|
r32665 | |||
|
r43347 | if mode == b'auto': | ||
|
r32665 | # 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. | ||||
|
r41006 | # w32effects is not populated when stdout is redirected, so checking | ||
|
r32665 | # it first avoids win32 calls in a state known to error out. | ||
if ansienviron or not w32effects or win32.enablevtmode(): | ||||
|
r43347 | realmode = b'ansi' | ||
|
r31101 | else: | ||
|
r43347 | realmode = b'win32' | ||
|
r32665 | # An empty w32effects is a clue that stdout is redirected, and thus | ||
# cannot enable VT mode. | ||||
|
r43347 | elif mode == b'ansi' and w32effects and not ansienviron: | ||
|
r32665 | win32.enablevtmode() | ||
|
r43347 | elif mode == b'auto': | ||
realmode = b'ansi' | ||||
|
r31101 | |||
def modewarn(): | ||||
# only warn if color.mode was explicitly set and we're in | ||||
# a formatted terminal | ||||
|
r33643 | if mode == realmode and formatted: | ||
|
r43347 | ui.warn(_(b'warning: failed to set color mode to %s\n') % mode) | ||
|
r31101 | |||
|
r43347 | if realmode == b'win32': | ||
|
r31113 | ui._terminfoparams.clear() | ||
|
r31101 | if not w32effects: | ||
modewarn() | ||||
return None | ||||
|
r43347 | elif realmode == b'ansi': | ||
|
r31113 | ui._terminfoparams.clear() | ||
|
r43347 | elif realmode == b'terminfo': | ||
|
r33643 | _terminfosetup(ui, mode, formatted) | ||
|
r31113 | if not ui._terminfoparams: | ||
|
r31101 | ## FIXME Shouldn't we return None in this case too? | ||
modewarn() | ||||
|
r43347 | realmode = b'ansi' | ||
|
r31101 | else: | ||
return None | ||||
if always or (auto and formatted): | ||||
return realmode | ||||
return None | ||||
|
r43346 | |||
|
r30971 | def configstyles(ui): | ||
|
r31116 | ui._styles.update(_defaultstyles) | ||
|
r43347 | for status, cfgeffects in ui.configitems(b'color'): | ||
if b'.' not in status or status.startswith((b'color.', b'terminfo.')): | ||||
|
r30971 | continue | ||
|
r43347 | cfgeffects = ui.configlist(b'color', status) | ||
|
r30971 | if cfgeffects: | ||
good = [] | ||||
for e in cfgeffects: | ||||
|
r31112 | if valideffect(ui, e): | ||
|
r30971 | good.append(e) | ||
else: | ||||
|
r43346 | ui.warn( | ||
_( | ||||
|
r43347 | b"ignoring unknown color/effect %s " | ||
b"(configured in color.%s)\n" | ||||
|
r43346 | ) | ||
% (stringutil.pprint(e), status) | ||||
) | ||||
|
r43347 | ui._styles[status] = b' '.join(good) | ||
|
r30971 | |||
|
r43346 | |||
|
r31689 | def _activeeffects(ui): | ||
'''Return the effects map for the color mode set on the ui.''' | ||||
|
r43347 | if ui._colormode == b'win32': | ||
|
r31689 | return w32effects | ||
elif ui._colormode is not None: | ||||
return _effects | ||||
return {} | ||||
|
r43346 | |||
|
r31112 | def valideffect(ui, effect): | ||
|
r44226 | """Determine if the effect is valid or not.""" | ||
|
r43346 | return (not ui._terminfoparams and effect in _activeeffects(ui)) or ( | ||
effect in ui._terminfoparams or effect[:-11] in ui._terminfoparams | ||||
) | ||||
|
r30972 | |||
|
r31112 | def _effect_str(ui, effect): | ||
|
r30972 | '''Helper function for render_effects().''' | ||
bg = False | ||||
|
r43347 | if effect.endswith(b'_background'): | ||
|
r30972 | bg = True | ||
effect = effect[:-11] | ||||
try: | ||||
|
r31113 | attr, val, termcode = ui._terminfoparams[effect] | ||
|
r30972 | except KeyError: | ||
|
r43347 | return b'' | ||
|
r30972 | if attr: | ||
if termcode: | ||||
return termcode | ||||
else: | ||||
|
r37682 | return curses.tigetstr(pycompat.sysstr(val)) | ||
|
r30972 | elif bg: | ||
|
r43906 | return curses.tparm(curses.tigetstr('setab'), val) | ||
|
r30972 | else: | ||
|
r43906 | return curses.tparm(curses.tigetstr('setaf'), val) | ||
|
r30973 | |||
|
r43346 | |||
|
r31518 | def _mergeeffects(text, start, stop): | ||
"""Insert start sequence at every occurrence of stop sequence | ||||
|
r34133 | >>> s = _mergeeffects(b'cyan', b'[C]', b'|') | ||
>>> s = _mergeeffects(s + b'yellow', b'[Y]', b'|') | ||||
>>> s = _mergeeffects(b'ma' + s + b'genta', b'[M]', b'|') | ||||
>>> s = _mergeeffects(b'red' + s, b'[R]', b'|') | ||||
|
r31518 | >>> s | ||
'[R]red[M]ma[Y][C]cyan|[R][M][Y]yellow|[R][M]genta|' | ||||
""" | ||||
parts = [] | ||||
for t in text.split(stop): | ||||
if not t: | ||||
continue | ||||
parts.extend([start, t, stop]) | ||||
|
r43347 | return b''.join(parts) | ||
|
r31518 | |||
|
r43346 | |||
|
r31112 | def _render_effects(ui, text, effects): | ||
|
r44226 | """Wrap text in commands to turn on each effect.""" | ||
|
r30973 | if not text: | ||
return text | ||||
|
r31113 | if ui._terminfoparams: | ||
|
r43347 | start = b''.join( | ||
_effect_str(ui, effect) for effect in [b'none'] + effects.split() | ||||
|
r43346 | ) | ||
|
r43347 | stop = _effect_str(ui, b'none') | ||
|
r31071 | else: | ||
|
r31689 | activeeffects = _activeeffects(ui) | ||
|
r43346 | start = [ | ||
pycompat.bytestr(activeeffects[e]) | ||||
|
r43347 | for e in [b'none'] + effects.split() | ||
|
r43346 | ] | ||
|
r43347 | start = b'\033[' + b';'.join(start) + b'm' | ||
stop = b'\033[' + pycompat.bytestr(activeeffects[b'none']) + b'm' | ||||
|
r31518 | return _mergeeffects(text, start, stop) | ||
|
r31067 | |||
|
r43346 | |||
|
r31521 | _ansieffectre = re.compile(br'\x1b\[[0-9;]*m') | ||
|
r43346 | |||
|
r31521 | def stripeffects(text): | ||
"""Strip ANSI control codes which could be inserted by colorlabel()""" | ||||
|
r43347 | return _ansieffectre.sub(b'', text) | ||
|
r31521 | |||
|
r43346 | |||
|
r31086 | def colorlabel(ui, msg, label): | ||
"""add color control code according to the mode""" | ||||
|
r43347 | if ui._colormode == b'debug': | ||
|
r31086 | if label and msg: | ||
|
r43347 | if msg.endswith(b'\n'): | ||
msg = b"[%s|%s]\n" % (label, msg[:-1]) | ||||
|
r31086 | else: | ||
|
r43347 | msg = b"[%s|%s]" % (label, msg) | ||
|
r31086 | elif ui._colormode is not None: | ||
effects = [] | ||||
for l in label.split(): | ||||
|
r43347 | s = ui._styles.get(l, b'') | ||
|
r31086 | if s: | ||
effects.append(s) | ||||
|
r31112 | elif valideffect(ui, l): | ||
|
r31086 | effects.append(l) | ||
|
r43347 | effects = b' '.join(effects) | ||
|
r31086 | if effects: | ||
|
r43347 | msg = b'\n'.join( | ||
[ | ||||
_render_effects(ui, line, effects) | ||||
for line in msg.split(b'\n') | ||||
] | ||||
|
r43346 | ) | ||
|
r31086 | return msg | ||
|
r43346 | |||
|
r31067 | w32effects = None | ||
|
r34646 | if pycompat.iswindows: | ||
|
r31067 | import ctypes | ||
|
r43777 | _kernel32 = ctypes.windll.kernel32 # pytype: disable=module-attr | ||
|
r31067 | |||
_WORD = ctypes.c_ushort | ||||
_INVALID_HANDLE_VALUE = -1 | ||||
class _COORD(ctypes.Structure): | ||||
|
r43906 | _fields_ = [('X', ctypes.c_short), ('Y', ctypes.c_short)] | ||
|
r31067 | |||
class _SMALL_RECT(ctypes.Structure): | ||||
|
r43346 | _fields_ = [ | ||
|
r43906 | ('Left', ctypes.c_short), | ||
('Top', ctypes.c_short), | ||||
('Right', ctypes.c_short), | ||||
('Bottom', ctypes.c_short), | ||||
|
r43346 | ] | ||
|
r31067 | |||
class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure): | ||||
|
r43346 | _fields_ = [ | ||
|
r43906 | ('dwSize', _COORD), | ||
('dwCursorPosition', _COORD), | ||||
('wAttributes', _WORD), | ||||
('srWindow', _SMALL_RECT), | ||||
('dwMaximumWindowSize', _COORD), | ||||
|
r43346 | ] | ||
|
r31067 | |||
|
r43346 | _STD_OUTPUT_HANDLE = 0xFFFFFFF5 # (DWORD)-11 | ||
_STD_ERROR_HANDLE = 0xFFFFFFF4 # (DWORD)-12 | ||||
|
r31067 | |||
_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 | ||||
# http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx | ||||
w32effects = { | ||||
|
r43347 | b'none': -1, | ||
b'black': 0, | ||||
b'red': _FOREGROUND_RED, | ||||
b'green': _FOREGROUND_GREEN, | ||||
b'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN, | ||||
b'blue': _FOREGROUND_BLUE, | ||||
b'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED, | ||||
b'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN, | ||||
b'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE, | ||||
b'bold': _FOREGROUND_INTENSITY, | ||||
b'black_background': 0x100, # unused value > 0x0f | ||||
b'red_background': _BACKGROUND_RED, | ||||
b'green_background': _BACKGROUND_GREEN, | ||||
b'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN, | ||||
b'blue_background': _BACKGROUND_BLUE, | ||||
b'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED, | ||||
b'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN, | ||||
b'white_background': ( | ||||
|
r43346 | _BACKGROUND_RED | _BACKGROUND_GREEN | _BACKGROUND_BLUE | ||
), | ||||
|
r43347 | b'bold_background': _BACKGROUND_INTENSITY, | ||
b'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only | ||||
b'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only | ||||
|
r31067 | } | ||
|
r43346 | passthrough = { | ||
_FOREGROUND_INTENSITY, | ||||
_BACKGROUND_INTENSITY, | ||||
_COMMON_LVB_UNDERSCORE, | ||||
_COMMON_LVB_REVERSE_VIDEO, | ||||
} | ||||
|
r31067 | |||
stdout = _kernel32.GetStdHandle( | ||||
|
r43346 | _STD_OUTPUT_HANDLE | ||
) # don't close the handle returned | ||||
|
r31067 | if stdout is None or stdout == _INVALID_HANDLE_VALUE: | ||
w32effects = None | ||||
else: | ||||
csbi = _CONSOLE_SCREEN_BUFFER_INFO() | ||||
|
r43346 | if not _kernel32.GetConsoleScreenBufferInfo(stdout, ctypes.byref(csbi)): | ||
|
r31067 | # stdout may not support GetConsoleScreenBufferInfo() | ||
# when called from subprocess or redirected | ||||
w32effects = None | ||||
else: | ||||
origattr = csbi.wAttributes | ||||
|
r43346 | ansire = re.compile( | ||
br'\033\[([^m]*)m([^\033]*)(.*)', re.MULTILINE | re.DOTALL | ||||
) | ||||
|
r31067 | |||
|
r40557 | def win32print(ui, writefunc, text, **opts): | ||
|
r43906 | label = opts.get('label', b'') | ||
|
r31067 | attr = origattr | ||
def mapcolor(val, attr): | ||||
if val == -1: | ||||
return origattr | ||||
elif val in passthrough: | ||||
return attr | val | ||||
|
r43346 | elif val > 0x0F: | ||
return (val & 0x70) | (attr & 0x8F) | ||||
|
r31067 | else: | ||
|
r43346 | return (val & 0x07) | (attr & 0xF8) | ||
|
r31067 | |||
# determine console attributes based on labels | ||||
for l in label.split(): | ||||
|
r43347 | style = ui._styles.get(l, b'') | ||
|
r31067 | for effect in style.split(): | ||
try: | ||||
attr = mapcolor(w32effects[effect], attr) | ||||
except KeyError: | ||||
# w32effects could not have certain attributes so we skip | ||||
# them if not found | ||||
pass | ||||
# hack to ensure regexp finds data | ||||
|
r39680 | if not text.startswith(b'\033['): | ||
text = b'\033[m' + text | ||||
|
r31067 | |||
# Look for ANSI-like codes embedded in text | ||||
m = re.match(ansire, text) | ||||
try: | ||||
while m: | ||||
|
r39680 | for sattr in m.group(1).split(b';'): | ||
|
r31067 | if sattr: | ||
attr = mapcolor(int(sattr), attr) | ||||
|
r31499 | ui.flush() | ||
|
r31067 | _kernel32.SetConsoleTextAttribute(stdout, attr) | ||
|
r40556 | writefunc(m.group(2)) | ||
|
r31067 | m = re.match(ansire, m.group(3)) | ||
finally: | ||||
# Explicitly reset original attributes | ||||
|
r31499 | ui.flush() | ||
|
r31067 | _kernel32.SetConsoleTextAttribute(stdout, origattr) | ||