17330f729Sjoerg //== HTMLRewrite.cpp - Translate source code into prettified HTML --*- C++ -*-//
27330f729Sjoerg //
37330f729Sjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
47330f729Sjoerg // See https://llvm.org/LICENSE.txt for license information.
57330f729Sjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
67330f729Sjoerg //
77330f729Sjoerg //===----------------------------------------------------------------------===//
87330f729Sjoerg //
97330f729Sjoerg // This file defines the HTMLRewriter class, which is used to translate the
107330f729Sjoerg // text of a source file into prettified HTML.
117330f729Sjoerg //
127330f729Sjoerg //===----------------------------------------------------------------------===//
137330f729Sjoerg
147330f729Sjoerg #include "clang/Rewrite/Core/HTMLRewrite.h"
157330f729Sjoerg #include "clang/Basic/SourceManager.h"
167330f729Sjoerg #include "clang/Lex/Preprocessor.h"
177330f729Sjoerg #include "clang/Lex/TokenConcatenation.h"
187330f729Sjoerg #include "clang/Rewrite/Core/Rewriter.h"
197330f729Sjoerg #include "llvm/ADT/SmallString.h"
207330f729Sjoerg #include "llvm/Support/ErrorHandling.h"
217330f729Sjoerg #include "llvm/Support/MemoryBuffer.h"
227330f729Sjoerg #include "llvm/Support/raw_ostream.h"
237330f729Sjoerg #include <memory>
247330f729Sjoerg using namespace clang;
257330f729Sjoerg
267330f729Sjoerg
277330f729Sjoerg /// HighlightRange - Highlight a range in the source code with the specified
287330f729Sjoerg /// start/end tags. B/E must be in the same file. This ensures that
297330f729Sjoerg /// start/end tags are placed at the start/end of each line if the range is
307330f729Sjoerg /// multiline.
HighlightRange(Rewriter & R,SourceLocation B,SourceLocation E,const char * StartTag,const char * EndTag,bool IsTokenRange)317330f729Sjoerg void html::HighlightRange(Rewriter &R, SourceLocation B, SourceLocation E,
327330f729Sjoerg const char *StartTag, const char *EndTag,
337330f729Sjoerg bool IsTokenRange) {
347330f729Sjoerg SourceManager &SM = R.getSourceMgr();
357330f729Sjoerg B = SM.getExpansionLoc(B);
367330f729Sjoerg E = SM.getExpansionLoc(E);
377330f729Sjoerg FileID FID = SM.getFileID(B);
387330f729Sjoerg assert(SM.getFileID(E) == FID && "B/E not in the same file!");
397330f729Sjoerg
407330f729Sjoerg unsigned BOffset = SM.getFileOffset(B);
417330f729Sjoerg unsigned EOffset = SM.getFileOffset(E);
427330f729Sjoerg
437330f729Sjoerg // Include the whole end token in the range.
447330f729Sjoerg if (IsTokenRange)
457330f729Sjoerg EOffset += Lexer::MeasureTokenLength(E, R.getSourceMgr(), R.getLangOpts());
467330f729Sjoerg
477330f729Sjoerg bool Invalid = false;
487330f729Sjoerg const char *BufferStart = SM.getBufferData(FID, &Invalid).data();
497330f729Sjoerg if (Invalid)
507330f729Sjoerg return;
517330f729Sjoerg
527330f729Sjoerg HighlightRange(R.getEditBuffer(FID), BOffset, EOffset,
537330f729Sjoerg BufferStart, StartTag, EndTag);
547330f729Sjoerg }
557330f729Sjoerg
567330f729Sjoerg /// HighlightRange - This is the same as the above method, but takes
577330f729Sjoerg /// decomposed file locations.
HighlightRange(RewriteBuffer & RB,unsigned B,unsigned E,const char * BufferStart,const char * StartTag,const char * EndTag)587330f729Sjoerg void html::HighlightRange(RewriteBuffer &RB, unsigned B, unsigned E,
597330f729Sjoerg const char *BufferStart,
607330f729Sjoerg const char *StartTag, const char *EndTag) {
617330f729Sjoerg // Insert the tag at the absolute start/end of the range.
627330f729Sjoerg RB.InsertTextAfter(B, StartTag);
637330f729Sjoerg RB.InsertTextBefore(E, EndTag);
647330f729Sjoerg
657330f729Sjoerg // Scan the range to see if there is a \r or \n. If so, and if the line is
667330f729Sjoerg // not blank, insert tags on that line as well.
677330f729Sjoerg bool HadOpenTag = true;
687330f729Sjoerg
697330f729Sjoerg unsigned LastNonWhiteSpace = B;
707330f729Sjoerg for (unsigned i = B; i != E; ++i) {
717330f729Sjoerg switch (BufferStart[i]) {
727330f729Sjoerg case '\r':
737330f729Sjoerg case '\n':
747330f729Sjoerg // Okay, we found a newline in the range. If we have an open tag, we need
757330f729Sjoerg // to insert a close tag at the first non-whitespace before the newline.
767330f729Sjoerg if (HadOpenTag)
777330f729Sjoerg RB.InsertTextBefore(LastNonWhiteSpace+1, EndTag);
787330f729Sjoerg
797330f729Sjoerg // Instead of inserting an open tag immediately after the newline, we
807330f729Sjoerg // wait until we see a non-whitespace character. This prevents us from
817330f729Sjoerg // inserting tags around blank lines, and also allows the open tag to
827330f729Sjoerg // be put *after* whitespace on a non-blank line.
837330f729Sjoerg HadOpenTag = false;
847330f729Sjoerg break;
857330f729Sjoerg case '\0':
867330f729Sjoerg case ' ':
877330f729Sjoerg case '\t':
887330f729Sjoerg case '\f':
897330f729Sjoerg case '\v':
907330f729Sjoerg // Ignore whitespace.
917330f729Sjoerg break;
927330f729Sjoerg
937330f729Sjoerg default:
947330f729Sjoerg // If there is no tag open, do it now.
957330f729Sjoerg if (!HadOpenTag) {
967330f729Sjoerg RB.InsertTextAfter(i, StartTag);
977330f729Sjoerg HadOpenTag = true;
987330f729Sjoerg }
997330f729Sjoerg
1007330f729Sjoerg // Remember this character.
1017330f729Sjoerg LastNonWhiteSpace = i;
1027330f729Sjoerg break;
1037330f729Sjoerg }
1047330f729Sjoerg }
1057330f729Sjoerg }
1067330f729Sjoerg
EscapeText(Rewriter & R,FileID FID,bool EscapeSpaces,bool ReplaceTabs)1077330f729Sjoerg void html::EscapeText(Rewriter &R, FileID FID,
1087330f729Sjoerg bool EscapeSpaces, bool ReplaceTabs) {
1097330f729Sjoerg
110*e038c9c4Sjoerg llvm::MemoryBufferRef Buf = R.getSourceMgr().getBufferOrFake(FID);
111*e038c9c4Sjoerg const char* C = Buf.getBufferStart();
112*e038c9c4Sjoerg const char* FileEnd = Buf.getBufferEnd();
1137330f729Sjoerg
1147330f729Sjoerg assert (C <= FileEnd);
1157330f729Sjoerg
1167330f729Sjoerg RewriteBuffer &RB = R.getEditBuffer(FID);
1177330f729Sjoerg
1187330f729Sjoerg unsigned ColNo = 0;
1197330f729Sjoerg for (unsigned FilePos = 0; C != FileEnd ; ++C, ++FilePos) {
1207330f729Sjoerg switch (*C) {
1217330f729Sjoerg default: ++ColNo; break;
1227330f729Sjoerg case '\n':
1237330f729Sjoerg case '\r':
1247330f729Sjoerg ColNo = 0;
1257330f729Sjoerg break;
1267330f729Sjoerg
1277330f729Sjoerg case ' ':
1287330f729Sjoerg if (EscapeSpaces)
1297330f729Sjoerg RB.ReplaceText(FilePos, 1, " ");
1307330f729Sjoerg ++ColNo;
1317330f729Sjoerg break;
1327330f729Sjoerg case '\f':
1337330f729Sjoerg RB.ReplaceText(FilePos, 1, "<hr>");
1347330f729Sjoerg ColNo = 0;
1357330f729Sjoerg break;
1367330f729Sjoerg
1377330f729Sjoerg case '\t': {
1387330f729Sjoerg if (!ReplaceTabs)
1397330f729Sjoerg break;
1407330f729Sjoerg unsigned NumSpaces = 8-(ColNo&7);
1417330f729Sjoerg if (EscapeSpaces)
1427330f729Sjoerg RB.ReplaceText(FilePos, 1,
1437330f729Sjoerg StringRef(" "
1447330f729Sjoerg " ", 6*NumSpaces));
1457330f729Sjoerg else
1467330f729Sjoerg RB.ReplaceText(FilePos, 1, StringRef(" ", NumSpaces));
1477330f729Sjoerg ColNo += NumSpaces;
1487330f729Sjoerg break;
1497330f729Sjoerg }
1507330f729Sjoerg case '<':
1517330f729Sjoerg RB.ReplaceText(FilePos, 1, "<");
1527330f729Sjoerg ++ColNo;
1537330f729Sjoerg break;
1547330f729Sjoerg
1557330f729Sjoerg case '>':
1567330f729Sjoerg RB.ReplaceText(FilePos, 1, ">");
1577330f729Sjoerg ++ColNo;
1587330f729Sjoerg break;
1597330f729Sjoerg
1607330f729Sjoerg case '&':
1617330f729Sjoerg RB.ReplaceText(FilePos, 1, "&");
1627330f729Sjoerg ++ColNo;
1637330f729Sjoerg break;
1647330f729Sjoerg }
1657330f729Sjoerg }
1667330f729Sjoerg }
1677330f729Sjoerg
EscapeText(StringRef s,bool EscapeSpaces,bool ReplaceTabs)1687330f729Sjoerg std::string html::EscapeText(StringRef s, bool EscapeSpaces, bool ReplaceTabs) {
1697330f729Sjoerg
1707330f729Sjoerg unsigned len = s.size();
1717330f729Sjoerg std::string Str;
1727330f729Sjoerg llvm::raw_string_ostream os(Str);
1737330f729Sjoerg
1747330f729Sjoerg for (unsigned i = 0 ; i < len; ++i) {
1757330f729Sjoerg
1767330f729Sjoerg char c = s[i];
1777330f729Sjoerg switch (c) {
1787330f729Sjoerg default:
1797330f729Sjoerg os << c; break;
1807330f729Sjoerg
1817330f729Sjoerg case ' ':
1827330f729Sjoerg if (EscapeSpaces) os << " ";
1837330f729Sjoerg else os << ' ';
1847330f729Sjoerg break;
1857330f729Sjoerg
1867330f729Sjoerg case '\t':
1877330f729Sjoerg if (ReplaceTabs) {
1887330f729Sjoerg if (EscapeSpaces)
1897330f729Sjoerg for (unsigned i = 0; i < 4; ++i)
1907330f729Sjoerg os << " ";
1917330f729Sjoerg else
1927330f729Sjoerg for (unsigned i = 0; i < 4; ++i)
1937330f729Sjoerg os << " ";
1947330f729Sjoerg }
1957330f729Sjoerg else
1967330f729Sjoerg os << c;
1977330f729Sjoerg
1987330f729Sjoerg break;
1997330f729Sjoerg
2007330f729Sjoerg case '<': os << "<"; break;
2017330f729Sjoerg case '>': os << ">"; break;
2027330f729Sjoerg case '&': os << "&"; break;
2037330f729Sjoerg }
2047330f729Sjoerg }
2057330f729Sjoerg
2067330f729Sjoerg return os.str();
2077330f729Sjoerg }
2087330f729Sjoerg
AddLineNumber(RewriteBuffer & RB,unsigned LineNo,unsigned B,unsigned E)2097330f729Sjoerg static void AddLineNumber(RewriteBuffer &RB, unsigned LineNo,
2107330f729Sjoerg unsigned B, unsigned E) {
2117330f729Sjoerg SmallString<256> Str;
2127330f729Sjoerg llvm::raw_svector_ostream OS(Str);
2137330f729Sjoerg
2147330f729Sjoerg OS << "<tr class=\"codeline\" data-linenumber=\"" << LineNo << "\">"
2157330f729Sjoerg << "<td class=\"num\" id=\"LN" << LineNo << "\">" << LineNo
2167330f729Sjoerg << "</td><td class=\"line\">";
2177330f729Sjoerg
2187330f729Sjoerg if (B == E) { // Handle empty lines.
2197330f729Sjoerg OS << " </td></tr>";
2207330f729Sjoerg RB.InsertTextBefore(B, OS.str());
2217330f729Sjoerg } else {
2227330f729Sjoerg RB.InsertTextBefore(B, OS.str());
2237330f729Sjoerg RB.InsertTextBefore(E, "</td></tr>");
2247330f729Sjoerg }
2257330f729Sjoerg }
2267330f729Sjoerg
AddLineNumbers(Rewriter & R,FileID FID)2277330f729Sjoerg void html::AddLineNumbers(Rewriter& R, FileID FID) {
2287330f729Sjoerg
229*e038c9c4Sjoerg llvm::MemoryBufferRef Buf = R.getSourceMgr().getBufferOrFake(FID);
230*e038c9c4Sjoerg const char* FileBeg = Buf.getBufferStart();
231*e038c9c4Sjoerg const char* FileEnd = Buf.getBufferEnd();
2327330f729Sjoerg const char* C = FileBeg;
2337330f729Sjoerg RewriteBuffer &RB = R.getEditBuffer(FID);
2347330f729Sjoerg
2357330f729Sjoerg assert (C <= FileEnd);
2367330f729Sjoerg
2377330f729Sjoerg unsigned LineNo = 0;
2387330f729Sjoerg unsigned FilePos = 0;
2397330f729Sjoerg
2407330f729Sjoerg while (C != FileEnd) {
2417330f729Sjoerg
2427330f729Sjoerg ++LineNo;
2437330f729Sjoerg unsigned LineStartPos = FilePos;
2447330f729Sjoerg unsigned LineEndPos = FileEnd - FileBeg;
2457330f729Sjoerg
2467330f729Sjoerg assert (FilePos <= LineEndPos);
2477330f729Sjoerg assert (C < FileEnd);
2487330f729Sjoerg
2497330f729Sjoerg // Scan until the newline (or end-of-file).
2507330f729Sjoerg
2517330f729Sjoerg while (C != FileEnd) {
2527330f729Sjoerg char c = *C;
2537330f729Sjoerg ++C;
2547330f729Sjoerg
2557330f729Sjoerg if (c == '\n') {
2567330f729Sjoerg LineEndPos = FilePos++;
2577330f729Sjoerg break;
2587330f729Sjoerg }
2597330f729Sjoerg
2607330f729Sjoerg ++FilePos;
2617330f729Sjoerg }
2627330f729Sjoerg
2637330f729Sjoerg AddLineNumber(RB, LineNo, LineStartPos, LineEndPos);
2647330f729Sjoerg }
2657330f729Sjoerg
2667330f729Sjoerg // Add one big table tag that surrounds all of the code.
2677330f729Sjoerg std::string s;
2687330f729Sjoerg llvm::raw_string_ostream os(s);
2697330f729Sjoerg os << "<table class=\"code\" data-fileid=\"" << FID.getHashValue() << "\">\n";
2707330f729Sjoerg RB.InsertTextBefore(0, os.str());
2717330f729Sjoerg RB.InsertTextAfter(FileEnd - FileBeg, "</table>");
2727330f729Sjoerg }
2737330f729Sjoerg
AddHeaderFooterInternalBuiltinCSS(Rewriter & R,FileID FID,StringRef title)2747330f729Sjoerg void html::AddHeaderFooterInternalBuiltinCSS(Rewriter &R, FileID FID,
2757330f729Sjoerg StringRef title) {
2767330f729Sjoerg
277*e038c9c4Sjoerg llvm::MemoryBufferRef Buf = R.getSourceMgr().getBufferOrFake(FID);
278*e038c9c4Sjoerg const char* FileStart = Buf.getBufferStart();
279*e038c9c4Sjoerg const char* FileEnd = Buf.getBufferEnd();
2807330f729Sjoerg
2817330f729Sjoerg SourceLocation StartLoc = R.getSourceMgr().getLocForStartOfFile(FID);
2827330f729Sjoerg SourceLocation EndLoc = StartLoc.getLocWithOffset(FileEnd-FileStart);
2837330f729Sjoerg
2847330f729Sjoerg std::string s;
2857330f729Sjoerg llvm::raw_string_ostream os(s);
2867330f729Sjoerg os << "<!doctype html>\n" // Use HTML 5 doctype
2877330f729Sjoerg "<html>\n<head>\n";
2887330f729Sjoerg
2897330f729Sjoerg if (!title.empty())
2907330f729Sjoerg os << "<title>" << html::EscapeText(title) << "</title>\n";
2917330f729Sjoerg
2927330f729Sjoerg os << R"<<<(
2937330f729Sjoerg <style type="text/css">
2947330f729Sjoerg body { color:#000000; background-color:#ffffff }
2957330f729Sjoerg body { font-family:Helvetica, sans-serif; font-size:10pt }
2967330f729Sjoerg h1 { font-size:14pt }
2977330f729Sjoerg .FileName { margin-top: 5px; margin-bottom: 5px; display: inline; }
2987330f729Sjoerg .FileNav { margin-left: 5px; margin-right: 5px; display: inline; }
2997330f729Sjoerg .FileNav a { text-decoration:none; font-size: larger; }
3007330f729Sjoerg .divider { margin-top: 30px; margin-bottom: 30px; height: 15px; }
3017330f729Sjoerg .divider { background-color: gray; }
3027330f729Sjoerg .code { border-collapse:collapse; width:100%; }
3037330f729Sjoerg .code { font-family: "Monospace", monospace; font-size:10pt }
3047330f729Sjoerg .code { line-height: 1.2em }
3057330f729Sjoerg .comment { color: green; font-style: oblique }
3067330f729Sjoerg .keyword { color: blue }
3077330f729Sjoerg .string_literal { color: red }
3087330f729Sjoerg .directive { color: darkmagenta }
3097330f729Sjoerg
3107330f729Sjoerg /* Macros and variables could have pop-up notes hidden by default.
3117330f729Sjoerg - Macro pop-up: expansion of the macro
3127330f729Sjoerg - Variable pop-up: value (table) of the variable */
3137330f729Sjoerg .macro_popup, .variable_popup { display: none; }
3147330f729Sjoerg
3157330f729Sjoerg /* Pop-up appears on mouse-hover event. */
3167330f729Sjoerg .macro:hover .macro_popup, .variable:hover .variable_popup {
3177330f729Sjoerg display: block;
3187330f729Sjoerg padding: 2px;
3197330f729Sjoerg -webkit-border-radius:5px;
3207330f729Sjoerg -webkit-box-shadow:1px 1px 7px #000;
3217330f729Sjoerg border-radius:5px;
3227330f729Sjoerg box-shadow:1px 1px 7px #000;
3237330f729Sjoerg position: absolute;
3247330f729Sjoerg top: -1em;
3257330f729Sjoerg left:10em;
3267330f729Sjoerg z-index: 1
3277330f729Sjoerg }
3287330f729Sjoerg
3297330f729Sjoerg .macro_popup {
3307330f729Sjoerg border: 2px solid red;
3317330f729Sjoerg background-color:#FFF0F0;
3327330f729Sjoerg font-weight: normal;
3337330f729Sjoerg }
3347330f729Sjoerg
3357330f729Sjoerg .variable_popup {
3367330f729Sjoerg border: 2px solid blue;
3377330f729Sjoerg background-color:#F0F0FF;
3387330f729Sjoerg font-weight: bold;
3397330f729Sjoerg font-family: Helvetica, sans-serif;
3407330f729Sjoerg font-size: 9pt;
3417330f729Sjoerg }
3427330f729Sjoerg
3437330f729Sjoerg /* Pop-up notes needs a relative position as a base where they pops up. */
3447330f729Sjoerg .macro, .variable {
3457330f729Sjoerg background-color: PaleGoldenRod;
3467330f729Sjoerg position: relative;
3477330f729Sjoerg }
3487330f729Sjoerg .macro { color: DarkMagenta; }
3497330f729Sjoerg
3507330f729Sjoerg #tooltiphint {
3517330f729Sjoerg position: fixed;
3527330f729Sjoerg width: 50em;
3537330f729Sjoerg margin-left: -25em;
3547330f729Sjoerg left: 50%;
3557330f729Sjoerg padding: 10px;
3567330f729Sjoerg border: 1px solid #b0b0b0;
3577330f729Sjoerg border-radius: 2px;
3587330f729Sjoerg box-shadow: 1px 1px 7px black;
3597330f729Sjoerg background-color: #c0c0c0;
3607330f729Sjoerg z-index: 2;
3617330f729Sjoerg }
3627330f729Sjoerg
3637330f729Sjoerg .num { width:2.5em; padding-right:2ex; background-color:#eeeeee }
3647330f729Sjoerg .num { text-align:right; font-size:8pt }
3657330f729Sjoerg .num { color:#444444 }
3667330f729Sjoerg .line { padding-left: 1ex; border-left: 3px solid #ccc }
3677330f729Sjoerg .line { white-space: pre }
3687330f729Sjoerg .msg { -webkit-box-shadow:1px 1px 7px #000 }
3697330f729Sjoerg .msg { box-shadow:1px 1px 7px #000 }
3707330f729Sjoerg .msg { -webkit-border-radius:5px }
3717330f729Sjoerg .msg { border-radius:5px }
3727330f729Sjoerg .msg { font-family:Helvetica, sans-serif; font-size:8pt }
3737330f729Sjoerg .msg { float:left }
3747330f729Sjoerg .msg { padding:0.25em 1ex 0.25em 1ex }
3757330f729Sjoerg .msg { margin-top:10px; margin-bottom:10px }
3767330f729Sjoerg .msg { font-weight:bold }
3777330f729Sjoerg .msg { max-width:60em; word-wrap: break-word; white-space: pre-wrap }
3787330f729Sjoerg .msgT { padding:0x; spacing:0x }
3797330f729Sjoerg .msgEvent { background-color:#fff8b4; color:#000000 }
3807330f729Sjoerg .msgControl { background-color:#bbbbbb; color:#000000 }
3817330f729Sjoerg .msgNote { background-color:#ddeeff; color:#000000 }
3827330f729Sjoerg .mrange { background-color:#dfddf3 }
3837330f729Sjoerg .mrange { border-bottom:1px solid #6F9DBE }
3847330f729Sjoerg .PathIndex { font-weight: bold; padding:0px 5px; margin-right:5px; }
3857330f729Sjoerg .PathIndex { -webkit-border-radius:8px }
3867330f729Sjoerg .PathIndex { border-radius:8px }
3877330f729Sjoerg .PathIndexEvent { background-color:#bfba87 }
3887330f729Sjoerg .PathIndexControl { background-color:#8c8c8c }
3897330f729Sjoerg .PathIndexPopUp { background-color: #879abc; }
3907330f729Sjoerg .PathNav a { text-decoration:none; font-size: larger }
3917330f729Sjoerg .CodeInsertionHint { font-weight: bold; background-color: #10dd10 }
3927330f729Sjoerg .CodeRemovalHint { background-color:#de1010 }
3937330f729Sjoerg .CodeRemovalHint { border-bottom:1px solid #6F9DBE }
3947330f729Sjoerg .selected{ background-color:orange !important; }
3957330f729Sjoerg
3967330f729Sjoerg table.simpletable {
3977330f729Sjoerg padding: 5px;
3987330f729Sjoerg font-size:12pt;
3997330f729Sjoerg margin:20px;
4007330f729Sjoerg border-collapse: collapse; border-spacing: 0px;
4017330f729Sjoerg }
4027330f729Sjoerg td.rowname {
4037330f729Sjoerg text-align: right;
4047330f729Sjoerg vertical-align: top;
4057330f729Sjoerg font-weight: bold;
4067330f729Sjoerg color:#444444;
4077330f729Sjoerg padding-right:2ex;
4087330f729Sjoerg }
4097330f729Sjoerg
4107330f729Sjoerg /* Hidden text. */
4117330f729Sjoerg input.spoilerhider + label {
4127330f729Sjoerg cursor: pointer;
4137330f729Sjoerg text-decoration: underline;
4147330f729Sjoerg display: block;
4157330f729Sjoerg }
4167330f729Sjoerg input.spoilerhider {
4177330f729Sjoerg display: none;
4187330f729Sjoerg }
4197330f729Sjoerg input.spoilerhider ~ .spoiler {
4207330f729Sjoerg overflow: hidden;
4217330f729Sjoerg margin: 10px auto 0;
4227330f729Sjoerg height: 0;
4237330f729Sjoerg opacity: 0;
4247330f729Sjoerg }
4257330f729Sjoerg input.spoilerhider:checked + label + .spoiler{
4267330f729Sjoerg height: auto;
4277330f729Sjoerg opacity: 1;
4287330f729Sjoerg }
4297330f729Sjoerg </style>
4307330f729Sjoerg </head>
4317330f729Sjoerg <body>)<<<";
4327330f729Sjoerg
4337330f729Sjoerg // Generate header
4347330f729Sjoerg R.InsertTextBefore(StartLoc, os.str());
4357330f729Sjoerg // Generate footer
4367330f729Sjoerg
4377330f729Sjoerg R.InsertTextAfter(EndLoc, "</body></html>\n");
4387330f729Sjoerg }
4397330f729Sjoerg
4407330f729Sjoerg /// SyntaxHighlight - Relex the specified FileID and annotate the HTML with
4417330f729Sjoerg /// information about keywords, macro expansions etc. This uses the macro
4427330f729Sjoerg /// table state from the end of the file, so it won't be perfectly perfect,
4437330f729Sjoerg /// but it will be reasonably close.
SyntaxHighlight(Rewriter & R,FileID FID,const Preprocessor & PP)4447330f729Sjoerg void html::SyntaxHighlight(Rewriter &R, FileID FID, const Preprocessor &PP) {
4457330f729Sjoerg RewriteBuffer &RB = R.getEditBuffer(FID);
4467330f729Sjoerg
4477330f729Sjoerg const SourceManager &SM = PP.getSourceManager();
448*e038c9c4Sjoerg llvm::MemoryBufferRef FromFile = SM.getBufferOrFake(FID);
4497330f729Sjoerg Lexer L(FID, FromFile, SM, PP.getLangOpts());
4507330f729Sjoerg const char *BufferStart = L.getBuffer().data();
4517330f729Sjoerg
4527330f729Sjoerg // Inform the preprocessor that we want to retain comments as tokens, so we
4537330f729Sjoerg // can highlight them.
4547330f729Sjoerg L.SetCommentRetentionState(true);
4557330f729Sjoerg
4567330f729Sjoerg // Lex all the tokens in raw mode, to avoid entering #includes or expanding
4577330f729Sjoerg // macros.
4587330f729Sjoerg Token Tok;
4597330f729Sjoerg L.LexFromRawLexer(Tok);
4607330f729Sjoerg
4617330f729Sjoerg while (Tok.isNot(tok::eof)) {
4627330f729Sjoerg // Since we are lexing unexpanded tokens, all tokens are from the main
4637330f729Sjoerg // FileID.
4647330f729Sjoerg unsigned TokOffs = SM.getFileOffset(Tok.getLocation());
4657330f729Sjoerg unsigned TokLen = Tok.getLength();
4667330f729Sjoerg switch (Tok.getKind()) {
4677330f729Sjoerg default: break;
4687330f729Sjoerg case tok::identifier:
4697330f729Sjoerg llvm_unreachable("tok::identifier in raw lexing mode!");
4707330f729Sjoerg case tok::raw_identifier: {
4717330f729Sjoerg // Fill in Result.IdentifierInfo and update the token kind,
4727330f729Sjoerg // looking up the identifier in the identifier table.
4737330f729Sjoerg PP.LookUpIdentifierInfo(Tok);
4747330f729Sjoerg
4757330f729Sjoerg // If this is a pp-identifier, for a keyword, highlight it as such.
4767330f729Sjoerg if (Tok.isNot(tok::identifier))
4777330f729Sjoerg HighlightRange(RB, TokOffs, TokOffs+TokLen, BufferStart,
4787330f729Sjoerg "<span class='keyword'>", "</span>");
4797330f729Sjoerg break;
4807330f729Sjoerg }
4817330f729Sjoerg case tok::comment:
4827330f729Sjoerg HighlightRange(RB, TokOffs, TokOffs+TokLen, BufferStart,
4837330f729Sjoerg "<span class='comment'>", "</span>");
4847330f729Sjoerg break;
4857330f729Sjoerg case tok::utf8_string_literal:
4867330f729Sjoerg // Chop off the u part of u8 prefix
4877330f729Sjoerg ++TokOffs;
4887330f729Sjoerg --TokLen;
4897330f729Sjoerg // FALL THROUGH to chop the 8
4907330f729Sjoerg LLVM_FALLTHROUGH;
4917330f729Sjoerg case tok::wide_string_literal:
4927330f729Sjoerg case tok::utf16_string_literal:
4937330f729Sjoerg case tok::utf32_string_literal:
4947330f729Sjoerg // Chop off the L, u, U or 8 prefix
4957330f729Sjoerg ++TokOffs;
4967330f729Sjoerg --TokLen;
4977330f729Sjoerg LLVM_FALLTHROUGH;
4987330f729Sjoerg case tok::string_literal:
4997330f729Sjoerg // FIXME: Exclude the optional ud-suffix from the highlighted range.
5007330f729Sjoerg HighlightRange(RB, TokOffs, TokOffs+TokLen, BufferStart,
5017330f729Sjoerg "<span class='string_literal'>", "</span>");
5027330f729Sjoerg break;
5037330f729Sjoerg case tok::hash: {
5047330f729Sjoerg // If this is a preprocessor directive, all tokens to end of line are too.
5057330f729Sjoerg if (!Tok.isAtStartOfLine())
5067330f729Sjoerg break;
5077330f729Sjoerg
5087330f729Sjoerg // Eat all of the tokens until we get to the next one at the start of
5097330f729Sjoerg // line.
5107330f729Sjoerg unsigned TokEnd = TokOffs+TokLen;
5117330f729Sjoerg L.LexFromRawLexer(Tok);
5127330f729Sjoerg while (!Tok.isAtStartOfLine() && Tok.isNot(tok::eof)) {
5137330f729Sjoerg TokEnd = SM.getFileOffset(Tok.getLocation())+Tok.getLength();
5147330f729Sjoerg L.LexFromRawLexer(Tok);
5157330f729Sjoerg }
5167330f729Sjoerg
5177330f729Sjoerg // Find end of line. This is a hack.
5187330f729Sjoerg HighlightRange(RB, TokOffs, TokEnd, BufferStart,
5197330f729Sjoerg "<span class='directive'>", "</span>");
5207330f729Sjoerg
5217330f729Sjoerg // Don't skip the next token.
5227330f729Sjoerg continue;
5237330f729Sjoerg }
5247330f729Sjoerg }
5257330f729Sjoerg
5267330f729Sjoerg L.LexFromRawLexer(Tok);
5277330f729Sjoerg }
5287330f729Sjoerg }
5297330f729Sjoerg
5307330f729Sjoerg /// HighlightMacros - This uses the macro table state from the end of the
5317330f729Sjoerg /// file, to re-expand macros and insert (into the HTML) information about the
5327330f729Sjoerg /// macro expansions. This won't be perfectly perfect, but it will be
5337330f729Sjoerg /// reasonably close.
HighlightMacros(Rewriter & R,FileID FID,const Preprocessor & PP)5347330f729Sjoerg void html::HighlightMacros(Rewriter &R, FileID FID, const Preprocessor& PP) {
5357330f729Sjoerg // Re-lex the raw token stream into a token buffer.
5367330f729Sjoerg const SourceManager &SM = PP.getSourceManager();
5377330f729Sjoerg std::vector<Token> TokenStream;
5387330f729Sjoerg
539*e038c9c4Sjoerg llvm::MemoryBufferRef FromFile = SM.getBufferOrFake(FID);
5407330f729Sjoerg Lexer L(FID, FromFile, SM, PP.getLangOpts());
5417330f729Sjoerg
5427330f729Sjoerg // Lex all the tokens in raw mode, to avoid entering #includes or expanding
5437330f729Sjoerg // macros.
5447330f729Sjoerg while (1) {
5457330f729Sjoerg Token Tok;
5467330f729Sjoerg L.LexFromRawLexer(Tok);
5477330f729Sjoerg
5487330f729Sjoerg // If this is a # at the start of a line, discard it from the token stream.
5497330f729Sjoerg // We don't want the re-preprocess step to see #defines, #includes or other
5507330f729Sjoerg // preprocessor directives.
5517330f729Sjoerg if (Tok.is(tok::hash) && Tok.isAtStartOfLine())
5527330f729Sjoerg continue;
5537330f729Sjoerg
5547330f729Sjoerg // If this is a ## token, change its kind to unknown so that repreprocessing
5557330f729Sjoerg // it will not produce an error.
5567330f729Sjoerg if (Tok.is(tok::hashhash))
5577330f729Sjoerg Tok.setKind(tok::unknown);
5587330f729Sjoerg
5597330f729Sjoerg // If this raw token is an identifier, the raw lexer won't have looked up
5607330f729Sjoerg // the corresponding identifier info for it. Do this now so that it will be
5617330f729Sjoerg // macro expanded when we re-preprocess it.
5627330f729Sjoerg if (Tok.is(tok::raw_identifier))
5637330f729Sjoerg PP.LookUpIdentifierInfo(Tok);
5647330f729Sjoerg
5657330f729Sjoerg TokenStream.push_back(Tok);
5667330f729Sjoerg
5677330f729Sjoerg if (Tok.is(tok::eof)) break;
5687330f729Sjoerg }
5697330f729Sjoerg
5707330f729Sjoerg // Temporarily change the diagnostics object so that we ignore any generated
5717330f729Sjoerg // diagnostics from this pass.
5727330f729Sjoerg DiagnosticsEngine TmpDiags(PP.getDiagnostics().getDiagnosticIDs(),
5737330f729Sjoerg &PP.getDiagnostics().getDiagnosticOptions(),
5747330f729Sjoerg new IgnoringDiagConsumer);
5757330f729Sjoerg
5767330f729Sjoerg // FIXME: This is a huge hack; we reuse the input preprocessor because we want
5777330f729Sjoerg // its state, but we aren't actually changing it (we hope). This should really
5787330f729Sjoerg // construct a copy of the preprocessor.
5797330f729Sjoerg Preprocessor &TmpPP = const_cast<Preprocessor&>(PP);
5807330f729Sjoerg DiagnosticsEngine *OldDiags = &TmpPP.getDiagnostics();
5817330f729Sjoerg TmpPP.setDiagnostics(TmpDiags);
5827330f729Sjoerg
5837330f729Sjoerg // Inform the preprocessor that we don't want comments.
5847330f729Sjoerg TmpPP.SetCommentRetentionState(false, false);
5857330f729Sjoerg
5867330f729Sjoerg // We don't want pragmas either. Although we filtered out #pragma, removing
5877330f729Sjoerg // _Pragma and __pragma is much harder.
5887330f729Sjoerg bool PragmasPreviouslyEnabled = TmpPP.getPragmasEnabled();
5897330f729Sjoerg TmpPP.setPragmasEnabled(false);
5907330f729Sjoerg
5917330f729Sjoerg // Enter the tokens we just lexed. This will cause them to be macro expanded
5927330f729Sjoerg // but won't enter sub-files (because we removed #'s).
5937330f729Sjoerg TmpPP.EnterTokenStream(TokenStream, false, /*IsReinject=*/false);
5947330f729Sjoerg
5957330f729Sjoerg TokenConcatenation ConcatInfo(TmpPP);
5967330f729Sjoerg
5977330f729Sjoerg // Lex all the tokens.
5987330f729Sjoerg Token Tok;
5997330f729Sjoerg TmpPP.Lex(Tok);
6007330f729Sjoerg while (Tok.isNot(tok::eof)) {
6017330f729Sjoerg // Ignore non-macro tokens.
6027330f729Sjoerg if (!Tok.getLocation().isMacroID()) {
6037330f729Sjoerg TmpPP.Lex(Tok);
6047330f729Sjoerg continue;
6057330f729Sjoerg }
6067330f729Sjoerg
6077330f729Sjoerg // Okay, we have the first token of a macro expansion: highlight the
6087330f729Sjoerg // expansion by inserting a start tag before the macro expansion and
6097330f729Sjoerg // end tag after it.
6107330f729Sjoerg CharSourceRange LLoc = SM.getExpansionRange(Tok.getLocation());
6117330f729Sjoerg
6127330f729Sjoerg // Ignore tokens whose instantiation location was not the main file.
6137330f729Sjoerg if (SM.getFileID(LLoc.getBegin()) != FID) {
6147330f729Sjoerg TmpPP.Lex(Tok);
6157330f729Sjoerg continue;
6167330f729Sjoerg }
6177330f729Sjoerg
6187330f729Sjoerg assert(SM.getFileID(LLoc.getEnd()) == FID &&
6197330f729Sjoerg "Start and end of expansion must be in the same ultimate file!");
6207330f729Sjoerg
6217330f729Sjoerg std::string Expansion = EscapeText(TmpPP.getSpelling(Tok));
6227330f729Sjoerg unsigned LineLen = Expansion.size();
6237330f729Sjoerg
6247330f729Sjoerg Token PrevPrevTok;
6257330f729Sjoerg Token PrevTok = Tok;
6267330f729Sjoerg // Okay, eat this token, getting the next one.
6277330f729Sjoerg TmpPP.Lex(Tok);
6287330f729Sjoerg
6297330f729Sjoerg // Skip all the rest of the tokens that are part of this macro
6307330f729Sjoerg // instantiation. It would be really nice to pop up a window with all the
6317330f729Sjoerg // spelling of the tokens or something.
6327330f729Sjoerg while (!Tok.is(tok::eof) &&
6337330f729Sjoerg SM.getExpansionLoc(Tok.getLocation()) == LLoc.getBegin()) {
6347330f729Sjoerg // Insert a newline if the macro expansion is getting large.
6357330f729Sjoerg if (LineLen > 60) {
6367330f729Sjoerg Expansion += "<br>";
6377330f729Sjoerg LineLen = 0;
6387330f729Sjoerg }
6397330f729Sjoerg
6407330f729Sjoerg LineLen -= Expansion.size();
6417330f729Sjoerg
6427330f729Sjoerg // If the tokens were already space separated, or if they must be to avoid
6437330f729Sjoerg // them being implicitly pasted, add a space between them.
6447330f729Sjoerg if (Tok.hasLeadingSpace() ||
6457330f729Sjoerg ConcatInfo.AvoidConcat(PrevPrevTok, PrevTok, Tok))
6467330f729Sjoerg Expansion += ' ';
6477330f729Sjoerg
6487330f729Sjoerg // Escape any special characters in the token text.
6497330f729Sjoerg Expansion += EscapeText(TmpPP.getSpelling(Tok));
6507330f729Sjoerg LineLen += Expansion.size();
6517330f729Sjoerg
6527330f729Sjoerg PrevPrevTok = PrevTok;
6537330f729Sjoerg PrevTok = Tok;
6547330f729Sjoerg TmpPP.Lex(Tok);
6557330f729Sjoerg }
6567330f729Sjoerg
6577330f729Sjoerg // Insert the 'macro_popup' as the end tag, so that multi-line macros all
6587330f729Sjoerg // get highlighted.
6597330f729Sjoerg Expansion = "<span class='macro_popup'>" + Expansion + "</span></span>";
6607330f729Sjoerg
6617330f729Sjoerg HighlightRange(R, LLoc.getBegin(), LLoc.getEnd(), "<span class='macro'>",
6627330f729Sjoerg Expansion.c_str(), LLoc.isTokenRange());
6637330f729Sjoerg }
6647330f729Sjoerg
6657330f729Sjoerg // Restore the preprocessor's old state.
6667330f729Sjoerg TmpPP.setDiagnostics(*OldDiags);
6677330f729Sjoerg TmpPP.setPragmasEnabled(PragmasPreviouslyEnabled);
6687330f729Sjoerg }
669