[PATCH 1 of 1] diff colorization, --color switch

Brodie Rao dackze at gmail.com
Tue Nov 25 22:22:24 CST 2008


# HG changeset patch
# User Brodie Rao <me+hg at dackz.net>
# Date 1227672871 18000
# Node ID a72e98a589f5529852d8d7176637fb1569050f2c
# Parent  efe31fbe6cf0d3fd2949fc2a74e66a7a2d42595a
diff colorization, --color switch

This colorizes diff, qdiff, log -p, outgoing -p, incoming -p, and tip -p.
--color works like GNU grep --color. --color=auto only colorizes output
for non-dumb terminals that are TTYs.

diff -r efe31fbe6cf0 -r a72e98a589f5 hgext/color.py
--- a/hgext/color.py	Tue Nov 25 18:32:44 2008 -0800
+++ b/hgext/color.py	Tue Nov 25 23:14:31 2008 -0500
@@ -16,15 +16,16 @@
 # with this program; if not, write to the Free Software Foundation, Inc.,
 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
-'''add color output to the status and qseries commands
+'''add color output to status, qseries, and diff-related commands
 
 This extension modifies the status command to add color to its output to
-reflect file status, and the qseries command to add color to reflect patch
-status (applied, unapplied, missing).  Other effects in addition to color,
-like bold and underlined text, are also available.  Effects are rendered
-with the ECMA-48 SGR control function (aka ANSI escape codes).  This module
-also provides the render_text function, which can be used to add effects to
-any text.
+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.  Other effects in
+addition to color, like bold and underlined text, are also available.
+Effects are rendered with the ECMA-48 SGR control function (aka ANSI escape
+codes).  This module also provides the render_text function, which can be
+used to add effects to any text.
 
 To enable this extension, add this to your .hgrc file:
 [extensions]
@@ -47,11 +48,21 @@
 qseries.applied = blue bold underline
 qseries.unapplied = black bold
 qseries.missing = red bold
+
+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.whitespace = bold red_background
 '''
 
-import re, sys
+import os, re, sys
 
-from mercurial import commands, extensions
+from mercurial import cmdutil, commands, extensions
 from mercurial.i18n import _
 
 # start and stop parameters for effects
@@ -113,7 +124,7 @@
         effects = _status_effects[status]
         if effects:
             lines[i] = render_effects(lines[i], *effects)
-        sys.stdout.write(lines[i] + delimiter)
+        ui.write(lines[i] + delimiter)
     return retval
 
 _status_abbreviations = { 'M': 'modified',
@@ -154,30 +165,109 @@
             effects = _patch_effects['applied']
         else:
             effects = _patch_effects['unapplied']
-        sys.stdout.write(render_effects(patch, *effects) + '\n')
+        ui.write(render_effects(patch, *effects) + '\n')
     return retval
 
 _patch_effects = { 'applied': ('blue', 'bold', 'underline'),
                    'missing': ('red', 'bold'),
                    'unapplied': ('black', 'bold'), }
 
+def _color_wrapper(orig, s):
+    lines = s.split('\n')
+    for i, line in enumerate(lines):
+        for prefix, style in _diff_prefixes:
+            if line.startswith(prefix):
+                effects = _diff_effects[style]
+                lines[i] = render_effects(line, *_diff_effects[style])
+                break
+    orig('\n'.join(lines))
+
+def showpatch_color(orig, self, node):
+    old_write = extensions.wrapfunction(self.ui, 'write', _color_wrapper)
+    try:
+        orig(self, node)
+    finally:
+        self.ui.write = old_write
+
+def colordiff(orig, ui, repo, *pats, **opts):
+    '''run the diff command with colored output'''
+
+    old_write = extensions.wrapfunction(ui, 'write', _color_wrapper)
+    try:
+        orig(ui, repo, *pats, **opts)
+    finally:
+        ui.write = old_write
+
+_diff_prefixes = [ ('diff', 'diffline'),
+                   ('copy', 'extended'),
+                   ('rename', 'extended'),
+                   ('new', 'extended'),
+                   ('deleted', 'extended'),
+                   ('---', 'file_a'),
+                   ('+++', 'file_b'),
+                   ('@', 'hunk'),
+                   ('-', 'deleted'),
+                   ('+', 'inserted'), ]
+
+_diff_effects = { 'diffline': ('bold', ),
+                  'extended': ('cyan', 'bold'),
+                  'file_a': ('red', 'bold'),
+                  'file_b': ('green', 'bold'),
+                  'hunk': ('magenta', ),
+                  'deleted': ('red', ),
+                  'inserted': ('green', ),
+                  'changed': ('white', ), }
+
 def uisetup(ui):
     '''Initialize the extension.'''
+    _setupcmd(ui, 'diff', commands.table, colordiff, _diff_effects)
+    _setupcmd(ui, 'incoming', commands.table, None, _diff_effects)
+    _setupcmd(ui, 'log', commands.table, None, _diff_effects)
+    _setupcmd(ui, 'outgoing', commands.table, None, _diff_effects)
+    _setupcmd(ui, 'tip', commands.table, None, _diff_effects)
+    _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects)
     _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects)
     if ui.config('extensions', 'hgext.mq') is not None or \
             ui.config('extensions', 'mq') is not None:
         from hgext import mq
+        _setupcmd(ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects)
         _setupcmd(ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects)
 
 def _setupcmd(ui, cmd, table, func, effectsmap):
     '''patch in command to command table and load effect map'''
-    def nocolor(orig, *args, **kwargs):
-        if kwargs['no_color']:
-            return orig(*args, **kwargs)
-        return func(orig, *args, **kwargs)
+    def nocolor(orig, *args, **opts):
+        def isatty():
+            # Duplicate stdout in case sys.stdout has been reassigned
+            if sys.stdout.fileno() != 1:
+                try:
+                    stdout = os.fdopen(os.dup(1), 'w')
+                    try:
+                        return stdout.isatty()
+                    finally:
+                        stdout.close()
+                except Exception:
+                    pass
+            return sys.stdout.isatty()
+
+        if (opts['no_color'] or opts['color'] == 'never' or
+            (opts['color'] == 'auto' and
+             (os.environ.get('TERM') == 'dumb' or not isatty()))):
+            return orig(*args, **opts)
+
+        old_showpatch = extensions.wrapfunction(cmdutil.changeset_printer,
+                                                'showpatch', showpatch_color)
+        try:
+            if func is not None:
+                return func(orig, *args, **opts)
+            return orig(*args, **opts)
+        finally:
+            cmdutil.changeset_printer.showpatch = old_showpatch
 
     entry = extensions.wrapcommand(table, cmd, nocolor)
-    entry[1].append(('', 'no-color', None, _("don't colorize output")))
+    entry[1].extend([
+        ('', 'color', 'auto', _("when to colorize (always, auto, or never)")),
+        ('', 'no-color', None, _("don't colorize output")),
+    ])
 
     for status in effectsmap:
         effects = ui.config('color', cmd + '.' + status)


More information about the Mercurial-devel mailing list