HTMLRewrite.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  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. const llvm::MemoryBuffer *Buf = R.getSourceMgr().getBuffer(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 os.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. const llvm::MemoryBuffer *Buf = R.getSourceMgr().getBuffer(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. const llvm::MemoryBuffer *Buf = R.getSourceMgr().getBuffer(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. /* Macro expansions. */
  257. .expansion { display: none; }
  258. .macro:hover .expansion {
  259. display: block;
  260. border: 2px solid #FF0000;
  261. padding: 2px;
  262. background-color:#FFF0F0;
  263. font-weight: normal;
  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. #tooltiphint {
  274. position: fixed;
  275. width: 50em;
  276. margin-left: -25em;
  277. left: 50%;
  278. padding: 10px;
  279. border: 1px solid #b0b0b0;
  280. border-radius: 2px;
  281. box-shadow: 1px 1px 7px black;
  282. background-color: #c0c0c0;
  283. z-index: 2;
  284. }
  285. .macro {
  286. color: darkmagenta;
  287. background-color:LemonChiffon;
  288. /* Macros are position: relative to provide base for expansions. */
  289. position: relative;
  290. }
  291. .num { width:2.5em; padding-right:2ex; background-color:#eeeeee }
  292. .num { text-align:right; font-size:8pt }
  293. .num { color:#444444 }
  294. .line { padding-left: 1ex; border-left: 3px solid #ccc }
  295. .line { white-space: pre }
  296. .msg { -webkit-box-shadow:1px 1px 7px #000 }
  297. .msg { box-shadow:1px 1px 7px #000 }
  298. .msg { -webkit-border-radius:5px }
  299. .msg { border-radius:5px }
  300. .msg { font-family:Helvetica, sans-serif; font-size:8pt }
  301. .msg { float:left }
  302. .msg { padding:0.25em 1ex 0.25em 1ex }
  303. .msg { margin-top:10px; margin-bottom:10px }
  304. .msg { font-weight:bold }
  305. .msg { max-width:60em; word-wrap: break-word; white-space: pre-wrap }
  306. .msgT { padding:0x; spacing:0x }
  307. .msgEvent { background-color:#fff8b4; color:#000000 }
  308. .msgControl { background-color:#bbbbbb; color:#000000 }
  309. .msgNote { background-color:#ddeeff; color:#000000 }
  310. .mrange { background-color:#dfddf3 }
  311. .mrange { border-bottom:1px solid #6F9DBE }
  312. .PathIndex { font-weight: bold; padding:0px 5px; margin-right:5px; }
  313. .PathIndex { -webkit-border-radius:8px }
  314. .PathIndex { border-radius:8px }
  315. .PathIndexEvent { background-color:#bfba87 }
  316. .PathIndexControl { background-color:#8c8c8c }
  317. .PathNav a { text-decoration:none; font-size: larger }
  318. .CodeInsertionHint { font-weight: bold; background-color: #10dd10 }
  319. .CodeRemovalHint { background-color:#de1010 }
  320. .CodeRemovalHint { border-bottom:1px solid #6F9DBE }
  321. .selected{ background-color:orange !important; }
  322. table.simpletable {
  323. padding: 5px;
  324. font-size:12pt;
  325. margin:20px;
  326. border-collapse: collapse; border-spacing: 0px;
  327. }
  328. td.rowname {
  329. text-align: right;
  330. vertical-align: top;
  331. font-weight: bold;
  332. color:#444444;
  333. padding-right:2ex;
  334. }
  335. /* Hidden text. */
  336. input.spoilerhider + label {
  337. cursor: pointer;
  338. text-decoration: underline;
  339. display: block;
  340. }
  341. input.spoilerhider {
  342. display: none;
  343. }
  344. input.spoilerhider ~ .spoiler {
  345. overflow: hidden;
  346. margin: 10px auto 0;
  347. height: 0;
  348. opacity: 0;
  349. }
  350. input.spoilerhider:checked + label + .spoiler{
  351. height: auto;
  352. opacity: 1;
  353. }
  354. </style>
  355. </head>
  356. <body>)<<<";
  357. // Generate header
  358. R.InsertTextBefore(StartLoc, os.str());
  359. // Generate footer
  360. R.InsertTextAfter(EndLoc, "</body></html>\n");
  361. }
  362. /// SyntaxHighlight - Relex the specified FileID and annotate the HTML with
  363. /// information about keywords, macro expansions etc. This uses the macro
  364. /// table state from the end of the file, so it won't be perfectly perfect,
  365. /// but it will be reasonably close.
  366. void html::SyntaxHighlight(Rewriter &R, FileID FID, const Preprocessor &PP) {
  367. RewriteBuffer &RB = R.getEditBuffer(FID);
  368. const SourceManager &SM = PP.getSourceManager();
  369. const llvm::MemoryBuffer *FromFile = SM.getBuffer(FID);
  370. Lexer L(FID, FromFile, SM, PP.getLangOpts());
  371. const char *BufferStart = L.getBuffer().data();
  372. // Inform the preprocessor that we want to retain comments as tokens, so we
  373. // can highlight them.
  374. L.SetCommentRetentionState(true);
  375. // Lex all the tokens in raw mode, to avoid entering #includes or expanding
  376. // macros.
  377. Token Tok;
  378. L.LexFromRawLexer(Tok);
  379. while (Tok.isNot(tok::eof)) {
  380. // Since we are lexing unexpanded tokens, all tokens are from the main
  381. // FileID.
  382. unsigned TokOffs = SM.getFileOffset(Tok.getLocation());
  383. unsigned TokLen = Tok.getLength();
  384. switch (Tok.getKind()) {
  385. default: break;
  386. case tok::identifier:
  387. llvm_unreachable("tok::identifier in raw lexing mode!");
  388. case tok::raw_identifier: {
  389. // Fill in Result.IdentifierInfo and update the token kind,
  390. // looking up the identifier in the identifier table.
  391. PP.LookUpIdentifierInfo(Tok);
  392. // If this is a pp-identifier, for a keyword, highlight it as such.
  393. if (Tok.isNot(tok::identifier))
  394. HighlightRange(RB, TokOffs, TokOffs+TokLen, BufferStart,
  395. "<span class='keyword'>", "</span>");
  396. break;
  397. }
  398. case tok::comment:
  399. HighlightRange(RB, TokOffs, TokOffs+TokLen, BufferStart,
  400. "<span class='comment'>", "</span>");
  401. break;
  402. case tok::utf8_string_literal:
  403. // Chop off the u part of u8 prefix
  404. ++TokOffs;
  405. --TokLen;
  406. // FALL THROUGH to chop the 8
  407. LLVM_FALLTHROUGH;
  408. case tok::wide_string_literal:
  409. case tok::utf16_string_literal:
  410. case tok::utf32_string_literal:
  411. // Chop off the L, u, U or 8 prefix
  412. ++TokOffs;
  413. --TokLen;
  414. LLVM_FALLTHROUGH;
  415. case tok::string_literal:
  416. // FIXME: Exclude the optional ud-suffix from the highlighted range.
  417. HighlightRange(RB, TokOffs, TokOffs+TokLen, BufferStart,
  418. "<span class='string_literal'>", "</span>");
  419. break;
  420. case tok::hash: {
  421. // If this is a preprocessor directive, all tokens to end of line are too.
  422. if (!Tok.isAtStartOfLine())
  423. break;
  424. // Eat all of the tokens until we get to the next one at the start of
  425. // line.
  426. unsigned TokEnd = TokOffs+TokLen;
  427. L.LexFromRawLexer(Tok);
  428. while (!Tok.isAtStartOfLine() && Tok.isNot(tok::eof)) {
  429. TokEnd = SM.getFileOffset(Tok.getLocation())+Tok.getLength();
  430. L.LexFromRawLexer(Tok);
  431. }
  432. // Find end of line. This is a hack.
  433. HighlightRange(RB, TokOffs, TokEnd, BufferStart,
  434. "<span class='directive'>", "</span>");
  435. // Don't skip the next token.
  436. continue;
  437. }
  438. }
  439. L.LexFromRawLexer(Tok);
  440. }
  441. }
  442. /// HighlightMacros - This uses the macro table state from the end of the
  443. /// file, to re-expand macros and insert (into the HTML) information about the
  444. /// macro expansions. This won't be perfectly perfect, but it will be
  445. /// reasonably close.
  446. void html::HighlightMacros(Rewriter &R, FileID FID, const Preprocessor& PP) {
  447. // Re-lex the raw token stream into a token buffer.
  448. const SourceManager &SM = PP.getSourceManager();
  449. std::vector<Token> TokenStream;
  450. const llvm::MemoryBuffer *FromFile = SM.getBuffer(FID);
  451. Lexer L(FID, FromFile, SM, PP.getLangOpts());
  452. // Lex all the tokens in raw mode, to avoid entering #includes or expanding
  453. // macros.
  454. while (1) {
  455. Token Tok;
  456. L.LexFromRawLexer(Tok);
  457. // If this is a # at the start of a line, discard it from the token stream.
  458. // We don't want the re-preprocess step to see #defines, #includes or other
  459. // preprocessor directives.
  460. if (Tok.is(tok::hash) && Tok.isAtStartOfLine())
  461. continue;
  462. // If this is a ## token, change its kind to unknown so that repreprocessing
  463. // it will not produce an error.
  464. if (Tok.is(tok::hashhash))
  465. Tok.setKind(tok::unknown);
  466. // If this raw token is an identifier, the raw lexer won't have looked up
  467. // the corresponding identifier info for it. Do this now so that it will be
  468. // macro expanded when we re-preprocess it.
  469. if (Tok.is(tok::raw_identifier))
  470. PP.LookUpIdentifierInfo(Tok);
  471. TokenStream.push_back(Tok);
  472. if (Tok.is(tok::eof)) break;
  473. }
  474. // Temporarily change the diagnostics object so that we ignore any generated
  475. // diagnostics from this pass.
  476. DiagnosticsEngine TmpDiags(PP.getDiagnostics().getDiagnosticIDs(),
  477. &PP.getDiagnostics().getDiagnosticOptions(),
  478. new IgnoringDiagConsumer);
  479. // FIXME: This is a huge hack; we reuse the input preprocessor because we want
  480. // its state, but we aren't actually changing it (we hope). This should really
  481. // construct a copy of the preprocessor.
  482. Preprocessor &TmpPP = const_cast<Preprocessor&>(PP);
  483. DiagnosticsEngine *OldDiags = &TmpPP.getDiagnostics();
  484. TmpPP.setDiagnostics(TmpDiags);
  485. // Inform the preprocessor that we don't want comments.
  486. TmpPP.SetCommentRetentionState(false, false);
  487. // We don't want pragmas either. Although we filtered out #pragma, removing
  488. // _Pragma and __pragma is much harder.
  489. bool PragmasPreviouslyEnabled = TmpPP.getPragmasEnabled();
  490. TmpPP.setPragmasEnabled(false);
  491. // Enter the tokens we just lexed. This will cause them to be macro expanded
  492. // but won't enter sub-files (because we removed #'s).
  493. TmpPP.EnterTokenStream(TokenStream, false, /*IsReinject=*/false);
  494. TokenConcatenation ConcatInfo(TmpPP);
  495. // Lex all the tokens.
  496. Token Tok;
  497. TmpPP.Lex(Tok);
  498. while (Tok.isNot(tok::eof)) {
  499. // Ignore non-macro tokens.
  500. if (!Tok.getLocation().isMacroID()) {
  501. TmpPP.Lex(Tok);
  502. continue;
  503. }
  504. // Okay, we have the first token of a macro expansion: highlight the
  505. // expansion by inserting a start tag before the macro expansion and
  506. // end tag after it.
  507. CharSourceRange LLoc = SM.getExpansionRange(Tok.getLocation());
  508. // Ignore tokens whose instantiation location was not the main file.
  509. if (SM.getFileID(LLoc.getBegin()) != FID) {
  510. TmpPP.Lex(Tok);
  511. continue;
  512. }
  513. assert(SM.getFileID(LLoc.getEnd()) == FID &&
  514. "Start and end of expansion must be in the same ultimate file!");
  515. std::string Expansion = EscapeText(TmpPP.getSpelling(Tok));
  516. unsigned LineLen = Expansion.size();
  517. Token PrevPrevTok;
  518. Token PrevTok = Tok;
  519. // Okay, eat this token, getting the next one.
  520. TmpPP.Lex(Tok);
  521. // Skip all the rest of the tokens that are part of this macro
  522. // instantiation. It would be really nice to pop up a window with all the
  523. // spelling of the tokens or something.
  524. while (!Tok.is(tok::eof) &&
  525. SM.getExpansionLoc(Tok.getLocation()) == LLoc.getBegin()) {
  526. // Insert a newline if the macro expansion is getting large.
  527. if (LineLen > 60) {
  528. Expansion += "<br>";
  529. LineLen = 0;
  530. }
  531. LineLen -= Expansion.size();
  532. // If the tokens were already space separated, or if they must be to avoid
  533. // them being implicitly pasted, add a space between them.
  534. if (Tok.hasLeadingSpace() ||
  535. ConcatInfo.AvoidConcat(PrevPrevTok, PrevTok, Tok))
  536. Expansion += ' ';
  537. // Escape any special characters in the token text.
  538. Expansion += EscapeText(TmpPP.getSpelling(Tok));
  539. LineLen += Expansion.size();
  540. PrevPrevTok = PrevTok;
  541. PrevTok = Tok;
  542. TmpPP.Lex(Tok);
  543. }
  544. // Insert the expansion as the end tag, so that multi-line macros all get
  545. // highlighted.
  546. Expansion = "<span class='expansion'>" + Expansion + "</span></span>";
  547. HighlightRange(R, LLoc.getBegin(), LLoc.getEnd(), "<span class='macro'>",
  548. Expansion.c_str(), LLoc.isTokenRange());
  549. }
  550. // Restore the preprocessor's old state.
  551. TmpPP.setDiagnostics(*OldDiags);
  552. TmpPP.setPragmasEnabled(PragmasPreviouslyEnabled);
  553. }