diff --git a/scripts/DiffHighlight.pm b/scripts/DiffHighlight.pm index e258992..9feb06c 100644 --- a/scripts/DiffHighlight.pm +++ b/scripts/DiffHighlight.pm @@ -11,17 +11,19 @@ my $NULL = File::Spec->devnull(); # Highlight by reversing foreground and background. You could do # other things like bold or underline if you prefer. -my @OLD_HIGHLIGHT = ( - color_config('color.diff-highlight.oldnormal'), - color_config('color.diff-highlight.oldhighlight', "\x1b[7m"), - color_config('color.diff-highlight.oldreset', "\x1b[27m") +our @OLD_HIGHLIGHT = ( + color_config('color.diff-highlight.oldnormal', "\e[1;31m"), + color_config('color.diff-highlight.oldhighlight', "\e[1;31;48;5;52m"), + "\x1b[27m", ); -my @NEW_HIGHLIGHT = ( - color_config('color.diff-highlight.newnormal', $OLD_HIGHLIGHT[0]), - color_config('color.diff-highlight.newhighlight', $OLD_HIGHLIGHT[1]), - color_config('color.diff-highlight.newreset', $OLD_HIGHLIGHT[2]) +our @NEW_HIGHLIGHT = ( + color_config('color.diff-highlight.newnormal', "\e[1;32m"), + color_config('color.diff-highlight.newhighlight', "\e[1;32;48;5;22m"), + $OLD_HIGHLIGHT[2], ); + + my $RESET = "\x1b[m"; my $COLOR = qr/\x1b\[[0-9;]*m/; my $BORING = qr/$COLOR|\s/; @@ -72,7 +74,7 @@ sub handle_line { (?:$COLOR?\|$COLOR?[ ])* # zero or more trailing "|" [ ]* # trailing whitespace for merges /x) { - my $graph_prefix = $&; + my $graph_prefix = $&; # We must flush before setting graph indent, since the # new commit may be indented differently from what we diff --git a/scripts/diff-so-fancy b/scripts/diff-so-fancy index 4df0aad..ebd0b7e 100755 --- a/scripts/diff-so-fancy +++ b/scripts/diff-so-fancy @@ -1,346 +1,15 @@ #!/usr/bin/env perl -# This chunk of stuff was generated by App::FatPacker. To find the original -# file's code, look for the end of this BEGIN block or the string 'FATPACK' -BEGIN { -my %fatpacked; - -$fatpacked{"DiffHighlight.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'DIFFHIGHLIGHT'; - package DiffHighlight; - - use 5.008; - use warnings FATAL => 'all'; - use strict; - - # Use the correct value for both UNIX and Windows (/dev/null vs nul) - use File::Spec; - - my $NULL = File::Spec->devnull(); - - # Highlight by reversing foreground and background. You could do - # other things like bold or underline if you prefer. - our @OLD_HIGHLIGHT = ( - color_config('color.diff-highlight.oldnormal', "\e[1;31m"), - color_config('color.diff-highlight.oldhighlight', "\e[1;31;48;5;52m"), - "\x1b[27m", - ); - our @NEW_HIGHLIGHT = ( - color_config('color.diff-highlight.newnormal', "\e[1;32m"), - color_config('color.diff-highlight.newhighlight', "\e[1;32;48;5;22m"), - $OLD_HIGHLIGHT[2], - ); - - - - my $RESET = "\x1b[m"; - my $COLOR = qr/\x1b\[[0-9;]*m/; - my $BORING = qr/$COLOR|\s/; - - my @removed; - my @added; - my $in_hunk; - my $graph_indent = 0; - - our $line_cb = sub { print @_ }; - our $flush_cb = sub { local $| = 1 }; - - # Count the visible width of a string, excluding any terminal color sequences. - sub visible_width { - local $_ = shift; - my $ret = 0; - while (length) { - if (s/^$COLOR//) { - # skip colors - } elsif (s/^.//) { - $ret++; - } - } - return $ret; - } - - # Return a substring of $str, omitting $len visible characters from the - # beginning, where terminal color sequences do not count as visible. - sub visible_substr { - my ($str, $len) = @_; - while ($len > 0) { - if ($str =~ s/^$COLOR//) { - next - } - $str =~ s/^.//; - $len--; - } - return $str; - } - - sub handle_line { - my $orig = shift; - local $_ = $orig; - - # match a graph line that begins a commit - if (/^(?:$COLOR?\|$COLOR?[ ])* # zero or more leading "|" with space - $COLOR?\*$COLOR?[ ] # a "*" with its trailing space - (?:$COLOR?\|$COLOR?[ ])* # zero or more trailing "|" - [ ]* # trailing whitespace for merges - /x) { - my $graph_prefix = $&; - - # We must flush before setting graph indent, since the - # new commit may be indented differently from what we - # queued. - flush(); - $graph_indent = visible_width($graph_prefix); - - } elsif ($graph_indent) { - if (length($_) < $graph_indent) { - $graph_indent = 0; - } else { - $_ = visible_substr($_, $graph_indent); - } - } - - if (!$in_hunk) { - $line_cb->($orig); - $in_hunk = /^$COLOR*\@\@ /; - } - elsif (/^$COLOR*-/) { - push @removed, $orig; - } - elsif (/^$COLOR*\+/) { - push @added, $orig; - } - else { - flush(); - $line_cb->($orig); - $in_hunk = /^$COLOR*[\@ ]/; - } - - # Most of the time there is enough output to keep things streaming, - # but for something like "git log -Sfoo", you can get one early - # commit and then many seconds of nothing. We want to show - # that one commit as soon as possible. - # - # Since we can receive arbitrary input, there's no optimal - # place to flush. Flushing on a blank line is a heuristic that - # happens to match git-log output. - if (!length) { - $flush_cb->(); - } - } - - sub flush { - # Flush any queued hunk (this can happen when there is no trailing - # context in the final diff of the input). - show_hunk(\@removed, \@added); - @removed = (); - @added = (); - } - - sub highlight_stdin { - while () { - handle_line($_); - } - flush(); - } - - # Ideally we would feed the default as a human-readable color to - # git-config as the fallback value. But diff-highlight does - # not otherwise depend on git at all, and there are reports - # of it being used in other settings. Let's handle our own - # fallback, which means we will work even if git can't be run. - sub color_config { - my ($key, $default) = @_; - my $s = `git config --get-color $key 2>$NULL`; - return length($s) ? $s : $default; - } - - sub show_hunk { - my ($a, $b) = @_; - - # If one side is empty, then there is nothing to compare or highlight. - if (!@$a || !@$b) { - $line_cb->(@$a, @$b); - return; - } - - # If we have mismatched numbers of lines on each side, we could try to - # be clever and match up similar lines. But for now we are simple and - # stupid, and only handle multi-line hunks that remove and add the same - # number of lines. - if (@$a != @$b) { - $line_cb->(@$a, @$b); - return; - } - - my @queue; - for (my $i = 0; $i < @$a; $i++) { - my ($rm, $add) = highlight_pair($a->[$i], $b->[$i]); - $line_cb->($rm); - push @queue, $add; - } - $line_cb->(@queue); - } - - sub highlight_pair { - my @a = split_line(shift); - my @b = split_line(shift); - - # Find common prefix, taking care to skip any ansi - # color codes. - my $seen_plusminus; - my ($pa, $pb) = (0, 0); - while ($pa < @a && $pb < @b) { - if ($a[$pa] =~ /$COLOR/) { - $pa++; - } - elsif ($b[$pb] =~ /$COLOR/) { - $pb++; - } - elsif ($a[$pa] eq $b[$pb]) { - $pa++; - $pb++; - } - elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') { - $seen_plusminus = 1; - $pa++; - $pb++; - } - else { - last; - } - } - - # Find common suffix, ignoring colors. - my ($sa, $sb) = ($#a, $#b); - while ($sa >= $pa && $sb >= $pb) { - if ($a[$sa] =~ /$COLOR/) { - $sa--; - } - elsif ($b[$sb] =~ /$COLOR/) { - $sb--; - } - elsif ($a[$sa] eq $b[$sb]) { - $sa--; - $sb--; - } - else { - last; - } - } - - if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) { - return highlight_line(\@a, $pa, $sa, \@OLD_HIGHLIGHT), - highlight_line(\@b, $pb, $sb, \@NEW_HIGHLIGHT); - } - else { - return join('', @a), - join('', @b); - } - } - - # we split either by $COLOR or by character. This has the side effect of - # leaving in graph cruft. It works because the graph cruft does not contain "-" - # or "+" - sub split_line { - local $_ = shift; - return utf8::decode($_) ? - map { utf8::encode($_); $_ } - map { /$COLOR/ ? $_ : (split //) } - split /($COLOR+)/ : - map { /$COLOR/ ? $_ : (split //) } - split /($COLOR+)/; - } - - sub highlight_line { - my ($line, $prefix, $suffix, $theme) = @_; - - my $start = join('', @{$line}[0..($prefix-1)]); - my $mid = join('', @{$line}[$prefix..$suffix]); - my $end = join('', @{$line}[($suffix+1)..$#$line]); - - # If we have a "normal" color specified, then take over the whole line. - # Otherwise, we try to just manipulate the highlighted bits. - if (defined $theme->[0]) { - s/$COLOR//g for ($start, $mid, $end); - chomp $end; - return join('', - $theme->[0], $start, $RESET, - $theme->[1], $mid, $RESET, - $theme->[0], $end, $RESET, - "\n" - ); - } else { - return join('', - $start, - $theme->[1], $mid, $theme->[2], - $end - ); - } - } - - # Pairs are interesting to highlight only if we are going to end up - # highlighting a subset (i.e., not the whole line). Otherwise, the highlighting - # is just useless noise. We can detect this by finding either a matching prefix - # or suffix (disregarding boring bits like whitespace and colorization). - sub is_pair_interesting { - my ($a, $pa, $sa, $b, $pb, $sb) = @_; - my $prefix_a = join('', @$a[0..($pa-1)]); - my $prefix_b = join('', @$b[0..($pb-1)]); - my $suffix_a = join('', @$a[($sa+1)..$#$a]); - my $suffix_b = join('', @$b[($sb+1)..$#$b]); - - return visible_substr($prefix_a, $graph_indent) !~ /^$COLOR*-$BORING*$/ || - visible_substr($prefix_b, $graph_indent) !~ /^$COLOR*\+$BORING*$/ || - $suffix_a !~ /^$BORING*$/ || - $suffix_b !~ /^$BORING*$/; - } -DIFFHIGHLIGHT - -s/^ //mg for values %fatpacked; - -my $class = 'FatPacked::'.(0+\%fatpacked); -no strict 'refs'; -*{"${class}::files"} = sub { keys %{$_[0]} }; - -if ($] < 5.008) { - *{"${class}::INC"} = sub { - if (my $fat = $_[0]{$_[1]}) { - my $pos = 0; - my $last = length $fat; - return (sub { - return 0 if $pos == $last; - my $next = (1 + index $fat, "\n", $pos) || $last; - $_ .= substr $fat, $pos, $next - $pos; - $pos = $next; - return 1; - }); - } - }; -} - -else { - *{"${class}::INC"} = sub { - if (my $fat = $_[0]{$_[1]}) { - open my $fh, '<', \$fat - or die "FatPacker error loading $_[1] (could be a perl installation issue?)"; - return $fh; - } - return; - }; -} - -unshift @INC, bless \%fatpacked, $class; - } # END OF FATPACK CODE - - -my $VERSION = "1.2.6"; +my $VERSION = "1.3.0"; ################################################################################# +use 5.010; # Require Perl 5.10 for 'state' variables use File::Spec; # For catdir use File::Basename; # For dirname use Encode; # For handling UTF8 stuff use Cwd qw(abs_path); # For realpath() -use lib dirname(abs_path(File::Spec->catdir($0))) . "/lib"; # Add the local lib/ to @INC +use lib dirname(abs_path(File::Spec->catdir($0))) . "/"; # Add the local lib/ to @INC use DiffHighlight; use strict; @@ -497,7 +166,7 @@ sub do_dsf_stuff { # Look for the filename # ######################### # $4 $5 - } elsif ($line =~ /^${ansi_color_regex}diff (-r|--git|--cc) (.+?)(\s|\e|$)/) { + } elsif ($line =~ /^${ansi_color_regex}diff (-r|--git|--cc) (.*?)(\e| b\/|$)/) { # Mercurial looks like: diff -r 82e55d328c8c hello.c if ($4 eq "-r") { @@ -557,8 +226,13 @@ sub do_dsf_stuff { ######################################## # Check for "@@ -3,41 +3,63 @@" syntax # ######################################## + } elsif (!$change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { + $in_hunk = 1; + + print $line; } elsif ($change_hunk_indicators && $line =~ /^${ansi_color_regex}(@@@* .+? @@@*)(.*)/) { - $in_hunk = 1; + $in_hunk = 1; + my $hunk_header = $4; my $remain = bleach_text($5); @@ -580,7 +254,21 @@ sub do_dsf_stuff { my $start_line = start_line_calc($new_offset,$new_count); # Last function has it's own color - my $last_function_color = get_config_color("last_function"); + my $last_function_color = ""; + if ($remain) { + $last_function_color = get_config_color("last_function"); + } + + # Check to see if we have the color for the fragment from git + if ($5 =~ /\e\[\d/) { + #print "Has ANSI color for fragment\n"; + } else { + # We don't have the ANSI sequence so we shell out to get it + #print "No ANSI color for fragment\n"; + my $frag_color = get_config_color("fragment"); + print $frag_color; + } + print "@ $last_file_seen:$start_line \@${bold}${last_function_color}${remain}${reset_color}\n"; ################################### # Remove any new file permissions # @@ -688,8 +376,19 @@ sub mark_empty_line { my $reset_color = "\e\\[0?m"; my $reset_escape = "\e\[m"; my $invert_color = "\e\[7m"; - - $line =~ s/^($ansi_color_regex)[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/; + my $add_color = $DiffHighlight::NEW_HIGHLIGHT[1]; + my $del_color = $DiffHighlight::OLD_HIGHLIGHT[1]; + + # This captures lines that do not have any ANSI in them (raw vanilla diff) + if ($line eq "+\n") { + $line = $invert_color . $add_color . " " . color('reset') . "\n"; + # This captures lines that do not have any ANSI in them (raw vanilla diff) + } elsif ($line eq "-\n") { + $line = $invert_color . $del_color . " " . color('reset') . "\n"; + # This handles everything else + } else { + $line =~ s/^($ansi_color_regex)[+-]$reset_color\s*$/$invert_color$1 $reset_escape\n/; + } return $line; } @@ -890,11 +589,7 @@ sub trim { # Print a line of em-dash or line-drawing chars the full width of the screen sub horizontal_rule { my $color = $_[0] || ""; - my $width = $ruler_width || `tput cols`; - - if (is_windows()) { - $width--; - } + my $width = get_terminal_width(); # em-dash http://www.fileformat.info/info/unicode/char/2014/index.htm #my $dash = "\x{2014}"; @@ -907,7 +602,7 @@ sub horizontal_rule { } # Draw the line - my $ret = $color . ($dash x $width) . "\n"; + my $ret = $color . ($dash x $width) . "$reset_color\n"; return $ret; } @@ -984,9 +679,12 @@ sub usage { $out .= "Usage: -git diff --color | diff-so-fancy # Use d-s-f on one diff -diff-so-fancy --colors # View the commands to set the recommended colors -diff-so-fancy --set-defaults # Configure git-diff to use diff-so-fancy and suggested colors +git diff --color | diff-so-fancy # Use d-s-f on one diff +cat diff.txt | diff-so-fancy # Use d-s-f on a diff/patch file +diff -u one.txt two.txt | diff-so-fancy # Use d-s-f on unified diff output + +diff-so-fancy --colors # View the commands to set the recommended colors +diff-so-fancy --set-defaults # Configure git-diff to use diff-so-fancy and suggested colors # Configure git to use d-s-f for *all* diff operations git config --global core.pager \"diff-so-fancy | less --tabs=4 -RFX\"\n"; @@ -1130,6 +828,8 @@ sub color { return $static_config->{$str}; } + #print color(15) . "Shelling out for color: '$str'\n" . color('reset'); + if ($str eq "meta") { # Default ANSI yellow $ret = DiffHighlight::color_config('color.diff.meta', color(11)); @@ -1141,8 +841,10 @@ sub color { } elsif ($str eq "remove_line") { # Default ANSI red $ret = DiffHighlight::color_config('color.diff.old', color('bold') . color(1)); + } elsif ($str eq "fragment") { + $ret = DiffHighlight::color_config('color.diff.frag', color('13_bold')); } elsif ($str eq "last_function") { - $ret = DiffHighlight::color_config('color.diff.func', color(146)); + $ret = DiffHighlight::color_config('color.diff.func', color('bold') . color(146)); } # Cache (memoize) the entry for later @@ -1162,4 +864,34 @@ sub starts_with_ansi { } } +sub get_terminal_width { + # Make width static so we only calculate it once + state $width; + + if ($width) { + return $width; + } + + # If there is a ruler width in the config we use that + if ($ruler_width) { + $width = $ruler_width; + # Otherwise we check the terminal width using tput + } else { + my $tput = `tput cols`; + + if ($tput) { + $width = int($tput); + + if (is_windows()) { + $width--; + } + } else { + print color('orange') . "Warning: `tput cols` did not return numeric input" . color('reset') . "\n"; + $width = 80; + } + } + + return $width; +} + # vim: tabstop=4 shiftwidth=4 noexpandtab autoindent softtabstop=4