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