@@ -1732,20 +1732,29 @@ sub chop_and_escape_str {
1732
1732
# '<span class="mark">foo</span>bar'
1733
1733
sub esc_html_hl_regions {
1734
1734
my ($str , $css_class , @sel ) = @_ ;
1735
- return esc_html($str ) unless @sel ;
1735
+ my %opts = grep { ref ($_ ) ne ' ARRAY' } @sel ;
1736
+ @sel = grep { ref ($_ ) eq ' ARRAY' } @sel ;
1737
+ return esc_html($str , %opts ) unless @sel ;
1736
1738
1737
1739
my $out = ' ' ;
1738
1740
my $pos = 0;
1739
1741
1740
1742
for my $s (@sel ) {
1741
- $out .= esc_html(substr ($str , $pos , $s -> [0] - $pos ))
1742
- if ($s -> [0] - $pos > 0);
1743
- $out .= $cgi -> span({-class => $css_class },
1744
- esc_html(substr ($str , $s -> [0], $s -> [1] - $s -> [0])));
1743
+ my ($begin , $end ) = @$s ;
1745
1744
1746
- $pos = $s -> [1];
1745
+ # Don't create empty <span> elements.
1746
+ next if $end <= $begin ;
1747
+
1748
+ my $escaped = esc_html(substr ($str , $begin , $end - $begin ),
1749
+ %opts );
1750
+
1751
+ $out .= esc_html(substr ($str , $pos , $begin - $pos ), %opts )
1752
+ if ($begin - $pos > 0);
1753
+ $out .= $cgi -> span({-class => $css_class }, $escaped );
1754
+
1755
+ $pos = $end ;
1747
1756
}
1748
- $out .= esc_html(substr ($str , $pos ))
1757
+ $out .= esc_html(substr ($str , $pos ), %opts )
1749
1758
if ($pos < length ($str ));
1750
1759
1751
1760
return $out ;
@@ -2421,26 +2430,32 @@ sub format_cc_diff_chunk_header {
2421
2430
}
2422
2431
2423
2432
# process patch (diff) line (not to be used for diff headers),
2424
- # returning class and HTML-formatted (but not wrapped) line
2425
- sub process_diff_line {
2426
- my $line = shift ;
2427
- my ($from , $to ) = @_ ;
2428
-
2429
- my $diff_class = diff_line_class($line , $from , $to );
2430
-
2431
- chomp $line ;
2432
- $line = untabify($line );
2433
+ # returning HTML-formatted (but not wrapped) line.
2434
+ # If the line is passed as a reference, it is treated as HTML and not
2435
+ # esc_html()'ed.
2436
+ sub format_diff_line {
2437
+ my ($line , $diff_class , $from , $to ) = @_ ;
2438
+
2439
+ if (ref ($line )) {
2440
+ $line = $$line ;
2441
+ } else {
2442
+ chomp $line ;
2443
+ $line = untabify($line );
2433
2444
2434
- if ($from && $to && $line =~ m / ^\@ {2} / ) {
2435
- $line = format_unidiff_chunk_header($line , $from , $to );
2436
- return $diff_class , $line ;
2445
+ if ($from && $to && $line =~ m / ^\@ {2} / ) {
2446
+ $line = format_unidiff_chunk_header($line , $from , $to );
2447
+ } elsif ($from && $to && $line =~ m / ^\@ {3}/ ) {
2448
+ $line = format_cc_diff_chunk_header($line , $from , $to );
2449
+ } else {
2450
+ $line = esc_html($line , -nbsp => 1);
2451
+ }
2452
+ }
2437
2453
2438
- } elsif ( $from && $to && $line =~ m / ^ \@ {3} / ) {
2439
- $line = format_cc_diff_chunk_header( $line , $from , $to );
2440
- return $diff_class , $ line ;
2454
+ my $diff_classes = " diff " ;
2455
+ $diff_classes .= " $diff_class " if ( $diff_class );
2456
+ $line = " <div class= \" $diff_classes \" > $ line</div> \n " ;
2441
2457
2442
- }
2443
- return $diff_class , esc_html($line , -nbsp => 1);
2458
+ return $line ;
2444
2459
}
2445
2460
2446
2461
# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
@@ -4994,10 +5009,186 @@ sub git_difftree_body {
4994
5009
print " </table>\n " ;
4995
5010
}
4996
5011
4997
- sub print_sidebyside_diff_chunk {
4998
- my @chunk = @_ ;
5012
+ # Print context lines and then rem/add lines in a side-by-side manner.
5013
+ sub print_sidebyside_diff_lines {
5014
+ my ($ctx , $rem , $add ) = @_ ;
5015
+
5016
+ # print context block before add/rem block
5017
+ if (@$ctx ) {
5018
+ print join ' ' ,
5019
+ ' <div class="chunk_block ctx">' ,
5020
+ ' <div class="old">' ,
5021
+ @$ctx ,
5022
+ ' </div>' ,
5023
+ ' <div class="new">' ,
5024
+ @$ctx ,
5025
+ ' </div>' ,
5026
+ ' </div>' ;
5027
+ }
5028
+
5029
+ if (!@$add ) {
5030
+ # pure removal
5031
+ print join ' ' ,
5032
+ ' <div class="chunk_block rem">' ,
5033
+ ' <div class="old">' ,
5034
+ @$rem ,
5035
+ ' </div>' ,
5036
+ ' </div>' ;
5037
+ } elsif (!@$rem ) {
5038
+ # pure addition
5039
+ print join ' ' ,
5040
+ ' <div class="chunk_block add">' ,
5041
+ ' <div class="new">' ,
5042
+ @$add ,
5043
+ ' </div>' ,
5044
+ ' </div>' ;
5045
+ } else {
5046
+ print join ' ' ,
5047
+ ' <div class="chunk_block chg">' ,
5048
+ ' <div class="old">' ,
5049
+ @$rem ,
5050
+ ' </div>' ,
5051
+ ' <div class="new">' ,
5052
+ @$add ,
5053
+ ' </div>' ,
5054
+ ' </div>' ;
5055
+ }
5056
+ }
5057
+
5058
+ # Print context lines and then rem/add lines in inline manner.
5059
+ sub print_inline_diff_lines {
5060
+ my ($ctx , $rem , $add ) = @_ ;
5061
+
5062
+ print @$ctx , @$rem , @$add ;
5063
+ }
5064
+
5065
+ # Format removed and added line, mark changed part and HTML-format them.
5066
+ # Implementation is based on contrib/diff-highlight
5067
+ sub format_rem_add_lines_pair {
5068
+ my ($rem , $add , $num_parents ) = @_ ;
5069
+
5070
+ # We need to untabify lines before split()'ing them;
5071
+ # otherwise offsets would be invalid.
5072
+ chomp $rem ;
5073
+ chomp $add ;
5074
+ $rem = untabify($rem );
5075
+ $add = untabify($add );
5076
+
5077
+ my @rem = split (// , $rem );
5078
+ my @add = split (// , $add );
5079
+ my ($esc_rem , $esc_add );
5080
+ # Ignore leading +/- characters for each parent.
5081
+ my ($prefix_len , $suffix_len ) = ($num_parents , 0);
5082
+ my ($prefix_has_nonspace , $suffix_has_nonspace );
5083
+
5084
+ my $shorter = (@rem < @add ) ? @rem : @add ;
5085
+ while ($prefix_len < $shorter ) {
5086
+ last if ($rem [$prefix_len ] ne $add [$prefix_len ]);
5087
+
5088
+ $prefix_has_nonspace = 1 if ($rem [$prefix_len ] !~ / \s / );
5089
+ $prefix_len ++;
5090
+ }
5091
+
5092
+ while ($prefix_len + $suffix_len < $shorter ) {
5093
+ last if ($rem [-1 - $suffix_len ] ne $add [-1 - $suffix_len ]);
5094
+
5095
+ $suffix_has_nonspace = 1 if ($rem [-1 - $suffix_len ] !~ / \s / );
5096
+ $suffix_len ++;
5097
+ }
5098
+
5099
+ # Mark lines that are different from each other, but have some common
5100
+ # part that isn't whitespace. If lines are completely different, don't
5101
+ # mark them because that would make output unreadable, especially if
5102
+ # diff consists of multiple lines.
5103
+ if ($prefix_has_nonspace || $suffix_has_nonspace ) {
5104
+ $esc_rem = esc_html_hl_regions($rem , ' marked' ,
5105
+ [$prefix_len , @rem - $suffix_len ], -nbsp => 1);
5106
+ $esc_add = esc_html_hl_regions($add , ' marked' ,
5107
+ [$prefix_len , @add - $suffix_len ], -nbsp => 1);
5108
+ } else {
5109
+ $esc_rem = esc_html($rem , -nbsp => 1);
5110
+ $esc_add = esc_html($add , -nbsp => 1);
5111
+ }
5112
+
5113
+ return format_diff_line(\$esc_rem , ' rem' ),
5114
+ format_diff_line(\$esc_add , ' add' );
5115
+ }
5116
+
5117
+ # HTML-format diff context, removed and added lines.
5118
+ sub format_ctx_rem_add_lines {
5119
+ my ($ctx , $rem , $add , $num_parents ) = @_ ;
5120
+ my (@new_ctx , @new_rem , @new_add );
5121
+ my $can_highlight = 0;
5122
+ my $is_combined = ($num_parents > 1);
5123
+
5124
+ # Highlight if every removed line has a corresponding added line.
5125
+ if (@$add > 0 && @$add == @$rem ) {
5126
+ $can_highlight = 1;
5127
+
5128
+ # Highlight lines in combined diff only if the chunk contains
5129
+ # diff between the same version, e.g.
5130
+ #
5131
+ # - a
5132
+ # - b
5133
+ # + c
5134
+ # + d
5135
+ #
5136
+ # Otherwise the highlightling would be confusing.
5137
+ if ($is_combined ) {
5138
+ for (my $i = 0; $i < @$add ; $i ++) {
5139
+ my $prefix_rem = substr ($rem -> [$i ], 0, $num_parents );
5140
+ my $prefix_add = substr ($add -> [$i ], 0, $num_parents );
5141
+
5142
+ $prefix_rem =~ s / -/ +/ g ;
5143
+
5144
+ if ($prefix_rem ne $prefix_add ) {
5145
+ $can_highlight = 0;
5146
+ last ;
5147
+ }
5148
+ }
5149
+ }
5150
+ }
5151
+
5152
+ if ($can_highlight ) {
5153
+ for (my $i = 0; $i < @$add ; $i ++) {
5154
+ my ($line_rem , $line_add ) = format_rem_add_lines_pair(
5155
+ $rem -> [$i ], $add -> [$i ], $num_parents );
5156
+ push @new_rem , $line_rem ;
5157
+ push @new_add , $line_add ;
5158
+ }
5159
+ } else {
5160
+ @new_rem = map { format_diff_line($_ , ' rem' ) } @$rem ;
5161
+ @new_add = map { format_diff_line($_ , ' add' ) } @$add ;
5162
+ }
5163
+
5164
+ @new_ctx = map { format_diff_line($_ , ' ctx' ) } @$ctx ;
5165
+
5166
+ return (\@new_ctx , \@new_rem , \@new_add );
5167
+ }
5168
+
5169
+ # Print context lines and then rem/add lines.
5170
+ sub print_diff_lines {
5171
+ my ($ctx , $rem , $add , $diff_style , $num_parents ) = @_ ;
5172
+ my $is_combined = $num_parents > 1;
5173
+
5174
+ ($ctx , $rem , $add ) = format_ctx_rem_add_lines($ctx , $rem , $add ,
5175
+ $num_parents );
5176
+
5177
+ if ($diff_style eq ' sidebyside' && !$is_combined ) {
5178
+ print_sidebyside_diff_lines($ctx , $rem , $add );
5179
+ } else {
5180
+ # default 'inline' style and unknown styles
5181
+ print_inline_diff_lines($ctx , $rem , $add );
5182
+ }
5183
+ }
5184
+
5185
+ sub print_diff_chunk {
5186
+ my ($diff_style , $num_parents , $from , $to , @chunk ) = @_ ;
4999
5187
my (@ctx , @rem , @add );
5000
5188
5189
+ # The class of the previous line.
5190
+ my $prev_class = ' ' ;
5191
+
5001
5192
return unless @chunk ;
5002
5193
5003
5194
# incomplete last line might be among removed or added lines,
@@ -5016,55 +5207,19 @@ sub print_sidebyside_diff_chunk {
5016
5207
5017
5208
# print chunk headers
5018
5209
if ($class && $class eq ' chunk_header' ) {
5019
- print $line ;
5210
+ print format_diff_line( $line , $class , $from , $to ) ;
5020
5211
next ;
5021
5212
}
5022
5213
5023
- # # print from accumulator when type of class of lines change
5024
- # empty contents block on start rem/add block, or end of chunk
5025
- if (@ctx && (!$class || $class eq ' rem' || $class eq ' add' )) {
5026
- print join ' ' ,
5027
- ' <div class="chunk_block ctx">' ,
5028
- ' <div class="old">' ,
5029
- @ctx ,
5030
- ' </div>' ,
5031
- ' <div class="new">' ,
5032
- @ctx ,
5033
- ' </div>' ,
5034
- ' </div>' ;
5035
- @ctx = ();
5036
- }
5037
- # empty add/rem block on start context block, or end of chunk
5038
- if ((@rem || @add ) && (!$class || $class eq ' ctx' )) {
5039
- if (!@add ) {
5040
- # pure removal
5041
- print join ' ' ,
5042
- ' <div class="chunk_block rem">' ,
5043
- ' <div class="old">' ,
5044
- @rem ,
5045
- ' </div>' ,
5046
- ' </div>' ;
5047
- } elsif (!@rem ) {
5048
- # pure addition
5049
- print join ' ' ,
5050
- ' <div class="chunk_block add">' ,
5051
- ' <div class="new">' ,
5052
- @add ,
5053
- ' </div>' ,
5054
- ' </div>' ;
5055
- } else {
5056
- # assume that it is change
5057
- print join ' ' ,
5058
- ' <div class="chunk_block chg">' ,
5059
- ' <div class="old">' ,
5060
- @rem ,
5061
- ' </div>' ,
5062
- ' <div class="new">' ,
5063
- @add ,
5064
- ' </div>' ,
5065
- ' </div>' ;
5066
- }
5067
- @rem = @add = ();
5214
+ # # print from accumulator when have some add/rem lines or end
5215
+ # of chunk (flush context lines), or when have add and rem
5216
+ # lines and new block is reached (otherwise add/rem lines could
5217
+ # be reordered)
5218
+ if (!$class || ((@rem || @add ) && $class eq ' ctx' ) ||
5219
+ (@rem && @add && $class ne $prev_class )) {
5220
+ print_diff_lines(\@ctx , \@rem , \@add ,
5221
+ $diff_style , $num_parents );
5222
+ @ctx = @rem = @add = ();
5068
5223
}
5069
5224
5070
5225
# # adding lines to accumulator
@@ -5080,6 +5235,8 @@ sub print_sidebyside_diff_chunk {
5080
5235
if ($class eq ' ctx' ) {
5081
5236
push @ctx , $line ;
5082
5237
}
5238
+
5239
+ $prev_class = $class ;
5083
5240
}
5084
5241
}
5085
5242
@@ -5201,27 +5358,19 @@ sub git_patchset_body {
5201
5358
5202
5359
next PATCH if ($patch_line =~ m / ^diff / );
5203
5360
5204
- my ($class , $line ) = process_diff_line($patch_line , \%from , \%to );
5205
- my $diff_classes = " diff" ;
5206
- $diff_classes .= " $class " if ($class );
5207
- $line = " <div class=\" $diff_classes \" >$line </div>\n " ;
5361
+ my $class = diff_line_class($patch_line , \%from , \%to );
5208
5362
5209
- if ($diff_style eq ' sidebyside' && !$is_combined ) {
5210
- if ($class eq ' chunk_header' ) {
5211
- print_sidebyside_diff_chunk(@chunk );
5212
- @chunk = ( [ $class , $line ] );
5213
- } else {
5214
- push @chunk , [ $class , $line ];
5215
- }
5216
- } else {
5217
- # default 'inline' style and unknown styles
5218
- print $line ;
5363
+ if ($class eq ' chunk_header' ) {
5364
+ print_diff_chunk($diff_style , scalar @hash_parents , \%from , \%to , @chunk );
5365
+ @chunk = ();
5219
5366
}
5367
+
5368
+ push @chunk , [ $class , $patch_line ];
5220
5369
}
5221
5370
5222
5371
} continue {
5223
5372
if (@chunk ) {
5224
- print_sidebyside_diff_chunk( @chunk );
5373
+ print_diff_chunk( $diff_style , scalar @hash_parents , \ %from , \ %to , @chunk );
5225
5374
@chunk = ();
5226
5375
}
5227
5376
print " </div>\n " ; # class="patch"
0 commit comments