HTMLRewrite.cpp 20 KB

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