color.py
374 lines
| 12.3 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. | ||||
|
r30176 | If the terminfo entry for your terminal is missing codes for an effect | ||
or has the wrong codes, you can add or override those codes in your | ||||
configuration:: | ||||
[color] | ||||
terminfo.dim = \E[2m | ||||
where '\E' is substituted with an escape character. | ||||
|
r22772 | 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 | |||
|
r30968 | try: | ||
import curses | ||||
curses.COLOR_BLACK # force import | ||||
except ImportError: | ||||
curses = None | ||||
|
r29205 | from mercurial.i18n import _ | ||
|
r28968 | from mercurial import ( | ||
cmdutil, | ||||
|
r30652 | color, | ||
|
r28968 | commands, | ||
dispatch, | ||||
|
r30638 | encoding, | ||
|
r28968 | extensions, | ||
|
r30640 | pycompat, | ||
|
r28968 | 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 | |||
|
r14495 | def _terminfosetup(ui, mode): | ||
|
r13987 | '''Initialize terminfo data and the terminal if we're in terminfo mode.''' | ||
# If we failed to load curses, we go ahead and return. | ||||
|
r31068 | if curses is None: | ||
|
r13987 | return | ||
# Otherwise, see what the config file says. | ||||
if mode not in ('auto', 'terminfo'): | ||||
return | ||||
|
r31069 | for key, val in ui.configitems('color'): | ||
if key.startswith('color.'): | ||||
newval = (False, int(val), '') | ||||
color._terminfo_params[key[6:]] = newval | ||||
|
r31070 | elif key.startswith('terminfo.'): | ||
|
r31069 | newval = (True, '', val.replace('\\E', '\x1b')) | ||
color._terminfo_params[key[9:]] = newval | ||||
|
r13987 | try: | ||
curses.setupterm() | ||||
|
r25660 | except curses.error as e: | ||
|
r30968 | color._terminfo_params.clear() | ||
|
r13987 | return | ||
|
r30968 | for key, (b, e, c) in color._terminfo_params.items(): | ||
|
r13987 | if not b: | ||
continue | ||||
|
r30173 | if not c and not curses.tigetstr(e): | ||
|
r13987 | # 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) | ||||
|
r30968 | del color._terminfo_params[key] | ||
|
r13987 | 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")) | ||||
|
r30968 | color._terminfo_params.clear() | ||
|
r13987 | |||
|
r19297 | def _modesetup(ui, coloropt): | ||
|
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 | ||||
|
r30638 | formatted = (always or (encoding.environ.get('TERM') != 'dumb' | ||
and ui.formatted())) | ||||
|
r14495 | |||
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': | ||||
|
r30640 | if pycompat.osname == 'nt': | ||
|
r30638 | term = encoding.environ.get('TERM') | ||
|
r24028 | # 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. | ||||
|
r31067 | if (term and 'xterm' in term) or not color.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 | ||||
|
r30367 | # a formatted terminal | ||
if mode == realmode and ui.formatted(): | ||||
|
r24266 | ui.warn(_('warning: failed to set color mode to %s\n') % mode) | ||
|
r14495 | if realmode == 'win32': | ||
|
r30968 | color._terminfo_params.clear() | ||
|
r31067 | if not color.w32effects: | ||
|
r24266 | modewarn() | ||
|
r14495 | return None | ||
|
r31067 | color._effects.update(color.w32effects) | ||
|
r14495 | elif realmode == 'ansi': | ||
|
r30968 | color._terminfo_params.clear() | ||
|
r14495 | elif realmode == 'terminfo': | ||
_terminfosetup(ui, mode) | ||||
|
r30968 | if not color._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 | ||||
|
r11555 | class colorui(uimod.ui): | ||
|
r31094 | pass | ||
|
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': | ||
|
r30971 | color.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 | |||
|
r30286 | @command('debugcolor', | ||
[('', 'style', None, _('show all configured styles'))], | ||||
'hg debugcolor') | ||||
|
r20455 | def debugcolor(ui, repo, **opts): | ||
|
r30286 | """show available color, effects or style""" | ||
|
r30284 | ui.write(('color mode: %s\n') % ui._colormode) | ||
|
r30286 | if opts.get('style'): | ||
return _debugdisplaystyle(ui) | ||||
else: | ||||
return _debugdisplaycolor(ui) | ||||
|
r30284 | |||
def _debugdisplaycolor(ui): | ||||
|
r30652 | oldstyle = color._styles.copy() | ||
|
r30283 | try: | ||
|
r30652 | color._styles.clear() | ||
|
r30967 | for effect in color._effects.keys(): | ||
|
r30652 | color._styles[effect] = effect | ||
|
r30968 | if color._terminfo_params: | ||
|
r30283 | for k, v in ui.configitems('color'): | ||
if k.startswith('color.'): | ||||
|
r30652 | color._styles[k] = k[6:] | ||
|
r30283 | elif k.startswith('terminfo.'): | ||
|
r30652 | color._styles[k] = k[9:] | ||
|
r30283 | ui.write(_('available colors:\n')) | ||
|
r30285 | # sort label with a '_' after the other to group '_background' entry. | ||
|
r30652 | items = sorted(color._styles.items(), | ||
|
r30285 | key=lambda i: ('_' in i[0], i[0], i[1])) | ||
for colorname, label in items: | ||||
|
r30283 | ui.write(('%s\n') % colorname, label=label) | ||
finally: | ||||
|
r30652 | color._styles.clear() | ||
color._styles.update(oldstyle) | ||||
|
r20455 | |||
|
r30286 | def _debugdisplaystyle(ui): | ||
ui.write(_('available style:\n')) | ||||
|
r30652 | width = max(len(s) for s in color._styles) | ||
for label, effects in sorted(color._styles.items()): | ||||
|
r30286 | ui.write('%s' % label, label=label) | ||
if effects: | ||||
# 50 | ||||
ui.write(': ') | ||||
ui.write(' ' * (max(0, width - len(label)))) | ||||
ui.write(', '.join(ui.label(e, e) for e in effects.split())) | ||||
ui.write('\n') | ||||