Code

diff-highlight: make perl strict and warnings fatal
[git.git] / contrib / diff-highlight / diff-highlight
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);
111 sub split_line {
112         local $_ = shift;
113         return map { /$COLOR/ ? $_ : (split //) }
114                split /($COLOR*)/;
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         );