c3302dd8171c47ba89523123cbb7a0de0588971f
1 #!/usr/bin/perl
3 use warnings FATAL => 'all';
4 use strict;
6 # Highlight by reversing foreground and background. You could do
7 # other things like bold or underline if you prefer.
8 my $HIGHLIGHT = "\x1b[7m";
9 my $UNHIGHLIGHT = "\x1b[27m";
10 my $COLOR = qr/\x1b\[[0-9;]*m/;
12 my @window;
14 while (<>) {
15 # We highlight only single-line changes, so we need
16 # a 4-line window to make a decision on whether
17 # to highlight.
18 push @window, $_;
19 next if @window < 4;
20 if ($window[0] =~ /^$COLOR*(\@| )/ &&
21 $window[1] =~ /^$COLOR*-/ &&
22 $window[2] =~ /^$COLOR*\+/ &&
23 $window[3] !~ /^$COLOR*\+/) {
24 print shift @window;
25 show_pair(shift @window, shift @window);
26 }
27 else {
28 print shift @window;
29 }
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.
39 if (!length) {
40 local $| = 1;
41 }
42 }
44 # Special case a single-line hunk at the end of file.
45 if (@window == 3 &&
46 $window[0] =~ /^$COLOR*(\@| )/ &&
47 $window[1] =~ /^$COLOR*-/ &&
48 $window[2] =~ /^$COLOR*\+/) {
49 print shift @window;
50 show_pair(shift @window, shift @window);
51 }
53 # And then flush any remaining lines.
54 while (@window) {
55 print shift @window;
56 }
58 exit 0;
60 sub show_pair {
61 my @a = split_line(shift);
62 my @b = split_line(shift);
64 # Find common prefix, taking care to skip any ansi
65 # color codes.
66 my $seen_plusminus;
67 my ($pa, $pb) = (0, 0);
68 while ($pa < @a && $pb < @b) {
69 if ($a[$pa] =~ /$COLOR/) {
70 $pa++;
71 }
72 elsif ($b[$pb] =~ /$COLOR/) {
73 $pb++;
74 }
75 elsif ($a[$pa] eq $b[$pb]) {
76 $pa++;
77 $pb++;
78 }
79 elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') {
80 $seen_plusminus = 1;
81 $pa++;
82 $pb++;
83 }
84 else {
85 last;
86 }
87 }
89 # Find common suffix, ignoring colors.
90 my ($sa, $sb) = ($#a, $#b);
91 while ($sa >= $pa && $sb >= $pb) {
92 if ($a[$sa] =~ /$COLOR/) {
93 $sa--;
94 }
95 elsif ($b[$sb] =~ /$COLOR/) {
96 $sb--;
97 }
98 elsif ($a[$sa] eq $b[$sb]) {
99 $sa--;
100 $sb--;
101 }
102 else {
103 last;
104 }
105 }
107 print highlight(\@a, $pa, $sa);
108 print highlight(\@b, $pb, $sb);
109 }
111 sub split_line {
112 local $_ = shift;
113 return map { /$COLOR/ ? $_ : (split //) }
114 split /($COLOR*)/;
115 }
117 sub highlight {
118 my ($line, $prefix, $suffix) = @_;
120 return join('',
121 @{$line}[0..($prefix-1)],
122 $HIGHLIGHT,
123 @{$line}[$prefix..$suffix],
124 $UNHIGHLIGHT,
125 @{$line}[($suffix+1)..$#$line]
126 );
127 }