[PATCH][RFC] llvm-cov HTML generation

Harlan Haskins via llvm-commits llvm-commits at lists.llvm.org
Wed Mar 2 11:09:40 PST 2016


Thanks for the review!

I implemented those changes and attached an updated patch.

I’m gonna tackle generating an index later today.

— Harlan

-------------- next part --------------
A non-text attachment was scrubbed...
Name: llvm-cov-html.diff
Type: application/octet-stream
Size: 41121 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/llvm-commits/attachments/20160302/9d1f74ea/attachment.obj>
-------------- next part --------------


> On Mar 1, 2016, at 11:18 PM, Justin Bogner via llvm-commits <llvm-commits at lists.llvm.org> wrote:
> 
> Harlan Haskins via llvm-commits <llvm-commits at lists.llvm.org> writes:
>> I’ve got a preliminary implementation of HTML generation for llvm-cov’s
>> coverage reports.
>> 
>> The patch adds 2 flags to llvm-cov show:
>> 
>>  * -format [html | console]
>>  * -output-dir <dirname>
> 
> This looks really useful. I've cc'd a couple of people I think will be
> interested.
> 
> Commments inline, mostly nitpicks.
> 
>> Specifying -format=console will perform the current behavior (now the
>> default), and -format=html will generate an HTML report.
>> If -output-dir is specified, then the output is split into one html or txt
>> file per source file, named <source-name>.<ext>, with a directory structure
>> that mimics the file system structure.
>> 
>> If neither are provided, the behavior remains the same.
>> 
>> I’m hoping to add an index with a browsable list of files within their
>> directories, but for now I’ve attached the patch and a sample HTML file (In
>> this case, AliasAnalysis.h, as included by swiftc).
> 
> Something as simple as a list of files with clickable links would
> probably be good enough for a start. You could also go further and do
> something like the output of llvm-cov report with clickable links, but
> that's probably better as a followup.
> 
> Harlan Haskins via llvm-commits <llvm-commits at lists.llvm.org> writes:
>> Oops, forgot to add a file to the patch.
>> 
>> New patch attached.
>> 
>> Index: CodeCoverage.cpp
> 
> Better to generate diffs from the top level llvm source directory, for
> future reference. Then people don't need to figure out where to cd first
> (tools/llvm-cov) to apply the patch.
> 
>> ===================================================================
>> --- CodeCoverage.cpp	(revision 262364)
>> +++ CodeCoverage.cpp	(working copy)
>> @@ -33,6 +33,7 @@
>> #include "llvm/Support/Signals.h"
>> #include <functional>
>> #include <system_error>
>> +#include <unistd.h>
> 
> We should probably avoid unistd here, it's not portable.
> 
>> 
>> using namespace llvm;
>> using namespace coverage;
>> @@ -47,10 +48,17 @@
>>     /// \brief The report command.
>>     Report
>>   };
>> -
>> +  
>> +  /// \brief Print an error message and exit.
>> +  void exitWithErrorCode(std::error_code Error, StringRef Whence);
>> +  
>>   /// \brief Print the error message to the error output stream.
>>   void error(const Twine &Message, StringRef Whence = "");
>> 
>> +  /// \brief Return a pointer to a stream for the given file.
>> +  ///        Passing "" yields stdout.
>> +  std::unique_ptr<raw_ostream> streamForFile(std::string Filename);
>> +  
>>   /// \brief Return a memory buffer for the given source file.
>>   ErrorOr<const MemoryBuffer &> getSourceFile(StringRef SourceFile);
>> 
>> @@ -135,7 +143,7 @@
>>       continue;
>> 
>>     auto SubViewExpansions = ExpansionCoverage.getExpansions();
>> -    auto SubView = llvm::make_unique<SourceCoverageView>(
>> +    auto SubView = SourceCoverageView::create(
>>         SourceBuffer.get(), ViewOpts, std::move(ExpansionCoverage));
>>     attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);
>>     View.addExpansion(Expansion.Region, std::move(SubView));
>> @@ -153,7 +161,7 @@
>>     return nullptr;
>> 
>>   auto Expansions = FunctionCoverage.getExpansions();
>> -  auto View = llvm::make_unique<SourceCoverageView>(
>> +  auto View = SourceCoverageView::create(
>>       SourceBuffer.get(), ViewOpts, std::move(FunctionCoverage));
>>   attachExpansionSubViews(*View, Expansions, Coverage);
>> 
>> @@ -171,14 +179,14 @@
>>     return nullptr;
>> 
>>   auto Expansions = FileCoverage.getExpansions();
>> -  auto View = llvm::make_unique<SourceCoverageView>(
>> +  auto View = SourceCoverageView::create(
>>       SourceBuffer.get(), ViewOpts, std::move(FileCoverage));
>>   attachExpansionSubViews(*View, Expansions, Coverage);
>> 
>>   for (auto Function : Coverage.getInstantiations(SourceFile)) {
>>     auto SubViewCoverage = Coverage.getCoverageForFunction(*Function);
>>     auto SubViewExpansions = SubViewCoverage.getExpansions();
>> -    auto SubView = llvm::make_unique<SourceCoverageView>(
>> +    auto SubView = SourceCoverageView::create(
>>         SourceBuffer.get(), ViewOpts, std::move(SubViewCoverage));
>>     attachExpansionSubViews(*SubView, SubViewExpansions, Coverage);
>> 
>> @@ -377,6 +385,26 @@
>>   return 0;
>> }
>> 
>> +std::unique_ptr<raw_ostream>
>> +CodeCoverageTool::streamForFile(std::string Filename) {
> 
> Functions should usually be verb phrases, like getStreamForFile.
> 
>> +  if (Filename == "") {
>> +    return make_unique<raw_fd_ostream>(STDOUT_FILENO, false);
>> +  } else {
> 
> The LLVM style avoids the "else" after an early return. Saves some
> indentation.
> 
> This applies to a few places later in the patch too.
> 
>> +    std::error_code Error;
>> +    auto OS = make_unique<raw_fd_ostream>(Filename,
>> +                                          Error, sys::fs::F_RW);
>> +    if (Error)
>> +      exitWithErrorCode(Error, __PRETTY_FUNCTION__);
> 
> Why __PRETTY_FUNCTION__?
> 
> I'm not a big fan of calling exit here - I think it'd be better to just
> call error(...) and return nullptr, then CodeCoverageTool::view can
> just return 1 or whatever.
> 
>> +    return move(OS);
> 
> This is std::move, right? Are we "using namespace std" somewhere? We
> shouldn't be.
> 
>> +  }
>> +}
>> +
>> +void CodeCoverageTool::exitWithErrorCode(std::error_code Error,
>> +                                         StringRef Whence = "") {
>> +  error(Error.message(), Whence);
>> +  exit(Error.value());
>> +}
>> +
>> int CodeCoverageTool::show(int argc, const char **argv,
>>                            CommandLineParserType commandLineParser) {
>> 
>> @@ -405,6 +433,20 @@
>>   cl::opt<bool> ShowInstantiations("show-instantiations", cl::Optional,
>>                                    cl::desc("Show function instantiations"),
>>                                    cl::cat(ViewCategory));
>> +  
>> +  cl::opt<std::string> OutputDirectory("o", "output-dir", cl::Optional,
>> +      cl::desc("Directory to output individual files"));
>> +  
>> +  cl::opt<CoverageViewOptions::OutputFormat> Format("f", "format",
>> +      cl::desc("Format to output comparison"),
>> +      cl::values(clEnumValN(CoverageViewOptions::HTML,
>> +                            "html",
>> +                            "HTML output"),
>> +                 clEnumValN(CoverageViewOptions::Console,
>> +                            "console",
>> +                            "Console table output"),
>> +                 clEnumValEnd),
>> +      cl::init(CoverageViewOptions::Console));
>> 
>>   auto Err = commandLineParser(argc, argv);
>>   if (Err)
>> @@ -417,12 +459,23 @@
>>   ViewOpts.ShowLineStatsOrRegionMarkers = ShowBestLineRegionsCounts;
>>   ViewOpts.ShowExpandedRegions = ShowExpansions;
>>   ViewOpts.ShowFunctionInstantiations = ShowInstantiations;
>> +  ViewOpts.Format = Format;
>> +  ViewOpts.Colors = ViewOpts.Colors && Format == CoverageViewOptions::Console;
>> 
>>   auto Coverage = load();
>>   if (!Coverage)
>>     return 1;
>> -
>> +  
>> +  
>>   if (!Filters.empty()) {
>> +    std::string outputPath = OutputDirectory;
>> +    if (outputPath != "") {
>> +      std::string ext = Format == CoverageViewOptions::HTML ? "html" : "txt";
>> +      outputPath += "/functions." + ext;
>> +    }
>> +    auto os = streamForFile(outputPath);
> 
> Variables should start with capital letters. Ie, `OutputPath`, `Ext`,
> and `OS`.
> 
>> +    SourceCoverageViewHTML::renderFileHeader(*os);
>> +    
>>     // Show functions
>>     for (const auto &Function : Coverage->getCoveredFunctions()) {
>>       if (!Filters.matches(Function))
>> @@ -435,12 +488,12 @@
>>         outs() << "\n";
>>         continue;
>>       }
>> -      ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << Function.Name
>> -                                                          << ":";
>> -      outs() << "\n";
>> -      mainView->render(outs(), /*WholeFile=*/false);
>> -      outs() << "\n";
>> +      mainView->renderTitle(*os, Function.Name);
>> +      *os << "\n";
>> +      mainView->render(*os, /*WholeFile=*/false);
>> +      *os << "\n";
>>     }
>> +    SourceCoverageViewHTML::renderFileFooter(*os);
>>     return 0;
>>   }
>> 
>> @@ -453,21 +506,28 @@
>>       SourceFiles.push_back(Filename);
>> 
>>   for (const auto &SourceFile : SourceFiles) {
>> +    auto basename = sys::path::parent_path(SourceFile);
>> +    sys::fs::create_directories(OutputDirectory + basename);
>> +    auto ext = Format == CoverageViewOptions::HTML ? "html" : "txt";
>> +    std::string outputPath = OutputDirectory;
>> +    if (outputPath != "") {
>> +      outputPath += "/" + std::string(SourceFile) + "." + ext;
>> +    }
>> +    std::unique_ptr<raw_ostream> os = streamForFile(outputPath);
> 
> The code duplication's kind of gross here. To be fair, that was true
> before this patch, but we should probably try to clean that up in a
> separate NFC patch either before or after this one.
> 
>>     auto mainView = createSourceFileView(SourceFile, *Coverage);
>>     if (!mainView) {
>> -      ViewOpts.colored_ostream(outs(), raw_ostream::RED)
>> +      ViewOpts.colored_ostream(errs(), raw_ostream::RED)
>>           << "warning: The file '" << SourceFile << "' isn't covered.";
>> -      outs() << "\n";
>> +      errs() << "\n";
>>       continue;
>>     }
>> -
>> +    
>>     if (ShowFilenames) {
>> -      ViewOpts.colored_ostream(outs(), raw_ostream::CYAN) << SourceFile << ":";
>> -      outs() << "\n";
>> +      mainView->renderTitle(*os, SourceFile);
>>     }
>> -    mainView->render(outs(), /*Wholefile=*/true);
>> +    mainView->render(*os, /*Wholefile=*/true);
>>     if (SourceFiles.size() > 1)
>> -      outs() << "\n";
>> +      *os << "\n";
>>   }
>> 
>>   return 0;
>> Index: CoverageViewOptions.h
>> ===================================================================
>> --- CoverageViewOptions.h	(revision 262364)
>> +++ CoverageViewOptions.h	(working copy)
>> @@ -16,6 +16,12 @@
>> 
>> /// \brief The options for displaying the code coverage information.
>> struct CoverageViewOptions {
>> +  enum OutputFormat {
>> +    /// \brief ASCII table output.
>> +    Console,
>> +    /// \brief An HTML directory.
>> +    HTML
> 
> These should probably be named something like OutputFormatConsole or
> OFConsole to avoid name collisions.
> 
>> +  };
>>   bool Debug;
>>   bool Colors;
>>   bool ShowLineNumbers;
>> @@ -25,6 +31,7 @@
>>   bool ShowExpandedRegions;
>>   bool ShowFunctionInstantiations;
>>   bool ShowFullFilenames;
>> +  OutputFormat Format;
>> 
>>   /// \brief Change the output's stream color if the colors are enabled.
>>   ColoredRawOstream colored_ostream(raw_ostream &OS,
>> Index: SourceCoverageView.cpp
>> ===================================================================
>> --- SourceCoverageView.cpp	(revision 262364)
>> +++ SourceCoverageView.cpp	(working copy)
>> @@ -12,13 +12,99 @@
>> //===----------------------------------------------------------------------===//
>> 
>> #include "SourceCoverageView.h"
>> -#include "llvm/ADT/Optional.h"
>> #include "llvm/ADT/SmallString.h"
>> #include "llvm/ADT/StringExtras.h"
>> -#include "llvm/Support/LineIterator.h"
>> 
>> using namespace llvm;
>> 
>> +namespace html {
>> +  using namespace std;
> 
> Please don't "using namespace std".
> 
>> +  /// \returns An HTML-escaped string.
> 
> I don't know what doxygen does with a \returns with no \brief. If you're
> sure it's reasonable this is fine, but otherwise I'd just drop the
> \returns and reword these like "Escapes the string for HTML".
> 
>> +  string escape(string s) {
>> +    string result;
>> +    for (size_t i = 0; i < s.size(); ++i) {
>> +      string token = s.substr(i,1);
>> +      if (token == "&")
>> +        token = "&";
>> +      else if (token == "<")
>> +        token = "<";
>> +      else if (token == "\"")
>> +        token = """;
>> +      else if (token == ">")
>> +        token = ">";
>> +      result += token;
>> +    }
>> +    return result;
>> +  }
>> +  
>> +  /// \returns A tag around the provided text
>> +  /// (expects the value to be properly escaped).
>> +  string tag(string name, string text, string _class = "") {
> 
> More variables that should start with capital letters, but note that
> _Class would be a reserved name (_Capital always is), so you'd have to
> go with Class_.
> 
>> +    string tag = "<" + name;
>> +    if (_class != "") {
>> +     tag += " class='" + _class + "'";
>> +    }
>> +    return tag + ">" + text + "</" + name + ">";
>> +  }
>> +}
>> +
>> +std::string classForColor(raw_ostream::Colors Color) {
>> +  switch (Color) {
>> +    case raw_ostream::RED: return "red";
>> +    case raw_ostream::CYAN: return "cyan";
>> +    case raw_ostream::BLUE: return "blue";
>> +    case raw_ostream::BLACK: return "black";
>> +    case raw_ostream::GREEN: return "green";
>> +    case raw_ostream::MAGENTA: return "magenta";
>> +    default: return "clear";
>> +  }
>> +}
>> +
>> +/// Format a count using engineering notation with 3 significant digits.
>> +static std::string formatCount(uint64_t N) {
>> +  std::string Number = utostr(N);
>> +  int Len = Number.size();
>> +  if (Len <= 3)
>> +    return Number;
>> +  int IntLen = Len % 3 == 0 ? 3 : Len % 3;
>> +  std::string Result(Number.data(), IntLen);
>> +  if (IntLen != 3) {
>> +    Result.push_back('.');
>> +    Result += Number.substr(IntLen, 3 - IntLen);
>> +  }
>> +  Result.push_back(" kMGTPEZY"[(Len - 1) / 3]);
>> +  return Result;
>> +}
>> +
>> +void SourceCoverageViewHTML::output(raw_ostream &OS, std::string Text,
>> +                                    Optional<raw_ostream::Colors> Highlight,
>> +                                    const coverage::CoverageSegment
>> +                                      *Segment) {
> 
> This is formatted funny. I recommend clang-format. You can use the
> clang-format-diff or git-clang-format script in the clang repo to just
> format your changes (and avoid arbitrary reformatting changes elsewhere).
> 
>> +  auto Color = Highlight ? *Highlight : raw_ostream::SAVEDCOLOR;
>> +  Text = html::escape(Text);
>> +  if (Highlight) {
>> +    Text = html::tag("span", Text, classForColor(Color));
>> +  }
>> +  if (Options.ShowRegionMarkers && Segment && Segment->IsRegionEntry) {
>> +    std::string C = formatCount(Segment->Count);
>> +    auto countTag = html::tag("span", html::escape(C));
>> +    Text = html::tag("span", Text + countTag, "tooltips");
>> +  }
>> +  OS << Text;
>> +}
>> +
>> +void SourceCoverageViewConsole::output(raw_ostream &OS,
>> +                                       std::string Text,
>> +                                       Optional<raw_ostream::Colors>
>> +                                        Highlight,
>> +                                       const coverage::CoverageSegment
>> +                                        *Segment) {
>> +  auto Color = Highlight ? *Highlight : raw_ostream::SAVEDCOLOR;
>> +  colored_ostream(OS, Color,
>> +                  Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true)
>> +  << Text;
>> +}
>> +
>> void SourceCoverageView::renderLine(
>>     raw_ostream &OS, StringRef Line, int64_t LineNumber,
>>     const coverage::CoverageSegment *WrappedSegment,
>> @@ -30,14 +116,13 @@
>>   // The first segment overlaps from a previous line, so we treat it specially.
>>   if (WrappedSegment && 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,
>> -                    Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true)
>> -        << Line.substr(Col - 1, End - Col);
>> +    auto Text = Line.substr(Col - 1, End - Col);
>> +    output(OS, Text, Highlight, S);
>>     if (Options.Debug && Highlight)
>>       HighlightedRanges.push_back(std::make_pair(Col, End));
>>     Col = End;
>> @@ -48,13 +133,10 @@
>>     else
>>       Highlight = None;
>>   }
>> +  
>> +  auto Rest = Line.substr(Col - 1, Line.size() - Col + 1);
>> +  output(OS, Rest, Highlight, nullptr);
>> 
>> -  // Show the rest of the line
>> -  colored_ostream(OS, Highlight ? *Highlight : raw_ostream::SAVEDCOLOR,
>> -                  Options.Colors && Highlight, /*Bold=*/false, /*BG=*/true)
>> -      << Line.substr(Col - 1, Line.size() - Col + 1);
>> -  OS << "\n";
>> -
>>   if (Options.Debug) {
>>     for (const auto &Range : HighlightedRanges)
>>       errs() << "Highlighted line " << LineNumber << ", " << Range.first
>> @@ -64,13 +146,60 @@
>>   }
>> }
>> 
>> -void SourceCoverageView::renderIndent(raw_ostream &OS, unsigned Level) {
>> +void SourceCoverageViewHTML::renderTitle(raw_ostream &OS,
>> +                                         StringRef Name) {
>> +  OS << "<div class='function-title'><pre>"
>> +     << std::string(Name) << ":</pre></div>";
>> +}
>> +
>> +void SourceCoverageViewConsole::renderTitle(raw_ostream &OS,
>> +                                            StringRef Name) {
>> +  Options.colored_ostream(OS, raw_ostream::CYAN) << Name << ":\n";
>> +}
>> +
>> +void SourceCoverageViewHTML::renderLine(raw_ostream &OS,
>> +                                        StringRef Line,
>> +                                        int64_t LineNumber,
>> +                                        const coverage::CoverageSegment
>> +                                            *WrappedSegment,
>> +                                        ArrayRef<const
>> +                                            coverage::CoverageSegment *>
>> +                                            Segments,
>> +                                        unsigned int ExpansionCol) {
>> +  OS << "<td><pre>\n";
>> +  SourceCoverageView::renderLine(OS, Line, LineNumber,
>> +                                 WrappedSegment, Segments,
>> +                                 ExpansionCol);
>> +  OS << "</pre>\n";
>> +  if (ExpansionSubViews.empty() && InstantiationSubViews.empty()) {
>> +    // don't end the source line if we're going to embed sub-tables.
>> +    OS << "</td>\n";
>> +  }
>> +}
>> +
>> +void SourceCoverageViewConsole::renderLine(raw_ostream &OS,
>> +                                           StringRef Line,
>> +                                           int64_t LineNumber,
>> +                                           const coverage::CoverageSegment
>> +                                              *WrappedSegment,
>> +                                           ArrayRef<const
>> +                                              coverage::CoverageSegment *>
>> +                                              Segments,
>> +                                           unsigned int ExpansionCol) {
>> +  SourceCoverageView::renderLine(OS, Line, LineNumber,
>> +                                 WrappedSegment, Segments,
>> +                                 ExpansionCol);
>> +  OS << "\n";
>> +}
>> +
>> +void SourceCoverageViewConsole::renderIndent(raw_ostream &OS, unsigned Level) {
>>   for (unsigned I = 0; I < Level; ++I)
>>     OS << "  |";
>> }
>> 
>> -void SourceCoverageView::renderViewDivider(unsigned Level, unsigned Length,
>> -                                           raw_ostream &OS) {
>> +void SourceCoverageViewConsole::renderViewDivider(unsigned Level,
>> +                                                  unsigned Length,
>> +                                                  raw_ostream &OS) {
>>   assert(Level != 0 && "Cannot render divider at top level");
>>   renderIndent(OS, Level - 1);
>>   OS.indent(2);
>> @@ -78,50 +207,61 @@
>>     OS << "-";
>> }
>> 
>> -/// Format a count using engineering notation with 3 significant digits.
>> -static std::string formatCount(uint64_t N) {
>> -  std::string Number = utostr(N);
>> -  int Len = Number.size();
>> -  if (Len <= 3)
>> -    return Number;
>> -  int IntLen = Len % 3 == 0 ? 3 : Len % 3;
>> -  std::string Result(Number.data(), IntLen);
>> -  if (IntLen != 3) {
>> -    Result.push_back('.');
>> -    Result += Number.substr(IntLen, 3 - IntLen);
>> -  }
>> -  Result.push_back(" kMGTPEZY"[(Len - 1) / 3]);
>> -  return Result;
>> -}
>> -
>> -void
>> -SourceCoverageView::renderLineCoverageColumn(raw_ostream &OS,
>> -                                             const LineCoverageInfo &Line) {
>> +void SourceCoverageViewConsole::renderLineCoverageColumn(raw_ostream &OS,
>> +                                                         const LineCoverageInfo
>> +                                                            &Line) {
>> +  
>> +  std::string C = formatCount(Line.ExecutionCount);
>>   if (!Line.isMapped()) {
>>     OS.indent(LineCoverageColumnWidth) << '|';
>>     return;
>>   }
>> -  std::string C = formatCount(Line.ExecutionCount);
>>   OS.indent(LineCoverageColumnWidth - C.size());
>>   colored_ostream(OS, raw_ostream::MAGENTA,
>>                   Line.hasMultipleRegions() && Options.Colors)
>> -      << C;
>> +  << C;
>>   OS << '|';
>> }
>> 
>> -void SourceCoverageView::renderLineNumberColumn(raw_ostream &OS,
>> -                                                unsigned LineNo) {
>> +void SourceCoverageViewHTML::renderLineCoverageColumn(raw_ostream &OS,
>> +                                                      const LineCoverageInfo
>> +                                                          &Line) {
>> +  if (Line.isMapped()) {
>> +    std::string C = formatCount(Line.ExecutionCount);
>> +    OS << html::tag("td",
>> +            html::tag("pre", html::escape(C)),
>> +            "numeric") << "\n";
>> +  } else {
>> +    OS << html::tag("td", "", "numeric") << "\n";
>> +  }
>> +}
>> +
>> +void SourceCoverageViewHTML::renderLineNumberColumn(raw_ostream &OS,
>> +                                                    unsigned int LineNo) {
>>   SmallString<32> Buffer;
>>   raw_svector_ostream BufferOS(Buffer);
>>   BufferOS << LineNo;
>>   auto Str = BufferOS.str();
>> +  OS << html::tag("td",
>> +          html::tag("pre", Str),
>> +          "numeric") << "\n";
>> +}
>> +
>> +void SourceCoverageViewConsole::renderLineNumberColumn(raw_ostream &OS,
>> +                                                       unsigned LineNo) {
>> +  SmallString<32> Buffer;
>> +  raw_svector_ostream BufferOS(Buffer);
>> +  BufferOS << LineNo;
>> +  auto Str = BufferOS.str();
>>   // Trim and align to the right
>>   Str = Str.substr(0, std::min(Str.size(), (size_t)LineNumberColumnWidth));
>>   OS.indent(LineNumberColumnWidth - Str.size()) << Str << '|';
>> }
>> 
>> -void SourceCoverageView::renderRegionMarkers(
>> -    raw_ostream &OS, ArrayRef<const coverage::CoverageSegment *> Segments) {
>> +void SourceCoverageView::renderRegionMarkers(raw_ostream &OS,
>> +      ArrayRef<const coverage::CoverageSegment *> Segments,
>> +      unsigned IndentLevel, unsigned CombinedColumnWidth) {
>> +  OS.indent(CombinedColumnWidth);
>>   unsigned PrevColumn = 1;
>>   for (const auto *S : Segments) {
>>     if (!S->IsRegionEntry)
>> @@ -135,23 +275,158 @@
>>     OS << '^' << C;
>>   }
>>   OS << "\n";
>> +}
>> 
>> -  if (Options.Debug)
>> -    for (const auto *S : Segments)
>> -      errs() << "Marker at " << S->Line << ":" << S->Col << " = "
>> -             << formatCount(S->Count) << (S->IsRegionEntry ? "\n" : " (pop)\n");
>> +void SourceCoverageViewConsole::renderRegionMarkers(raw_ostream &OS,
>> +      ArrayRef<const coverage::CoverageSegment *> Segments,
>> +      unsigned IndentLevel, unsigned CombinedColumnWidth) {
>> +  renderIndent(OS, IndentLevel);
>> +  SourceCoverageView::renderRegionMarkers(OS, Segments,
>> +                                          IndentLevel, CombinedColumnWidth);
>> }
>> 
>> +void SourceCoverageViewConsole::startLine(raw_ostream &OS,
>> +                                          unsigned IndentLevel) {
>> +  renderIndent(OS, IndentLevel);
>> +}
>> +
>> +void SourceCoverageViewHTML::renderFileHeader(raw_ostream &OS) {
>> +#include "css.inc"
> 
> A more specific name than css.inc would probably be better. This
> probably deserves a comment too.
> 
>> +  OS << "<!doctype html>\n"
>> +        "<html>\n"
>> +        "  <head>\n"
>> +        "    <meta name='viewport'"
>> +        "content='width=device-width,initial-scale=1'>\n"
>> +        "    <style>\n" << css <<
>> +        "    </style>\n"
>> +        "  </head>\n"
>> +        "  <body>\n";
>> +}
>> +
>> +void SourceCoverageViewHTML::renderFileFooter(raw_ostream &OS) {
>> +  OS << "  </body>\n"
>> +        "</html>\n";
>> +}
>> +
>> +void SourceCoverageViewHTML::render(raw_ostream &OS, bool WholeFile,
>> +                                    unsigned IndentLevel) {
>> +  if (WholeFile) {
>> +    renderFileHeader(OS);
>> +    OS << "    <table class='centered'>\n";
>> +  } else {
>> +    OS << "    <table>\n";
>> +  }
>> +  SourceCoverageView::render(OS, WholeFile, IndentLevel);
>> +  OS << "      </table>\n";
>> +  if (WholeFile) {
>> +    renderFileFooter(OS);
>> +  }
>> +}
>> +
>> +void SourceCoverageViewHTML::startLine(raw_ostream &OS, unsigned IndentLevel) {
>> +  OS << "<tr>\n";
>> +}
>> +
>> +void SourceCoverageViewHTML::endLine(raw_ostream &OS) {
>> +  OS << "</tr>\n";
>> +}
>> +
>> +void SourceCoverageViewConsole::renderSubviews(raw_ostream &OS,
>> +                                               line_iterator &LI,
>> +      std::vector<ExpansionView>::iterator &NextESV,
>> +      std::vector<ExpansionView>::iterator &EndESV,
>> +      std::vector<InstantiationView>::iterator &NextISV,
>> +      std::vector<InstantiationView>::iterator &EndISV,
>> +      const coverage::CoverageSegment *WrappedSegment,
>> +      SmallVector<const coverage::CoverageSegment *, 8> LineSegments,
>> +      unsigned IndentLevel, unsigned ExpansionColumn,
>> +      unsigned CombinedColumnWidth) {
>> +  
>> +  // The width of the line that is used to divide between the view and the
>> +  // subviews.
>> +  unsigned DividerWidth = CombinedColumnWidth + 4;
>> +  
>> +  // Show the expansions and instantiations for this line.
>> +  unsigned NestedIndent = IndentLevel + 1;
>> +  bool RenderedSubView = false;
>> +  for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();
>> +       ++NextESV) {
>> +    
>> +    renderViewDivider(NestedIndent, DividerWidth, OS);
>> +    OS << "\n";
>> +    if (RenderedSubView) {
>> +      // Re-render the current line and highlight the expansion range for
>> +      // this subview.
>> +      ExpansionColumn = NextESV->getStartCol();
>> +      renderIndent(OS, IndentLevel);
>> +      OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1));
>> +      renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments,
>> +                 ExpansionColumn);
>> +      renderViewDivider(NestedIndent, DividerWidth, OS);
>> +      OS << "\n";
>> +    }
>> +    // Render the child subview
>> +    if (Options.Debug)
>> +      errs() << "Expansion at line " << NextESV->getLine() << ", "
>> +      << NextESV->getStartCol() << " -> " << NextESV->getEndCol()
>> +      << "\n";
>> +    NextESV->View->render(OS, false, NestedIndent);
>> +    RenderedSubView = true;
>> +  }
>> +  for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {
>> +    renderViewDivider(NestedIndent, DividerWidth, OS);
>> +    OS << "\n";
>> +    renderIndent(OS, NestedIndent);
>> +    OS << ' ';
>> +    Options.colored_ostream(OS, raw_ostream::CYAN) << NextISV->FunctionName
>> +    << ":";
>> +    OS << "\n";
>> +    NextISV->View->render(OS, false, NestedIndent);
>> +    RenderedSubView = true;
>> +  }
>> +  if (RenderedSubView) {
>> +    renderViewDivider(NestedIndent, DividerWidth, OS);
>> +    OS << "\n";
>> +  }
>> +}
>> +
>> +void SourceCoverageViewHTML::renderSubviews(raw_ostream &OS,
>> +                                            line_iterator &LI,
>> +                    std::vector<ExpansionView>::iterator &NextESV,
>> +                    std::vector<ExpansionView>::iterator &EndESV,
>> +                    std::vector<InstantiationView>::iterator &NextISV,
>> +                    std::vector<InstantiationView>::iterator &EndISV,
>> +                    const coverage::CoverageSegment *WrappedSegment,
>> +                    SmallVector<const coverage::CoverageSegment *, 8>
>> +                    LineSegments,  unsigned IndentLevel,
>> +                    unsigned ExpansionColumn, unsigned CombinedColumnWidth) {
>> +  for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();
>> +       ++NextESV) {
>> +    OS << "<div class='expansion-view'>\n";
>> +    // Render the child subview
>> +    if (Options.Debug)
>> +      errs() << "Expansion at line " << NextESV->getLine() << ", "
>> +      << NextESV->getStartCol() << " -> " << NextESV->getEndCol()
>> +      << "\n";
>> +    NextESV->View->render(OS, false);
>> +    OS << "</div>\n";
>> +  }
>> +  
>> +  for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {
>> +    OS << "<div class='expansion-view'>\n";
>> +    NextISV->View->renderTitle(OS, NextISV->FunctionName);
>> +    NextISV->View->render(OS, false);
>> +    OS << "</div>\n";
>> +  }
>> +  OS << "</td>\n";
>> +}
>> +
>> void SourceCoverageView::render(raw_ostream &OS, bool WholeFile,
>>                                 unsigned IndentLevel) {
>>   // The width of the leading columns
>>   unsigned CombinedColumnWidth =
>>       (Options.ShowLineStats ? LineCoverageColumnWidth + 1 : 0) +
>>       (Options.ShowLineNumbers ? LineNumberColumnWidth + 1 : 0);
>> -  // The width of the line that is used to divide between the view and the
>> -  // subviews.
>> -  unsigned DividerWidth = CombinedColumnWidth + 4;
>> -
>>   // We need the expansions and instantiations sorted so we can go through them
>>   // while we iterate lines.
>>   std::sort(ExpansionSubViews.begin(), ExpansionSubViews.end());
>> @@ -193,8 +468,8 @@
>>       if (S->HasCount && S->IsRegionEntry)
>>           LineCount.addRegionStartCount(S->Count);
>> 
>> -    // Render the line prefix.
>> -    renderIndent(OS, IndentLevel);
>> +    startLine(OS, IndentLevel);
>> +
>>     if (Options.ShowLineStats)
>>       renderLineCoverageColumn(OS, LineCount);
>>     if (Options.ShowLineNumbers)
>> @@ -214,51 +489,30 @@
>>     if (Options.ShowRegionMarkers && (!Options.ShowLineStatsOrRegionMarkers ||
>>                                       LineCount.hasMultipleRegions()) &&
>>         !LineSegments.empty()) {
>> -      renderIndent(OS, IndentLevel);
>> -      OS.indent(CombinedColumnWidth);
>> -      renderRegionMarkers(OS, LineSegments);
>> -    }
>> +      renderRegionMarkers(OS, LineSegments, IndentLevel, CombinedColumnWidth);
>> 
>> -    // Show the expansions and instantiations for this line.
>> -    unsigned NestedIndent = IndentLevel + 1;
>> -    bool RenderedSubView = false;
>> -    for (; NextESV != EndESV && NextESV->getLine() == LI.line_number();
>> -         ++NextESV) {
>> -      renderViewDivider(NestedIndent, DividerWidth, OS);
>> -      OS << "\n";
>> -      if (RenderedSubView) {
>> -        // Re-render the current line and highlight the expansion range for
>> -        // this subview.
>> -        ExpansionColumn = NextESV->getStartCol();
>> -        renderIndent(OS, IndentLevel);
>> -        OS.indent(CombinedColumnWidth + (IndentLevel == 0 ? 0 : 1));
>> -        renderLine(OS, *LI, LI.line_number(), WrappedSegment, LineSegments,
>> -                   ExpansionColumn);
>> -        renderViewDivider(NestedIndent, DividerWidth, OS);
>> -        OS << "\n";
>> -      }
>> -      // Render the child subview
>>       if (Options.Debug)
>> -        errs() << "Expansion at line " << NextESV->getLine() << ", "
>> -               << NextESV->getStartCol() << " -> " << NextESV->getEndCol()
>> -               << "\n";
>> -      NextESV->View->render(OS, false, NestedIndent);
>> -      RenderedSubView = true;
>> +        for (const auto *S : LineSegments)
>> +          errs() << "Marker at " << S->Line << ":" << S->Col << " = "
>> +          << formatCount(S->Count) << (S->IsRegionEntry ? "\n" : " (pop)\n");
>>     }
>> -    for (; NextISV != EndISV && NextISV->Line == LI.line_number(); ++NextISV) {
>> -      renderViewDivider(NestedIndent, DividerWidth, OS);
>> -      OS << "\n";
>> -      renderIndent(OS, NestedIndent);
>> -      OS << ' ';
>> -      Options.colored_ostream(OS, raw_ostream::CYAN) << NextISV->FunctionName
>> -                                                     << ":";
>> -      OS << "\n";
>> -      NextISV->View->render(OS, false, NestedIndent);
>> -      RenderedSubView = true;
>> -    }
>> -    if (RenderedSubView) {
>> -      renderViewDivider(NestedIndent, DividerWidth, OS);
>> -      OS << "\n";
>> -    }
>> +    renderSubviews(OS, LI, NextESV, EndESV, NextISV, EndISV,
>> +                   WrappedSegment, LineSegments, IndentLevel,
>> +                   ExpansionColumn, CombinedColumnWidth);
>> +    
>> +    endLine(OS);
>>   }
>> }
>> +
>> +std::unique_ptr<SourceCoverageView>
>> +SourceCoverageView::create(const MemoryBuffer &File,
>> +                           const CoverageViewOptions &Options,
>> +                           coverage::CoverageData &&CoverageInfo)  {
>> +  if (Options.Format == CoverageViewOptions::HTML) {
>> +    return make_unique<SourceCoverageViewHTML>(File, Options,
>> +                                               std::move(CoverageInfo));
>> +  } else {
>> +    return make_unique<SourceCoverageViewConsole>(File, Options,
>> +                                                  std::move(CoverageInfo));
> 
> Better to use a switch statement - it'll give a warning if we add a
> value to the enum and forget to update here.
> 
>> +  }
>> +}
>> Index: SourceCoverageView.h
>> ===================================================================
>> --- SourceCoverageView.h	(revision 262364)
>> +++ SourceCoverageView.h	(working copy)
>> @@ -17,6 +17,9 @@
>> #include "CoverageViewOptions.h"
>> #include "llvm/ProfileData/CoverageMapping.h"
>> #include "llvm/Support/MemoryBuffer.h"
>> +#include "llvm/Support/LineIterator.h"
>> +#include "llvm/ADT/Optional.h"
>> +#include "llvm/ADT/STLExtras.h"
>> #include <vector>
>> 
>> namespace llvm {
>> @@ -76,7 +79,7 @@
>> /// \brief A code coverage view of a specific source file.
>> /// It can have embedded coverage views.
>> class SourceCoverageView {
>> -private:
>> +protected:
>>   /// \brief Coverage information for a single line.
>>   struct LineCoverageInfo {
>>     uint64_t ExecutionCount;
>> @@ -108,25 +111,39 @@
>>   std::vector<InstantiationView> InstantiationSubViews;
>> 
>>   /// \brief Render a source line with highlighting.
>> -  void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
>> -                  const coverage::CoverageSegment *WrappedSegment,
>> -                  ArrayRef<const coverage::CoverageSegment *> Segments,
>> -                  unsigned ExpansionCol);
>> +  virtual void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
>> +                          const coverage::CoverageSegment *WrappedSegment,
>> +                          ArrayRef<const coverage::CoverageSegment *> Segments,
>> +                          unsigned ExpansionCol);
>> 
>> -  void renderIndent(raw_ostream &OS, unsigned Level);
>> -
>> -  void renderViewDivider(unsigned Offset, unsigned Length, raw_ostream &OS);
>> -
>>   /// \brief Render the line's execution count column.
>> -  void renderLineCoverageColumn(raw_ostream &OS, const LineCoverageInfo &Line);
>> +  virtual void renderLineCoverageColumn(raw_ostream &OS,
>> +                                        const LineCoverageInfo &Line) = 0;
>> 
>>   /// \brief Render the line number column.
>> -  void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo);
>> +  virtual void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo) = 0;
>> 
>>   /// \brief Render all the region's execution counts on a line.
>> -  void
>> -  renderRegionMarkers(raw_ostream &OS,
>> -                      ArrayRef<const coverage::CoverageSegment *> Segments);
>> +  virtual void renderRegionMarkers(raw_ostream &OS,
>> +                 ArrayRef<const coverage::CoverageSegment *> Segments,
>> +                                   unsigned IndentLevel,
>> +                                   unsigned CombinedColumnWidth);
>> +  
>> +  virtual void renderSubviews(llvm::raw_ostream &OS,
>> +                              line_iterator &LI,
>> +                              std::vector<ExpansionView>::iterator &NextESV,
>> +                              std::vector<ExpansionView>::iterator &EndESV,
>> +                              std::vector<InstantiationView>::iterator &NextISV,
>> +                              std::vector<InstantiationView>::iterator &EndISV,
>> +                              const coverage::CoverageSegment *WrappedSegment,
>> +                              SmallVector<const coverage::CoverageSegment *, 8>
>> +                              LineSegments,
>> +                              unsigned IndentLevel,
>> +                              unsigned ExpansionColumn,
>> +                              unsigned CombinedColumnWidth) = 0;
>> +  
>> +  virtual void startLine(raw_ostream &OS, unsigned IndentLevel) {}
>> +  virtual void endLine(raw_ostream &OS) {}
>> 
>>   static const unsigned LineCoverageColumnWidth = 7;
>>   static const unsigned LineNumberColumnWidth = 5;
>> @@ -136,6 +153,11 @@
>>                      const CoverageViewOptions &Options,
>>                      coverage::CoverageData &&CoverageInfo)
>>       : File(File), Options(Options), CoverageInfo(std::move(CoverageInfo)) {}
>> +  
>> +  
>> +  static std::unique_ptr<SourceCoverageView> create(const MemoryBuffer &File,
>> +          const CoverageViewOptions &Options,
>> +          coverage::CoverageData &&CoverageInfo);
>> 
>>   const CoverageViewOptions &getOptions() const { return Options; }
>> 
>> @@ -150,12 +172,139 @@
>>                         std::unique_ptr<SourceCoverageView> View) {
>>     InstantiationSubViews.emplace_back(FunctionName, Line, std::move(View));
>>   }
>> +  
>> +  virtual void output(llvm::raw_ostream &OS, std::string Text,
>> +                      llvm::Optional<llvm::raw_ostream::Colors> Highlight,
>> +                      const coverage::CoverageSegment *Segment = nullptr) = 0;
>> 
>>   /// \brief Print the code coverage information for a specific
>>   /// portion of a source file to the output stream.
>> -  void render(raw_ostream &OS, bool WholeFile, unsigned IndentLevel = 0);
>> +  virtual void render(raw_ostream &OS, bool WholeFile,
>> +                      unsigned IndentLevel = 0);
>> +  
>> +  /// \brief Render the title for a given source region (function or file name)
>> +  virtual void renderTitle(raw_ostream &OS, StringRef Name) = 0;
>> +  
>> +  virtual ~SourceCoverageView() {}
>> };
>> +  
>> +class SourceCoverageViewHTML : public SourceCoverageView {
>> +  
>> +  /// \brief Render a source line with highlighting.
>> +  void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
>> +                  const coverage::CoverageSegment *WrappedSegment,
>> +                  ArrayRef<const coverage::CoverageSegment *> Segments,
>> +                  unsigned ExpansionCol) override;
>> +  
>> +  void startLine(raw_ostream &OS, unsigned IndentLevel) override;
>> +  void endLine(raw_ostream &OS) override;
>> +    
>> +  /// \brief Render the line's execution count column.
>> +  void renderLineCoverageColumn(raw_ostream &OS,
>> +                                const LineCoverageInfo &Line) override;
>> +  
>> +  void output(llvm::raw_ostream &OS, std::string Text,
>> +              llvm::Optional<llvm::raw_ostream::Colors> Highlight,
>> +              const coverage::CoverageSegment *Segment = nullptr) override;
>> +  
>> +  /// \brief Render the line number column.
>> +  void renderLineNumberColumn(raw_ostream &OS,
>> +                              unsigned LineNo) override;
>> +  
>> +  /// \brief Render all the region's execution counts on a line.
>> +  void renderRegionMarkers(raw_ostream &OS,
>> +                           ArrayRef<const coverage::CoverageSegment *> Segments,
>> +                           unsigned IndentLevel,
>> +                           unsigned CombinedColumnWidth) override {
>> +    // empty implementation (region markers are tooltips).
>> +  }
>> +  
>> +  void renderSubviews(llvm::raw_ostream &OS,
>> +                      line_iterator &LI,
>> +                      std::vector<ExpansionView>::iterator &NextESV,
>> +                      std::vector<ExpansionView>::iterator &EndESV,
>> +                      std::vector<InstantiationView>::iterator &NextISV,
>> +                      std::vector<InstantiationView>::iterator &EndISV,
>> +                      const coverage::CoverageSegment *WrappedSegment,
>> +                      SmallVector<const coverage::CoverageSegment *, 8>
>> +                      LineSegments,
>> +                      unsigned IndentLevel,
>> +                      unsigned ExpansionColumn,
>> +                      unsigned CombinedColumnWidth) override;
>> +  
>> +  /// \brief Print the code coverage information for a specific
>> +  /// portion of a source file to the output stream.
>> +  void render(raw_ostream &OS, bool WholeFile,
>> +                       unsigned IndentLevel = 0) override;
>> +public:
>> +  SourceCoverageViewHTML(const MemoryBuffer &File,
>> +                         const CoverageViewOptions &Options,
>> +                         coverage::CoverageData &&CoverageInfo)
>> +  : SourceCoverageView(File, Options, std::move(CoverageInfo)) {}
>> +  
>> +  // \brief Render the standard boilerplate HTML header.
>> +  static void renderFileHeader(raw_ostream &OS);
>> +  
>> +  // \brief Render the standard boilerplate HTML footer.
>> +  static void renderFileFooter(raw_ostream &OS);
>> +  
>> +  /// \brief Render the title for a given source region (function or file name)
>> +  void renderTitle(raw_ostream &OS, StringRef Name) override;
>> +};
>> +  
>> +class SourceCoverageViewConsole : public SourceCoverageView {
>> +    
>> +  /// \brief Render a source line with highlighting.
>> +  void renderLine(raw_ostream &OS, StringRef Line, int64_t LineNumber,
>> +                  const coverage::CoverageSegment *WrappedSegment,
>> +                  ArrayRef<const coverage::CoverageSegment *> Segments,
>> +                  unsigned ExpansionCol) override;
>> 
>> +  void startLine(raw_ostream &OS, unsigned IndentLevel) override;
>> +
>> +  /// \brief Render the line's execution count column.
>> +  void renderLineCoverageColumn(raw_ostream &OS,
>> +                                const LineCoverageInfo &Line) override;
>> +  
>> +  void output(llvm::raw_ostream &OS, std::string Text,
>> +              llvm::Optional<llvm::raw_ostream::Colors> Highlight,
>> +              const coverage::CoverageSegment *Segment = nullptr) override;
>> +    
>> +  /// \brief Render the line number column.
>> +  void renderLineNumberColumn(raw_ostream &OS, unsigned LineNo) override;
>> +  
>> +  /// \brief Render all the region's execution counts on a line.
>> +  void renderRegionMarkers(raw_ostream &OS,
>> +                           ArrayRef<const coverage::CoverageSegment *> Segments,
>> +                           unsigned IndentLevel,
>> +                           unsigned CombinedColumnWidth) override;
>> +  
>> +  void renderSubviews(raw_ostream &OS,
>> +                      line_iterator &LI,
>> +                      std::vector<ExpansionView>::iterator &NextESV,
>> +                      std::vector<ExpansionView>::iterator &EndESV,
>> +                      std::vector<InstantiationView>::iterator &NextISV,
>> +                      std::vector<InstantiationView>::iterator &EndISV,
>> +                      const coverage::CoverageSegment *WrappedSegment,
>> +                      SmallVector<const coverage::CoverageSegment *, 8>
>> +                        LineSegments,
>> +                      unsigned IndentLevel,
>> +                      unsigned ExpansionColumn,
>> +                      unsigned CombinedColumnWidth) override;
>> +  
>> +  void renderIndent(raw_ostream &OS, unsigned Level);
>> +    
>> +  void renderViewDivider(unsigned Offset, unsigned Length, raw_ostream &OS);
>> +public:
>> +  SourceCoverageViewConsole(const MemoryBuffer &File,
>> +                            const CoverageViewOptions &Options,
>> +                            coverage::CoverageData &&CoverageInfo)
>> +  : SourceCoverageView(File, Options, std::move(CoverageInfo)) {}
>> +  
>> +  /// \brief Render the title for a given source region (function or file name)
>> +  void renderTitle(raw_ostream &OS, StringRef Name) override;
>> +};
>> +
>> } // namespace llvm
>> 
>> #endif // LLVM_COV_SOURCECOVERAGEVIEW_H
>> Index: css.inc
>> ===================================================================
>> --- css.inc	(revision 0)
>> +++ css.inc	(working copy)
>> @@ -0,0 +1,108 @@
>> +//===------------ css.inc - css file for HTML coverage reports ------------===//
>> +//
>> +//                     The LLVM Compiler Infrastructure
>> +//
>> +// This file is distributed under the University of Illinois Open Source
>> +// License. See LICENSE.TXT for details.
>> +//
>> +//===----------------------------------------------------------------------===//
>> +//
>> +// This file has a raw string literal that is inserted as a CSS style into
>> +// HTML documents generated by llvm-cov.
>> +//
>> +// This file includes the necessary styling for centered tables of code and
>> +// tooltip generation.
>> +//
>> +//===----------------------------------------------------------------------===//
>> +
>> +StringRef css = R"(.red {
>> +  background-color: #FFD0D0;
>> +}
>> +.cyan {
>> +  background-color: cyan;
>> +}
>> +.black {
>> +  background-color: black;
>> +  color: white;
>> +}
>> +.green {
>> +  background-color: #98FFA6;
>> +  color: white;
>> +}
>> +.magenta {
>> +  background-color: #F998FF;
>> +  color: white;
>> +}
>> +tr:nth-child(odd) {
>> +  background-color: #fafafa;
>> +}
>> +tr:nth-child(even) {
>> +  background-color: #f0f0f0;
>> +}
>> +pre {
>> +  margin-top: 0px !important;
>> +  margin-bottom: 0px !important;
>> +}
>> +.function-title {
>> +  margin-top: 15px;
>> +  margin-bottom: 5px;
>> +}
>> +.centered {
>> +  margin-left: auto;
>> +  margin-right: auto;
>> +}
>> +.expansion-view {
>> +  border: 2px solid #d8d8d8;
>> +  padding-left: 10px;
>> +  padding-right: 10px;
>> +  padding-bottom: 10px;
>> +  margin-bottom: 5px;
>> +}
>> +table {
>> +  border: 2px solid #dbdbdb;
>> +  border-collapse: collapse;
>> +}
>> +.numeric {
>> +  text-align: right;
>> +}
>> +.tooltips {
>> +  position: relative;
>> +  display: inline;
>> +  background-color: #fffee6;
>> +  text-decoration: none;
>> +}
>> +.tooltips span {
>> +  position: absolute;
>> +  width:140px;
>> +  color: #FFFFFF;
>> +  background: #000000;
>> +  height: 30px;
>> +  line-height: 30px;
>> +  text-align: center;
>> +  visibility: hidden;
>> +  border-radius: 6px;
>> +}
>> +.tooltips span:after {
>> +  content: '';
>> +  position: absolute;
>> +  top: 100%;
>> +  left: 50%;
>> +  margin-left: -8px;
>> +  width: 0; height: 0;
>> +  border-top: 8px solid #000000;
>> +  border-right: 8px solid transparent;
>> +  border-left: 8px solid transparent;
>> +}
>> +:hover.tooltips span {
>> +  visibility: visible;
>> +  opacity: 0.8;
>> +  bottom: 30px;
>> +  left: 50%;
>> +  margin-left: -76px;
>> +  z-index: 999;
>> +}
>> +th, td {
>> +  vertical-align: top;
>> +  padding: 2px 5px;
>> +  border-collapse: collapse;
>> +})";
> _______________________________________________
> llvm-commits mailing list
> llvm-commits at lists.llvm.org
> http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits



More information about the llvm-commits mailing list