10621cb2eSAlp Toker //== HTMLRewrite.cpp - Translate source code into prettified HTML --*- C++ -*-//
20621cb2eSAlp Toker //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60621cb2eSAlp Toker //
70621cb2eSAlp Toker //===----------------------------------------------------------------------===//
80621cb2eSAlp Toker //
90621cb2eSAlp Toker //  This file defines the HTMLRewriter class, which is used to translate the
100621cb2eSAlp Toker //  text of a source file into prettified HTML.
110621cb2eSAlp Toker //
120621cb2eSAlp Toker //===----------------------------------------------------------------------===//
130621cb2eSAlp Toker 
140621cb2eSAlp Toker #include "clang/Rewrite/Core/HTMLRewrite.h"
150621cb2eSAlp Toker #include "clang/Basic/SourceManager.h"
160621cb2eSAlp Toker #include "clang/Lex/Preprocessor.h"
170621cb2eSAlp Toker #include "clang/Lex/TokenConcatenation.h"
180621cb2eSAlp Toker #include "clang/Rewrite/Core/Rewriter.h"
190621cb2eSAlp Toker #include "llvm/ADT/SmallString.h"
200621cb2eSAlp Toker #include "llvm/Support/ErrorHandling.h"
210621cb2eSAlp Toker #include "llvm/Support/MemoryBuffer.h"
220621cb2eSAlp Toker #include "llvm/Support/raw_ostream.h"
230621cb2eSAlp Toker #include <memory>
240621cb2eSAlp Toker using namespace clang;
250621cb2eSAlp Toker 
260621cb2eSAlp Toker 
270621cb2eSAlp Toker /// HighlightRange - Highlight a range in the source code with the specified
280621cb2eSAlp Toker /// start/end tags.  B/E must be in the same file.  This ensures that
290621cb2eSAlp Toker /// start/end tags are placed at the start/end of each line if the range is
300621cb2eSAlp Toker /// multiline.
HighlightRange(Rewriter & R,SourceLocation B,SourceLocation E,const char * StartTag,const char * EndTag,bool IsTokenRange)310621cb2eSAlp Toker void html::HighlightRange(Rewriter &R, SourceLocation B, SourceLocation E,
32b5f8171aSRichard Smith                           const char *StartTag, const char *EndTag,
33b5f8171aSRichard Smith                           bool IsTokenRange) {
340621cb2eSAlp Toker   SourceManager &SM = R.getSourceMgr();
350621cb2eSAlp Toker   B = SM.getExpansionLoc(B);
360621cb2eSAlp Toker   E = SM.getExpansionLoc(E);
370621cb2eSAlp Toker   FileID FID = SM.getFileID(B);
380621cb2eSAlp Toker   assert(SM.getFileID(E) == FID && "B/E not in the same file!");
390621cb2eSAlp Toker 
400621cb2eSAlp Toker   unsigned BOffset = SM.getFileOffset(B);
410621cb2eSAlp Toker   unsigned EOffset = SM.getFileOffset(E);
420621cb2eSAlp Toker 
430621cb2eSAlp Toker   // Include the whole end token in the range.
44b5f8171aSRichard Smith   if (IsTokenRange)
450621cb2eSAlp Toker     EOffset += Lexer::MeasureTokenLength(E, R.getSourceMgr(), R.getLangOpts());
460621cb2eSAlp Toker 
470621cb2eSAlp Toker   bool Invalid = false;
480621cb2eSAlp Toker   const char *BufferStart = SM.getBufferData(FID, &Invalid).data();
490621cb2eSAlp Toker   if (Invalid)
500621cb2eSAlp Toker     return;
510621cb2eSAlp Toker 
520621cb2eSAlp Toker   HighlightRange(R.getEditBuffer(FID), BOffset, EOffset,
530621cb2eSAlp Toker                  BufferStart, StartTag, EndTag);
540621cb2eSAlp Toker }
550621cb2eSAlp Toker 
560621cb2eSAlp Toker /// HighlightRange - This is the same as the above method, but takes
570621cb2eSAlp Toker /// decomposed file locations.
HighlightRange(RewriteBuffer & RB,unsigned B,unsigned E,const char * BufferStart,const char * StartTag,const char * EndTag)580621cb2eSAlp Toker void html::HighlightRange(RewriteBuffer &RB, unsigned B, unsigned E,
590621cb2eSAlp Toker                           const char *BufferStart,
600621cb2eSAlp Toker                           const char *StartTag, const char *EndTag) {
610621cb2eSAlp Toker   // Insert the tag at the absolute start/end of the range.
620621cb2eSAlp Toker   RB.InsertTextAfter(B, StartTag);
630621cb2eSAlp Toker   RB.InsertTextBefore(E, EndTag);
640621cb2eSAlp Toker 
650621cb2eSAlp Toker   // Scan the range to see if there is a \r or \n.  If so, and if the line is
660621cb2eSAlp Toker   // not blank, insert tags on that line as well.
670621cb2eSAlp Toker   bool HadOpenTag = true;
680621cb2eSAlp Toker 
690621cb2eSAlp Toker   unsigned LastNonWhiteSpace = B;
700621cb2eSAlp Toker   for (unsigned i = B; i != E; ++i) {
710621cb2eSAlp Toker     switch (BufferStart[i]) {
720621cb2eSAlp Toker     case '\r':
730621cb2eSAlp Toker     case '\n':
740621cb2eSAlp Toker       // Okay, we found a newline in the range.  If we have an open tag, we need
750621cb2eSAlp Toker       // to insert a close tag at the first non-whitespace before the newline.
760621cb2eSAlp Toker       if (HadOpenTag)
770621cb2eSAlp Toker         RB.InsertTextBefore(LastNonWhiteSpace+1, EndTag);
780621cb2eSAlp Toker 
790621cb2eSAlp Toker       // Instead of inserting an open tag immediately after the newline, we
800621cb2eSAlp Toker       // wait until we see a non-whitespace character.  This prevents us from
810621cb2eSAlp Toker       // inserting tags around blank lines, and also allows the open tag to
820621cb2eSAlp Toker       // be put *after* whitespace on a non-blank line.
830621cb2eSAlp Toker       HadOpenTag = false;
840621cb2eSAlp Toker       break;
850621cb2eSAlp Toker     case '\0':
860621cb2eSAlp Toker     case ' ':
870621cb2eSAlp Toker     case '\t':
880621cb2eSAlp Toker     case '\f':
890621cb2eSAlp Toker     case '\v':
900621cb2eSAlp Toker       // Ignore whitespace.
910621cb2eSAlp Toker       break;
920621cb2eSAlp Toker 
930621cb2eSAlp Toker     default:
940621cb2eSAlp Toker       // If there is no tag open, do it now.
950621cb2eSAlp Toker       if (!HadOpenTag) {
960621cb2eSAlp Toker         RB.InsertTextAfter(i, StartTag);
970621cb2eSAlp Toker         HadOpenTag = true;
980621cb2eSAlp Toker       }
990621cb2eSAlp Toker 
1000621cb2eSAlp Toker       // Remember this character.
1010621cb2eSAlp Toker       LastNonWhiteSpace = i;
1020621cb2eSAlp Toker       break;
1030621cb2eSAlp Toker     }
1040621cb2eSAlp Toker   }
1050621cb2eSAlp Toker }
1060621cb2eSAlp Toker 
EscapeText(Rewriter & R,FileID FID,bool EscapeSpaces,bool ReplaceTabs)1070621cb2eSAlp Toker void html::EscapeText(Rewriter &R, FileID FID,
1080621cb2eSAlp Toker                       bool EscapeSpaces, bool ReplaceTabs) {
1090621cb2eSAlp Toker 
1100ddf4bd4SDuncan P. N. Exon Smith   llvm::MemoryBufferRef Buf = R.getSourceMgr().getBufferOrFake(FID);
1110ddf4bd4SDuncan P. N. Exon Smith   const char* C = Buf.getBufferStart();
1120ddf4bd4SDuncan P. N. Exon Smith   const char* FileEnd = Buf.getBufferEnd();
1130621cb2eSAlp Toker 
1140621cb2eSAlp Toker   assert (C <= FileEnd);
1150621cb2eSAlp Toker 
1160621cb2eSAlp Toker   RewriteBuffer &RB = R.getEditBuffer(FID);
1170621cb2eSAlp Toker 
1180621cb2eSAlp Toker   unsigned ColNo = 0;
1190621cb2eSAlp Toker   for (unsigned FilePos = 0; C != FileEnd ; ++C, ++FilePos) {
1200621cb2eSAlp Toker     switch (*C) {
1210621cb2eSAlp Toker     default: ++ColNo; break;
1220621cb2eSAlp Toker     case '\n':
1230621cb2eSAlp Toker     case '\r':
1240621cb2eSAlp Toker       ColNo = 0;
1250621cb2eSAlp Toker       break;
1260621cb2eSAlp Toker 
1270621cb2eSAlp Toker     case ' ':
1280621cb2eSAlp Toker       if (EscapeSpaces)
1290621cb2eSAlp Toker         RB.ReplaceText(FilePos, 1, "&nbsp;");
1300621cb2eSAlp Toker       ++ColNo;
1310621cb2eSAlp Toker       break;
1320621cb2eSAlp Toker     case '\f':
1330621cb2eSAlp Toker       RB.ReplaceText(FilePos, 1, "<hr>");
1340621cb2eSAlp Toker       ColNo = 0;
1350621cb2eSAlp Toker       break;
1360621cb2eSAlp Toker 
1370621cb2eSAlp Toker     case '\t': {
1380621cb2eSAlp Toker       if (!ReplaceTabs)
1390621cb2eSAlp Toker         break;
1400621cb2eSAlp Toker       unsigned NumSpaces = 8-(ColNo&7);
1410621cb2eSAlp Toker       if (EscapeSpaces)
1420621cb2eSAlp Toker         RB.ReplaceText(FilePos, 1,
1430621cb2eSAlp Toker                        StringRef("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
1440621cb2eSAlp Toker                                        "&nbsp;&nbsp;&nbsp;", 6*NumSpaces));
1450621cb2eSAlp Toker       else
1460621cb2eSAlp Toker         RB.ReplaceText(FilePos, 1, StringRef("        ", NumSpaces));
1470621cb2eSAlp Toker       ColNo += NumSpaces;
1480621cb2eSAlp Toker       break;
1490621cb2eSAlp Toker     }
1500621cb2eSAlp Toker     case '<':
1510621cb2eSAlp Toker       RB.ReplaceText(FilePos, 1, "&lt;");
1520621cb2eSAlp Toker       ++ColNo;
1530621cb2eSAlp Toker       break;
1540621cb2eSAlp Toker 
1550621cb2eSAlp Toker     case '>':
1560621cb2eSAlp Toker       RB.ReplaceText(FilePos, 1, "&gt;");
1570621cb2eSAlp Toker       ++ColNo;
1580621cb2eSAlp Toker       break;
1590621cb2eSAlp Toker 
1600621cb2eSAlp Toker     case '&':
1610621cb2eSAlp Toker       RB.ReplaceText(FilePos, 1, "&amp;");
1620621cb2eSAlp Toker       ++ColNo;
1630621cb2eSAlp Toker       break;
1640621cb2eSAlp Toker     }
1650621cb2eSAlp Toker   }
1660621cb2eSAlp Toker }
1670621cb2eSAlp Toker 
EscapeText(StringRef s,bool EscapeSpaces,bool ReplaceTabs)1680621cb2eSAlp Toker std::string html::EscapeText(StringRef s, bool EscapeSpaces, bool ReplaceTabs) {
1690621cb2eSAlp Toker 
1700621cb2eSAlp Toker   unsigned len = s.size();
1710621cb2eSAlp Toker   std::string Str;
1720621cb2eSAlp Toker   llvm::raw_string_ostream os(Str);
1730621cb2eSAlp Toker 
1740621cb2eSAlp Toker   for (unsigned i = 0 ; i < len; ++i) {
1750621cb2eSAlp Toker 
1760621cb2eSAlp Toker     char c = s[i];
1770621cb2eSAlp Toker     switch (c) {
1780621cb2eSAlp Toker     default:
1790621cb2eSAlp Toker       os << c; break;
1800621cb2eSAlp Toker 
1810621cb2eSAlp Toker     case ' ':
1820621cb2eSAlp Toker       if (EscapeSpaces) os << "&nbsp;";
1830621cb2eSAlp Toker       else os << ' ';
1840621cb2eSAlp Toker       break;
1850621cb2eSAlp Toker 
1860621cb2eSAlp Toker     case '\t':
1870621cb2eSAlp Toker       if (ReplaceTabs) {
1880621cb2eSAlp Toker         if (EscapeSpaces)
1890621cb2eSAlp Toker           for (unsigned i = 0; i < 4; ++i)
1900621cb2eSAlp Toker             os << "&nbsp;";
1910621cb2eSAlp Toker         else
1920621cb2eSAlp Toker           for (unsigned i = 0; i < 4; ++i)
1930621cb2eSAlp Toker             os << " ";
1940621cb2eSAlp Toker       }
1950621cb2eSAlp Toker       else
1960621cb2eSAlp Toker         os << c;
1970621cb2eSAlp Toker 
1980621cb2eSAlp Toker       break;
1990621cb2eSAlp Toker 
2000621cb2eSAlp Toker     case '<': os << "&lt;"; break;
2010621cb2eSAlp Toker     case '>': os << "&gt;"; break;
2020621cb2eSAlp Toker     case '&': os << "&amp;"; break;
2030621cb2eSAlp Toker     }
2040621cb2eSAlp Toker   }
2050621cb2eSAlp Toker 
2060cf6f7b1SLogan Smith   return Str;
2070621cb2eSAlp Toker }
2080621cb2eSAlp Toker 
AddLineNumber(RewriteBuffer & RB,unsigned LineNo,unsigned B,unsigned E)2090621cb2eSAlp Toker static void AddLineNumber(RewriteBuffer &RB, unsigned LineNo,
2100621cb2eSAlp Toker                           unsigned B, unsigned E) {
2110621cb2eSAlp Toker   SmallString<256> Str;
2120621cb2eSAlp Toker   llvm::raw_svector_ostream OS(Str);
2130621cb2eSAlp Toker 
214a5ddd3caSGeorge Karpenkov   OS << "<tr class=\"codeline\" data-linenumber=\"" << LineNo << "\">"
215a5ddd3caSGeorge Karpenkov      << "<td class=\"num\" id=\"LN" << LineNo << "\">" << LineNo
216a5ddd3caSGeorge Karpenkov      << "</td><td class=\"line\">";
2170621cb2eSAlp Toker 
2180621cb2eSAlp Toker   if (B == E) { // Handle empty lines.
2190621cb2eSAlp Toker     OS << " </td></tr>";
2200621cb2eSAlp Toker     RB.InsertTextBefore(B, OS.str());
2210621cb2eSAlp Toker   } else {
2220621cb2eSAlp Toker     RB.InsertTextBefore(B, OS.str());
2230621cb2eSAlp Toker     RB.InsertTextBefore(E, "</td></tr>");
2240621cb2eSAlp Toker   }
2250621cb2eSAlp Toker }
2260621cb2eSAlp Toker 
AddLineNumbers(Rewriter & R,FileID FID)2270621cb2eSAlp Toker void html::AddLineNumbers(Rewriter& R, FileID FID) {
2280621cb2eSAlp Toker 
2290ddf4bd4SDuncan P. N. Exon Smith   llvm::MemoryBufferRef Buf = R.getSourceMgr().getBufferOrFake(FID);
2300ddf4bd4SDuncan P. N. Exon Smith   const char* FileBeg = Buf.getBufferStart();
2310ddf4bd4SDuncan P. N. Exon Smith   const char* FileEnd = Buf.getBufferEnd();
2320621cb2eSAlp Toker   const char* C = FileBeg;
2330621cb2eSAlp Toker   RewriteBuffer &RB = R.getEditBuffer(FID);
2340621cb2eSAlp Toker 
2350621cb2eSAlp Toker   assert (C <= FileEnd);
2360621cb2eSAlp Toker 
2370621cb2eSAlp Toker   unsigned LineNo = 0;
2380621cb2eSAlp Toker   unsigned FilePos = 0;
2390621cb2eSAlp Toker 
2400621cb2eSAlp Toker   while (C != FileEnd) {
2410621cb2eSAlp Toker 
2420621cb2eSAlp Toker     ++LineNo;
2430621cb2eSAlp Toker     unsigned LineStartPos = FilePos;
2440621cb2eSAlp Toker     unsigned LineEndPos = FileEnd - FileBeg;
2450621cb2eSAlp Toker 
2460621cb2eSAlp Toker     assert (FilePos <= LineEndPos);
2470621cb2eSAlp Toker     assert (C < FileEnd);
2480621cb2eSAlp Toker 
2490621cb2eSAlp Toker     // Scan until the newline (or end-of-file).
2500621cb2eSAlp Toker 
2510621cb2eSAlp Toker     while (C != FileEnd) {
2520621cb2eSAlp Toker       char c = *C;
2530621cb2eSAlp Toker       ++C;
2540621cb2eSAlp Toker 
2550621cb2eSAlp Toker       if (c == '\n') {
2560621cb2eSAlp Toker         LineEndPos = FilePos++;
2570621cb2eSAlp Toker         break;
2580621cb2eSAlp Toker       }
2590621cb2eSAlp Toker 
2600621cb2eSAlp Toker       ++FilePos;
2610621cb2eSAlp Toker     }
2620621cb2eSAlp Toker 
2630621cb2eSAlp Toker     AddLineNumber(RB, LineNo, LineStartPos, LineEndPos);
2640621cb2eSAlp Toker   }
2650621cb2eSAlp Toker 
2660621cb2eSAlp Toker   // Add one big table tag that surrounds all of the code.
267a5ddd3caSGeorge Karpenkov   std::string s;
268a5ddd3caSGeorge Karpenkov   llvm::raw_string_ostream os(s);
269a5ddd3caSGeorge Karpenkov   os << "<table class=\"code\" data-fileid=\"" << FID.getHashValue() << "\">\n";
270a5ddd3caSGeorge Karpenkov   RB.InsertTextBefore(0, os.str());
2710621cb2eSAlp Toker   RB.InsertTextAfter(FileEnd - FileBeg, "</table>");
2720621cb2eSAlp Toker }
2730621cb2eSAlp Toker 
AddHeaderFooterInternalBuiltinCSS(Rewriter & R,FileID FID,StringRef title)2740621cb2eSAlp Toker void html::AddHeaderFooterInternalBuiltinCSS(Rewriter &R, FileID FID,
27599d1b295SMehdi Amini                                              StringRef title) {
2760621cb2eSAlp Toker 
2770ddf4bd4SDuncan P. N. Exon Smith   llvm::MemoryBufferRef Buf = R.getSourceMgr().getBufferOrFake(FID);
2780ddf4bd4SDuncan P. N. Exon Smith   const char* FileStart = Buf.getBufferStart();
2790ddf4bd4SDuncan P. N. Exon Smith   const char* FileEnd = Buf.getBufferEnd();
2800621cb2eSAlp Toker 
2810621cb2eSAlp Toker   SourceLocation StartLoc = R.getSourceMgr().getLocForStartOfFile(FID);
2820621cb2eSAlp Toker   SourceLocation EndLoc = StartLoc.getLocWithOffset(FileEnd-FileStart);
2830621cb2eSAlp Toker 
2840621cb2eSAlp Toker   std::string s;
2850621cb2eSAlp Toker   llvm::raw_string_ostream os(s);
2860621cb2eSAlp Toker   os << "<!doctype html>\n" // Use HTML 5 doctype
2870621cb2eSAlp Toker         "<html>\n<head>\n";
2880621cb2eSAlp Toker 
28999d1b295SMehdi Amini   if (!title.empty())
2900621cb2eSAlp Toker     os << "<title>" << html::EscapeText(title) << "</title>\n";
2910621cb2eSAlp Toker 
2927c540debSGeorge Karpenkov   os << R"<<<(
2937c540debSGeorge Karpenkov <style type="text/css">
2947c540debSGeorge Karpenkov body { color:#000000; background-color:#ffffff }
2957c540debSGeorge Karpenkov body { font-family:Helvetica, sans-serif; font-size:10pt }
2967c540debSGeorge Karpenkov h1 { font-size:14pt }
2977c540debSGeorge Karpenkov .FileName { margin-top: 5px; margin-bottom: 5px; display: inline; }
2987c540debSGeorge Karpenkov .FileNav { margin-left: 5px; margin-right: 5px; display: inline; }
2997c540debSGeorge Karpenkov .FileNav a { text-decoration:none; font-size: larger; }
3007c540debSGeorge Karpenkov .divider { margin-top: 30px; margin-bottom: 30px; height: 15px; }
3017c540debSGeorge Karpenkov .divider { background-color: gray; }
3027c540debSGeorge Karpenkov .code { border-collapse:collapse; width:100%; }
3037c540debSGeorge Karpenkov .code { font-family: "Monospace", monospace; font-size:10pt }
3047c540debSGeorge Karpenkov .code { line-height: 1.2em }
3057c540debSGeorge Karpenkov .comment { color: green; font-style: oblique }
3067c540debSGeorge Karpenkov .keyword { color: blue }
3077c540debSGeorge Karpenkov .string_literal { color: red }
3087c540debSGeorge Karpenkov .directive { color: darkmagenta }
3091d7ca677SCsaba Dabis 
3101d7ca677SCsaba Dabis /* Macros and variables could have pop-up notes hidden by default.
3111d7ca677SCsaba Dabis   - Macro pop-up:    expansion of the macro
3121d7ca677SCsaba Dabis   - Variable pop-up: value (table) of the variable */
3131d7ca677SCsaba Dabis .macro_popup, .variable_popup { display: none; }
3141d7ca677SCsaba Dabis 
3151d7ca677SCsaba Dabis /* Pop-up appears on mouse-hover event. */
3161d7ca677SCsaba Dabis .macro:hover .macro_popup, .variable:hover .variable_popup {
3177c540debSGeorge Karpenkov   display: block;
3187c540debSGeorge Karpenkov   padding: 2px;
3197c540debSGeorge Karpenkov   -webkit-border-radius:5px;
3207c540debSGeorge Karpenkov   -webkit-box-shadow:1px 1px 7px #000;
3217c540debSGeorge Karpenkov   border-radius:5px;
3227c540debSGeorge Karpenkov   box-shadow:1px 1px 7px #000;
3237c540debSGeorge Karpenkov   position: absolute;
3247c540debSGeorge Karpenkov   top: -1em;
3257c540debSGeorge Karpenkov   left:10em;
3267c540debSGeorge Karpenkov   z-index: 1
3277c540debSGeorge Karpenkov }
3287c540debSGeorge Karpenkov 
3291d7ca677SCsaba Dabis .macro_popup {
3301d7ca677SCsaba Dabis   border: 2px solid red;
3311d7ca677SCsaba Dabis   background-color:#FFF0F0;
3321d7ca677SCsaba Dabis   font-weight: normal;
3331d7ca677SCsaba Dabis }
3341d7ca677SCsaba Dabis 
3351d7ca677SCsaba Dabis .variable_popup {
3361d7ca677SCsaba Dabis   border: 2px solid blue;
3371d7ca677SCsaba Dabis   background-color:#F0F0FF;
3381d7ca677SCsaba Dabis   font-weight: bold;
3391d7ca677SCsaba Dabis   font-family: Helvetica, sans-serif;
3401d7ca677SCsaba Dabis   font-size: 9pt;
3411d7ca677SCsaba Dabis }
3421d7ca677SCsaba Dabis 
3431d7ca677SCsaba Dabis /* Pop-up notes needs a relative position as a base where they pops up. */
3441d7ca677SCsaba Dabis .macro, .variable {
3451d7ca677SCsaba Dabis   background-color: PaleGoldenRod;
3461d7ca677SCsaba Dabis   position: relative;
3471d7ca677SCsaba Dabis }
3481d7ca677SCsaba Dabis .macro { color: DarkMagenta; }
3491d7ca677SCsaba Dabis 
3507c540debSGeorge Karpenkov #tooltiphint {
3517c540debSGeorge Karpenkov   position: fixed;
3527c540debSGeorge Karpenkov   width: 50em;
3537c540debSGeorge Karpenkov   margin-left: -25em;
3547c540debSGeorge Karpenkov   left: 50%;
3557c540debSGeorge Karpenkov   padding: 10px;
3567c540debSGeorge Karpenkov   border: 1px solid #b0b0b0;
3577c540debSGeorge Karpenkov   border-radius: 2px;
3587c540debSGeorge Karpenkov   box-shadow: 1px 1px 7px black;
3597c540debSGeorge Karpenkov   background-color: #c0c0c0;
3607c540debSGeorge Karpenkov   z-index: 2;
3617c540debSGeorge Karpenkov }
3627c540debSGeorge Karpenkov 
3637c540debSGeorge Karpenkov .num { width:2.5em; padding-right:2ex; background-color:#eeeeee }
3647c540debSGeorge Karpenkov .num { text-align:right; font-size:8pt }
3657c540debSGeorge Karpenkov .num { color:#444444 }
3667c540debSGeorge Karpenkov .line { padding-left: 1ex; border-left: 3px solid #ccc }
3677c540debSGeorge Karpenkov .line { white-space: pre }
3687c540debSGeorge Karpenkov .msg { -webkit-box-shadow:1px 1px 7px #000 }
3697c540debSGeorge Karpenkov .msg { box-shadow:1px 1px 7px #000 }
3707c540debSGeorge Karpenkov .msg { -webkit-border-radius:5px }
3717c540debSGeorge Karpenkov .msg { border-radius:5px }
3727c540debSGeorge Karpenkov .msg { font-family:Helvetica, sans-serif; font-size:8pt }
3737c540debSGeorge Karpenkov .msg { float:left }
37497bcafa2SValeriy Savchenko .msg { position:relative }
3757c540debSGeorge Karpenkov .msg { padding:0.25em 1ex 0.25em 1ex }
3767c540debSGeorge Karpenkov .msg { margin-top:10px; margin-bottom:10px }
3777c540debSGeorge Karpenkov .msg { font-weight:bold }
3787c540debSGeorge Karpenkov .msg { max-width:60em; word-wrap: break-word; white-space: pre-wrap }
3797c540debSGeorge Karpenkov .msgT { padding:0x; spacing:0x }
3807c540debSGeorge Karpenkov .msgEvent { background-color:#fff8b4; color:#000000 }
3817c540debSGeorge Karpenkov .msgControl { background-color:#bbbbbb; color:#000000 }
3827c540debSGeorge Karpenkov .msgNote { background-color:#ddeeff; color:#000000 }
3837c540debSGeorge Karpenkov .mrange { background-color:#dfddf3 }
3847c540debSGeorge Karpenkov .mrange { border-bottom:1px solid #6F9DBE }
3857c540debSGeorge Karpenkov .PathIndex { font-weight: bold; padding:0px 5px; margin-right:5px; }
3867c540debSGeorge Karpenkov .PathIndex { -webkit-border-radius:8px }
3877c540debSGeorge Karpenkov .PathIndex { border-radius:8px }
3887c540debSGeorge Karpenkov .PathIndexEvent { background-color:#bfba87 }
3897c540debSGeorge Karpenkov .PathIndexControl { background-color:#8c8c8c }
3901d7ca677SCsaba Dabis .PathIndexPopUp { background-color: #879abc; }
3917c540debSGeorge Karpenkov .PathNav a { text-decoration:none; font-size: larger }
3927c540debSGeorge Karpenkov .CodeInsertionHint { font-weight: bold; background-color: #10dd10 }
3937c540debSGeorge Karpenkov .CodeRemovalHint { background-color:#de1010 }
3947c540debSGeorge Karpenkov .CodeRemovalHint { border-bottom:1px solid #6F9DBE }
3959e02f587SValeriy Savchenko .msg.selected{ background-color:orange !important; }
3967c540debSGeorge Karpenkov 
3977c540debSGeorge Karpenkov table.simpletable {
3987c540debSGeorge Karpenkov   padding: 5px;
3997c540debSGeorge Karpenkov   font-size:12pt;
4007c540debSGeorge Karpenkov   margin:20px;
4017c540debSGeorge Karpenkov   border-collapse: collapse; border-spacing: 0px;
4027c540debSGeorge Karpenkov }
4037c540debSGeorge Karpenkov td.rowname {
4047c540debSGeorge Karpenkov   text-align: right;
4057c540debSGeorge Karpenkov   vertical-align: top;
4067c540debSGeorge Karpenkov   font-weight: bold;
4077c540debSGeorge Karpenkov   color:#444444;
4087c540debSGeorge Karpenkov   padding-right:2ex;
4097c540debSGeorge Karpenkov }
4104a190fe6SGeorge Karpenkov 
4114a190fe6SGeorge Karpenkov /* Hidden text. */
4124a190fe6SGeorge Karpenkov input.spoilerhider + label {
4134a190fe6SGeorge Karpenkov   cursor: pointer;
4144a190fe6SGeorge Karpenkov   text-decoration: underline;
4154a190fe6SGeorge Karpenkov   display: block;
4164a190fe6SGeorge Karpenkov }
4174a190fe6SGeorge Karpenkov input.spoilerhider {
4184a190fe6SGeorge Karpenkov  display: none;
4194a190fe6SGeorge Karpenkov }
4204a190fe6SGeorge Karpenkov input.spoilerhider ~ .spoiler {
4214a190fe6SGeorge Karpenkov   overflow: hidden;
4224a190fe6SGeorge Karpenkov   margin: 10px auto 0;
4234a190fe6SGeorge Karpenkov   height: 0;
4244a190fe6SGeorge Karpenkov   opacity: 0;
4254a190fe6SGeorge Karpenkov }
4264a190fe6SGeorge Karpenkov input.spoilerhider:checked + label + .spoiler{
4274a190fe6SGeorge Karpenkov   height: auto;
4284a190fe6SGeorge Karpenkov   opacity: 1;
4294a190fe6SGeorge Karpenkov }
4307c540debSGeorge Karpenkov </style>
4317c540debSGeorge Karpenkov </head>
4327c540debSGeorge Karpenkov <body>)<<<";
4330621cb2eSAlp Toker 
4340621cb2eSAlp Toker   // Generate header
4350621cb2eSAlp Toker   R.InsertTextBefore(StartLoc, os.str());
4360621cb2eSAlp Toker   // Generate footer
4370621cb2eSAlp Toker 
4380621cb2eSAlp Toker   R.InsertTextAfter(EndLoc, "</body></html>\n");
4390621cb2eSAlp Toker }
4400621cb2eSAlp Toker 
4410621cb2eSAlp Toker /// SyntaxHighlight - Relex the specified FileID and annotate the HTML with
4420621cb2eSAlp Toker /// information about keywords, macro expansions etc.  This uses the macro
4430621cb2eSAlp Toker /// table state from the end of the file, so it won't be perfectly perfect,
4440621cb2eSAlp Toker /// but it will be reasonably close.
SyntaxHighlight(Rewriter & R,FileID FID,const Preprocessor & PP)4450621cb2eSAlp Toker void html::SyntaxHighlight(Rewriter &R, FileID FID, const Preprocessor &PP) {
4460621cb2eSAlp Toker   RewriteBuffer &RB = R.getEditBuffer(FID);
4470621cb2eSAlp Toker 
4480621cb2eSAlp Toker   const SourceManager &SM = PP.getSourceManager();
449b3eff6b7SDuncan P. N. Exon Smith   llvm::MemoryBufferRef FromFile = SM.getBufferOrFake(FID);
4500621cb2eSAlp Toker   Lexer L(FID, FromFile, SM, PP.getLangOpts());
4510621cb2eSAlp Toker   const char *BufferStart = L.getBuffer().data();
4520621cb2eSAlp Toker 
4530621cb2eSAlp Toker   // Inform the preprocessor that we want to retain comments as tokens, so we
4540621cb2eSAlp Toker   // can highlight them.
4550621cb2eSAlp Toker   L.SetCommentRetentionState(true);
4560621cb2eSAlp Toker 
4570621cb2eSAlp Toker   // Lex all the tokens in raw mode, to avoid entering #includes or expanding
4580621cb2eSAlp Toker   // macros.
4590621cb2eSAlp Toker   Token Tok;
4600621cb2eSAlp Toker   L.LexFromRawLexer(Tok);
4610621cb2eSAlp Toker 
4620621cb2eSAlp Toker   while (Tok.isNot(tok::eof)) {
4630621cb2eSAlp Toker     // Since we are lexing unexpanded tokens, all tokens are from the main
4640621cb2eSAlp Toker     // FileID.
4650621cb2eSAlp Toker     unsigned TokOffs = SM.getFileOffset(Tok.getLocation());
4660621cb2eSAlp Toker     unsigned TokLen = Tok.getLength();
4670621cb2eSAlp Toker     switch (Tok.getKind()) {
4680621cb2eSAlp Toker     default: break;
4690621cb2eSAlp Toker     case tok::identifier:
4700621cb2eSAlp Toker       llvm_unreachable("tok::identifier in raw lexing mode!");
4710621cb2eSAlp Toker     case tok::raw_identifier: {
4720621cb2eSAlp Toker       // Fill in Result.IdentifierInfo and update the token kind,
4730621cb2eSAlp Toker       // looking up the identifier in the identifier table.
4740621cb2eSAlp Toker       PP.LookUpIdentifierInfo(Tok);
4750621cb2eSAlp Toker 
4760621cb2eSAlp Toker       // If this is a pp-identifier, for a keyword, highlight it as such.
4770621cb2eSAlp Toker       if (Tok.isNot(tok::identifier))
4780621cb2eSAlp Toker         HighlightRange(RB, TokOffs, TokOffs+TokLen, BufferStart,
4790621cb2eSAlp Toker                        "<span class='keyword'>", "</span>");
4800621cb2eSAlp Toker       break;
4810621cb2eSAlp Toker     }
4820621cb2eSAlp Toker     case tok::comment:
4830621cb2eSAlp Toker       HighlightRange(RB, TokOffs, TokOffs+TokLen, BufferStart,
4840621cb2eSAlp Toker                      "<span class='comment'>", "</span>");
4850621cb2eSAlp Toker       break;
4860621cb2eSAlp Toker     case tok::utf8_string_literal:
4870621cb2eSAlp Toker       // Chop off the u part of u8 prefix
4880621cb2eSAlp Toker       ++TokOffs;
4890621cb2eSAlp Toker       --TokLen;
4900621cb2eSAlp Toker       // FALL THROUGH to chop the 8
4914c7705c8SGalina Kistanova       LLVM_FALLTHROUGH;
4920621cb2eSAlp Toker     case tok::wide_string_literal:
4930621cb2eSAlp Toker     case tok::utf16_string_literal:
4940621cb2eSAlp Toker     case tok::utf32_string_literal:
4950621cb2eSAlp Toker       // Chop off the L, u, U or 8 prefix
4960621cb2eSAlp Toker       ++TokOffs;
4970621cb2eSAlp Toker       --TokLen;
4984dc0b1acSReid Kleckner       LLVM_FALLTHROUGH;
4990621cb2eSAlp Toker     case tok::string_literal:
5000621cb2eSAlp Toker       // FIXME: Exclude the optional ud-suffix from the highlighted range.
5010621cb2eSAlp Toker       HighlightRange(RB, TokOffs, TokOffs+TokLen, BufferStart,
5020621cb2eSAlp Toker                      "<span class='string_literal'>", "</span>");
5030621cb2eSAlp Toker       break;
5040621cb2eSAlp Toker     case tok::hash: {
5050621cb2eSAlp Toker       // If this is a preprocessor directive, all tokens to end of line are too.
5060621cb2eSAlp Toker       if (!Tok.isAtStartOfLine())
5070621cb2eSAlp Toker         break;
5080621cb2eSAlp Toker 
5090621cb2eSAlp Toker       // Eat all of the tokens until we get to the next one at the start of
5100621cb2eSAlp Toker       // line.
5110621cb2eSAlp Toker       unsigned TokEnd = TokOffs+TokLen;
5120621cb2eSAlp Toker       L.LexFromRawLexer(Tok);
5130621cb2eSAlp Toker       while (!Tok.isAtStartOfLine() && Tok.isNot(tok::eof)) {
5140621cb2eSAlp Toker         TokEnd = SM.getFileOffset(Tok.getLocation())+Tok.getLength();
5150621cb2eSAlp Toker         L.LexFromRawLexer(Tok);
5160621cb2eSAlp Toker       }
5170621cb2eSAlp Toker 
5180621cb2eSAlp Toker       // Find end of line.  This is a hack.
5190621cb2eSAlp Toker       HighlightRange(RB, TokOffs, TokEnd, BufferStart,
5200621cb2eSAlp Toker                      "<span class='directive'>", "</span>");
5210621cb2eSAlp Toker 
5220621cb2eSAlp Toker       // Don't skip the next token.
5230621cb2eSAlp Toker       continue;
5240621cb2eSAlp Toker     }
5250621cb2eSAlp Toker     }
5260621cb2eSAlp Toker 
5270621cb2eSAlp Toker     L.LexFromRawLexer(Tok);
5280621cb2eSAlp Toker   }
5290621cb2eSAlp Toker }
5300621cb2eSAlp Toker 
5310621cb2eSAlp Toker /// HighlightMacros - This uses the macro table state from the end of the
5320621cb2eSAlp Toker /// file, to re-expand macros and insert (into the HTML) information about the
5330621cb2eSAlp Toker /// macro expansions.  This won't be perfectly perfect, but it will be
5340621cb2eSAlp Toker /// reasonably close.
HighlightMacros(Rewriter & R,FileID FID,const Preprocessor & PP)5350621cb2eSAlp Toker void html::HighlightMacros(Rewriter &R, FileID FID, const Preprocessor& PP) {
5360621cb2eSAlp Toker   // Re-lex the raw token stream into a token buffer.
5370621cb2eSAlp Toker   const SourceManager &SM = PP.getSourceManager();
5380621cb2eSAlp Toker   std::vector<Token> TokenStream;
5390621cb2eSAlp Toker 
540b3eff6b7SDuncan P. N. Exon Smith   llvm::MemoryBufferRef FromFile = SM.getBufferOrFake(FID);
5410621cb2eSAlp Toker   Lexer L(FID, FromFile, SM, PP.getLangOpts());
5420621cb2eSAlp Toker 
5430621cb2eSAlp Toker   // Lex all the tokens in raw mode, to avoid entering #includes or expanding
5440621cb2eSAlp Toker   // macros.
545*40446663SKazu Hirata   while (true) {
5460621cb2eSAlp Toker     Token Tok;
5470621cb2eSAlp Toker     L.LexFromRawLexer(Tok);
5480621cb2eSAlp Toker 
5490621cb2eSAlp Toker     // If this is a # at the start of a line, discard it from the token stream.
5500621cb2eSAlp Toker     // We don't want the re-preprocess step to see #defines, #includes or other
5510621cb2eSAlp Toker     // preprocessor directives.
5520621cb2eSAlp Toker     if (Tok.is(tok::hash) && Tok.isAtStartOfLine())
5530621cb2eSAlp Toker       continue;
5540621cb2eSAlp Toker 
5550621cb2eSAlp Toker     // If this is a ## token, change its kind to unknown so that repreprocessing
5560621cb2eSAlp Toker     // it will not produce an error.
5570621cb2eSAlp Toker     if (Tok.is(tok::hashhash))
5580621cb2eSAlp Toker       Tok.setKind(tok::unknown);
5590621cb2eSAlp Toker 
5600621cb2eSAlp Toker     // If this raw token is an identifier, the raw lexer won't have looked up
5610621cb2eSAlp Toker     // the corresponding identifier info for it.  Do this now so that it will be
5620621cb2eSAlp Toker     // macro expanded when we re-preprocess it.
5630621cb2eSAlp Toker     if (Tok.is(tok::raw_identifier))
5640621cb2eSAlp Toker       PP.LookUpIdentifierInfo(Tok);
5650621cb2eSAlp Toker 
5660621cb2eSAlp Toker     TokenStream.push_back(Tok);
5670621cb2eSAlp Toker 
5680621cb2eSAlp Toker     if (Tok.is(tok::eof)) break;
5690621cb2eSAlp Toker   }
5700621cb2eSAlp Toker 
5710621cb2eSAlp Toker   // Temporarily change the diagnostics object so that we ignore any generated
5720621cb2eSAlp Toker   // diagnostics from this pass.
5730621cb2eSAlp Toker   DiagnosticsEngine TmpDiags(PP.getDiagnostics().getDiagnosticIDs(),
5740621cb2eSAlp Toker                              &PP.getDiagnostics().getDiagnosticOptions(),
5750621cb2eSAlp Toker                       new IgnoringDiagConsumer);
5760621cb2eSAlp Toker 
5770621cb2eSAlp Toker   // FIXME: This is a huge hack; we reuse the input preprocessor because we want
5780621cb2eSAlp Toker   // its state, but we aren't actually changing it (we hope). This should really
5790621cb2eSAlp Toker   // construct a copy of the preprocessor.
5800621cb2eSAlp Toker   Preprocessor &TmpPP = const_cast<Preprocessor&>(PP);
5810621cb2eSAlp Toker   DiagnosticsEngine *OldDiags = &TmpPP.getDiagnostics();
5820621cb2eSAlp Toker   TmpPP.setDiagnostics(TmpDiags);
5830621cb2eSAlp Toker 
5840621cb2eSAlp Toker   // Inform the preprocessor that we don't want comments.
5850621cb2eSAlp Toker   TmpPP.SetCommentRetentionState(false, false);
5860621cb2eSAlp Toker 
5870621cb2eSAlp Toker   // We don't want pragmas either. Although we filtered out #pragma, removing
5880621cb2eSAlp Toker   // _Pragma and __pragma is much harder.
5890621cb2eSAlp Toker   bool PragmasPreviouslyEnabled = TmpPP.getPragmasEnabled();
5900621cb2eSAlp Toker   TmpPP.setPragmasEnabled(false);
5910621cb2eSAlp Toker 
5920621cb2eSAlp Toker   // Enter the tokens we just lexed.  This will cause them to be macro expanded
5930621cb2eSAlp Toker   // but won't enter sub-files (because we removed #'s).
594929af673SIlya Biryukov   TmpPP.EnterTokenStream(TokenStream, false, /*IsReinject=*/false);
5950621cb2eSAlp Toker 
5960621cb2eSAlp Toker   TokenConcatenation ConcatInfo(TmpPP);
5970621cb2eSAlp Toker 
5980621cb2eSAlp Toker   // Lex all the tokens.
5990621cb2eSAlp Toker   Token Tok;
6000621cb2eSAlp Toker   TmpPP.Lex(Tok);
6010621cb2eSAlp Toker   while (Tok.isNot(tok::eof)) {
6020621cb2eSAlp Toker     // Ignore non-macro tokens.
6030621cb2eSAlp Toker     if (!Tok.getLocation().isMacroID()) {
6040621cb2eSAlp Toker       TmpPP.Lex(Tok);
6050621cb2eSAlp Toker       continue;
6060621cb2eSAlp Toker     }
6070621cb2eSAlp Toker 
6080621cb2eSAlp Toker     // Okay, we have the first token of a macro expansion: highlight the
6090621cb2eSAlp Toker     // expansion by inserting a start tag before the macro expansion and
6100621cb2eSAlp Toker     // end tag after it.
611b5f8171aSRichard Smith     CharSourceRange LLoc = SM.getExpansionRange(Tok.getLocation());
6120621cb2eSAlp Toker 
6130621cb2eSAlp Toker     // Ignore tokens whose instantiation location was not the main file.
614b5f8171aSRichard Smith     if (SM.getFileID(LLoc.getBegin()) != FID) {
6150621cb2eSAlp Toker       TmpPP.Lex(Tok);
6160621cb2eSAlp Toker       continue;
6170621cb2eSAlp Toker     }
6180621cb2eSAlp Toker 
619b5f8171aSRichard Smith     assert(SM.getFileID(LLoc.getEnd()) == FID &&
6200621cb2eSAlp Toker            "Start and end of expansion must be in the same ultimate file!");
6210621cb2eSAlp Toker 
6220621cb2eSAlp Toker     std::string Expansion = EscapeText(TmpPP.getSpelling(Tok));
6230621cb2eSAlp Toker     unsigned LineLen = Expansion.size();
6240621cb2eSAlp Toker 
6250621cb2eSAlp Toker     Token PrevPrevTok;
6260621cb2eSAlp Toker     Token PrevTok = Tok;
6270621cb2eSAlp Toker     // Okay, eat this token, getting the next one.
6280621cb2eSAlp Toker     TmpPP.Lex(Tok);
6290621cb2eSAlp Toker 
6300621cb2eSAlp Toker     // Skip all the rest of the tokens that are part of this macro
6310621cb2eSAlp Toker     // instantiation.  It would be really nice to pop up a window with all the
6320621cb2eSAlp Toker     // spelling of the tokens or something.
6330621cb2eSAlp Toker     while (!Tok.is(tok::eof) &&
634b5f8171aSRichard Smith            SM.getExpansionLoc(Tok.getLocation()) == LLoc.getBegin()) {
6350621cb2eSAlp Toker       // Insert a newline if the macro expansion is getting large.
6360621cb2eSAlp Toker       if (LineLen > 60) {
6370621cb2eSAlp Toker         Expansion += "<br>";
6380621cb2eSAlp Toker         LineLen = 0;
6390621cb2eSAlp Toker       }
6400621cb2eSAlp Toker 
6410621cb2eSAlp Toker       LineLen -= Expansion.size();
6420621cb2eSAlp Toker 
6430621cb2eSAlp Toker       // If the tokens were already space separated, or if they must be to avoid
6440621cb2eSAlp Toker       // them being implicitly pasted, add a space between them.
6450621cb2eSAlp Toker       if (Tok.hasLeadingSpace() ||
6460621cb2eSAlp Toker           ConcatInfo.AvoidConcat(PrevPrevTok, PrevTok, Tok))
6470621cb2eSAlp Toker         Expansion += ' ';
6480621cb2eSAlp Toker 
6490621cb2eSAlp Toker       // Escape any special characters in the token text.
6500621cb2eSAlp Toker       Expansion += EscapeText(TmpPP.getSpelling(Tok));
6510621cb2eSAlp Toker       LineLen += Expansion.size();
6520621cb2eSAlp Toker 
6530621cb2eSAlp Toker       PrevPrevTok = PrevTok;
6540621cb2eSAlp Toker       PrevTok = Tok;
6550621cb2eSAlp Toker       TmpPP.Lex(Tok);
6560621cb2eSAlp Toker     }
6570621cb2eSAlp Toker 
6581d7ca677SCsaba Dabis     // Insert the 'macro_popup' as the end tag, so that multi-line macros all
6591d7ca677SCsaba Dabis     // get highlighted.
6601d7ca677SCsaba Dabis     Expansion = "<span class='macro_popup'>" + Expansion + "</span></span>";
6610621cb2eSAlp Toker 
662b5f8171aSRichard Smith     HighlightRange(R, LLoc.getBegin(), LLoc.getEnd(), "<span class='macro'>",
663b5f8171aSRichard Smith                    Expansion.c_str(), LLoc.isTokenRange());
6640621cb2eSAlp Toker   }
6650621cb2eSAlp Toker 
6660621cb2eSAlp Toker   // Restore the preprocessor's old state.
6670621cb2eSAlp Toker   TmpPP.setDiagnostics(*OldDiags);
6680621cb2eSAlp Toker   TmpPP.setPragmasEnabled(PragmasPreviouslyEnabled);
6690621cb2eSAlp Toker }
670