2250 buildopts = { |
2251 buildopts = { |
2251 'nodates': get('nodates'), |
2252 'nodates': get('nodates'), |
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') |
2258 |
2260 |
2259 # since this is in the experimental section, we need to call |
2261 # since this is in the experimental section, we need to call |
2461 else: |
2463 else: |
2462 return difffn(opts, None) |
2464 return difffn(opts, None) |
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'), |
2469 ('old', 'diff.extended'), |
2474 ('old', 'diff.extended'), |
2470 ('new', 'diff.extended'), |
2475 ('new', 'diff.extended'), |
2477 ('-', 'diff.deleted'), |
2482 ('-', 'diff.deleted'), |
2478 ('+', 'diff.inserted')] |
2483 ('+', 'diff.inserted')] |
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', '') |
2485 if head: |
2493 if head: |
2486 if line.startswith('@'): |
2494 if line.startswith('@'): |
2504 if diffline: |
2512 if diffline: |
2505 for token in tabsplitter.findall(stripline): |
2513 for token in tabsplitter.findall(stripline): |
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 yield (token, label) |
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 |
2513 else: |
2528 else: |
2514 yield (line, '') |
2529 yield (line, '') |
2515 if line != stripline: |
2530 if line != stripline: |
2516 yield (line[len(stripline):], 'diff.trailingwhitespace') |
2531 yield (line[len(stripline):], 'diff.trailingwhitespace') |
|
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 |
2517 |
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) |
2521 |
2600 |