Show More
@@ -1518,7 +1518,7 b' def diffordiffstat(ui, repo, diffopts, n' | |||||
1518 | width = 80 |
|
1518 | width = 80 | |
1519 | if not ui.plain(): |
|
1519 | if not ui.plain(): | |
1520 | width = ui.termwidth() |
|
1520 | width = ui.termwidth() | |
1521 | chunks = patch.diff(repo, node1, node2, match, changes, diffopts, |
|
1521 | chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts, | |
1522 | prefix=prefix, relroot=relroot, |
|
1522 | prefix=prefix, relroot=relroot, | |
1523 | hunksfilterfn=hunksfilterfn) |
|
1523 | hunksfilterfn=hunksfilterfn) | |
1524 | for chunk, label in patch.diffstatui(util.iterlines(chunks), |
|
1524 | for chunk, label in patch.diffstatui(util.iterlines(chunks), | |
@@ -1526,7 +1526,7 b' def diffordiffstat(ui, repo, diffopts, n' | |||||
1526 | write(chunk, label=label) |
|
1526 | write(chunk, label=label) | |
1527 | else: |
|
1527 | else: | |
1528 | for chunk, label in patch.diffui(repo, node1, node2, match, |
|
1528 | for chunk, label in patch.diffui(repo, node1, node2, match, | |
1529 | changes, diffopts, prefix=prefix, |
|
1529 | changes, opts=diffopts, prefix=prefix, | |
1530 | relroot=relroot, |
|
1530 | relroot=relroot, | |
1531 | hunksfilterfn=hunksfilterfn): |
|
1531 | hunksfilterfn=hunksfilterfn): | |
1532 | write(chunk, label=label) |
|
1532 | write(chunk, label=label) |
@@ -87,12 +87,14 b' except ImportError:' | |||||
87 | 'branches.inactive': 'none', |
|
87 | 'branches.inactive': 'none', | |
88 | 'diff.changed': 'white', |
|
88 | 'diff.changed': 'white', | |
89 | 'diff.deleted': 'red', |
|
89 | 'diff.deleted': 'red', | |
|
90 | 'diff.deleted.highlight': 'red bold underline', | |||
90 | 'diff.diffline': 'bold', |
|
91 | 'diff.diffline': 'bold', | |
91 | 'diff.extended': 'cyan bold', |
|
92 | 'diff.extended': 'cyan bold', | |
92 | 'diff.file_a': 'red bold', |
|
93 | 'diff.file_a': 'red bold', | |
93 | 'diff.file_b': 'green bold', |
|
94 | 'diff.file_b': 'green bold', | |
94 | 'diff.hunk': 'magenta', |
|
95 | 'diff.hunk': 'magenta', | |
95 | 'diff.inserted': 'green', |
|
96 | 'diff.inserted': 'green', | |
|
97 | 'diff.inserted.highlight': 'green bold underline', | |||
96 | 'diff.tab': '', |
|
98 | 'diff.tab': '', | |
97 | 'diff.trailingwhitespace': 'bold red_background', |
|
99 | 'diff.trailingwhitespace': 'bold red_background', | |
98 | 'changeset.public': '', |
|
100 | 'changeset.public': '', |
@@ -481,6 +481,9 b" coreconfigitem('experimental', 'evolutio" | |||||
481 | coreconfigitem('experimental', 'evolution.track-operation', |
|
481 | coreconfigitem('experimental', 'evolution.track-operation', | |
482 | default=True, |
|
482 | default=True, | |
483 | ) |
|
483 | ) | |
|
484 | coreconfigitem('experimental', 'worddiff', | |||
|
485 | default=False, | |||
|
486 | ) | |||
484 | coreconfigitem('experimental', 'maxdeltachainspan', |
|
487 | coreconfigitem('experimental', 'maxdeltachainspan', | |
485 | default=-1, |
|
488 | default=-1, | |
486 | ) |
|
489 | ) |
@@ -67,6 +67,7 b' class diffopts(object):' | |||||
67 | 'ignoreblanklines': False, |
|
67 | 'ignoreblanklines': False, | |
68 | 'upgrade': False, |
|
68 | 'upgrade': False, | |
69 | 'showsimilarity': False, |
|
69 | 'showsimilarity': False, | |
|
70 | 'worddiff': False, | |||
70 | } |
|
71 | } | |
71 |
|
72 | |||
72 | def __init__(self, **opts): |
|
73 | def __init__(self, **opts): |
@@ -10,6 +10,7 b' from __future__ import absolute_import, ' | |||||
10 |
|
10 | |||
11 | import collections |
|
11 | import collections | |
12 | import copy |
|
12 | import copy | |
|
13 | import difflib | |||
13 | import email |
|
14 | import email | |
14 | import errno |
|
15 | import errno | |
15 | import hashlib |
|
16 | import hashlib | |
@@ -2252,6 +2253,7 b' def difffeatureopts(ui, opts=None, untru' | |||||
2252 | 'showfunc': get('show_function', 'showfunc'), |
|
2253 | 'showfunc': get('show_function', 'showfunc'), | |
2253 | 'context': get('unified', getter=ui.config), |
|
2254 | 'context': get('unified', getter=ui.config), | |
2254 | } |
|
2255 | } | |
|
2256 | buildopts['worddiff'] = ui.configbool('experimental', 'worddiff') | |||
2255 |
|
2257 | |||
2256 | if git: |
|
2258 | if git: | |
2257 | buildopts['git'] = get('git') |
|
2259 | buildopts['git'] = get('git') | |
@@ -2463,6 +2465,9 b' def diffhunks(repo, node1=None, node2=No' | |||||
2463 |
|
2465 | |||
2464 | def difflabel(func, *args, **kw): |
|
2466 | def difflabel(func, *args, **kw): | |
2465 | '''yields 2-tuples of (output, label) based on the output of func()''' |
|
2467 | '''yields 2-tuples of (output, label) based on the output of func()''' | |
|
2468 | inlinecolor = False | |||
|
2469 | if kw.get('opts'): | |||
|
2470 | inlinecolor = kw['opts'].worddiff | |||
2466 | headprefixes = [('diff', 'diff.diffline'), |
|
2471 | headprefixes = [('diff', 'diff.diffline'), | |
2467 | ('copy', 'diff.extended'), |
|
2472 | ('copy', 'diff.extended'), | |
2468 | ('rename', 'diff.extended'), |
|
2473 | ('rename', 'diff.extended'), | |
@@ -2479,6 +2484,9 b' def difflabel(func, *args, **kw):' | |||||
2479 | head = False |
|
2484 | head = False | |
2480 | for chunk in func(*args, **kw): |
|
2485 | for chunk in func(*args, **kw): | |
2481 | lines = chunk.split('\n') |
|
2486 | lines = chunk.split('\n') | |
|
2487 | matches = {} | |||
|
2488 | if inlinecolor: | |||
|
2489 | matches = _findmatches(lines) | |||
2482 | for i, line in enumerate(lines): |
|
2490 | for i, line in enumerate(lines): | |
2483 | if i != 0: |
|
2491 | if i != 0: | |
2484 | yield ('\n', '') |
|
2492 | yield ('\n', '') | |
@@ -2506,7 +2514,14 b' def difflabel(func, *args, **kw):' | |||||
2506 | if '\t' == token[0]: |
|
2514 | if '\t' == token[0]: | |
2507 | yield (token, 'diff.tab') |
|
2515 | yield (token, 'diff.tab') | |
2508 | else: |
|
2516 | else: | |
2509 |
|
|
2517 | if i in matches: | |
|
2518 | for l, t in _inlinediff( | |||
|
2519 | lines[i].rstrip(), | |||
|
2520 | lines[matches[i]].rstrip(), | |||
|
2521 | label): | |||
|
2522 | yield (t, l) | |||
|
2523 | else: | |||
|
2524 | yield (token, label) | |||
2510 | else: |
|
2525 | else: | |
2511 | yield (stripline, label) |
|
2526 | yield (stripline, label) | |
2512 | break |
|
2527 | break | |
@@ -2515,6 +2530,70 b' def difflabel(func, *args, **kw):' | |||||
2515 | if line != stripline: |
|
2530 | if line != stripline: | |
2516 | yield (line[len(stripline):], 'diff.trailingwhitespace') |
|
2531 | yield (line[len(stripline):], 'diff.trailingwhitespace') | |
2517 |
|
2532 | |||
|
2533 | def _findmatches(slist): | |||
|
2534 | '''Look for insertion matches to deletion and returns a dict of | |||
|
2535 | correspondences. | |||
|
2536 | ''' | |||
|
2537 | lastmatch = 0 | |||
|
2538 | matches = {} | |||
|
2539 | for i, line in enumerate(slist): | |||
|
2540 | if line == '': | |||
|
2541 | continue | |||
|
2542 | if line[0] == '-': | |||
|
2543 | lastmatch = max(lastmatch, i) | |||
|
2544 | newgroup = False | |||
|
2545 | for j, newline in enumerate(slist[lastmatch + 1:]): | |||
|
2546 | if newline == '': | |||
|
2547 | continue | |||
|
2548 | if newline[0] == '-' and newgroup: # too far, no match | |||
|
2549 | break | |||
|
2550 | if newline[0] == '+': # potential match | |||
|
2551 | newgroup = True | |||
|
2552 | sim = difflib.SequenceMatcher(None, line, newline).ratio() | |||
|
2553 | if sim > 0.7: | |||
|
2554 | lastmatch = lastmatch + 1 + j | |||
|
2555 | matches[i] = lastmatch | |||
|
2556 | matches[lastmatch] = i | |||
|
2557 | break | |||
|
2558 | return matches | |||
|
2559 | ||||
|
2560 | def _inlinediff(s1, s2, operation): | |||
|
2561 | '''Perform string diff to highlight specific changes.''' | |||
|
2562 | operation_skip = '+?' if operation == 'diff.deleted' else '-?' | |||
|
2563 | if operation == 'diff.deleted': | |||
|
2564 | s2, s1 = s1, s2 | |||
|
2565 | ||||
|
2566 | buff = [] | |||
|
2567 | # we never want to higlight the leading +- | |||
|
2568 | if operation == 'diff.deleted' and s2.startswith('-'): | |||
|
2569 | label = operation | |||
|
2570 | token = '-' | |||
|
2571 | s2 = s2[1:] | |||
|
2572 | s1 = s1[1:] | |||
|
2573 | elif operation == 'diff.inserted' and s1.startswith('+'): | |||
|
2574 | label = operation | |||
|
2575 | token = '+' | |||
|
2576 | s2 = s2[1:] | |||
|
2577 | s1 = s1[1:] | |||
|
2578 | ||||
|
2579 | s = difflib.ndiff(re.split(br'(\W)', s2), re.split(br'(\W)', s1)) | |||
|
2580 | for part in s: | |||
|
2581 | if part[0] in operation_skip: | |||
|
2582 | continue | |||
|
2583 | l = operation + '.highlight' | |||
|
2584 | if part[0] in ' ': | |||
|
2585 | l = operation | |||
|
2586 | if l == label: # contiguous token with same label | |||
|
2587 | token += part[2:] | |||
|
2588 | continue | |||
|
2589 | else: | |||
|
2590 | buff.append((label, token)) | |||
|
2591 | label = l | |||
|
2592 | token = part[2:] | |||
|
2593 | buff.append((label, token)) | |||
|
2594 | ||||
|
2595 | return buff | |||
|
2596 | ||||
2518 | def diffui(*args, **kw): |
|
2597 | def diffui(*args, **kw): | |
2519 | '''like diff(), but yields 2-tuples of (output, label) for ui.write()''' |
|
2598 | '''like diff(), but yields 2-tuples of (output, label) for ui.write()''' | |
2520 | return difflabel(diff, *args, **kw) |
|
2599 | return difflabel(diff, *args, **kw) |
@@ -259,3 +259,95 b' test tabs' | |||||
259 | \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) |
|
259 | \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) | |
260 |
|
260 | |||
261 | $ cd .. |
|
261 | $ cd .. | |
|
262 | ||||
|
263 | test inline color diff | |||
|
264 | ||||
|
265 | $ hg init inline | |||
|
266 | $ cd inline | |||
|
267 | $ cat > file1 << EOF | |||
|
268 | > this is the first line | |||
|
269 | > this is the second line | |||
|
270 | > third line starts with space | |||
|
271 | > + starts with a plus sign | |||
|
272 | > | |||
|
273 | > this line won't change | |||
|
274 | > | |||
|
275 | > two lines are going to | |||
|
276 | > be changed into three! | |||
|
277 | > | |||
|
278 | > three of those lines will | |||
|
279 | > collapse onto one | |||
|
280 | > (to see if it works) | |||
|
281 | > EOF | |||
|
282 | $ hg add file1 | |||
|
283 | $ hg ci -m 'commit' | |||
|
284 | $ cat > file1 << EOF | |||
|
285 | > that is the first paragraph | |||
|
286 | > this is the second line | |||
|
287 | > third line starts with space | |||
|
288 | > - starts with a minus sign | |||
|
289 | > | |||
|
290 | > this line won't change | |||
|
291 | > | |||
|
292 | > two lines are going to | |||
|
293 | > (entirely magically, | |||
|
294 | > assuming this works) | |||
|
295 | > be changed into four! | |||
|
296 | > | |||
|
297 | > three of those lines have | |||
|
298 | > collapsed onto one | |||
|
299 | > EOF | |||
|
300 | $ hg diff --config experimental.worddiff=False --color=debug | |||
|
301 | [diff.diffline|diff --git a/file1 b/file1] | |||
|
302 | [diff.file_a|--- a/file1] | |||
|
303 | [diff.file_b|+++ b/file1] | |||
|
304 | [diff.hunk|@@ -1,13 +1,14 @@] | |||
|
305 | [diff.deleted|-this is the first line] | |||
|
306 | [diff.deleted|-this is the second line] | |||
|
307 | [diff.deleted|- third line starts with space] | |||
|
308 | [diff.deleted|-+ starts with a plus sign] | |||
|
309 | [diff.inserted|+that is the first paragraph] | |||
|
310 | [diff.inserted|+ this is the second line] | |||
|
311 | [diff.inserted|+third line starts with space] | |||
|
312 | [diff.inserted|+- starts with a minus sign] | |||
|
313 | ||||
|
314 | this line won't change | |||
|
315 | ||||
|
316 | two lines are going to | |||
|
317 | [diff.deleted|-be changed into three!] | |||
|
318 | [diff.inserted|+(entirely magically,] | |||
|
319 | [diff.inserted|+ assuming this works)] | |||
|
320 | [diff.inserted|+be changed into four!] | |||
|
321 | ||||
|
322 | [diff.deleted|-three of those lines will] | |||
|
323 | [diff.deleted|-collapse onto one] | |||
|
324 | [diff.deleted|-(to see if it works)] | |||
|
325 | [diff.inserted|+three of those lines have] | |||
|
326 | [diff.inserted|+collapsed onto one] | |||
|
327 | $ hg diff --config experimental.worddiff=True --color=debug | |||
|
328 | [diff.diffline|diff --git a/file1 b/file1] | |||
|
329 | [diff.file_a|--- a/file1] | |||
|
330 | [diff.file_b|+++ b/file1] | |||
|
331 | [diff.hunk|@@ -1,13 +1,14 @@] | |||
|
332 | [diff.deleted|-this is the ][diff.deleted.highlight|first][diff.deleted| line] | |||
|
333 | [diff.deleted|-this is the second line] | |||
|
334 | [diff.deleted|-][diff.deleted.highlight| ][diff.deleted|third line starts with space] | |||
|
335 | [diff.deleted|-][diff.deleted.highlight|+][diff.deleted| starts with a ][diff.deleted.highlight|plus][diff.deleted| sign] | |||
|
336 | [diff.inserted|+that is the first paragraph] | |||
|
337 | [diff.inserted|+][diff.inserted.highlight| ][diff.inserted|this is the ][diff.inserted.highlight|second][diff.inserted| line] | |||
|
338 | [diff.inserted|+third line starts with space] | |||
|
339 | [diff.inserted|+][diff.inserted.highlight|-][diff.inserted| starts with a ][diff.inserted.highlight|minus][diff.inserted| sign] | |||
|
340 | ||||
|
341 | this line won't change | |||
|
342 | ||||
|
343 | two lines are going to | |||
|
344 | [diff.deleted|-be changed into ][diff.deleted.highlight|three][diff.deleted|!] | |||
|
345 | [diff.inserted|+(entirely magically,] | |||
|
346 | [diff.inserted|+ assuming this works)] | |||
|
347 | [diff.inserted|+be changed into ][diff.inserted.highlight|four][diff.inserted|!] | |||
|
348 | ||||
|
349 | [diff.deleted|-three of those lines ][diff.deleted.highlight|will] | |||
|
350 | [diff.deleted|-][diff.deleted.highlight|collapse][diff.deleted| onto one] | |||
|
351 | [diff.deleted|-(to see if it works)] | |||
|
352 | [diff.inserted|+three of those lines ][diff.inserted.highlight|have] | |||
|
353 | [diff.inserted|+][diff.inserted.highlight|collapsed][diff.inserted| onto one] |
General Comments 0
You need to be logged in to leave comments.
Login now