color.py
361 lines
| 12.5 KiB
| text/x-python
|
PythonLexer
/ hgext / color.py
Kevin Christen
|
r5787 | # color.py color output for the status and qseries commands | ||
# | ||||
# Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> | ||||
# | ||||
Kevin Christen
|
r5792 | # This program is free software; you can redistribute it and/or modify it | ||
Kevin Christen
|
r5787 | # under the terms of the GNU General Public License as published by the | ||
Kevin Christen
|
r5792 | # Free Software Foundation; either version 2 of the License, or (at your | ||
Kevin Christen
|
r5787 | # option) any later version. | ||
# | ||||
# This program is distributed in the hope that it will be useful, but | ||||
# WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General | ||||
# Public License for more details. | ||||
# | ||||
# You should have received a copy of the GNU General Public License along | ||||
Kevin Christen
|
r5792 | # with this program; if not, write to the Free Software Foundation, Inc., | ||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
Kevin Christen
|
r5787 | |||
Cédric Duval
|
r8894 | '''colorize output from some commands | ||
Kevin Christen
|
r5787 | |||
Martin Geisler
|
r13638 | This extension modifies the status and resolve commands to add color | ||
to their output to reflect file status, the qseries command to add | ||||
color to reflect patch status (applied, unapplied, missing), and to | ||||
diff-related commands to highlight additions, removals, diff headers, | ||||
and trailing whitespace. | ||||
Georg Brandl
|
r7457 | |||
Martin Geisler
|
r7988 | Other effects in addition to color, like bold and underlined text, are | ||
also available. Effects are rendered with the ECMA-48 SGR control | ||||
Martin Geisler
|
r13635 | function (aka ANSI escape codes). | ||
Kevin Christen
|
r5787 | |||
Brodie Rao
|
r12083 | Default effects may be overridden from your configuration file:: | ||
Kevin Christen
|
r5787 | |||
Martin Geisler
|
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 | ||||
Kevin Christen
|
r5787 | |||
Martin Geisler
|
r9206 | # 'none' turns off all effects | ||
status.clean = none | ||||
status.copied = none | ||||
Kevin Christen
|
r5787 | |||
Martin Geisler
|
r9206 | qseries.applied = blue bold underline | ||
qseries.unapplied = black bold | ||||
qseries.missing = red bold | ||||
Brodie Rao
|
r7456 | |||
Martin Geisler
|
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 | ||||
diff.trailingwhitespace = bold red_background | ||||
David Soria Parra
|
r10046 | |||
Georg Brandl
|
r10223 | resolve.unresolved = red bold | ||
resolve.resolved = green bold | ||||
David Soria Parra
|
r10046 | bookmarks.current = green | ||
Steve Borho
|
r10870 | |||
Jeremy Whitlock
|
r11969 | branches.active = none | ||
branches.closed = black bold | ||||
branches.current = green | ||||
branches.inactive = none | ||||
Steve Borho
|
r10870 | The color extension will try to detect whether to use ANSI codes or | ||
Win32 console APIs, unless it is made explicit:: | ||||
[color] | ||||
mode = ansi | ||||
Any value other than 'ansi', 'win32', or 'auto' will disable color. | ||||
Kevin Christen
|
r5787 | ''' | ||
Brodie Rao
|
r12062 | import os | ||
Kevin Christen
|
r5787 | |||
Augie Fackler
|
r12089 | from mercurial import commands, dispatch, extensions, ui as uimod, util | ||
Kevin Christen
|
r5787 | from mercurial.i18n import _ | ||
# start and stop parameters for effects | ||||
Brodie Rao
|
r10826 | _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, | ||
'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1, | ||||
'italic': 3, 'underline': 4, 'inverse': 7, | ||||
'black_background': 40, 'red_background': 41, | ||||
'green_background': 42, 'yellow_background': 43, | ||||
'blue_background': 44, 'purple_background': 45, | ||||
'cyan_background': 46, 'white_background': 47} | ||||
_styles = {'grep.match': 'red bold', | ||||
Matt Mackall
|
r13361 | 'bookmarks.current': 'green', | ||
Jeremy Whitlock
|
r11969 | 'branches.active': 'none', | ||
'branches.closed': 'black bold', | ||||
'branches.current': 'green', | ||||
'branches.inactive': 'none', | ||||
Brodie Rao
|
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', | ||||
'diff.trailingwhitespace': 'bold red_background', | ||||
'diffstat.deleted': 'red', | ||||
'diffstat.inserted': 'green', | ||||
Martin Geisler
|
r13774 | 'ui.prompt': 'yellow', | ||
Brodie Rao
|
r10826 | 'log.changeset': 'yellow', | ||
'resolve.resolved': 'green bold', | ||||
'resolve.unresolved': 'red bold', | ||||
'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', | ||||
Steve Borho
|
r11310 | 'status.unknown': 'magenta bold underline'} | ||
Brodie Rao
|
r10826 | |||
Kevin Christen
|
r5787 | |||
Martin Geisler
|
r8622 | def render_effects(text, effects): | ||
Kevin Christen
|
r5787 | 'Wrap text in commands to turn on each effect.' | ||
Brodie Rao
|
r10826 | if not text: | ||
return text | ||||
start = [str(_effects[e]) for e in ['none'] + effects.split()] | ||||
Kevin Christen
|
r5787 | start = '\033[' + ';'.join(start) + 'm' | ||
Brodie Rao
|
r10826 | stop = '\033[' + str(_effects['none']) + 'm' | ||
Brodie Rao
|
r7459 | return ''.join([start, text, stop]) | ||
Kevin Christen
|
r5787 | |||
Brodie Rao
|
r10826 | def extstyles(): | ||
for name, ext in extensions.extensions(): | ||||
_styles.update(getattr(ext, 'colortable', {})) | ||||
Brodie Rao
|
r7456 | |||
Brodie Rao
|
r10826 | def configstyles(ui): | ||
for status, cfgeffects in ui.configitems('color'): | ||||
if '.' not in status: | ||||
continue | ||||
cfgeffects = ui.configlist('color', status) | ||||
if cfgeffects: | ||||
Greg Ward
|
r8945 | good = [] | ||
Brodie Rao
|
r10826 | for e in cfgeffects: | ||
if e in _effects: | ||||
Greg Ward
|
r8945 | good.append(e) | ||
else: | ||||
ui.warn(_("ignoring unknown color/effect %r " | ||||
"(configured in color.%s)\n") | ||||
Brodie Rao
|
r10826 | % (e, status)) | ||
_styles[status] = ' '.join(good) | ||||
Brodie Rao
|
r11555 | class colorui(uimod.ui): | ||
def popbuffer(self, labeled=False): | ||||
if labeled: | ||||
return ''.join(self.label(a, label) for a, label | ||||
in self._buffers.pop()) | ||||
return ''.join(a for a, label in self._buffers.pop()) | ||||
Brodie Rao
|
r10826 | |||
Brodie Rao
|
r11555 | _colormode = 'ansi' | ||
def write(self, *args, **opts): | ||||
label = opts.get('label', '') | ||||
if self._buffers: | ||||
self._buffers[-1].extend([(str(a), label) for a in args]) | ||||
elif self._colormode == 'win32': | ||||
for a in args: | ||||
Brodie Rao
|
r11727 | win32print(a, super(colorui, self).write, **opts) | ||
Brodie Rao
|
r11555 | else: | ||
return super(colorui, self).write( | ||||
*[self.label(str(a), label) for a in args], **opts) | ||||
Brodie Rao
|
r10826 | |||
Brodie Rao
|
r11555 | def write_err(self, *args, **opts): | ||
label = opts.get('label', '') | ||||
if self._colormode == 'win32': | ||||
for a in args: | ||||
Brodie Rao
|
r11727 | win32print(a, super(colorui, self).write_err, **opts) | ||
Brodie Rao
|
r11555 | else: | ||
Brodie Rao
|
r11732 | return super(colorui, self).write_err( | ||
Brodie Rao
|
r11555 | *[self.label(str(a), label) for a in args], **opts) | ||
Brodie Rao
|
r10826 | |||
Brodie Rao
|
r11555 | def label(self, msg, label): | ||
effects = [] | ||||
for l in label.split(): | ||||
s = _styles.get(l, '') | ||||
if s: | ||||
effects.append(s) | ||||
effects = ''.join(effects) | ||||
if effects: | ||||
return '\n'.join([render_effects(s, effects) | ||||
for s in msg.split('\n')]) | ||||
return msg | ||||
Brodie Rao
|
r10826 | |||
def uisetup(ui): | ||||
Steve Borho
|
r10871 | if ui.plain(): | ||
return | ||||
Steve Borho
|
r10870 | mode = ui.config('color', 'mode', 'auto') | ||
if mode == 'auto': | ||||
if os.name == 'nt' and 'TERM' not in os.environ: | ||||
# looks line a cmd.exe console, use win32 API or nothing | ||||
mode = w32effects and 'win32' or 'none' | ||||
else: | ||||
mode = 'ansi' | ||||
if mode == 'win32': | ||||
if w32effects is None: | ||||
# only warn if color.mode is explicitly set to win32 | ||||
Adrian Buehlmann
|
r13641 | ui.warn(_('warning: failed to set color mode to %s\n') % mode) | ||
Steve Borho
|
r10870 | return | ||
_effects.update(w32effects) | ||||
elif mode != 'ansi': | ||||
return | ||||
Brodie Rao
|
r10826 | def colorcmd(orig, ui_, opts, cmd, cmdfunc): | ||
Augie Fackler
|
r12089 | coloropt = opts['color'] | ||
auto = coloropt == 'auto' | ||||
always = util.parsebool(coloropt) | ||||
if (always or | ||||
(always is None and | ||||
(auto and (os.environ.get('TERM') != 'dumb' and ui_.formatted())))): | ||||
Brodie Rao
|
r11555 | colorui._colormode = mode | ||
colorui.__bases__ = (ui_.__class__,) | ||||
ui_.__class__ = colorui | ||||
Brodie Rao
|
r10826 | extstyles() | ||
Brodie Rao
|
r11555 | configstyles(ui_) | ||
Brodie Rao
|
r10826 | return orig(ui_, opts, cmd, cmdfunc) | ||
extensions.wrapfunction(dispatch, '_runcommand', colorcmd) | ||||
Brodie Rao
|
r12693 | def extsetup(ui): | ||
commands.globalopts.append( | ||||
('', 'color', 'auto', | ||||
Martin Geisler
|
r12807 | # i18n: 'always', 'auto', and 'never' are keywords and should | ||
# not be translated | ||||
Brodie Rao
|
r12693 | _("when to colorize (boolean, always, auto, or never)"), | ||
_('TYPE'))) | ||||
Steve Borho
|
r10870 | |||
Adrian Buehlmann
|
r13641 | if os.name != 'nt': | ||
w32effects = None | ||||
else: | ||||
import re, ctypes | ||||
_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 | ||||
Steve Borho
|
r10870 | |||
# http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx | ||||
w32effects = { | ||||
Sune Foldager
|
r12277 | 'none': -1, | ||
Steve Borho
|
r10870 | 'black': 0, | ||
Adrian Buehlmann
|
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, | ||||
Sune Foldager
|
r12278 | 'black_background': 0x100, # unused value > 0x0f | ||
Adrian Buehlmann
|
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 | ||||
Steve Borho
|
r10870 | } | ||
Adrian Buehlmann
|
r13641 | passthrough = set([_FOREGROUND_INTENSITY, | ||
_BACKGROUND_INTENSITY, | ||||
_COMMON_LVB_UNDERSCORE, | ||||
_COMMON_LVB_REVERSE_VIDEO]) | ||||
Sune Foldager
|
r12277 | |||
Adrian Buehlmann
|
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) | ||||
Steve Borho
|
r10870 | |||
def win32print(text, orig, **opts): | ||||
label = opts.get('label', '') | ||||
Sune Foldager
|
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) | ||||
Steve Borho
|
r10870 | |||
# determine console attributes based on labels | ||||
for l in label.split(): | ||||
style = _styles.get(l, '') | ||||
for effect in style.split(): | ||||
Sune Foldager
|
r12277 | attr = mapcolor(w32effects[effect], attr) | ||
Steve Borho
|
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) | ||||
while m: | ||||
for sattr in m.group(1).split(';'): | ||||
if sattr: | ||||
Sune Foldager
|
r12277 | attr = mapcolor(int(sattr), attr) | ||
Adrian Buehlmann
|
r13641 | _kernel32.SetConsoleTextAttribute(stdout, attr) | ||
Steve Borho
|
r10870 | orig(m.group(2), **opts) | ||
m = re.match(ansire, m.group(3)) | ||||
# Explicity reset original attributes | ||||
Adrian Buehlmann
|
r13641 | _kernel32.SetConsoleTextAttribute(stdout, origattr) | ||