1#!/usr/bin/perl 2 3use warnings FATAL =>'all'; 4use strict; 5 6# Highlight by reversing foreground and background. You could do 7# other things like bold or underline if you prefer. 8my$HIGHLIGHT="\x1b[7m"; 9my$UNHIGHLIGHT="\x1b[27m"; 10my$COLOR=qr/\x1b\[[0-9;]*m/; 11 12my@window; 13 14while(<>) { 15# We highlight only single-line changes, so we need 16# a 4-line window to make a decision on whether 17# to highlight. 18push@window,$_; 19next if@window<4; 20if($window[0] =~/^$COLOR*(\@| )/&& 21$window[1] =~/^$COLOR*-/&& 22$window[2] =~/^$COLOR*\+/&& 23$window[3] !~/^$COLOR*\+/) { 24print shift@window; 25 show_pair(shift@window,shift@window); 26} 27else{ 28print shift@window; 29} 30 31# Most of the time there is enough output to keep things streaming, 32# but for something like "git log -Sfoo", you can get one early 33# commit and then many seconds of nothing. We want to show 34# that one commit as soon as possible. 35# 36# Since we can receive arbitrary input, there's no optimal 37# place to flush. Flushing on a blank line is a heuristic that 38# happens to match git-log output. 39if(!length) { 40local$| =1; 41} 42} 43 44# Special case a single-line hunk at the end of file. 45if(@window==3&& 46$window[0] =~/^$COLOR*(\@| )/&& 47$window[1] =~/^$COLOR*-/&& 48$window[2] =~/^$COLOR*\+/) { 49print shift@window; 50 show_pair(shift@window,shift@window); 51} 52 53# And then flush any remaining lines. 54while(@window) { 55print shift@window; 56} 57 58exit0; 59 60sub show_pair { 61my@a= split_line(shift); 62my@b= split_line(shift); 63 64# Find common prefix, taking care to skip any ansi 65# color codes. 66my$seen_plusminus; 67my($pa,$pb) = (0,0); 68while($pa<@a&&$pb<@b) { 69if($a[$pa] =~/$COLOR/) { 70$pa++; 71} 72elsif($b[$pb] =~/$COLOR/) { 73$pb++; 74} 75elsif($a[$pa]eq$b[$pb]) { 76$pa++; 77$pb++; 78} 79elsif(!$seen_plusminus&&$a[$pa]eq'-'&&$b[$pb]eq'+') { 80$seen_plusminus=1; 81$pa++; 82$pb++; 83} 84else{ 85last; 86} 87} 88 89# Find common suffix, ignoring colors. 90my($sa,$sb) = ($#a,$#b); 91while($sa>=$pa&&$sb>=$pb) { 92if($a[$sa] =~/$COLOR/) { 93$sa--; 94} 95elsif($b[$sb] =~/$COLOR/) { 96$sb--; 97} 98elsif($a[$sa]eq$b[$sb]) { 99$sa--; 100$sb--; 101} 102else{ 103last; 104} 105} 106 107print highlight(\@a,$pa,$sa); 108print highlight(\@b,$pb,$sb); 109} 110 111sub split_line { 112local$_=shift; 113returnmap{/$COLOR/?$_: (split//) } 114split/($COLOR*)/; 115} 116 117sub highlight { 118my($line,$prefix,$suffix) =@_; 119 120returnjoin('', 121@{$line}[0..($prefix-1)], 122$HIGHLIGHT, 123@{$line}[$prefix..$suffix], 124$UNHIGHLIGHT, 125@{$line}[($suffix+1)..$#$line] 126); 127}