[PATCH 3 of 4] color: automatically define 16 and 256 colors if supported

Gregory Szorc gregory.szorc at gmail.com
Sun Jul 9 19:46:16 EDT 2017


# HG changeset patch
# User Gregory Szorc <gregory.szorc at gmail.com>
# Date 1499641339 25200
#      Sun Jul 09 16:02:19 2017 -0700
# Node ID fa6223b9e2a0d9fbfa81329b83c0512417cee713
# Parent  98c54db9407a7e0ba94a632aafdbbec3fc76fa8b
color: automatically define 16 and 256 colors if supported

The "colors" terminfo capability returns the number of colors
supported by the terminal profile.

But with terminals, nothing is straightforward. The "colors"
capability only returns what the current terminal profile (likely
defined by $TERM) supports. The current terminal type or its profile
may be wrong (most likely the former). So, any consumer of this
capability needs to take this scenario into account.

This commit adds code for querying the "colors" terminfo capability
and for defining additional colors if 16 or 256 color support is
present. Because of the aforementioned limitations with capability
accuracy, a config option is introduced that allows the color count
to be limited. If the extra colors cause any problems, once can simply
set this option to restore existing 8 color behavior.

As part of this feature, a dictionary containing cherry-picked colors
from the 256 color spectrum was created. Choices are very subjective
and can and should be adjusted, as appropriate.

The help documentation for color support has been significantly
overhauled as part of this commit.

diff --git a/mercurial/color.py b/mercurial/color.py
--- a/mercurial/color.py
+++ b/mercurial/color.py
@@ -45,10 +45,43 @@ try:
         'white': curses.COLOR_WHITE,
     }
 
+    # Bright versions of the built-in colors are colors 8-15, in the same order.
+    TERMINFO_COLOR_16 = {
+        'brightblack': 8,
+        'brightred': 9,
+        'brightgreen': 10,
+        'brightyellow': 11,
+        'brightblue': 12,
+        'brightmagenta': 13,
+        'brightcyan': 14,
+        'brightwhite': 15,
+    }
+
+    # 6x6x6 color cube is 16-231. We define some common ones.
+    TERMINFO_COLOR_256 = {
+        'darkgreen': 28,
+        'turquoise': 45,
+        'lightgreen': 70,
+        'bluegreen': 73,
+        'deeppurple': 89,
+        'purple': 129,
+        'lightpurple': 177,
+        'lightorange': 178,
+        'lightyellow': 191,
+        'pink': 201,
+        'darkorange': 208,
+    }
+
+    # 232-255 is a continuous spectrum of grey.
+    for i, value in enumerate(range(232, 256)):
+        TERMINFO_COLOR_256['grey%02d' % (i + 1)] = value
+
 except ImportError:
     curses = None
     _baseterminfoparams = {}
     TERMINFO_COLOR_8 = {}
+    TERMINFO_COLOR_16 = {}
+    TERMINFO_COLOR_256 = {}
 
 _enabledbydefault = True
 
@@ -146,6 +179,43 @@ def _terminfocolors(ui):
     for color, value in TERMINFO_COLOR_8.items():
         colors[color] = (value, False)
 
+    # Add 16 and 256 bit colors if supported and allowed by config policy.
+    #
+    # There's no reliable way to detect if the extra colors will actually work.
+    # The terminfo database just reports what the current terminal is
+    # advertising. Some terminals support querying the value of a color via
+    # e.g. \e]4;%d;?\a. However, this isn't universally supported. So set
+    # colors based purely on terminfo and provide users a way to disable in
+    # case it doesn't work.
+
+    # Will likely return -1 on failure.
+    termcolors = curses.tigetnum('colors')
+    if termcolors < 8:
+        termcolors = 8
+
+    # Default is to allow as many colors as the terminal supports.
+    colorlimit = ui.configint('color', 'colorlimit', 256)
+
+    if colorlimit not in (8, 16, 256):
+        ui.warn(_('unsupported color.colorlimit value %d; use 8, 16, '
+                  'or 256\n') % colorlimit)
+        colorlimit = 8
+
+    usecolors = min(termcolors, colorlimit)
+
+    if usecolors >= 16:
+        for color, value in TERMINFO_COLOR_16.items():
+            colors[color] = (value, False)
+
+    if usecolors >= 256:
+        for color, value in TERMINFO_COLOR_256.items():
+            colors[color] = (value, False)
+
+    # TODO consider filtering color values by usecolors. This has 2
+    # implications: 1) users can't force colors beyond the supported
+    # color range (this arguably makes sense but is BC) 2) configstyles()
+    # emits a warning if there is a reference to this discarded color
+    # (it may be desirable to suppress that warning).
     for key, value in ui.configitems('color'):
         if key.startswith('color.'):
             colors[key[6:]] = (int(value), True)
@@ -186,6 +256,7 @@ def _terminfosetup(ui, mode):
             # noisy and use ui.debug().
             ui.debug("no terminfo entry for %s\n" % e)
             del ui._terminfoparams[key]
+
     if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
         # Only warn about missing terminfo entries if we explicitly asked for
         # terminfo mode.
diff --git a/mercurial/help/color.txt b/mercurial/help/color.txt
--- a/mercurial/help/color.txt
+++ b/mercurial/help/color.txt
@@ -131,19 +131,38 @@ effects may be overridden from your conf
 
   histedit.remaining = red bold
 
+Supported Colors
+================
+
+There are 8 standard colors: ``black``, ``red``, ``green``, ``yellow``,
+``blue``, ``magenta``, ``cyan``, and ``white``.
+
+If the ``terminfo`` color mode is being used and the terminfo database
+reports that the current terminal supports more colors, additional colors
+will be defined.
+
+If 16 colors are supported, the 8 additional colors are the 8 standard
+colors prefixed with "bright". e.g. ``brightred`` and ``brightyellow``.
+
+If 256 colors are supported, an assortment of additional colors are
+available. These include ``darkgreen``, ``turquoise``, ``purple``, and
+``pink``. For the list of all defined colors, run :hg:`debugcolor`.
+
+In some cases, not all colors will render properly. See
+:hg:`help config.color.colorlimit` for how to limit Mercurial to a smaller
+set of colors.
+
 Custom colors
 =============
 
-Because there are only eight standard colors, Mercurial allows you
-to define color names for other color slots which might be available
-for your terminal type, assuming terminfo mode.  For instance::
+When using ``terminfo`` mode, Mercurial supports defining colors via
+config options. You can either declare new colors or change the value
+for a built-in color. e.g.::
 
-  color.brightblue = 12
+  [color]
+  color.mydarkblue = 18
   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.
+To see what color values are supported and how they render, perform
+an Internet search for "xterm color cube."
diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -432,6 +432,22 @@ effect and style see :hg:`help color`.
     On some systems (such as MSYS in Windows), the terminal may support
     a different color mode than the pager program.
 
+``colorlimit``
+    Limits how many colors will be defined and used.
+
+    Most terminals support 8 standard colors. Mercurial will detect
+    and use additional colors if the terminal indicates support for them.
+
+    In some cases, the terminal advertises support for additional colors
+    but doesn't actually support them. This can lead to poor formatting
+    or even gibberish being printed.
+
+    Setting this option to a value of ``8``, ``16``, or ``256`` will
+    explicitly limit Mercurial to a maximum of that many colors.
+
+    This option has no effect unless the ``terminfo`` color mode is being
+    used.
+
 ``commands``
 ------------
 
diff --git a/tests/hgterm.ti b/tests/hgterm16.ti
copy from tests/hgterm.ti
copy to tests/hgterm16.ti
--- a/tests/hgterm.ti
+++ b/tests/hgterm16.ti
@@ -1,6 +1,6 @@
-hgterm,
+hgterm-16color,
 	am, km, mir, msgr, xenl,
-	colors#8, cols#80, it#8, lines#24, pairs#64,
+	colors#16, cols#80, it#8, lines#24, pairs#64,
 	acsc=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
 	bel=^G, bold=\E[1m, clear=\E[H\E[2J, cr=\r,
 	csr=\E[%i%p1%d;%p2%dr, cub=\E[%p1%dD, cub1=\b,
diff --git a/tests/hgterm.ti b/tests/hgterm256.ti
copy from tests/hgterm.ti
copy to tests/hgterm256.ti
--- a/tests/hgterm.ti
+++ b/tests/hgterm256.ti
@@ -1,6 +1,6 @@
-hgterm,
+hgterm-256color,
 	am, km, mir, msgr, xenl,
-	colors#8, cols#80, it#8, lines#24, pairs#64,
+	colors#256, cols#80, it#8, lines#24, pairs#64,
 	acsc=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
 	bel=^G, bold=\E[1m, clear=\E[H\E[2J, cr=\r,
 	csr=\E[%i%p1%d;%p2%dr, cub=\E[%p1%dD, cub1=\b,
diff --git a/tests/test-status-color.t b/tests/test-status-color.t
--- a/tests/test-status-color.t
+++ b/tests/test-status-color.t
@@ -226,6 +226,9 @@ hg status -A (with terminfo color):
 
   $ mkdir "$TESTTMP/terminfo"
   $ TERMINFO="$TESTTMP/terminfo" tic "$TESTDIR/hgterm.ti"
+  $ TERMINFO="$TESTTMP/terminfo" tic "$TESTDIR/hgterm16.ti"
+  $ TERMINFO="$TESTTMP/terminfo" tic "$TESTDIR/hgterm256.ti"
+
   $ TERM=hgterm TERMINFO="$TESTTMP/terminfo" hg status --config color.mode=terminfo -A
   \x1b[30m\x1b[32m\x1b[1mA \x1b[30m\x1b[30m\x1b[32m\x1b[1madded\x1b[30m (esc)
   \x1b[30m\x1b[32m\x1b[1mA \x1b[30m\x1b[30m\x1b[32m\x1b[1mcopied\x1b[30m (esc)
@@ -256,6 +259,58 @@ The user can define effects with raw ter
   \x1b[30m\x1b[88mC \x1b[30m\x1b[30m\x1b[88m.hgignore\x1b[30m (esc)
   \x1b[30m\x1b[88mC \x1b[30m\x1b[30m\x1b[88mmodified\x1b[30m (esc)
 
+A 16 and 256 colors won't be used if the terminal doesn't advertise support
+
+  $ TERM=hgterm TERMINFO="$TESTTMP/terminfo" hg status -a --config color.mode=terminfo --config color.status.added=brightred
+  ignoring unknown color/effect 'brightred' (configured in color.status.added)
+  ignoring unknown color/effect 'brightred' (configured in color.status.added)
+  ignoring unknown color/effect 'brightred' (configured in color.status.added)
+  A added
+  A copied
+
+  $ TERM=hgterm TERMINFO="$TESTTMP/terminfo" hg status -a --config color.mode=terminfo --config color.status.added=purple
+  ignoring unknown color/effect 'purple' (configured in color.status.added)
+  ignoring unknown color/effect 'purple' (configured in color.status.added)
+  ignoring unknown color/effect 'purple' (configured in color.status.added)
+  A added
+  A copied
+
+A 16 color can be used if terminal advertises 16 color support
+
+  $ TERM=hgterm-16color TERMINFO="$TESTTMP/terminfo" hg status -a --config color.mode=terminfo --config color.status.added=brightred
+  \x1b[30m\x1b[39mA \x1b[30m\x1b[30m\x1b[39madded\x1b[30m (esc)
+  \x1b[30m\x1b[39mA \x1b[30m\x1b[30m\x1b[39mcopied\x1b[30m (esc)
+
+A 256 color can be used if terminal advertises 256 color support
+
+  $ TERM=hgterm-256color TERMINFO="$TESTTMP/terminfo" hg status -a --config color.mode=terminfo --config color.status.added=purple
+  \x1b[30m\x1b[3129mA \x1b[30m\x1b[30m\x1b[3129madded\x1b[30m (esc)
+  \x1b[30m\x1b[3129mA \x1b[30m\x1b[30m\x1b[3129mcopied\x1b[30m (esc)
+
+color.colorlimit can narrow allowed color range
+
+  $ TERM=hgterm-256color TERMINFO="$TESTTMP/terminfo" hg status -a --config color.mode=terminfo --config color.status.added=purple --config color.colorlimit=8
+  ignoring unknown color/effect 'purple' (configured in color.status.added)
+  ignoring unknown color/effect 'purple' (configured in color.status.added)
+  ignoring unknown color/effect 'purple' (configured in color.status.added)
+  A added
+  A copied
+  $ TERM=hgterm-256color TERMINFO="$TESTTMP/terminfo" hg status -a --config color.mode=terminfo --config color.status.added=purple --config color.colorlimit=16
+  ignoring unknown color/effect 'purple' (configured in color.status.added)
+  ignoring unknown color/effect 'purple' (configured in color.status.added)
+  ignoring unknown color/effect 'purple' (configured in color.status.added)
+  A added
+  A copied
+
+Invalid color.colorlimit is normalized to 8
+
+  $ TERM=hgterm-256color TERMINFO="$TESTTMP/terminfo" hg status -a --config color.mode=terminfo --config color.colorlimit=128
+  unsupported color.colorlimit value 128; use 8, 16, or 256
+  unsupported color.colorlimit value 128; use 8, 16, or 256
+  unsupported color.colorlimit value 128; use 8, 16, or 256
+  \x1b[30m\x1b[32m\x1b[2mA \x1b[30m\x1b[30m\x1b[32m\x1b[2madded\x1b[30m (esc)
+  \x1b[30m\x1b[32m\x1b[2mA \x1b[30m\x1b[30m\x1b[32m\x1b[2mcopied\x1b[30m (esc)
+
 #endif
 
 


More information about the Mercurial-devel mailing list