eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/hgext/color.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # color.py color output for the status and qseries commands
       
     2 #
       
     3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
       
     4 #
       
     5 # This program is free software; you can redistribute it and/or modify it
       
     6 # under the terms of the GNU General Public License as published by the
       
     7 # Free Software Foundation; either version 2 of the License, or (at your
       
     8 # option) any later version.
       
     9 #
       
    10 # This program is distributed in the hope that it will be useful, but
       
    11 # WITHOUT ANY WARRANTY; without even the implied warranty of
       
    12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
       
    13 # Public License for more details.
       
    14 #
       
    15 # You should have received a copy of the GNU General Public License along
       
    16 # with this program; if not, write to the Free Software Foundation, Inc.,
       
    17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18 
       
    19 '''colorize output from some commands
       
    20 
       
    21 This extension modifies the status and resolve commands to add color to their
       
    22 output to reflect file status, the qseries command to add color to reflect
       
    23 patch status (applied, unapplied, missing), and to diff-related
       
    24 commands to highlight additions, removals, diff headers, and trailing
       
    25 whitespace.
       
    26 
       
    27 Other effects in addition to color, like bold and underlined text, are
       
    28 also available. Effects are rendered with the ECMA-48 SGR control
       
    29 function (aka ANSI escape codes). This module also provides the
       
    30 render_text function, which can be used to add effects to any text.
       
    31 
       
    32 Default effects may be overridden from your configuration file::
       
    33 
       
    34   [color]
       
    35   status.modified = blue bold underline red_background
       
    36   status.added = green bold
       
    37   status.removed = red bold blue_background
       
    38   status.deleted = cyan bold underline
       
    39   status.unknown = magenta bold underline
       
    40   status.ignored = black bold
       
    41 
       
    42   # 'none' turns off all effects
       
    43   status.clean = none
       
    44   status.copied = none
       
    45 
       
    46   qseries.applied = blue bold underline
       
    47   qseries.unapplied = black bold
       
    48   qseries.missing = red bold
       
    49 
       
    50   diff.diffline = bold
       
    51   diff.extended = cyan bold
       
    52   diff.file_a = red bold
       
    53   diff.file_b = green bold
       
    54   diff.hunk = magenta
       
    55   diff.deleted = red
       
    56   diff.inserted = green
       
    57   diff.changed = white
       
    58   diff.trailingwhitespace = bold red_background
       
    59 
       
    60   resolve.unresolved = red bold
       
    61   resolve.resolved = green bold
       
    62 
       
    63   bookmarks.current = green
       
    64 
       
    65   branches.active = none
       
    66   branches.closed = black bold
       
    67   branches.current = green
       
    68   branches.inactive = none
       
    69 
       
    70 The color extension will try to detect whether to use ANSI codes or
       
    71 Win32 console APIs, unless it is made explicit::
       
    72 
       
    73   [color]
       
    74   mode = ansi
       
    75 
       
    76 Any value other than 'ansi', 'win32', or 'auto' will disable color.
       
    77 
       
    78 '''
       
    79 
       
    80 import os
       
    81 
       
    82 from mercurial import commands, dispatch, extensions, ui as uimod, util
       
    83 from mercurial.i18n import _
       
    84 
       
    85 # start and stop parameters for effects
       
    86 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
       
    87             'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
       
    88             'italic': 3, 'underline': 4, 'inverse': 7,
       
    89             'black_background': 40, 'red_background': 41,
       
    90             'green_background': 42, 'yellow_background': 43,
       
    91             'blue_background': 44, 'purple_background': 45,
       
    92             'cyan_background': 46, 'white_background': 47}
       
    93 
       
    94 _styles = {'grep.match': 'red bold',
       
    95            'branches.active': 'none',
       
    96            'branches.closed': 'black bold',
       
    97            'branches.current': 'green',
       
    98            'branches.inactive': 'none',
       
    99            'diff.changed': 'white',
       
   100            'diff.deleted': 'red',
       
   101            'diff.diffline': 'bold',
       
   102            'diff.extended': 'cyan bold',
       
   103            'diff.file_a': 'red bold',
       
   104            'diff.file_b': 'green bold',
       
   105            'diff.hunk': 'magenta',
       
   106            'diff.inserted': 'green',
       
   107            'diff.trailingwhitespace': 'bold red_background',
       
   108            'diffstat.deleted': 'red',
       
   109            'diffstat.inserted': 'green',
       
   110            'log.changeset': 'yellow',
       
   111            'resolve.resolved': 'green bold',
       
   112            'resolve.unresolved': 'red bold',
       
   113            'status.added': 'green bold',
       
   114            'status.clean': 'none',
       
   115            'status.copied': 'none',
       
   116            'status.deleted': 'cyan bold underline',
       
   117            'status.ignored': 'black bold',
       
   118            'status.modified': 'blue bold',
       
   119            'status.removed': 'red bold',
       
   120            'status.unknown': 'magenta bold underline'}
       
   121 
       
   122 
       
   123 def render_effects(text, effects):
       
   124     'Wrap text in commands to turn on each effect.'
       
   125     if not text:
       
   126         return text
       
   127     start = [str(_effects[e]) for e in ['none'] + effects.split()]
       
   128     start = '\033[' + ';'.join(start) + 'm'
       
   129     stop = '\033[' + str(_effects['none']) + 'm'
       
   130     return ''.join([start, text, stop])
       
   131 
       
   132 def extstyles():
       
   133     for name, ext in extensions.extensions():
       
   134         _styles.update(getattr(ext, 'colortable', {}))
       
   135 
       
   136 def configstyles(ui):
       
   137     for status, cfgeffects in ui.configitems('color'):
       
   138         if '.' not in status:
       
   139             continue
       
   140         cfgeffects = ui.configlist('color', status)
       
   141         if cfgeffects:
       
   142             good = []
       
   143             for e in cfgeffects:
       
   144                 if e in _effects:
       
   145                     good.append(e)
       
   146                 else:
       
   147                     ui.warn(_("ignoring unknown color/effect %r "
       
   148                               "(configured in color.%s)\n")
       
   149                             % (e, status))
       
   150             _styles[status] = ' '.join(good)
       
   151 
       
   152 class colorui(uimod.ui):
       
   153     def popbuffer(self, labeled=False):
       
   154         if labeled:
       
   155             return ''.join(self.label(a, label) for a, label
       
   156                            in self._buffers.pop())
       
   157         return ''.join(a for a, label in self._buffers.pop())
       
   158 
       
   159     _colormode = 'ansi'
       
   160     def write(self, *args, **opts):
       
   161         label = opts.get('label', '')
       
   162         if self._buffers:
       
   163             self._buffers[-1].extend([(str(a), label) for a in args])
       
   164         elif self._colormode == 'win32':
       
   165             for a in args:
       
   166                 win32print(a, super(colorui, self).write, **opts)
       
   167         else:
       
   168             return super(colorui, self).write(
       
   169                 *[self.label(str(a), label) for a in args], **opts)
       
   170 
       
   171     def write_err(self, *args, **opts):
       
   172         label = opts.get('label', '')
       
   173         if self._colormode == 'win32':
       
   174             for a in args:
       
   175                 win32print(a, super(colorui, self).write_err, **opts)
       
   176         else:
       
   177             return super(colorui, self).write_err(
       
   178                 *[self.label(str(a), label) for a in args], **opts)
       
   179 
       
   180     def label(self, msg, label):
       
   181         effects = []
       
   182         for l in label.split():
       
   183             s = _styles.get(l, '')
       
   184             if s:
       
   185                 effects.append(s)
       
   186         effects = ''.join(effects)
       
   187         if effects:
       
   188             return '\n'.join([render_effects(s, effects)
       
   189                               for s in msg.split('\n')])
       
   190         return msg
       
   191 
       
   192 
       
   193 def uisetup(ui):
       
   194     if ui.plain():
       
   195         return
       
   196     mode = ui.config('color', 'mode', 'auto')
       
   197     if mode == 'auto':
       
   198         if os.name == 'nt' and 'TERM' not in os.environ:
       
   199             # looks line a cmd.exe console, use win32 API or nothing
       
   200             mode = w32effects and 'win32' or 'none'
       
   201         else:
       
   202             mode = 'ansi'
       
   203     if mode == 'win32':
       
   204         if w32effects is None:
       
   205             # only warn if color.mode is explicitly set to win32
       
   206             ui.warn(_('win32console not found, please install pywin32\n'))
       
   207             return
       
   208         _effects.update(w32effects)
       
   209     elif mode != 'ansi':
       
   210         return
       
   211     def colorcmd(orig, ui_, opts, cmd, cmdfunc):
       
   212         coloropt = opts['color']
       
   213         auto = coloropt == 'auto'
       
   214         always = util.parsebool(coloropt)
       
   215         if (always or
       
   216             (always is None and
       
   217              (auto and (os.environ.get('TERM') != 'dumb' and ui_.formatted())))):
       
   218             colorui._colormode = mode
       
   219             colorui.__bases__ = (ui_.__class__,)
       
   220             ui_.__class__ = colorui
       
   221             extstyles()
       
   222             configstyles(ui_)
       
   223         return orig(ui_, opts, cmd, cmdfunc)
       
   224     extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
       
   225 
       
   226 def extsetup(ui):
       
   227     commands.globalopts.append(
       
   228         ('', 'color', 'auto',
       
   229          # i18n: 'always', 'auto', and 'never' are keywords and should
       
   230          # not be translated
       
   231          _("when to colorize (boolean, always, auto, or never)"),
       
   232          _('TYPE')))
       
   233 
       
   234 try:
       
   235     import re, pywintypes, win32console as win32c
       
   236 
       
   237     # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
       
   238     w32effects = {
       
   239         'none': -1,
       
   240         'black': 0,
       
   241         'red': win32c.FOREGROUND_RED,
       
   242         'green': win32c.FOREGROUND_GREEN,
       
   243         'yellow': win32c.FOREGROUND_RED | win32c.FOREGROUND_GREEN,
       
   244         'blue': win32c.FOREGROUND_BLUE,
       
   245         'magenta': win32c.FOREGROUND_BLUE | win32c.FOREGROUND_RED,
       
   246         'cyan': win32c.FOREGROUND_BLUE | win32c.FOREGROUND_GREEN,
       
   247         'white': (win32c.FOREGROUND_RED | win32c.FOREGROUND_GREEN |
       
   248                   win32c.FOREGROUND_BLUE),
       
   249         'bold': win32c.FOREGROUND_INTENSITY,
       
   250         'black_background': 0x100,                  # unused value > 0x0f
       
   251         'red_background': win32c.BACKGROUND_RED,
       
   252         'green_background': win32c.BACKGROUND_GREEN,
       
   253         'yellow_background': win32c.BACKGROUND_RED | win32c.BACKGROUND_GREEN,
       
   254         'blue_background': win32c.BACKGROUND_BLUE,
       
   255         'purple_background': win32c.BACKGROUND_BLUE | win32c.BACKGROUND_RED,
       
   256         'cyan_background': win32c.BACKGROUND_BLUE | win32c.BACKGROUND_GREEN,
       
   257         'white_background': (win32c.BACKGROUND_RED | win32c.BACKGROUND_GREEN |
       
   258                              win32c.BACKGROUND_BLUE),
       
   259         'bold_background': win32c.BACKGROUND_INTENSITY,
       
   260         'underline': win32c.COMMON_LVB_UNDERSCORE,  # double-byte charsets only
       
   261         'inverse': win32c.COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
       
   262     }
       
   263 
       
   264     passthrough = set([win32c.FOREGROUND_INTENSITY,
       
   265                        win32c.BACKGROUND_INTENSITY,
       
   266                        win32c.COMMON_LVB_UNDERSCORE,
       
   267                        win32c.COMMON_LVB_REVERSE_VIDEO])
       
   268 
       
   269     try:
       
   270         stdout = win32c.GetStdHandle(win32c.STD_OUTPUT_HANDLE)
       
   271         if stdout is None:
       
   272             raise ImportError()
       
   273         origattr = stdout.GetConsoleScreenBufferInfo()['Attributes']
       
   274     except pywintypes.error:
       
   275         # stdout may be defined but not support
       
   276         # GetConsoleScreenBufferInfo(), when called from subprocess or
       
   277         # redirected.
       
   278         raise ImportError()
       
   279     ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)', re.MULTILINE | re.DOTALL)
       
   280 
       
   281     def win32print(text, orig, **opts):
       
   282         label = opts.get('label', '')
       
   283         attr = origattr
       
   284 
       
   285         def mapcolor(val, attr):
       
   286             if val == -1:
       
   287                 return origattr
       
   288             elif val in passthrough:
       
   289                 return attr | val
       
   290             elif val > 0x0f:
       
   291                 return (val & 0x70) | (attr & 0x8f)
       
   292             else:
       
   293                 return (val & 0x07) | (attr & 0xf8)
       
   294 
       
   295         # determine console attributes based on labels
       
   296         for l in label.split():
       
   297             style = _styles.get(l, '')
       
   298             for effect in style.split():
       
   299                 attr = mapcolor(w32effects[effect], attr)
       
   300 
       
   301         # hack to ensure regexp finds data
       
   302         if not text.startswith('\033['):
       
   303             text = '\033[m' + text
       
   304 
       
   305         # Look for ANSI-like codes embedded in text
       
   306         m = re.match(ansire, text)
       
   307         while m:
       
   308             for sattr in m.group(1).split(';'):
       
   309                 if sattr:
       
   310                     attr = mapcolor(int(sattr), attr)
       
   311             stdout.SetConsoleTextAttribute(attr)
       
   312             orig(m.group(2), **opts)
       
   313             m = re.match(ansire, m.group(3))
       
   314 
       
   315         # Explicity reset original attributes
       
   316         stdout.SetConsoleTextAttribute(origattr)
       
   317 
       
   318 except ImportError:
       
   319     w32effects = None