@@ -1724,6 +1724,88 @@ sub chop_and_escape_str {
17241724 }
17251725}
17261726
1727+ # Highlight selected fragments of string, using given CSS class,
1728+ # and escape HTML. It is assumed that fragments do not overlap.
1729+ # Regions are passed as list of pairs (array references).
1730+ #
1731+ # Example: esc_html_hl_regions("foobar", "mark", [ 0, 3 ]) returns
1732+ # '<span class="mark">foo</span>bar'
1733+ sub esc_html_hl_regions {
1734+ my ($str , $css_class , @sel ) = @_ ;
1735+ return esc_html($str ) unless @sel ;
1736+
1737+ my $out = ' ' ;
1738+ my $pos = 0;
1739+
1740+ 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])));
1745+
1746+ $pos = $s -> [1];
1747+ }
1748+ $out .= esc_html(substr ($str , $pos ))
1749+ if ($pos < length ($str ));
1750+
1751+ return $out ;
1752+ }
1753+
1754+ # return positions of beginning and end of each match
1755+ sub matchpos_list {
1756+ my ($str , $regexp ) = @_ ;
1757+ return unless (defined $str && defined $regexp );
1758+
1759+ my @matches ;
1760+ while ($str =~ / $regexp /g ) {
1761+ push @matches , [$- [0], $+ [0]];
1762+ }
1763+ return @matches ;
1764+ }
1765+
1766+ # highlight match (if any), and escape HTML
1767+ sub esc_html_match_hl {
1768+ my ($str , $regexp ) = @_ ;
1769+ return esc_html($str ) unless defined $regexp ;
1770+
1771+ my @matches = matchpos_list($str , $regexp );
1772+ return esc_html($str ) unless @matches ;
1773+
1774+ return esc_html_hl_regions($str , ' match' , @matches );
1775+ }
1776+
1777+
1778+ # highlight match (if any) of shortened string, and escape HTML
1779+ sub esc_html_match_hl_chopped {
1780+ my ($str , $chopped , $regexp ) = @_ ;
1781+ return esc_html_match_hl($str , $regexp ) unless defined $chopped ;
1782+
1783+ my @matches = matchpos_list($str , $regexp );
1784+ return esc_html($chopped ) unless @matches ;
1785+
1786+ # filter matches so that we mark chopped string
1787+ my $tail = " ... " ; # see chop_str
1788+ unless ($chopped =~ s /\Q $tail\E $// ) {
1789+ $tail = ' ' ;
1790+ }
1791+ my $chop_len = length ($chopped );
1792+ my $tail_len = length ($tail );
1793+ my @filtered ;
1794+
1795+ for my $m (@matches ) {
1796+ if ($m -> [0] > $chop_len ) {
1797+ push @filtered , [ $chop_len , $chop_len + $tail_len ] if ($tail_len > 0);
1798+ last ;
1799+ } elsif ($m -> [1] > $chop_len ) {
1800+ push @filtered , [ $m -> [0], $chop_len + $tail_len ];
1801+ last ;
1802+ }
1803+ push @filtered , $m ;
1804+ }
1805+
1806+ return esc_html_hl_regions($chopped . $tail , ' match' , @filtered );
1807+ }
1808+
17271809# # ----------------------------------------------------------------------
17281810# # functions returning short strings
17291811
@@ -5368,10 +5450,17 @@ sub git_project_list_rows {
53685450 print " </td>\n " ;
53695451 }
53705452 print " <td>" . $cgi -> a({-href => href(project => $pr -> {' path' }, action => " summary" ),
5371- -class => " list" }, esc_html($pr -> {' path' })) . " </td>\n " .
5453+ -class => " list" },
5454+ esc_html_match_hl($pr -> {' path' }, $search_regexp )) .
5455+ " </td>\n " .
53725456 " <td>" . $cgi -> a({-href => href(project => $pr -> {' path' }, action => " summary" ),
5373- -class => " list" , -title => $pr -> {' descr_long' }},
5374- esc_html($pr -> {' descr' })) . " </td>\n " .
5457+ -class => " list" ,
5458+ -title => $pr -> {' descr_long' }},
5459+ $search_regexp
5460+ ? esc_html_match_hl_chopped($pr -> {' descr_long' },
5461+ $pr -> {' descr' }, $search_regexp )
5462+ : esc_html($pr -> {' descr' })) .
5463+ " </td>\n " .
53755464 " <td><i>" . chop_and_escape_str($pr -> {' owner' }, 15) . " </i></td>\n " ;
53765465 print " <td class=\" " . age_class($pr -> {' age' }) . " \" >" .
53775466 (defined $pr -> {' age_string' } ? $pr -> {' age_string' } : " No commits" ) . " </td>\n " .
0 commit comments