[PATCH] color: add support for terminfo-based attributes and color

Brodie Rao brodie at bitheap.org
Sat Mar 19 00:49:43 CDT 2011


On Mar 14, 2011, at 4:02 PM, Danek Duvall wrote:

> # HG changeset patch
> # User Danek Duvall <duvall at comfychair.org>
> # Date 1300143477 25200
> # Node ID 136b5e89112caf2483cd31cfa107063904577aa4
> # Parent  d943efb9701f088313de17f6c32cad22928b96c7
> color: add support for terminfo-based attributes and color
>
> Using terminfo instead of hard-coding ECMA-48 control sequences  
> provides a
> greater assurance that the terminal codes are correct for the current
> terminal type; not everything supports the ANSI escape codes.
>
> It also allows us to use a wider range of colors when a terminal  
> emulator
> supports it (such as 16- or 256-color xterm), and a few more non-color
> attributes, such as the ever-popular blink.

Your revised patch looks good.

Does this affect the test suite at all? Does the test runner need to  
default to using ansi mode? Could we add an hghave for curses/terminfo  
and test that? Or would that be too inconsistent/unreliable (with  
varying term databases across systems)?

> diff --git a/hgext/color.py b/hgext/color.py
> --- a/hgext/color.py
> +++ b/hgext/color.py
> @@ -25,7 +25,9 @@ diff-related commands to highlight addit
> and trailing whitespace.
>
> Other effects in addition to color, like bold and underlined text, are
> -also available. Effects are rendered with the ECMA-48 SGR control
> +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
> function (aka ANSI escape codes).
>
> Default effects may be overridden from your configuration file::
> @@ -66,13 +68,35 @@ Default effects may be overridden from y
>   branches.current = green
>   branches.inactive = none
>
> -The color extension will try to detect whether to use ANSI codes or
> -Win32 console APIs, unless it is made explicit::
> +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.
> +
> +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.
> +
> +The color extension will try to detect whether to use terminfo, ANSI
> +codes or Win32 console APIs, unless it is made explicit; e.g.::
>
>   [color]
>   mode = ansi
>
> -Any value other than 'ansi', 'win32', or 'auto' will disable color.
> +Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
> +disable color.
>
> '''
>
> @@ -90,6 +114,63 @@ _effects = {'none': 0, 'black': 30, 'red
>             'blue_background': 44, 'purple_background': 45,
>             'cyan_background': 46, 'white_background': 47}
>
> +def _terminfosetup(ui):
> +    '''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.
> +    mode = ui.config('color', 'mode', 'auto')
> +    if mode not in ('auto', 'terminfo'):
> +        return
> +
> +    _terminfo_params.update(dict((
> +        (key[6:], (False, int(val)))
> +        for key, val in ui.configitems('color')
> +        if key.startswith('color.')
> +    )))
> +
> +    curses.setupterm()
> +    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'):
> +        ui.warn(_("no terminfo entry for setab/setaf: reverting to "
> +          "ECMA-48 color\n"))
> +        _terminfo_params = {}
> +
> +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:
> +    _terminfo_params = False
> +
> _styles = {'grep.match': 'red bold',
>            'bookmarks.current': 'green',
>            'branches.active': 'none',
> @@ -120,13 +201,33 @@ _styles = {'grep.match': 'red bold',
>            'status.unknown': 'magenta bold underline'}
>
>
> +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)
> +
> def render_effects(text, effects):
>     'Wrap text in commands to turn on each effect.'
>     if not text:
>         return text
> -    start = [str(_effects[e]) for e in ['none'] + effects.split()]
> -    start = '\033[' + ';'.join(start) + 'm'
> -    stop = '\033[' + str(_effects['none']) + 'm'
> +    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')
>     return ''.join([start, text, stop])
>
> def extstyles():
> @@ -135,13 +236,15 @@ def extstyles():
>
> def configstyles(ui):
>     for status, cfgeffects in ui.configitems('color'):
> -        if '.' not in status:
> +        if '.' not in status or status.startswith('color.'):
>             continue
>         cfgeffects = ui.configlist('color', status)
>         if cfgeffects:
>             good = []
>             for e in cfgeffects:
> -                if e in _effects:
> +                if not _terminfo_params and e in _effects:
> +                    good.append(e)
> +                elif e in _terminfo_params or e[:-11] in  
> _terminfo_params:
>                     good.append(e)
>                 else:
>                     ui.warn(_("ignoring unknown color/effect %r "
> @@ -199,14 +302,18 @@ def uisetup(ui):
>             # looks line a cmd.exe console, use win32 API or nothing
>             mode = w32effects and 'win32' or 'none'
>         else:
> -            mode = 'ansi'
> +            _terminfosetup(ui)
> +            if not _terminfo_params:
> +                mode = 'ansi'
> +            else:
> +                mode = 'terminfo'
>     if mode == 'win32':
>         if w32effects is None:
>             # only warn if color.mode is explicitly set to win32
>             ui.warn(_('warning: failed to set color mode to %s\n') %  
> mode)
>             return
>         _effects.update(w32effects)
> -    elif mode != 'ansi':
> +    elif mode not in ('ansi', 'terminfo'):
>         return
>     def colorcmd(orig, ui_, opts, cmd, cmdfunc):
>         coloropt = opts['color']
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel



More information about the Mercurial-devel mailing list