@@ -1732,20 +1732,29 @@ sub chop_and_escape_str {
17321732# '<span class="mark">foo</span>bar'
17331733sub esc_html_hl_regions {
17341734 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 ;
17361738
17371739 my $out = ' ' ;
17381740 my $pos = 0;
17391741
17401742 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 ;
17451744
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 ;
17471756 }
1748- $out .= esc_html(substr ($str , $pos ))
1757+ $out .= esc_html(substr ($str , $pos ), %opts )
17491758 if ($pos < length ($str ));
17501759
17511760 return $out ;
@@ -2421,26 +2430,32 @@ sub format_cc_diff_chunk_header {
24212430}
24222431
24232432# 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 );
24332444
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+ }
24372453
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 " ;
24412457
2442- }
2443- return $diff_class , esc_html($line , -nbsp => 1);
2458+ return $line ;
24442459}
24452460
24462461# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
@@ -4994,10 +5009,186 @@ sub git_difftree_body {
49945009 print " </table>\n " ;
49955010}
49965011
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 ) = @_ ;
49995187 my (@ctx , @rem , @add );
50005188
5189+ # The class of the previous line.
5190+ my $prev_class = ' ' ;
5191+
50015192 return unless @chunk ;
50025193
50035194 # incomplete last line might be among removed or added lines,
@@ -5016,55 +5207,19 @@ sub print_sidebyside_diff_chunk {
50165207
50175208 # print chunk headers
50185209 if ($class && $class eq ' chunk_header' ) {
5019- print $line ;
5210+ print format_diff_line( $line , $class , $from , $to ) ;
50205211 next ;
50215212 }
50225213
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 = ();
50685223 }
50695224
50705225 # # adding lines to accumulator
@@ -5080,6 +5235,8 @@ sub print_sidebyside_diff_chunk {
50805235 if ($class eq ' ctx' ) {
50815236 push @ctx , $line ;
50825237 }
5238+
5239+ $prev_class = $class ;
50835240 }
50845241}
50855242
@@ -5201,27 +5358,19 @@ sub git_patchset_body {
52015358
52025359 next PATCH if ($patch_line =~ m / ^diff / );
52035360
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 );
52085362
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 = ();
52195366 }
5367+
5368+ push @chunk , [ $class , $patch_line ];
52205369 }
52215370
52225371 } continue {
52235372 if (@chunk ) {
5224- print_sidebyside_diff_chunk( @chunk );
5373+ print_diff_chunk( $diff_style , scalar @hash_parents , \ %from , \ %to , @chunk );
52255374 @chunk = ();
52265375 }
52275376 print " </div>\n " ; # class="patch"
0 commit comments