Submitter | matthieu.laneuville@octobus.net |
---|---|
Date | Nov. 20, 2017, 3:24 p.m. |
Message ID | <3f1788e4b038e5fdf9eb.1511191453@carbon> |
Download | mbox | patch |
Permalink | /patch/25653/ |
State | Superseded |
Headers | show |
Comments
On Tue, Nov 21, 2017 at 12:24:13AM +0900, matthieu.laneuville@octobus.net wrote: > # HG changeset patch > # User Matthieu Laneuville <matthieu.laneuville@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 Nice. does this have any perf implications? > > 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 .. > _______________________________________________ > Mercurial-devel mailing list > Mercurial-devel@mercurial-scm.org > https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Patch
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 ..