Merge branch 'mk/gitweb-diff-hl'
"gitweb" learns to highlight the patch it outputs even more. By Michał Kiedrowicz (7) and Jakub Narębski (1) * mk/gitweb-diff-hl: gitweb: Refinement highlightning in combined diffs gitweb: Highlight interesting parts of diff gitweb: Push formatting diff lines to print_diff_chunk() gitweb: Use print_diff_chunk() for both side-by-side and inline diffs gitweb: Extract print_sidebyside_diff_lines() gitweb: Pass esc_html_hl_regions() options to esc_html() gitweb: esc_html_hl_regions(): Don't create empty <span> elements gitweb: Use descriptive names in esc_html_hl_regions()
This commit is contained in:
commit
c6cfa5bbbb
@ -1732,20 +1732,29 @@ sub chop_and_escape_str {
|
|||||||
# '<span class="mark">foo</span>bar'
|
# '<span class="mark">foo</span>bar'
|
||||||
sub esc_html_hl_regions {
|
sub esc_html_hl_regions {
|
||||||
my ($str, $css_class, @sel) = @_;
|
my ($str, $css_class, @sel) = @_;
|
||||||
return esc_html($str) unless @sel;
|
my %opts = grep { ref($_) ne 'ARRAY' } @sel;
|
||||||
|
@sel = grep { ref($_) eq 'ARRAY' } @sel;
|
||||||
|
return esc_html($str, %opts) unless @sel;
|
||||||
|
|
||||||
my $out = '';
|
my $out = '';
|
||||||
my $pos = 0;
|
my $pos = 0;
|
||||||
|
|
||||||
for my $s (@sel) {
|
for my $s (@sel) {
|
||||||
$out .= esc_html(substr($str, $pos, $s->[0] - $pos))
|
my ($begin, $end) = @$s;
|
||||||
if ($s->[0] - $pos > 0);
|
|
||||||
$out .= $cgi->span({-class => $css_class},
|
|
||||||
esc_html(substr($str, $s->[0], $s->[1] - $s->[0])));
|
|
||||||
|
|
||||||
$pos = $s->[1];
|
# Don't create empty <span> elements.
|
||||||
|
next if $end <= $begin;
|
||||||
|
|
||||||
|
my $escaped = esc_html(substr($str, $begin, $end - $begin),
|
||||||
|
%opts);
|
||||||
|
|
||||||
|
$out .= esc_html(substr($str, $pos, $begin - $pos), %opts)
|
||||||
|
if ($begin - $pos > 0);
|
||||||
|
$out .= $cgi->span({-class => $css_class}, $escaped);
|
||||||
|
|
||||||
|
$pos = $end;
|
||||||
}
|
}
|
||||||
$out .= esc_html(substr($str, $pos))
|
$out .= esc_html(substr($str, $pos), %opts)
|
||||||
if ($pos < length($str));
|
if ($pos < length($str));
|
||||||
|
|
||||||
return $out;
|
return $out;
|
||||||
@ -2421,26 +2430,32 @@ sub format_cc_diff_chunk_header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# process patch (diff) line (not to be used for diff headers),
|
# process patch (diff) line (not to be used for diff headers),
|
||||||
# returning class and HTML-formatted (but not wrapped) line
|
# returning HTML-formatted (but not wrapped) line.
|
||||||
sub process_diff_line {
|
# If the line is passed as a reference, it is treated as HTML and not
|
||||||
my $line = shift;
|
# esc_html()'ed.
|
||||||
my ($from, $to) = @_;
|
sub format_diff_line {
|
||||||
|
my ($line, $diff_class, $from, $to) = @_;
|
||||||
|
|
||||||
my $diff_class = diff_line_class($line, $from, $to);
|
if (ref($line)) {
|
||||||
|
$line = $$line;
|
||||||
chomp $line;
|
} else {
|
||||||
$line = untabify($line);
|
chomp $line;
|
||||||
|
$line = untabify($line);
|
||||||
if ($from && $to && $line =~ m/^\@{2} /) {
|
|
||||||
$line = format_unidiff_chunk_header($line, $from, $to);
|
|
||||||
return $diff_class, $line;
|
|
||||||
|
|
||||||
} elsif ($from && $to && $line =~ m/^\@{3}/) {
|
|
||||||
$line = format_cc_diff_chunk_header($line, $from, $to);
|
|
||||||
return $diff_class, $line;
|
|
||||||
|
|
||||||
|
if ($from && $to && $line =~ m/^\@{2} /) {
|
||||||
|
$line = format_unidiff_chunk_header($line, $from, $to);
|
||||||
|
} elsif ($from && $to && $line =~ m/^\@{3}/) {
|
||||||
|
$line = format_cc_diff_chunk_header($line, $from, $to);
|
||||||
|
} else {
|
||||||
|
$line = esc_html($line, -nbsp=>1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return $diff_class, esc_html($line, -nbsp=>1);
|
|
||||||
|
my $diff_classes = "diff";
|
||||||
|
$diff_classes .= " $diff_class" if ($diff_class);
|
||||||
|
$line = "<div class=\"$diff_classes\">$line</div>\n";
|
||||||
|
|
||||||
|
return $line;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
|
# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
|
||||||
@ -4994,10 +5009,186 @@ sub git_difftree_body {
|
|||||||
print "</table>\n";
|
print "</table>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
sub print_sidebyside_diff_chunk {
|
# Print context lines and then rem/add lines in a side-by-side manner.
|
||||||
my @chunk = @_;
|
sub print_sidebyside_diff_lines {
|
||||||
|
my ($ctx, $rem, $add) = @_;
|
||||||
|
|
||||||
|
# print context block before add/rem block
|
||||||
|
if (@$ctx) {
|
||||||
|
print join '',
|
||||||
|
'<div class="chunk_block ctx">',
|
||||||
|
'<div class="old">',
|
||||||
|
@$ctx,
|
||||||
|
'</div>',
|
||||||
|
'<div class="new">',
|
||||||
|
@$ctx,
|
||||||
|
'</div>',
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!@$add) {
|
||||||
|
# pure removal
|
||||||
|
print join '',
|
||||||
|
'<div class="chunk_block rem">',
|
||||||
|
'<div class="old">',
|
||||||
|
@$rem,
|
||||||
|
'</div>',
|
||||||
|
'</div>';
|
||||||
|
} elsif (!@$rem) {
|
||||||
|
# pure addition
|
||||||
|
print join '',
|
||||||
|
'<div class="chunk_block add">',
|
||||||
|
'<div class="new">',
|
||||||
|
@$add,
|
||||||
|
'</div>',
|
||||||
|
'</div>';
|
||||||
|
} else {
|
||||||
|
print join '',
|
||||||
|
'<div class="chunk_block chg">',
|
||||||
|
'<div class="old">',
|
||||||
|
@$rem,
|
||||||
|
'</div>',
|
||||||
|
'<div class="new">',
|
||||||
|
@$add,
|
||||||
|
'</div>',
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print context lines and then rem/add lines in inline manner.
|
||||||
|
sub print_inline_diff_lines {
|
||||||
|
my ($ctx, $rem, $add) = @_;
|
||||||
|
|
||||||
|
print @$ctx, @$rem, @$add;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Format removed and added line, mark changed part and HTML-format them.
|
||||||
|
# Implementation is based on contrib/diff-highlight
|
||||||
|
sub format_rem_add_lines_pair {
|
||||||
|
my ($rem, $add, $num_parents) = @_;
|
||||||
|
|
||||||
|
# We need to untabify lines before split()'ing them;
|
||||||
|
# otherwise offsets would be invalid.
|
||||||
|
chomp $rem;
|
||||||
|
chomp $add;
|
||||||
|
$rem = untabify($rem);
|
||||||
|
$add = untabify($add);
|
||||||
|
|
||||||
|
my @rem = split(//, $rem);
|
||||||
|
my @add = split(//, $add);
|
||||||
|
my ($esc_rem, $esc_add);
|
||||||
|
# Ignore leading +/- characters for each parent.
|
||||||
|
my ($prefix_len, $suffix_len) = ($num_parents, 0);
|
||||||
|
my ($prefix_has_nonspace, $suffix_has_nonspace);
|
||||||
|
|
||||||
|
my $shorter = (@rem < @add) ? @rem : @add;
|
||||||
|
while ($prefix_len < $shorter) {
|
||||||
|
last if ($rem[$prefix_len] ne $add[$prefix_len]);
|
||||||
|
|
||||||
|
$prefix_has_nonspace = 1 if ($rem[$prefix_len] !~ /\s/);
|
||||||
|
$prefix_len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ($prefix_len + $suffix_len < $shorter) {
|
||||||
|
last if ($rem[-1 - $suffix_len] ne $add[-1 - $suffix_len]);
|
||||||
|
|
||||||
|
$suffix_has_nonspace = 1 if ($rem[-1 - $suffix_len] !~ /\s/);
|
||||||
|
$suffix_len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mark lines that are different from each other, but have some common
|
||||||
|
# part that isn't whitespace. If lines are completely different, don't
|
||||||
|
# mark them because that would make output unreadable, especially if
|
||||||
|
# diff consists of multiple lines.
|
||||||
|
if ($prefix_has_nonspace || $suffix_has_nonspace) {
|
||||||
|
$esc_rem = esc_html_hl_regions($rem, 'marked',
|
||||||
|
[$prefix_len, @rem - $suffix_len], -nbsp=>1);
|
||||||
|
$esc_add = esc_html_hl_regions($add, 'marked',
|
||||||
|
[$prefix_len, @add - $suffix_len], -nbsp=>1);
|
||||||
|
} else {
|
||||||
|
$esc_rem = esc_html($rem, -nbsp=>1);
|
||||||
|
$esc_add = esc_html($add, -nbsp=>1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return format_diff_line(\$esc_rem, 'rem'),
|
||||||
|
format_diff_line(\$esc_add, 'add');
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTML-format diff context, removed and added lines.
|
||||||
|
sub format_ctx_rem_add_lines {
|
||||||
|
my ($ctx, $rem, $add, $num_parents) = @_;
|
||||||
|
my (@new_ctx, @new_rem, @new_add);
|
||||||
|
my $can_highlight = 0;
|
||||||
|
my $is_combined = ($num_parents > 1);
|
||||||
|
|
||||||
|
# Highlight if every removed line has a corresponding added line.
|
||||||
|
if (@$add > 0 && @$add == @$rem) {
|
||||||
|
$can_highlight = 1;
|
||||||
|
|
||||||
|
# Highlight lines in combined diff only if the chunk contains
|
||||||
|
# diff between the same version, e.g.
|
||||||
|
#
|
||||||
|
# - a
|
||||||
|
# - b
|
||||||
|
# + c
|
||||||
|
# + d
|
||||||
|
#
|
||||||
|
# Otherwise the highlightling would be confusing.
|
||||||
|
if ($is_combined) {
|
||||||
|
for (my $i = 0; $i < @$add; $i++) {
|
||||||
|
my $prefix_rem = substr($rem->[$i], 0, $num_parents);
|
||||||
|
my $prefix_add = substr($add->[$i], 0, $num_parents);
|
||||||
|
|
||||||
|
$prefix_rem =~ s/-/+/g;
|
||||||
|
|
||||||
|
if ($prefix_rem ne $prefix_add) {
|
||||||
|
$can_highlight = 0;
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($can_highlight) {
|
||||||
|
for (my $i = 0; $i < @$add; $i++) {
|
||||||
|
my ($line_rem, $line_add) = format_rem_add_lines_pair(
|
||||||
|
$rem->[$i], $add->[$i], $num_parents);
|
||||||
|
push @new_rem, $line_rem;
|
||||||
|
push @new_add, $line_add;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@new_rem = map { format_diff_line($_, 'rem') } @$rem;
|
||||||
|
@new_add = map { format_diff_line($_, 'add') } @$add;
|
||||||
|
}
|
||||||
|
|
||||||
|
@new_ctx = map { format_diff_line($_, 'ctx') } @$ctx;
|
||||||
|
|
||||||
|
return (\@new_ctx, \@new_rem, \@new_add);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print context lines and then rem/add lines.
|
||||||
|
sub print_diff_lines {
|
||||||
|
my ($ctx, $rem, $add, $diff_style, $num_parents) = @_;
|
||||||
|
my $is_combined = $num_parents > 1;
|
||||||
|
|
||||||
|
($ctx, $rem, $add) = format_ctx_rem_add_lines($ctx, $rem, $add,
|
||||||
|
$num_parents);
|
||||||
|
|
||||||
|
if ($diff_style eq 'sidebyside' && !$is_combined) {
|
||||||
|
print_sidebyside_diff_lines($ctx, $rem, $add);
|
||||||
|
} else {
|
||||||
|
# default 'inline' style and unknown styles
|
||||||
|
print_inline_diff_lines($ctx, $rem, $add);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub print_diff_chunk {
|
||||||
|
my ($diff_style, $num_parents, $from, $to, @chunk) = @_;
|
||||||
my (@ctx, @rem, @add);
|
my (@ctx, @rem, @add);
|
||||||
|
|
||||||
|
# The class of the previous line.
|
||||||
|
my $prev_class = '';
|
||||||
|
|
||||||
return unless @chunk;
|
return unless @chunk;
|
||||||
|
|
||||||
# incomplete last line might be among removed or added lines,
|
# incomplete last line might be among removed or added lines,
|
||||||
@ -5016,55 +5207,19 @@ sub print_sidebyside_diff_chunk {
|
|||||||
|
|
||||||
# print chunk headers
|
# print chunk headers
|
||||||
if ($class && $class eq 'chunk_header') {
|
if ($class && $class eq 'chunk_header') {
|
||||||
print $line;
|
print format_diff_line($line, $class, $from, $to);
|
||||||
next;
|
next;
|
||||||
}
|
}
|
||||||
|
|
||||||
## print from accumulator when type of class of lines change
|
## print from accumulator when have some add/rem lines or end
|
||||||
# empty contents block on start rem/add block, or end of chunk
|
# of chunk (flush context lines), or when have add and rem
|
||||||
if (@ctx && (!$class || $class eq 'rem' || $class eq 'add')) {
|
# lines and new block is reached (otherwise add/rem lines could
|
||||||
print join '',
|
# be reordered)
|
||||||
'<div class="chunk_block ctx">',
|
if (!$class || ((@rem || @add) && $class eq 'ctx') ||
|
||||||
'<div class="old">',
|
(@rem && @add && $class ne $prev_class)) {
|
||||||
@ctx,
|
print_diff_lines(\@ctx, \@rem, \@add,
|
||||||
'</div>',
|
$diff_style, $num_parents);
|
||||||
'<div class="new">',
|
@ctx = @rem = @add = ();
|
||||||
@ctx,
|
|
||||||
'</div>',
|
|
||||||
'</div>';
|
|
||||||
@ctx = ();
|
|
||||||
}
|
|
||||||
# empty add/rem block on start context block, or end of chunk
|
|
||||||
if ((@rem || @add) && (!$class || $class eq 'ctx')) {
|
|
||||||
if (!@add) {
|
|
||||||
# pure removal
|
|
||||||
print join '',
|
|
||||||
'<div class="chunk_block rem">',
|
|
||||||
'<div class="old">',
|
|
||||||
@rem,
|
|
||||||
'</div>',
|
|
||||||
'</div>';
|
|
||||||
} elsif (!@rem) {
|
|
||||||
# pure addition
|
|
||||||
print join '',
|
|
||||||
'<div class="chunk_block add">',
|
|
||||||
'<div class="new">',
|
|
||||||
@add,
|
|
||||||
'</div>',
|
|
||||||
'</div>';
|
|
||||||
} else {
|
|
||||||
# assume that it is change
|
|
||||||
print join '',
|
|
||||||
'<div class="chunk_block chg">',
|
|
||||||
'<div class="old">',
|
|
||||||
@rem,
|
|
||||||
'</div>',
|
|
||||||
'<div class="new">',
|
|
||||||
@add,
|
|
||||||
'</div>',
|
|
||||||
'</div>';
|
|
||||||
}
|
|
||||||
@rem = @add = ();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
## adding lines to accumulator
|
## adding lines to accumulator
|
||||||
@ -5080,6 +5235,8 @@ sub print_sidebyside_diff_chunk {
|
|||||||
if ($class eq 'ctx') {
|
if ($class eq 'ctx') {
|
||||||
push @ctx, $line;
|
push @ctx, $line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$prev_class = $class;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5201,27 +5358,19 @@ sub git_patchset_body {
|
|||||||
|
|
||||||
next PATCH if ($patch_line =~ m/^diff /);
|
next PATCH if ($patch_line =~ m/^diff /);
|
||||||
|
|
||||||
my ($class, $line) = process_diff_line($patch_line, \%from, \%to);
|
my $class = diff_line_class($patch_line, \%from, \%to);
|
||||||
my $diff_classes = "diff";
|
|
||||||
$diff_classes .= " $class" if ($class);
|
|
||||||
$line = "<div class=\"$diff_classes\">$line</div>\n";
|
|
||||||
|
|
||||||
if ($diff_style eq 'sidebyside' && !$is_combined) {
|
if ($class eq 'chunk_header') {
|
||||||
if ($class eq 'chunk_header') {
|
print_diff_chunk($diff_style, scalar @hash_parents, \%from, \%to, @chunk);
|
||||||
print_sidebyside_diff_chunk(@chunk);
|
@chunk = ();
|
||||||
@chunk = ( [ $class, $line ] );
|
|
||||||
} else {
|
|
||||||
push @chunk, [ $class, $line ];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
# default 'inline' style and unknown styles
|
|
||||||
print $line;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
push @chunk, [ $class, $patch_line ];
|
||||||
}
|
}
|
||||||
|
|
||||||
} continue {
|
} continue {
|
||||||
if (@chunk) {
|
if (@chunk) {
|
||||||
print_sidebyside_diff_chunk(@chunk);
|
print_diff_chunk($diff_style, scalar @hash_parents, \%from, \%to, @chunk);
|
||||||
@chunk = ();
|
@chunk = ();
|
||||||
}
|
}
|
||||||
print "</div>\n"; # class="patch"
|
print "</div>\n"; # class="patch"
|
||||||
|
@ -438,6 +438,10 @@ div.diff.add {
|
|||||||
color: #008800;
|
color: #008800;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.diff.add span.marked {
|
||||||
|
background-color: #aaffaa;
|
||||||
|
}
|
||||||
|
|
||||||
div.diff.from_file a.path,
|
div.diff.from_file a.path,
|
||||||
div.diff.from_file {
|
div.diff.from_file {
|
||||||
color: #aa0000;
|
color: #aa0000;
|
||||||
@ -447,6 +451,10 @@ div.diff.rem {
|
|||||||
color: #cc0000;
|
color: #cc0000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.diff.rem span.marked {
|
||||||
|
background-color: #ffaaaa;
|
||||||
|
}
|
||||||
|
|
||||||
div.diff.chunk_header a,
|
div.diff.chunk_header a,
|
||||||
div.diff.chunk_header {
|
div.diff.chunk_header {
|
||||||
color: #990099;
|
color: #990099;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user