@@ -88,13 +88,113 @@ const char *BeginHeader =
8888 " <meta name='viewport' content='width=device-width,initial-scale=1'>"
8989 " <meta charset='UTF-8'>" ;
9090
91+ const char *JSForCoverage =
92+ R"javascript(
93+ function next_uncovered(selector, reverse) {
94+ function visit_element(element) {
95+ element.scrollIntoView({behavior: "smooth", block: "center"});
96+ element.classList.add("seen");
97+ element.classList.add("selected");
98+ }
99+
100+ function select_one() {
101+ if (!reverse) {
102+ const previously_selected = document.querySelector(".selected");
103+
104+ if (previously_selected) {
105+ previously_selected.classList.remove("selected");
106+ }
107+
108+ return document.querySelector(selector + ":not(.seen)");
109+ } else {
110+ const previously_selected = document.querySelector(".selected");
111+
112+ if (previously_selected) {
113+ previously_selected.classList.remove("selected");
114+ previously_selected.classList.remove("seen");
115+ }
116+
117+ const nodes = document.querySelectorAll(selector + ".seen");
118+ if (nodes) {
119+ const last = nodes[nodes.length - 1]; // last
120+ return last;
121+ } else {
122+ return undefined;
123+ }
124+ }
125+ }
126+
127+ function reset_all() {
128+ if (!reverse) {
129+ const all_seen = document.querySelectorAll(selector + ".seen");
130+
131+ if (all_seen) {
132+ all_seen.forEach(e => e.classList.remove("seen"));
133+ }
134+ } else {
135+ const all_seen = document.querySelectorAll(selector + ":not(.seen)");
136+
137+ if (all_seen) {
138+ all_seen.forEach(e => e.classList.add("seen"));
139+ }
140+ }
141+
142+ }
143+
144+ const uncovered = select_one();
145+
146+ if (uncovered) {
147+ visit_element(uncovered);
148+ } else {
149+ reset_all();
150+
151+
152+ const uncovered = select_one();
153+
154+ if (uncovered) {
155+ visit_element(uncovered);
156+ }
157+ }
158+ }
159+
160+ function next_line(reverse) {
161+ next_uncovered("td.uncovered-line", reverse)
162+ }
163+
164+ function next_region(reverse) {
165+ next_uncovered("span.red.region", reverse);
166+ }
167+
168+ function next_branch(reverse) {
169+ next_uncovered("span.red.branch", reverse);
170+ }
171+
172+ document.addEventListener("keypress", function(event) {
173+ console.log(event);
174+ const reverse = event.shiftKey;
175+ if (event.code == "KeyL") {
176+ next_line(reverse);
177+ }
178+ if (event.code == "KeyB") {
179+ next_branch(reverse);
180+ }
181+ if (event.code == "KeyR") {
182+ next_region(reverse);
183+ }
184+
185+ });
186+ )javascript" ;
187+
91188const char *CSSForCoverage =
92189 R"( .red {
93190 background-color: #f004;
94191}
95192.cyan {
96193 background-color: cyan;
97194}
195+ html {
196+ scroll-behavior: smooth;
197+ }
98198body {
99199 font-family: -apple-system, sans-serif;
100200}
@@ -171,6 +271,18 @@ table {
171271 text-align: right;
172272 color: #d00;
173273}
274+ .uncovered-line.selected {
275+ color: #f00;
276+ font-weight: bold;
277+ }
278+ .region.red.selected {
279+ background-color: #f008;
280+ font-weight: bold;
281+ }
282+ .branch.red.selected {
283+ background-color: #f008;
284+ font-weight: bold;
285+ }
174286.tooltip {
175287 position: relative;
176288 display: inline;
@@ -237,6 +349,13 @@ tr:has(> td >a:target) {
237349a {
238350 color: inherit;
239351}
352+ .control {
353+ position: fixed;
354+ top: 0em;
355+ right: 0em;
356+ padding: 1em;
357+ background: #FFF8;
358+ }
240359@media (prefers-color-scheme: dark) {
241360 body {
242361 background-color: #222;
254373 .tooltip {
255374 background-color: #068;
256375 }
376+ .control {
377+ background: #2228;
378+ }
257379}
258380)" ;
259381
@@ -298,8 +420,18 @@ std::string getPathToStyle(StringRef ViewPath) {
298420 return PathToStyle + " style.css" ;
299421}
300422
423+ std::string getPathToJavaScript (StringRef ViewPath) {
424+ std::string PathToJavaScript;
425+ std::string PathSep = std::string (sys::path::get_separator ());
426+ unsigned NumSeps = ViewPath.count (PathSep);
427+ for (unsigned I = 0 , E = NumSeps; I < E; ++I)
428+ PathToJavaScript += " .." + PathSep;
429+ return PathToJavaScript + " control.js" ;
430+ }
431+
301432void emitPrelude (raw_ostream &OS, const CoverageViewOptions &Opts,
302- const std::string &PathToStyle = " " ) {
433+ const std::string &PathToStyle = " " ,
434+ const std::string &PathToJavaScript = " " ) {
303435 OS << " <!doctype html>"
304436 " <html>"
305437 << BeginHeader;
@@ -311,6 +443,13 @@ void emitPrelude(raw_ostream &OS, const CoverageViewOptions &Opts,
311443 OS << " <link rel='stylesheet' type='text/css' href='"
312444 << escape (PathToStyle, Opts) << " '>" ;
313445
446+ // Link to a JavaScript if one is available
447+ if (PathToJavaScript.empty ())
448+ ;
449+ // OS << "<style>" << CSSForCoverage << "</style>";
450+ else
451+ OS << " <script src='" << escape (PathToJavaScript, Opts) << " '></script>" ;
452+
314453 OS << EndHeader << " <body>" ;
315454}
316455
@@ -390,7 +529,8 @@ CoveragePrinterHTML::createViewFile(StringRef Path, bool InToplevel) {
390529 emitPrelude (*OS.get (), Opts);
391530 } else {
392531 std::string ViewPath = getOutputPath (Path, " html" , InToplevel);
393- emitPrelude (*OS.get (), Opts, getPathToStyle (ViewPath));
532+ emitPrelude (*OS.get (), Opts, getPathToStyle (ViewPath),
533+ getPathToJavaScript (ViewPath));
394534 }
395535
396536 return std::move (OS);
@@ -442,6 +582,17 @@ Error CoveragePrinterHTML::emitStyleSheet() {
442582 return Error::success ();
443583}
444584
585+ Error CoveragePrinterHTML::emitJavaScript () {
586+ auto JSOrErr = createOutputStream (" control" , " js" , /* InToplevel=*/ true );
587+ if (Error E = JSOrErr.takeError ())
588+ return E;
589+
590+ OwnedStream JS = std::move (JSOrErr.get ());
591+ JS->operator <<(JSForCoverage);
592+
593+ return Error::success ();
594+ }
595+
445596void CoveragePrinterHTML::emitReportHeader (raw_ostream &OSRef,
446597 const std::string &Title) {
447598 // Emit some basic information about the coverage report.
@@ -487,6 +638,9 @@ Error CoveragePrinterHTML::createIndexFile(
487638 if (Error E = emitStyleSheet ())
488639 return E;
489640
641+ if (Error E = emitJavaScript ())
642+ return E;
643+
490644 // Emit a file index along with some coverage statistics.
491645 auto OSOrErr = createOutputStream (" index" , " html" , /* InToplevel=*/ true );
492646 if (Error E = OSOrErr.takeError ())
@@ -495,7 +649,7 @@ Error CoveragePrinterHTML::createIndexFile(
495649 raw_ostream &OSRef = *OS.get ();
496650
497651 assert (Opts.hasOutputDirectory () && " No output directory for index file" );
498- emitPrelude (OSRef, Opts, getPathToStyle (" " ));
652+ emitPrelude (OSRef, Opts, getPathToStyle (" " ), getPathToJavaScript ( " " ) );
499653
500654 emitReportHeader (OSRef, " Coverage Report" );
501655
@@ -561,7 +715,8 @@ struct CoveragePrinterHTMLDirectory::Reporter : public DirectoryCoverageReport {
561715
562716 auto IndexHtmlPath = Printer.getOutputPath ((LCPath + " index" ).str (), " html" ,
563717 /* InToplevel=*/ false );
564- emitPrelude (OSRef, Options, getPathToStyle (IndexHtmlPath));
718+ emitPrelude (OSRef, Options, getPathToStyle (IndexHtmlPath),
719+ getPathToJavaScript (IndexHtmlPath));
565720
566721 auto NavLink = buildTitleLinks (LCPath);
567722 Printer.emitReportHeader (OSRef, " Coverage Report (" + NavLink + " )" );
@@ -800,7 +955,10 @@ void SourceCoverageViewHTML::renderLine(raw_ostream &OS, LineRef L,
800955 auto Highlight = [&](const std::string &Snippet, unsigned LC, unsigned RC) {
801956 if (getOptions ().Debug )
802957 HighlightedRanges.emplace_back (LC, RC);
803- return tag (" span" , Snippet, std::string (*Color));
958+ if (Snippet.empty ())
959+ return tag (" span" , Snippet, std::string (*Color));
960+ else
961+ return tag (" span" , Snippet, " region " + std::string (*Color));
804962 };
805963
806964 auto CheckIfUncovered = [&](const CoverageSegment *S) {
@@ -883,7 +1041,9 @@ void SourceCoverageViewHTML::renderLineCoverageColumn(
8831041 if (Line.isMapped ())
8841042 Count = tag (" pre" , formatCount (Line.getExecutionCount ()));
8851043 std::string CoverageClass =
886- (Line.getExecutionCount () > 0 ) ? " covered-line" : " uncovered-line" ;
1044+ (Line.getExecutionCount () > 0 )
1045+ ? " covered-line"
1046+ : (Line.isMapped () ? " uncovered-line" : " skipped-line" );
8871047 OS << tag (" td" , Count, CoverageClass);
8881048}
8891049
@@ -957,7 +1117,7 @@ void SourceCoverageViewHTML::renderBranchView(raw_ostream &OS, BranchView &BRV,
9571117 }
9581118
9591119 // Display TrueCount or TruePercent.
960- std::string TrueColor = R.ExecutionCount ? " None" : " red" ;
1120+ std::string TrueColor = R.ExecutionCount ? " None" : " red branch " ;
9611121 std::string TrueCovClass =
9621122 (R.ExecutionCount > 0 ) ? " covered-line" : " uncovered-line" ;
9631123
@@ -969,7 +1129,7 @@ void SourceCoverageViewHTML::renderBranchView(raw_ostream &OS, BranchView &BRV,
9691129 OS << format (" %0.2f" , TruePercent) << " %, " ;
9701130
9711131 // Display FalseCount or FalsePercent.
972- std::string FalseColor = R.FalseExecutionCount ? " None" : " red" ;
1132+ std::string FalseColor = R.FalseExecutionCount ? " None" : " red branch " ;
9731133 std::string FalseCovClass =
9741134 (R.FalseExecutionCount > 0 ) ? " covered-line" : " uncovered-line" ;
9751135
@@ -1053,24 +1213,21 @@ void SourceCoverageViewHTML::renderTitle(raw_ostream &OS, StringRef Title) {
10531213 if (getOptions ().hasCreatedTime ())
10541214 OS << tag (CreatedTimeTag,
10551215 escape (getOptions ().CreatedTimeStr , getOptions ()));
1216+
1217+ OS << tag (" span" ,
1218+ a (" javascript:next_line()" , " next uncovered line (L)" ) + " , " +
1219+ a (" javascript:next_region()" , " next uncovered region (R)" ) +
1220+ " , " +
1221+ a (" javascript:next_branch()" , " next uncovered branch (B)" ),
1222+ " control" );
10561223}
10571224
10581225void SourceCoverageViewHTML::renderTableHeader (raw_ostream &OS,
1059- unsigned FirstUncoveredLineNo,
10601226 unsigned ViewDepth) {
1061- std::string SourceLabel;
1062- if (FirstUncoveredLineNo == 0 ) {
1063- SourceLabel = tag (" td" , tag (" pre" , " Source" ));
1064- } else {
1065- std::string LinkTarget = " #L" + utostr (uint64_t (FirstUncoveredLineNo));
1066- SourceLabel =
1067- tag (" td" , tag (" pre" , " Source (" +
1068- a (LinkTarget, " jump to first uncovered line" ) +
1069- " )" ));
1070- }
1227+ std::string Links;
10711228
10721229 renderLinePrefix (OS, ViewDepth);
1073- OS << tag (" td" , tag (" pre" , " Line" )) << tag (" td" , tag (" pre" , " Count" ))
1074- << SourceLabel ;
1230+ OS << tag (" td" , tag (" pre" , " Line" )) << tag (" td" , tag (" pre" , " Count" ));
1231+ OS << tag ( " td " , tag ( " pre " , " Source " + Links)) ;
10751232 renderLineSuffix (OS, ViewDepth);
10761233}
0 commit comments