void SourceCoverageViewText::renderLine(raw_ostream &OS, LineRef L, const LineCoverageStats &LCS, unsigned ExpansionCol, unsigned ViewDepth) { StringRef Line = L.Line; unsigned LineNumber = L.LineNo; auto *WrappedSegment = LCS.getWrappedSegment(); CoverageSegmentArray Segments = LCS.getLineSegments(); Optional<raw_ostream::Colors> Highlight; SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges; // The first segment overlaps from a previous line, so we treat it specially. if (WrappedSegment && !WrappedSegment->IsGapRegion && WrappedSegment->HasCount && WrappedSegment->Count == 0) Highlight = raw_ostream::RED; // Output each segment of the line, possibly highlighted. unsigned Col = 1; for (const auto *S : Segments) { unsigned End = std::min(S->Col, static_cast<unsigned>(Line.size()) + 1); colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR, getOptions().Colors && Highlight, /*Bold=*/false, /*BG=*/true) << Line.substr(Col - 1, End - Col); if (getOptions().Debug && Highlight) HighlightedRanges.push_back(std::make_pair(Col, End)); Col = End; if ((!S->IsGapRegion || (Highlight && *Highlight == raw_ostream::RED)) && S->HasCount && S->Count == 0) Highlight = raw_ostream::RED; else if (Col == ExpansionCol) Highlight = raw_ostream::CYAN; else Highlight = None; } // Show the rest of the line. colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR, getOptions().Colors && Highlight, /*Bold=*/false, /*BG=*/true) << Line.substr(Col - 1, Line.size() - Col + 1); OS << '\n'; if (getOptions().Debug) { for (const auto &Range : HighlightedRanges) errs() << "Highlighted line " << LineNumber << ", " << Range.first << " -> " << Range.second << '\n'; if (Highlight) errs() << "Highlighted line " << LineNumber << ", " << Col << " -> ?\n"; } }
void SourceCoverageViewHTML::renderLine(raw_ostream &OS, LineRef L, const LineCoverageStats &LCS, unsigned ExpansionCol, unsigned) { StringRef Line = L.Line; unsigned LineNo = L.LineNo; // Steps for handling text-escaping, highlighting, and tooltip creation: // // 1. Split the line into N+1 snippets, where N = |Segments|. The first // snippet starts from Col=1 and ends at the start of the first segment. // The last snippet starts at the last mapped column in the line and ends // at the end of the line. Both are required but may be empty. SmallVector<std::string, 8> Snippets; CoverageSegmentArray Segments = LCS.getLineSegments(); unsigned LCol = 1; auto Snip = [&](unsigned Start, unsigned Len) { Snippets.push_back(Line.substr(Start, Len)); LCol += Len; }; Snip(LCol - 1, Segments.empty() ? 0 : (Segments.front()->Col - 1)); for (unsigned I = 1, E = Segments.size(); I < E; ++I) Snip(LCol - 1, Segments[I]->Col - LCol); // |Line| + 1 is needed to avoid underflow when, e.g |Line| = 0 and LCol = 1. Snip(LCol - 1, Line.size() + 1 - LCol); // 2. Escape all of the snippets. for (unsigned I = 0, E = Snippets.size(); I < E; ++I) Snippets[I] = escape(Snippets[I], getOptions()); // 3. Use \p WrappedSegment to set the highlight for snippet 0. Use segment // 1 to set the highlight for snippet 2, segment 2 to set the highlight for // snippet 3, and so on. Optional<StringRef> Color; SmallVector<std::pair<unsigned, unsigned>, 2> HighlightedRanges; auto Highlight = [&](const std::string &Snippet, unsigned LC, unsigned RC) { if (getOptions().Debug) HighlightedRanges.emplace_back(LC, RC); return tag("span", Snippet, Color.getValue()); }; auto CheckIfUncovered = [&](const CoverageSegment *S) { return S && (!S->IsGapRegion || (Color && *Color == "red")) && S->HasCount && S->Count == 0; }; if (CheckIfUncovered(LCS.getWrappedSegment())) { Color = "red"; if (!Snippets[0].empty()) Snippets[0] = Highlight(Snippets[0], 1, 1 + Snippets[0].size()); } for (unsigned I = 0, E = Segments.size(); I < E; ++I) { const auto *CurSeg = Segments[I]; if (CheckIfUncovered(CurSeg)) Color = "red"; else if (CurSeg->Col == ExpansionCol) Color = "cyan"; else Color = None; if (Color.hasValue()) Snippets[I + 1] = Highlight(Snippets[I + 1], CurSeg->Col, CurSeg->Col + Snippets[I + 1].size()); } if (Color.hasValue() && Segments.empty()) Snippets.back() = Highlight(Snippets.back(), 1, 1 + Snippets.back().size()); if (getOptions().Debug) { for (const auto &Range : HighlightedRanges) { errs() << "Highlighted line " << LineNo << ", " << Range.first << " -> "; if (Range.second == 0) errs() << "?"; else errs() << Range.second; errs() << "\n"; } } // 4. Snippets[1:N+1] correspond to \p Segments[0:N]: use these to generate // sub-line region count tooltips if needed. if (shouldRenderRegionMarkers(LCS)) { // Just consider the segments which start *and* end on this line. for (unsigned I = 0, E = Segments.size() - 1; I < E; ++I) { const auto *CurSeg = Segments[I]; if (!CurSeg->IsRegionEntry) continue; if (CurSeg->Count == LCS.getExecutionCount()) continue; Snippets[I + 1] = tag("div", Snippets[I + 1] + tag("span", formatCount(CurSeg->Count), "tooltip-content"), "tooltip"); if (getOptions().Debug) errs() << "Marker at " << CurSeg->Line << ":" << CurSeg->Col << " = " << formatCount(CurSeg->Count) << "\n"; } } OS << BeginCodeTD; OS << BeginPre; for (const auto &Snippet : Snippets) OS << Snippet; OS << EndPre; // If there are no sub-views left to attach to this cell, end the cell. // Otherwise, end it after the sub-views are rendered (renderLineSuffix()). if (!hasSubViews()) OS << EndCodeTD; }