[PATCH v2] cmdutil: add within-line color diff capacity

matthieu.laneuville at octobus.net matthieu.laneuville at octobus.net
Mon Nov 20 15:24:13 UTC 2017


# HG changeset patch
# User Matthieu Laneuville <matthieu.laneuville at octobus.net>
# Date 1508944418 -32400
#      Thu Oct 26 00:13:38 2017 +0900
# Node ID 3f1788e4b038e5fdf9eb1c3bd4f2a7071f4db931
# Parent  75013952d8d9608f73cd45f68405fbd6ec112bf2
# EXP-Topic hg248
cmdutil: add within-line color diff capacity

The `diff' command usually writes deletion in red and insertions in green. This
patch adds within-line colors, to highlight which part of the lines differ.

The patch passes all tests except `test-diff-color.t' that had to be amended
because of the new default inline coloring.

diff -r 75013952d8d9 -r 3f1788e4b038 mercurial/cmdutil.py
--- a/mercurial/cmdutil.py	Fri Nov 10 19:14:06 2017 +0800
+++ b/mercurial/cmdutil.py	Thu Oct 26 00:13:38 2017 +0900
@@ -7,6 +7,7 @@
 
 from __future__ import absolute_import
 
+import difflib
 import errno
 import itertools
 import os
@@ -1513,6 +1514,11 @@ def diffordiffstat(ui, repo, diffopts, n
                 ui.warn(_('warning: %s not inside relative root %s\n') % (
                     match.uipath(matchroot), uirelroot))
 
+    store = {
+        'diff.inserted': [],
+        'diff.deleted': []
+    }
+    status = False
     if stat:
         diffopts = diffopts.copy(context=0)
         width = 80
@@ -1529,7 +1535,56 @@ def diffordiffstat(ui, repo, diffopts, n
                                          changes, diffopts, prefix=prefix,
                                          relroot=relroot,
                                          hunksfilterfn=hunksfilterfn):
-            write(chunk, label=label)
+            # Each deleted/inserted chunk is followed by an EOL chunk with ''
+            # label. The 'status' flag helps us grab that second line.
+            if label in ['diff.deleted', 'diff.inserted'] or status:
+                if status:
+                    store[status].append(chunk)
+                    status = False
+                else:
+                    store[label].append(chunk)
+                    status = label
+                continue
+
+            if store['diff.inserted'] or store['diff.deleted']:
+                # It is possible that the amount of deleted/inserted lines
+                # differ, therefore we have to pad them before 1-on-1 comparison
+                while len(store['diff.deleted']) < len(store['diff.inserted']):
+                    store['diff.deleted'].append(None)
+                while len(store['diff.deleted']) > len(store['diff.inserted']):
+                    store['diff.inserted'].append(None)
+
+                # First print all deletions
+                for insert, delete in zip(store['diff.inserted'],
+                                          store['diff.deleted']):
+                    if not delete:
+                        continue
+                    if not insert: # no matching insertion, no diff needed
+                        write(delete, label='diff.deleted')
+
+                    else:
+                        buff = _inlinediff(insert, delete, direction='deleted')
+                        for line in buff:
+                            write(line[1], label=line[0])
+
+                # Then print all deletions
+                for insert, delete in zip(store['diff.inserted'],
+                                          store['diff.deleted']):
+                    if not insert:
+                        continue
+                    if not delete: # no matching insertion, no diff needed
+                        write(insert, label='diff.inserted')
+
+                    else:
+                        buff = _inlinediff(insert, delete, direction='inserted')
+                        for line in buff:
+                            write(line[1], label=line[0])
+
+                store['diff.inserted'] = []
+                store['diff.deleted'] = []
+
+            if chunk:
+                write(chunk, label=label)
 
     if listsubrepos:
         ctx1 = repo[node1]
@@ -1548,6 +1603,23 @@ def diffordiffstat(ui, repo, diffopts, n
             sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
                      stat=stat, fp=fp, prefix=prefix)
 
+def _inlinediff(s1, s2, direction):
+    '''Perform string diff to highlight specific changes.'''
+    direction_skip = '+?' if direction == 'deleted' else '-?'
+    s = difflib.ndiff(s2.split(' '), s1.split(' '))
+    # buffer required to remove last space, there may be smarter ways to do this
+    buff = []
+    for line in s:
+        if line[0] in direction_skip:
+            continue
+        l = 'diff.' + direction + '.highlight'
+        if line[0] == ' ':
+            l = 'diff.' + direction
+        buff.append((l, line[2:] + ' '))
+
+    buff[-1] = (buff[-1][0], buff[-1][1].strip(' '))
+    return buff
+
 def _changesetlabels(ctx):
     labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
     if ctx.obsolete():
diff -r 75013952d8d9 -r 3f1788e4b038 mercurial/color.py
--- a/mercurial/color.py	Fri Nov 10 19:14:06 2017 +0800
+++ b/mercurial/color.py	Thu Oct 26 00:13:38 2017 +0900
@@ -87,12 +87,14 @@ except ImportError:
     'branches.inactive': 'none',
     'diff.changed': 'white',
     'diff.deleted': 'red',
+    'diff.deleted.highlight': 'red bold',
     'diff.diffline': 'bold',
     'diff.extended': 'cyan bold',
     'diff.file_a': 'red bold',
     'diff.file_b': 'green bold',
     'diff.hunk': 'magenta',
     'diff.inserted': 'green',
+    'diff.inserted.highlight': 'green bold',
     'diff.tab': '',
     'diff.trailingwhitespace': 'bold red_background',
     'changeset.public': '',
diff -r 75013952d8d9 -r 3f1788e4b038 tests/test-diff-color.t
--- a/tests/test-diff-color.t	Fri Nov 10 19:14:06 2017 +0800
+++ b/tests/test-diff-color.t	Thu Oct 26 00:13:38 2017 +0900
@@ -45,8 +45,8 @@ default context
    c
    a
    a
-  \x1b[0;31m-b\x1b[0m (esc)
-  \x1b[0;32m+dd\x1b[0m (esc)
+  \x1b[0;31;1m-b\x1b[0m (esc)
+  \x1b[0;32;1m+dd\x1b[0m (esc)
    a
    a
    c
@@ -93,8 +93,8 @@ default context
    c
    a
    a
-  \x1b[0;31m-b\x1b[0m (esc)
-  \x1b[0;32m+dd\x1b[0m (esc)
+  \x1b[0;31;1m-b\x1b[0m (esc)
+  \x1b[0;32;1m+dd\x1b[0m (esc)
    a
    a
    c
@@ -108,8 +108,8 @@ default context
   \x1b[0;35m@@ -3,5 +3,5 @@\x1b[0m (esc)
    a
    a
-  \x1b[0;31m-b\x1b[0m (esc)
-  \x1b[0;32m+dd\x1b[0m (esc)
+  \x1b[0;31;1m-b\x1b[0m (esc)
+  \x1b[0;32;1m+dd\x1b[0m (esc)
    a
    a
 
@@ -236,11 +236,11 @@ test tabs
    c
    c
   \x1b[0;32m+aa\x1b[0m (esc)
-  \x1b[0;32m+\x1b[0m	\x1b[0;32mone tab\x1b[0m (esc)
-  \x1b[0;32m+\x1b[0m		\x1b[0;32mtwo tabs\x1b[0m (esc)
-  \x1b[0;32m+end tab\x1b[0m\x1b[0;1;41m	\x1b[0m (esc)
-  \x1b[0;32m+mid\x1b[0m	\x1b[0;32mtab\x1b[0m (esc)
-  \x1b[0;32m+\x1b[0m	\x1b[0;32mall\x1b[0m		\x1b[0;32mtabs\x1b[0m\x1b[0;1;41m	\x1b[0m (esc)
+  \x1b[0;32m+\x1b[0m\x1b[0;32m	\x1b[0m\x1b[0;32mone tab\x1b[0m (esc)
+  \x1b[0;32m+\x1b[0m\x1b[0;32m		\x1b[0m\x1b[0;32mtwo tabs\x1b[0m (esc)
+  \x1b[0;32m+end tab\x1b[0m\x1b[0;32m	\x1b[0m (esc)
+  \x1b[0;32m+mid\x1b[0m\x1b[0;32m	\x1b[0m\x1b[0;32mtab\x1b[0m (esc)
+  \x1b[0;32m+\x1b[0m\x1b[0;32m	\x1b[0m\x1b[0;32mall\x1b[0m\x1b[0;32m		\x1b[0m\x1b[0;32mtabs\x1b[0m\x1b[0;32m	\x1b[0m (esc)
   $ echo "[color]" >> $HGRCPATH
   $ echo "diff.tab = bold magenta" >> $HGRCPATH
   $ hg diff --nodates
@@ -252,10 +252,10 @@ test tabs
    c
    c
   \x1b[0;32m+aa\x1b[0m (esc)
-  \x1b[0;32m+\x1b[0m\x1b[0;1;35m	\x1b[0m\x1b[0;32mone tab\x1b[0m (esc)
-  \x1b[0;32m+\x1b[0m\x1b[0;1;35m		\x1b[0m\x1b[0;32mtwo tabs\x1b[0m (esc)
-  \x1b[0;32m+end tab\x1b[0m\x1b[0;1;41m	\x1b[0m (esc)
-  \x1b[0;32m+mid\x1b[0m\x1b[0;1;35m	\x1b[0m\x1b[0;32mtab\x1b[0m (esc)
-  \x1b[0;32m+\x1b[0m\x1b[0;1;35m	\x1b[0m\x1b[0;32mall\x1b[0m\x1b[0;1;35m		\x1b[0m\x1b[0;32mtabs\x1b[0m\x1b[0;1;41m	\x1b[0m (esc)
+  \x1b[0;32m+\x1b[0m\x1b[0;32m	\x1b[0m\x1b[0;32mone tab\x1b[0m (esc)
+  \x1b[0;32m+\x1b[0m\x1b[0;32m		\x1b[0m\x1b[0;32mtwo tabs\x1b[0m (esc)
+  \x1b[0;32m+end tab\x1b[0m\x1b[0;32m	\x1b[0m (esc)
+  \x1b[0;32m+mid\x1b[0m\x1b[0;32m	\x1b[0m\x1b[0;32mtab\x1b[0m (esc)
+  \x1b[0;32m+\x1b[0m\x1b[0;32m	\x1b[0m\x1b[0;32mall\x1b[0m\x1b[0;32m		\x1b[0m\x1b[0;32mtabs\x1b[0m\x1b[0;32m	\x1b[0m (esc)
 
   $ cd ..


More information about the Mercurial-devel mailing list