xref: /freebsd-src/contrib/llvm-project/clang/lib/Rewrite/HTMLRewrite.cpp (revision 0b57cec536236d46e3dba9bd041533462f33dbb7)
1*0b57cec5SDimitry Andric //== HTMLRewrite.cpp - Translate source code into prettified HTML --*- C++ -*-//
2*0b57cec5SDimitry Andric //
3*0b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*0b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5*0b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*0b57cec5SDimitry Andric //
7*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
8*0b57cec5SDimitry Andric //
9*0b57cec5SDimitry Andric //  This file defines the HTMLRewriter class, which is used to translate the
10*0b57cec5SDimitry Andric //  text of a source file into prettified HTML.
11*0b57cec5SDimitry Andric //
12*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
13*0b57cec5SDimitry Andric 
14*0b57cec5SDimitry Andric #include "clang/Rewrite/Core/HTMLRewrite.h"
15*0b57cec5SDimitry Andric #include "clang/Basic/SourceManager.h"
16*0b57cec5SDimitry Andric #include "clang/Lex/Preprocessor.h"
17*0b57cec5SDimitry Andric #include "clang/Lex/TokenConcatenation.h"
18*0b57cec5SDimitry Andric #include "clang/Rewrite/Core/Rewriter.h"
19*0b57cec5SDimitry Andric #include "llvm/ADT/SmallString.h"
20*0b57cec5SDimitry Andric #include "llvm/Support/ErrorHandling.h"
21*0b57cec5SDimitry Andric #include "llvm/Support/MemoryBuffer.h"
22*0b57cec5SDimitry Andric #include "llvm/Support/raw_ostream.h"
23*0b57cec5SDimitry Andric #include <memory>
24*0b57cec5SDimitry Andric using namespace clang;
25*0b57cec5SDimitry Andric 
26*0b57cec5SDimitry Andric 
27*0b57cec5SDimitry Andric /// HighlightRange - Highlight a range in the source code with the specified
28*0b57cec5SDimitry Andric /// start/end tags.  B/E must be in the same file.  This ensures that
29*0b57cec5SDimitry Andric /// start/end tags are placed at the start/end of each line if the range is
30*0b57cec5SDimitry Andric /// multiline.
31*0b57cec5SDimitry Andric void html::HighlightRange(Rewriter &R, SourceLocation B, SourceLocation E,
32*0b57cec5SDimitry Andric                           const char *StartTag, const char *EndTag,
33*0b57cec5SDimitry Andric                           bool IsTokenRange) {
34*0b57cec5SDimitry Andric   SourceManager &SM = R.getSourceMgr();
35*0b57cec5SDimitry Andric   B = SM.getExpansionLoc(B);
36*0b57cec5SDimitry Andric   E = SM.getExpansionLoc(E);
37*0b57cec5SDimitry Andric   FileID FID = SM.getFileID(B);
38*0b57cec5SDimitry Andric   assert(SM.getFileID(E) == FID && "B/E not in the same file!");
39*0b57cec5SDimitry Andric 
40*0b57cec5SDimitry Andric   unsigned BOffset = SM.getFileOffset(B);
41*0b57cec5SDimitry Andric   unsigned EOffset = SM.getFileOffset(E);
42*0b57cec5SDimitry Andric 
43*0b57cec5SDimitry Andric   // Include the whole end token in the range.
44*0b57cec5SDimitry Andric   if (IsTokenRange)
45*0b57cec5SDimitry Andric     EOffset += Lexer::MeasureTokenLength(E, R.getSourceMgr(), R.getLangOpts());
46*0b57cec5SDimitry Andric 
47*0b57cec5SDimitry Andric   bool Invalid = false;
48*0b57cec5SDimitry Andric   const char *BufferStart = SM.getBufferData(FID, &Invalid).data();
49*0b57cec5SDimitry Andric   if (Invalid)
50*0b57cec5SDimitry Andric     return;
51*0b57cec5SDimitry Andric 
52*0b57cec5SDimitry Andric   HighlightRange(R.getEditBuffer(FID), BOffset, EOffset,
53*0b57cec5SDimitry Andric                  BufferStart, StartTag, EndTag);
54*0b57cec5SDimitry Andric }
55*0b57cec5SDimitry Andric 
56*0b57cec5SDimitry Andric /// HighlightRange - This is the same as the above method, but takes
57*0b57cec5SDimitry Andric /// decomposed file locations.
58*0b57cec5SDimitry Andric void html::HighlightRange(RewriteBuffer &RB, unsigned B, unsigned E,
59*0b57cec5SDimitry Andric                           const char *BufferStart,
60*0b57cec5SDimitry Andric                           const char *StartTag, const char *EndTag) {
61*0b57cec5SDimitry Andric   // Insert the tag at the absolute start/end of the range.
62*0b57cec5SDimitry Andric   RB.InsertTextAfter(B, StartTag);
63*0b57cec5SDimitry Andric   RB.InsertTextBefore(E, EndTag);
64*0b57cec5SDimitry Andric 
65*0b57cec5SDimitry Andric   // Scan the range to see if there is a \r or \n.  If so, and if the line is
66*0b57cec5SDimitry Andric   // not blank, insert tags on that line as well.
67*0b57cec5SDimitry Andric   bool HadOpenTag = true;
68*0b57cec5SDimitry Andric 
69*0b57cec5SDimitry Andric   unsigned LastNonWhiteSpace = B;
70*0b57cec5SDimitry Andric   for (unsigned i = B; i != E; ++i) {
71*0b57cec5SDimitry Andric     switch (BufferStart[i]) {
72*0b57cec5SDimitry Andric     case '\r':
73*0b57cec5SDimitry Andric     case '\n':
74*0b57cec5SDimitry Andric       // Okay, we found a newline in the range.  If we have an open tag, we need
75*0b57cec5SDimitry Andric       // to insert a close tag at the first non-whitespace before the newline.
76*0b57cec5SDimitry Andric       if (HadOpenTag)
77*0b57cec5SDimitry Andric         RB.InsertTextBefore(LastNonWhiteSpace+1, EndTag);
78*0b57cec5SDimitry Andric 
79*0b57cec5SDimitry Andric       // Instead of inserting an open tag immediately after the newline, we
80*0b57cec5SDimitry Andric       // wait until we see a non-whitespace character.  This prevents us from
81*0b57cec5SDimitry Andric       // inserting tags around blank lines, and also allows the open tag to
82*0b57cec5SDimitry Andric       // be put *after* whitespace on a non-blank line.
83*0b57cec5SDimitry Andric       HadOpenTag = false;
84*0b57cec5SDimitry Andric       break;
85*0b57cec5SDimitry Andric     case '\0':
86*0b57cec5SDimitry Andric     case ' ':
87*0b57cec5SDimitry Andric     case '\t':
88*0b57cec5SDimitry Andric     case '\f':
89*0b57cec5SDimitry Andric     case '\v':
90*0b57cec5SDimitry Andric       // Ignore whitespace.
91*0b57cec5SDimitry Andric       break;
92*0b57cec5SDimitry Andric 
93*0b57cec5SDimitry Andric     default:
94*0b57cec5SDimitry Andric       // If there is no tag open, do it now.
95*0b57cec5SDimitry Andric       if (!HadOpenTag) {
96*0b57cec5SDimitry Andric         RB.InsertTextAfter(i, StartTag);
97*0b57cec5SDimitry Andric         HadOpenTag = true;
98*0b57cec5SDimitry Andric       }
99*0b57cec5SDimitry Andric 
100*0b57cec5SDimitry Andric       // Remember this character.
101*0b57cec5SDimitry Andric       LastNonWhiteSpace = i;
102*0b57cec5SDimitry Andric       break;
103*0b57cec5SDimitry Andric     }
104*0b57cec5SDimitry Andric   }
105*0b57cec5SDimitry Andric }
106*0b57cec5SDimitry Andric 
107*0b57cec5SDimitry Andric void html::EscapeText(Rewriter &R, FileID FID,
108*0b57cec5SDimitry Andric                       bool EscapeSpaces, bool ReplaceTabs) {
109*0b57cec5SDimitry Andric 
110*0b57cec5SDimitry Andric   const llvm::MemoryBuffer *Buf = R.getSourceMgr().getBuffer(FID);
111*0b57cec5SDimitry Andric   const char* C = Buf->getBufferStart();
112*0b57cec5SDimitry Andric   const char* FileEnd = Buf->getBufferEnd();
113*0b57cec5SDimitry Andric 
114*0b57cec5SDimitry Andric   assert (C <= FileEnd);
115*0b57cec5SDimitry Andric 
116*0b57cec5SDimitry Andric   RewriteBuffer &RB = R.getEditBuffer(FID);
117*0b57cec5SDimitry Andric 
118*0b57cec5SDimitry Andric   unsigned ColNo = 0;
119*0b57cec5SDimitry Andric   for (unsigned FilePos = 0; C != FileEnd ; ++C, ++FilePos) {
120*0b57cec5SDimitry Andric     switch (*C) {
121*0b57cec5SDimitry Andric     default: ++ColNo; break;
122*0b57cec5SDimitry Andric     case '\n':
123*0b57cec5SDimitry Andric     case '\r':
124*0b57cec5SDimitry Andric       ColNo = 0;
125*0b57cec5SDimitry Andric       break;
126*0b57cec5SDimitry Andric 
127*0b57cec5SDimitry Andric     case ' ':
128*0b57cec5SDimitry Andric       if (EscapeSpaces)
129*0b57cec5SDimitry Andric         RB.ReplaceText(FilePos, 1, "&nbsp;");
130*0b57cec5SDimitry Andric       ++ColNo;
131*0b57cec5SDimitry Andric       break;
132*0b57cec5SDimitry Andric     case '\f':
133*0b57cec5SDimitry Andric       RB.ReplaceText(FilePos, 1, "<hr>");
134*0b57cec5SDimitry Andric       ColNo = 0;
135*0b57cec5SDimitry Andric       break;
136*0b57cec5SDimitry Andric 
137*0b57cec5SDimitry Andric     case '\t': {
138*0b57cec5SDimitry Andric       if (!ReplaceTabs)
139*0b57cec5SDimitry Andric         break;
140*0b57cec5SDimitry Andric       unsigned NumSpaces = 8-(ColNo&7);
141*0b57cec5SDimitry Andric       if (EscapeSpaces)
142*0b57cec5SDimitry Andric         RB.ReplaceText(FilePos, 1,
143*0b57cec5SDimitry Andric                        StringRef("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
144*0b57cec5SDimitry Andric                                        "&nbsp;&nbsp;&nbsp;", 6*NumSpaces));
145*0b57cec5SDimitry Andric       else
146*0b57cec5SDimitry Andric         RB.ReplaceText(FilePos, 1, StringRef("        ", NumSpaces));
147*0b57cec5SDimitry Andric       ColNo += NumSpaces;
148*0b57cec5SDimitry Andric       break;
149*0b57cec5SDimitry Andric     }
150*0b57cec5SDimitry Andric     case '<':
151*0b57cec5SDimitry Andric       RB.ReplaceText(FilePos, 1, "&lt;");
152*0b57cec5SDimitry Andric       ++ColNo;
153*0b57cec5SDimitry Andric       break;
154*0b57cec5SDimitry Andric 
155*0b57cec5SDimitry Andric     case '>':
156*0b57cec5SDimitry Andric       RB.ReplaceText(FilePos, 1, "&gt;");
157*0b57cec5SDimitry Andric       ++ColNo;
158*0b57cec5SDimitry Andric       break;
159*0b57cec5SDimitry Andric 
160*0b57cec5SDimitry Andric     case '&':
161*0b57cec5SDimitry Andric       RB.ReplaceText(FilePos, 1, "&amp;");
162*0b57cec5SDimitry Andric       ++ColNo;
163*0b57cec5SDimitry Andric       break;
164*0b57cec5SDimitry Andric     }
165*0b57cec5SDimitry Andric   }
166*0b57cec5SDimitry Andric }
167*0b57cec5SDimitry Andric 
168*0b57cec5SDimitry Andric std::string html::EscapeText(StringRef s, bool EscapeSpaces, bool ReplaceTabs) {
169*0b57cec5SDimitry Andric 
170*0b57cec5SDimitry Andric   unsigned len = s.size();
171*0b57cec5SDimitry Andric   std::string Str;
172*0b57cec5SDimitry Andric   llvm::raw_string_ostream os(Str);
173*0b57cec5SDimitry Andric 
174*0b57cec5SDimitry Andric   for (unsigned i = 0 ; i < len; ++i) {
175*0b57cec5SDimitry Andric 
176*0b57cec5SDimitry Andric     char c = s[i];
177*0b57cec5SDimitry Andric     switch (c) {
178*0b57cec5SDimitry Andric     default:
179*0b57cec5SDimitry Andric       os << c; break;
180*0b57cec5SDimitry Andric 
181*0b57cec5SDimitry Andric     case ' ':
182*0b57cec5SDimitry Andric       if (EscapeSpaces) os << "&nbsp;";
183*0b57cec5SDimitry Andric       else os << ' ';
184*0b57cec5SDimitry Andric       break;
185*0b57cec5SDimitry Andric 
186*0b57cec5SDimitry Andric     case '\t':
187*0b57cec5SDimitry Andric       if (ReplaceTabs) {
188*0b57cec5SDimitry Andric         if (EscapeSpaces)
189*0b57cec5SDimitry Andric           for (unsigned i = 0; i < 4; ++i)
190*0b57cec5SDimitry Andric             os << "&nbsp;";
191*0b57cec5SDimitry Andric         else
192*0b57cec5SDimitry Andric           for (unsigned i = 0; i < 4; ++i)
193*0b57cec5SDimitry Andric             os << " ";
194*0b57cec5SDimitry Andric       }
195*0b57cec5SDimitry Andric       else
196*0b57cec5SDimitry Andric         os << c;
197*0b57cec5SDimitry Andric 
198*0b57cec5SDimitry Andric       break;
199*0b57cec5SDimitry Andric 
200*0b57cec5SDimitry Andric     case '<': os << "&lt;"; break;
201*0b57cec5SDimitry Andric     case '>': os << "&gt;"; break;
202*0b57cec5SDimitry Andric     case '&': os << "&amp;"; break;
203*0b57cec5SDimitry Andric     }
204*0b57cec5SDimitry Andric   }
205*0b57cec5SDimitry Andric 
206*0b57cec5SDimitry Andric   return os.str();
207*0b57cec5SDimitry Andric }
208*0b57cec5SDimitry Andric 
209*0b57cec5SDimitry Andric static void AddLineNumber(RewriteBuffer &RB, unsigned LineNo,
210*0b57cec5SDimitry Andric                           unsigned B, unsigned E) {
211*0b57cec5SDimitry Andric   SmallString<256> Str;
212*0b57cec5SDimitry Andric   llvm::raw_svector_ostream OS(Str);
213*0b57cec5SDimitry Andric 
214*0b57cec5SDimitry Andric   OS << "<tr class=\"codeline\" data-linenumber=\"" << LineNo << "\">"
215*0b57cec5SDimitry Andric      << "<td class=\"num\" id=\"LN" << LineNo << "\">" << LineNo
216*0b57cec5SDimitry Andric      << "</td><td class=\"line\">";
217*0b57cec5SDimitry Andric 
218*0b57cec5SDimitry Andric   if (B == E) { // Handle empty lines.
219*0b57cec5SDimitry Andric     OS << " </td></tr>";
220*0b57cec5SDimitry Andric     RB.InsertTextBefore(B, OS.str());
221*0b57cec5SDimitry Andric   } else {
222*0b57cec5SDimitry Andric     RB.InsertTextBefore(B, OS.str());
223*0b57cec5SDimitry Andric     RB.InsertTextBefore(E, "</td></tr>");
224*0b57cec5SDimitry Andric   }
225*0b57cec5SDimitry Andric }
226*0b57cec5SDimitry Andric 
227*0b57cec5SDimitry Andric void html::AddLineNumbers(Rewriter& R, FileID FID) {
228*0b57cec5SDimitry Andric 
229*0b57cec5SDimitry Andric   const llvm::MemoryBuffer *Buf = R.getSourceMgr().getBuffer(FID);
230*0b57cec5SDimitry Andric   const char* FileBeg = Buf->getBufferStart();
231*0b57cec5SDimitry Andric   const char* FileEnd = Buf->getBufferEnd();
232*0b57cec5SDimitry Andric   const char* C = FileBeg;
233*0b57cec5SDimitry Andric   RewriteBuffer &RB = R.getEditBuffer(FID);
234*0b57cec5SDimitry Andric 
235*0b57cec5SDimitry Andric   assert (C <= FileEnd);
236*0b57cec5SDimitry Andric 
237*0b57cec5SDimitry Andric   unsigned LineNo = 0;
238*0b57cec5SDimitry Andric   unsigned FilePos = 0;
239*0b57cec5SDimitry Andric 
240*0b57cec5SDimitry Andric   while (C != FileEnd) {
241*0b57cec5SDimitry Andric 
242*0b57cec5SDimitry Andric     ++LineNo;
243*0b57cec5SDimitry Andric     unsigned LineStartPos = FilePos;
244*0b57cec5SDimitry Andric     unsigned LineEndPos = FileEnd - FileBeg;
245*0b57cec5SDimitry Andric 
246*0b57cec5SDimitry Andric     assert (FilePos <= LineEndPos);
247*0b57cec5SDimitry Andric     assert (C < FileEnd);
248*0b57cec5SDimitry Andric 
249*0b57cec5SDimitry Andric     // Scan until the newline (or end-of-file).
250*0b57cec5SDimitry Andric 
251*0b57cec5SDimitry Andric     while (C != FileEnd) {
252*0b57cec5SDimitry Andric       char c = *C;
253*0b57cec5SDimitry Andric       ++C;
254*0b57cec5SDimitry Andric 
255*0b57cec5SDimitry Andric       if (c == '\n') {
256*0b57cec5SDimitry Andric         LineEndPos = FilePos++;
257*0b57cec5SDimitry Andric         break;
258*0b57cec5SDimitry Andric       }
259*0b57cec5SDimitry Andric 
260*0b57cec5SDimitry Andric       ++FilePos;
261*0b57cec5SDimitry Andric     }
262*0b57cec5SDimitry Andric 
263*0b57cec5SDimitry Andric     AddLineNumber(RB, LineNo, LineStartPos, LineEndPos);
264*0b57cec5SDimitry Andric   }
265*0b57cec5SDimitry Andric 
266*0b57cec5SDimitry Andric   // Add one big table tag that surrounds all of the code.
267*0b57cec5SDimitry Andric   std::string s;
268*0b57cec5SDimitry Andric   llvm::raw_string_ostream os(s);
269*0b57cec5SDimitry Andric   os << "<table class=\"code\" data-fileid=\"" << FID.getHashValue() << "\">\n";
270*0b57cec5SDimitry Andric   RB.InsertTextBefore(0, os.str());
271*0b57cec5SDimitry Andric   RB.InsertTextAfter(FileEnd - FileBeg, "</table>");
272*0b57cec5SDimitry Andric }
273*0b57cec5SDimitry Andric 
274*0b57cec5SDimitry Andric void html::AddHeaderFooterInternalBuiltinCSS(Rewriter &R, FileID FID,
275*0b57cec5SDimitry Andric                                              StringRef title) {
276*0b57cec5SDimitry Andric 
277*0b57cec5SDimitry Andric   const llvm::MemoryBuffer *Buf = R.getSourceMgr().getBuffer(FID);
278*0b57cec5SDimitry Andric   const char* FileStart = Buf->getBufferStart();
279*0b57cec5SDimitry Andric   const char* FileEnd = Buf->getBufferEnd();
280*0b57cec5SDimitry Andric 
281*0b57cec5SDimitry Andric   SourceLocation StartLoc = R.getSourceMgr().getLocForStartOfFile(FID);
282*0b57cec5SDimitry Andric   SourceLocation EndLoc = StartLoc.getLocWithOffset(FileEnd-FileStart);
283*0b57cec5SDimitry Andric 
284*0b57cec5SDimitry Andric   std::string s;
285*0b57cec5SDimitry Andric   llvm::raw_string_ostream os(s);
286*0b57cec5SDimitry Andric   os << "<!doctype html>\n" // Use HTML 5 doctype
287*0b57cec5SDimitry Andric         "<html>\n<head>\n";
288*0b57cec5SDimitry Andric 
289*0b57cec5SDimitry Andric   if (!title.empty())
290*0b57cec5SDimitry Andric     os << "<title>" << html::EscapeText(title) << "</title>\n";
291*0b57cec5SDimitry Andric 
292*0b57cec5SDimitry Andric   os << R"<<<(
293*0b57cec5SDimitry Andric <style type="text/css">
294*0b57cec5SDimitry Andric body { color:#000000; background-color:#ffffff }
295*0b57cec5SDimitry Andric body { font-family:Helvetica, sans-serif; font-size:10pt }
296*0b57cec5SDimitry Andric h1 { font-size:14pt }
297*0b57cec5SDimitry Andric .FileName { margin-top: 5px; margin-bottom: 5px; display: inline; }
298*0b57cec5SDimitry Andric .FileNav { margin-left: 5px; margin-right: 5px; display: inline; }
299*0b57cec5SDimitry Andric .FileNav a { text-decoration:none; font-size: larger; }
300*0b57cec5SDimitry Andric .divider { margin-top: 30px; margin-bottom: 30px; height: 15px; }
301*0b57cec5SDimitry Andric .divider { background-color: gray; }
302*0b57cec5SDimitry Andric .code { border-collapse:collapse; width:100%; }
303*0b57cec5SDimitry Andric .code { font-family: "Monospace", monospace; font-size:10pt }
304*0b57cec5SDimitry Andric .code { line-height: 1.2em }
305*0b57cec5SDimitry Andric .comment { color: green; font-style: oblique }
306*0b57cec5SDimitry Andric .keyword { color: blue }
307*0b57cec5SDimitry Andric .string_literal { color: red }
308*0b57cec5SDimitry Andric .directive { color: darkmagenta }
309*0b57cec5SDimitry Andric 
310*0b57cec5SDimitry Andric /* Macros and variables could have pop-up notes hidden by default.
311*0b57cec5SDimitry Andric   - Macro pop-up:    expansion of the macro
312*0b57cec5SDimitry Andric   - Variable pop-up: value (table) of the variable */
313*0b57cec5SDimitry Andric .macro_popup, .variable_popup { display: none; }
314*0b57cec5SDimitry Andric 
315*0b57cec5SDimitry Andric /* Pop-up appears on mouse-hover event. */
316*0b57cec5SDimitry Andric .macro:hover .macro_popup, .variable:hover .variable_popup {
317*0b57cec5SDimitry Andric   display: block;
318*0b57cec5SDimitry Andric   padding: 2px;
319*0b57cec5SDimitry Andric   -webkit-border-radius:5px;
320*0b57cec5SDimitry Andric   -webkit-box-shadow:1px 1px 7px #000;
321*0b57cec5SDimitry Andric   border-radius:5px;
322*0b57cec5SDimitry Andric   box-shadow:1px 1px 7px #000;
323*0b57cec5SDimitry Andric   position: absolute;
324*0b57cec5SDimitry Andric   top: -1em;
325*0b57cec5SDimitry Andric   left:10em;
326*0b57cec5SDimitry Andric   z-index: 1
327*0b57cec5SDimitry Andric }
328*0b57cec5SDimitry Andric 
329*0b57cec5SDimitry Andric .macro_popup {
330*0b57cec5SDimitry Andric   border: 2px solid red;
331*0b57cec5SDimitry Andric   background-color:#FFF0F0;
332*0b57cec5SDimitry Andric   font-weight: normal;
333*0b57cec5SDimitry Andric }
334*0b57cec5SDimitry Andric 
335*0b57cec5SDimitry Andric .variable_popup {
336*0b57cec5SDimitry Andric   border: 2px solid blue;
337*0b57cec5SDimitry Andric   background-color:#F0F0FF;
338*0b57cec5SDimitry Andric   font-weight: bold;
339*0b57cec5SDimitry Andric   font-family: Helvetica, sans-serif;
340*0b57cec5SDimitry Andric   font-size: 9pt;
341*0b57cec5SDimitry Andric }
342*0b57cec5SDimitry Andric 
343*0b57cec5SDimitry Andric /* Pop-up notes needs a relative position as a base where they pops up. */
344*0b57cec5SDimitry Andric .macro, .variable {
345*0b57cec5SDimitry Andric   background-color: PaleGoldenRod;
346*0b57cec5SDimitry Andric   position: relative;
347*0b57cec5SDimitry Andric }
348*0b57cec5SDimitry Andric .macro { color: DarkMagenta; }
349*0b57cec5SDimitry Andric 
350*0b57cec5SDimitry Andric #tooltiphint {
351*0b57cec5SDimitry Andric   position: fixed;
352*0b57cec5SDimitry Andric   width: 50em;
353*0b57cec5SDimitry Andric   margin-left: -25em;
354*0b57cec5SDimitry Andric   left: 50%;
355*0b57cec5SDimitry Andric   padding: 10px;
356*0b57cec5SDimitry Andric   border: 1px solid #b0b0b0;
357*0b57cec5SDimitry Andric   border-radius: 2px;
358*0b57cec5SDimitry Andric   box-shadow: 1px 1px 7px black;
359*0b57cec5SDimitry Andric   background-color: #c0c0c0;
360*0b57cec5SDimitry Andric   z-index: 2;
361*0b57cec5SDimitry Andric }
362*0b57cec5SDimitry Andric 
363*0b57cec5SDimitry Andric .num { width:2.5em; padding-right:2ex; background-color:#eeeeee }
364*0b57cec5SDimitry Andric .num { text-align:right; font-size:8pt }
365*0b57cec5SDimitry Andric .num { color:#444444 }
366*0b57cec5SDimitry Andric .line { padding-left: 1ex; border-left: 3px solid #ccc }
367*0b57cec5SDimitry Andric .line { white-space: pre }
368*0b57cec5SDimitry Andric .msg { -webkit-box-shadow:1px 1px 7px #000 }
369*0b57cec5SDimitry Andric .msg { box-shadow:1px 1px 7px #000 }
370*0b57cec5SDimitry Andric .msg { -webkit-border-radius:5px }
371*0b57cec5SDimitry Andric .msg { border-radius:5px }
372*0b57cec5SDimitry Andric .msg { font-family:Helvetica, sans-serif; font-size:8pt }
373*0b57cec5SDimitry Andric .msg { float:left }
374*0b57cec5SDimitry Andric .msg { padding:0.25em 1ex 0.25em 1ex }
375*0b57cec5SDimitry Andric .msg { margin-top:10px; margin-bottom:10px }
376*0b57cec5SDimitry Andric .msg { font-weight:bold }
377*0b57cec5SDimitry Andric .msg { max-width:60em; word-wrap: break-word; white-space: pre-wrap }
378*0b57cec5SDimitry Andric .msgT { padding:0x; spacing:0x }
379*0b57cec5SDimitry Andric .msgEvent { background-color:#fff8b4; color:#000000 }
380*0b57cec5SDimitry Andric .msgControl { background-color:#bbbbbb; color:#000000 }
381*0b57cec5SDimitry Andric .msgNote { background-color:#ddeeff; color:#000000 }
382*0b57cec5SDimitry Andric .mrange { background-color:#dfddf3 }
383*0b57cec5SDimitry Andric .mrange { border-bottom:1px solid #6F9DBE }
384*0b57cec5SDimitry Andric .PathIndex { font-weight: bold; padding:0px 5px; margin-right:5px; }
385*0b57cec5SDimitry Andric .PathIndex { -webkit-border-radius:8px }
386*0b57cec5SDimitry Andric .PathIndex { border-radius:8px }
387*0b57cec5SDimitry Andric .PathIndexEvent { background-color:#bfba87 }
388*0b57cec5SDimitry Andric .PathIndexControl { background-color:#8c8c8c }
389*0b57cec5SDimitry Andric .PathIndexPopUp { background-color: #879abc; }
390*0b57cec5SDimitry Andric .PathNav a { text-decoration:none; font-size: larger }
391*0b57cec5SDimitry Andric .CodeInsertionHint { font-weight: bold; background-color: #10dd10 }
392*0b57cec5SDimitry Andric .CodeRemovalHint { background-color:#de1010 }
393*0b57cec5SDimitry Andric .CodeRemovalHint { border-bottom:1px solid #6F9DBE }
394*0b57cec5SDimitry Andric .selected{ background-color:orange !important; }
395*0b57cec5SDimitry Andric 
396*0b57cec5SDimitry Andric table.simpletable {
397*0b57cec5SDimitry Andric   padding: 5px;
398*0b57cec5SDimitry Andric   font-size:12pt;
399*0b57cec5SDimitry Andric   margin:20px;
400*0b57cec5SDimitry Andric   border-collapse: collapse; border-spacing: 0px;
401*0b57cec5SDimitry Andric }
402*0b57cec5SDimitry Andric td.rowname {
403*0b57cec5SDimitry Andric   text-align: right;
404*0b57cec5SDimitry Andric   vertical-align: top;
405*0b57cec5SDimitry Andric   font-weight: bold;
406*0b57cec5SDimitry Andric   color:#444444;
407*0b57cec5SDimitry Andric   padding-right:2ex;
408*0b57cec5SDimitry Andric }
409*0b57cec5SDimitry Andric 
410*0b57cec5SDimitry Andric /* Hidden text. */
411*0b57cec5SDimitry Andric input.spoilerhider + label {
412*0b57cec5SDimitry Andric   cursor: pointer;
413*0b57cec5SDimitry Andric   text-decoration: underline;
414*0b57cec5SDimitry Andric   display: block;
415*0b57cec5SDimitry Andric }
416*0b57cec5SDimitry Andric input.spoilerhider {
417*0b57cec5SDimitry Andric  display: none;
418*0b57cec5SDimitry Andric }
419*0b57cec5SDimitry Andric input.spoilerhider ~ .spoiler {
420*0b57cec5SDimitry Andric   overflow: hidden;
421*0b57cec5SDimitry Andric   margin: 10px auto 0;
422*0b57cec5SDimitry Andric   height: 0;
423*0b57cec5SDimitry Andric   opacity: 0;
424*0b57cec5SDimitry Andric }
425*0b57cec5SDimitry Andric input.spoilerhider:checked + label + .spoiler{
426*0b57cec5SDimitry Andric   height: auto;
427*0b57cec5SDimitry Andric   opacity: 1;
428*0b57cec5SDimitry Andric }
429*0b57cec5SDimitry Andric </style>
430*0b57cec5SDimitry Andric </head>
431*0b57cec5SDimitry Andric <body>)<<<";
432*0b57cec5SDimitry Andric 
433*0b57cec5SDimitry Andric   // Generate header
434*0b57cec5SDimitry Andric   R.InsertTextBefore(StartLoc, os.str());
435*0b57cec5SDimitry Andric   // Generate footer
436*0b57cec5SDimitry Andric 
437*0b57cec5SDimitry Andric   R.InsertTextAfter(EndLoc, "</body></html>\n");
438*0b57cec5SDimitry Andric }
439*0b57cec5SDimitry Andric 
440*0b57cec5SDimitry Andric /// SyntaxHighlight - Relex the specified FileID and annotate the HTML with
441*0b57cec5SDimitry Andric /// information about keywords, macro expansions etc.  This uses the macro
442*0b57cec5SDimitry Andric /// table state from the end of the file, so it won't be perfectly perfect,
443*0b57cec5SDimitry Andric /// but it will be reasonably close.
444*0b57cec5SDimitry Andric void html::SyntaxHighlight(Rewriter &R, FileID FID, const Preprocessor &PP) {
445*0b57cec5SDimitry Andric   RewriteBuffer &RB = R.getEditBuffer(FID);
446*0b57cec5SDimitry Andric 
447*0b57cec5SDimitry Andric   const SourceManager &SM = PP.getSourceManager();
448*0b57cec5SDimitry Andric   const llvm::MemoryBuffer *FromFile = SM.getBuffer(FID);
449*0b57cec5SDimitry Andric   Lexer L(FID, FromFile, SM, PP.getLangOpts());
450*0b57cec5SDimitry Andric   const char *BufferStart = L.getBuffer().data();
451*0b57cec5SDimitry Andric 
452*0b57cec5SDimitry Andric   // Inform the preprocessor that we want to retain comments as tokens, so we
453*0b57cec5SDimitry Andric   // can highlight them.
454*0b57cec5SDimitry Andric   L.SetCommentRetentionState(true);
455*0b57cec5SDimitry Andric 
456*0b57cec5SDimitry Andric   // Lex all the tokens in raw mode, to avoid entering #includes or expanding
457*0b57cec5SDimitry Andric   // macros.
458*0b57cec5SDimitry Andric   Token Tok;
459*0b57cec5SDimitry Andric   L.LexFromRawLexer(Tok);
460*0b57cec5SDimitry Andric 
461*0b57cec5SDimitry Andric   while (Tok.isNot(tok::eof)) {
462*0b57cec5SDimitry Andric     // Since we are lexing unexpanded tokens, all tokens are from the main
463*0b57cec5SDimitry Andric     // FileID.
464*0b57cec5SDimitry Andric     unsigned TokOffs = SM.getFileOffset(Tok.getLocation());
465*0b57cec5SDimitry Andric     unsigned TokLen = Tok.getLength();
466*0b57cec5SDimitry Andric     switch (Tok.getKind()) {
467*0b57cec5SDimitry Andric     default: break;
468*0b57cec5SDimitry Andric     case tok::identifier:
469*0b57cec5SDimitry Andric       llvm_unreachable("tok::identifier in raw lexing mode!");
470*0b57cec5SDimitry Andric     case tok::raw_identifier: {
471*0b57cec5SDimitry Andric       // Fill in Result.IdentifierInfo and update the token kind,
472*0b57cec5SDimitry Andric       // looking up the identifier in the identifier table.
473*0b57cec5SDimitry Andric       PP.LookUpIdentifierInfo(Tok);
474*0b57cec5SDimitry Andric 
475*0b57cec5SDimitry Andric       // If this is a pp-identifier, for a keyword, highlight it as such.
476*0b57cec5SDimitry Andric       if (Tok.isNot(tok::identifier))
477*0b57cec5SDimitry Andric         HighlightRange(RB, TokOffs, TokOffs+TokLen, BufferStart,
478*0b57cec5SDimitry Andric                        "<span class='keyword'>", "</span>");
479*0b57cec5SDimitry Andric       break;
480*0b57cec5SDimitry Andric     }
481*0b57cec5SDimitry Andric     case tok::comment:
482*0b57cec5SDimitry Andric       HighlightRange(RB, TokOffs, TokOffs+TokLen, BufferStart,
483*0b57cec5SDimitry Andric                      "<span class='comment'>", "</span>");
484*0b57cec5SDimitry Andric       break;
485*0b57cec5SDimitry Andric     case tok::utf8_string_literal:
486*0b57cec5SDimitry Andric       // Chop off the u part of u8 prefix
487*0b57cec5SDimitry Andric       ++TokOffs;
488*0b57cec5SDimitry Andric       --TokLen;
489*0b57cec5SDimitry Andric       // FALL THROUGH to chop the 8
490*0b57cec5SDimitry Andric       LLVM_FALLTHROUGH;
491*0b57cec5SDimitry Andric     case tok::wide_string_literal:
492*0b57cec5SDimitry Andric     case tok::utf16_string_literal:
493*0b57cec5SDimitry Andric     case tok::utf32_string_literal:
494*0b57cec5SDimitry Andric       // Chop off the L, u, U or 8 prefix
495*0b57cec5SDimitry Andric       ++TokOffs;
496*0b57cec5SDimitry Andric       --TokLen;
497*0b57cec5SDimitry Andric       LLVM_FALLTHROUGH;
498*0b57cec5SDimitry Andric     case tok::string_literal:
499*0b57cec5SDimitry Andric       // FIXME: Exclude the optional ud-suffix from the highlighted range.
500*0b57cec5SDimitry Andric       HighlightRange(RB, TokOffs, TokOffs+TokLen, BufferStart,
501*0b57cec5SDimitry Andric                      "<span class='string_literal'>", "</span>");
502*0b57cec5SDimitry Andric       break;
503*0b57cec5SDimitry Andric     case tok::hash: {
504*0b57cec5SDimitry Andric       // If this is a preprocessor directive, all tokens to end of line are too.
505*0b57cec5SDimitry Andric       if (!Tok.isAtStartOfLine())
506*0b57cec5SDimitry Andric         break;
507*0b57cec5SDimitry Andric 
508*0b57cec5SDimitry Andric       // Eat all of the tokens until we get to the next one at the start of
509*0b57cec5SDimitry Andric       // line.
510*0b57cec5SDimitry Andric       unsigned TokEnd = TokOffs+TokLen;
511*0b57cec5SDimitry Andric       L.LexFromRawLexer(Tok);
512*0b57cec5SDimitry Andric       while (!Tok.isAtStartOfLine() && Tok.isNot(tok::eof)) {
513*0b57cec5SDimitry Andric         TokEnd = SM.getFileOffset(Tok.getLocation())+Tok.getLength();
514*0b57cec5SDimitry Andric         L.LexFromRawLexer(Tok);
515*0b57cec5SDimitry Andric       }
516*0b57cec5SDimitry Andric 
517*0b57cec5SDimitry Andric       // Find end of line.  This is a hack.
518*0b57cec5SDimitry Andric       HighlightRange(RB, TokOffs, TokEnd, BufferStart,
519*0b57cec5SDimitry Andric                      "<span class='directive'>", "</span>");
520*0b57cec5SDimitry Andric 
521*0b57cec5SDimitry Andric       // Don't skip the next token.
522*0b57cec5SDimitry Andric       continue;
523*0b57cec5SDimitry Andric     }
524*0b57cec5SDimitry Andric     }
525*0b57cec5SDimitry Andric 
526*0b57cec5SDimitry Andric     L.LexFromRawLexer(Tok);
527*0b57cec5SDimitry Andric   }
528*0b57cec5SDimitry Andric }
529*0b57cec5SDimitry Andric 
530*0b57cec5SDimitry Andric /// HighlightMacros - This uses the macro table state from the end of the
531*0b57cec5SDimitry Andric /// file, to re-expand macros and insert (into the HTML) information about the
532*0b57cec5SDimitry Andric /// macro expansions.  This won't be perfectly perfect, but it will be
533*0b57cec5SDimitry Andric /// reasonably close.
534*0b57cec5SDimitry Andric void html::HighlightMacros(Rewriter &R, FileID FID, const Preprocessor& PP) {
535*0b57cec5SDimitry Andric   // Re-lex the raw token stream into a token buffer.
536*0b57cec5SDimitry Andric   const SourceManager &SM = PP.getSourceManager();
537*0b57cec5SDimitry Andric   std::vector<Token> TokenStream;
538*0b57cec5SDimitry Andric 
539*0b57cec5SDimitry Andric   const llvm::MemoryBuffer *FromFile = SM.getBuffer(FID);
540*0b57cec5SDimitry Andric   Lexer L(FID, FromFile, SM, PP.getLangOpts());
541*0b57cec5SDimitry Andric 
542*0b57cec5SDimitry Andric   // Lex all the tokens in raw mode, to avoid entering #includes or expanding
543*0b57cec5SDimitry Andric   // macros.
544*0b57cec5SDimitry Andric   while (1) {
545*0b57cec5SDimitry Andric     Token Tok;
546*0b57cec5SDimitry Andric     L.LexFromRawLexer(Tok);
547*0b57cec5SDimitry Andric 
548*0b57cec5SDimitry Andric     // If this is a # at the start of a line, discard it from the token stream.
549*0b57cec5SDimitry Andric     // We don't want the re-preprocess step to see #defines, #includes or other
550*0b57cec5SDimitry Andric     // preprocessor directives.
551*0b57cec5SDimitry Andric     if (Tok.is(tok::hash) && Tok.isAtStartOfLine())
552*0b57cec5SDimitry Andric       continue;
553*0b57cec5SDimitry Andric 
554*0b57cec5SDimitry Andric     // If this is a ## token, change its kind to unknown so that repreprocessing
555*0b57cec5SDimitry Andric     // it will not produce an error.
556*0b57cec5SDimitry Andric     if (Tok.is(tok::hashhash))
557*0b57cec5SDimitry Andric       Tok.setKind(tok::unknown);
558*0b57cec5SDimitry Andric 
559*0b57cec5SDimitry Andric     // If this raw token is an identifier, the raw lexer won't have looked up
560*0b57cec5SDimitry Andric     // the corresponding identifier info for it.  Do this now so that it will be
561*0b57cec5SDimitry Andric     // macro expanded when we re-preprocess it.
562*0b57cec5SDimitry Andric     if (Tok.is(tok::raw_identifier))
563*0b57cec5SDimitry Andric       PP.LookUpIdentifierInfo(Tok);
564*0b57cec5SDimitry Andric 
565*0b57cec5SDimitry Andric     TokenStream.push_back(Tok);
566*0b57cec5SDimitry Andric 
567*0b57cec5SDimitry Andric     if (Tok.is(tok::eof)) break;
568*0b57cec5SDimitry Andric   }
569*0b57cec5SDimitry Andric 
570*0b57cec5SDimitry Andric   // Temporarily change the diagnostics object so that we ignore any generated
571*0b57cec5SDimitry Andric   // diagnostics from this pass.
572*0b57cec5SDimitry Andric   DiagnosticsEngine TmpDiags(PP.getDiagnostics().getDiagnosticIDs(),
573*0b57cec5SDimitry Andric                              &PP.getDiagnostics().getDiagnosticOptions(),
574*0b57cec5SDimitry Andric                       new IgnoringDiagConsumer);
575*0b57cec5SDimitry Andric 
576*0b57cec5SDimitry Andric   // FIXME: This is a huge hack; we reuse the input preprocessor because we want
577*0b57cec5SDimitry Andric   // its state, but we aren't actually changing it (we hope). This should really
578*0b57cec5SDimitry Andric   // construct a copy of the preprocessor.
579*0b57cec5SDimitry Andric   Preprocessor &TmpPP = const_cast<Preprocessor&>(PP);
580*0b57cec5SDimitry Andric   DiagnosticsEngine *OldDiags = &TmpPP.getDiagnostics();
581*0b57cec5SDimitry Andric   TmpPP.setDiagnostics(TmpDiags);
582*0b57cec5SDimitry Andric 
583*0b57cec5SDimitry Andric   // Inform the preprocessor that we don't want comments.
584*0b57cec5SDimitry Andric   TmpPP.SetCommentRetentionState(false, false);
585*0b57cec5SDimitry Andric 
586*0b57cec5SDimitry Andric   // We don't want pragmas either. Although we filtered out #pragma, removing
587*0b57cec5SDimitry Andric   // _Pragma and __pragma is much harder.
588*0b57cec5SDimitry Andric   bool PragmasPreviouslyEnabled = TmpPP.getPragmasEnabled();
589*0b57cec5SDimitry Andric   TmpPP.setPragmasEnabled(false);
590*0b57cec5SDimitry Andric 
591*0b57cec5SDimitry Andric   // Enter the tokens we just lexed.  This will cause them to be macro expanded
592*0b57cec5SDimitry Andric   // but won't enter sub-files (because we removed #'s).
593*0b57cec5SDimitry Andric   TmpPP.EnterTokenStream(TokenStream, false, /*IsReinject=*/false);
594*0b57cec5SDimitry Andric 
595*0b57cec5SDimitry Andric   TokenConcatenation ConcatInfo(TmpPP);
596*0b57cec5SDimitry Andric 
597*0b57cec5SDimitry Andric   // Lex all the tokens.
598*0b57cec5SDimitry Andric   Token Tok;
599*0b57cec5SDimitry Andric   TmpPP.Lex(Tok);
600*0b57cec5SDimitry Andric   while (Tok.isNot(tok::eof)) {
601*0b57cec5SDimitry Andric     // Ignore non-macro tokens.
602*0b57cec5SDimitry Andric     if (!Tok.getLocation().isMacroID()) {
603*0b57cec5SDimitry Andric       TmpPP.Lex(Tok);
604*0b57cec5SDimitry Andric       continue;
605*0b57cec5SDimitry Andric     }
606*0b57cec5SDimitry Andric 
607*0b57cec5SDimitry Andric     // Okay, we have the first token of a macro expansion: highlight the
608*0b57cec5SDimitry Andric     // expansion by inserting a start tag before the macro expansion and
609*0b57cec5SDimitry Andric     // end tag after it.
610*0b57cec5SDimitry Andric     CharSourceRange LLoc = SM.getExpansionRange(Tok.getLocation());
611*0b57cec5SDimitry Andric 
612*0b57cec5SDimitry Andric     // Ignore tokens whose instantiation location was not the main file.
613*0b57cec5SDimitry Andric     if (SM.getFileID(LLoc.getBegin()) != FID) {
614*0b57cec5SDimitry Andric       TmpPP.Lex(Tok);
615*0b57cec5SDimitry Andric       continue;
616*0b57cec5SDimitry Andric     }
617*0b57cec5SDimitry Andric 
618*0b57cec5SDimitry Andric     assert(SM.getFileID(LLoc.getEnd()) == FID &&
619*0b57cec5SDimitry Andric            "Start and end of expansion must be in the same ultimate file!");
620*0b57cec5SDimitry Andric 
621*0b57cec5SDimitry Andric     std::string Expansion = EscapeText(TmpPP.getSpelling(Tok));
622*0b57cec5SDimitry Andric     unsigned LineLen = Expansion.size();
623*0b57cec5SDimitry Andric 
624*0b57cec5SDimitry Andric     Token PrevPrevTok;
625*0b57cec5SDimitry Andric     Token PrevTok = Tok;
626*0b57cec5SDimitry Andric     // Okay, eat this token, getting the next one.
627*0b57cec5SDimitry Andric     TmpPP.Lex(Tok);
628*0b57cec5SDimitry Andric 
629*0b57cec5SDimitry Andric     // Skip all the rest of the tokens that are part of this macro
630*0b57cec5SDimitry Andric     // instantiation.  It would be really nice to pop up a window with all the
631*0b57cec5SDimitry Andric     // spelling of the tokens or something.
632*0b57cec5SDimitry Andric     while (!Tok.is(tok::eof) &&
633*0b57cec5SDimitry Andric            SM.getExpansionLoc(Tok.getLocation()) == LLoc.getBegin()) {
634*0b57cec5SDimitry Andric       // Insert a newline if the macro expansion is getting large.
635*0b57cec5SDimitry Andric       if (LineLen > 60) {
636*0b57cec5SDimitry Andric         Expansion += "<br>";
637*0b57cec5SDimitry Andric         LineLen = 0;
638*0b57cec5SDimitry Andric       }
639*0b57cec5SDimitry Andric 
640*0b57cec5SDimitry Andric       LineLen -= Expansion.size();
641*0b57cec5SDimitry Andric 
642*0b57cec5SDimitry Andric       // If the tokens were already space separated, or if they must be to avoid
643*0b57cec5SDimitry Andric       // them being implicitly pasted, add a space between them.
644*0b57cec5SDimitry Andric       if (Tok.hasLeadingSpace() ||
645*0b57cec5SDimitry Andric           ConcatInfo.AvoidConcat(PrevPrevTok, PrevTok, Tok))
646*0b57cec5SDimitry Andric         Expansion += ' ';
647*0b57cec5SDimitry Andric 
648*0b57cec5SDimitry Andric       // Escape any special characters in the token text.
649*0b57cec5SDimitry Andric       Expansion += EscapeText(TmpPP.getSpelling(Tok));
650*0b57cec5SDimitry Andric       LineLen += Expansion.size();
651*0b57cec5SDimitry Andric 
652*0b57cec5SDimitry Andric       PrevPrevTok = PrevTok;
653*0b57cec5SDimitry Andric       PrevTok = Tok;
654*0b57cec5SDimitry Andric       TmpPP.Lex(Tok);
655*0b57cec5SDimitry Andric     }
656*0b57cec5SDimitry Andric 
657*0b57cec5SDimitry Andric     // Insert the 'macro_popup' as the end tag, so that multi-line macros all
658*0b57cec5SDimitry Andric     // get highlighted.
659*0b57cec5SDimitry Andric     Expansion = "<span class='macro_popup'>" + Expansion + "</span></span>";
660*0b57cec5SDimitry Andric 
661*0b57cec5SDimitry Andric     HighlightRange(R, LLoc.getBegin(), LLoc.getEnd(), "<span class='macro'>",
662*0b57cec5SDimitry Andric                    Expansion.c_str(), LLoc.isTokenRange());
663*0b57cec5SDimitry Andric   }
664*0b57cec5SDimitry Andric 
665*0b57cec5SDimitry Andric   // Restore the preprocessor's old state.
666*0b57cec5SDimitry Andric   TmpPP.setDiagnostics(*OldDiags);
667*0b57cec5SDimitry Andric   TmpPP.setPragmasEnabled(PragmasPreviouslyEnabled);
668*0b57cec5SDimitry Andric }
669