1 // <details> html tag 2 // FIXME: KEY_VALUE like params 3 module arsd.docgen.comment; 4 5 import adrdox.main; 6 import arsd.dom; 7 8 import dparse.ast; 9 import dparse.lexer; 10 11 import std.string; 12 import std.algorithm; 13 14 import std.conv; 15 16 const(char)[] htmlEncode(const(char)[] s) { 17 return s. 18 replace("&", "&"). 19 replace("<", "<"). 20 replace(">", ">"); 21 } 22 23 24 static struct MyOutputRange { 25 this(string* output) { 26 this.output = output; 27 } 28 29 string* output; 30 void put(T...)(T s) { 31 foreach(i; s) 32 putTag(i.htmlEncode); 33 } 34 35 void putTag(in char[] s) { 36 if(s.length == 0) 37 return; 38 //foreach(ch; s) { 39 //assert(s.length); 40 //assert(s.indexOf("</body>") == -1); 41 //} 42 (*output) ~= s; 43 } 44 } 45 46 enum TexMathOpt { 47 LaTeX, 48 KaTeX, 49 } 50 51 TexMathOpt parseTexMathOpt(string str) { 52 switch (str) with(TexMathOpt) { 53 case "latex": return LaTeX; 54 case "katex": return KaTeX; 55 default: throw new Exception("Unsupported 'tex-math' option"); 56 } 57 } 58 59 /* 60 Params: 61 62 The line, excluding whitespace, must start with 63 identifier = to start a new thing. If a new thing starts, 64 it closes the old. 65 66 The description may be formatted however. Whitespace is stripped. 67 */ 68 69 string getIdent(T)(T t) { 70 if(t is null) 71 return null; 72 if(t.identifier == tok!"") 73 return null; 74 return t.identifier.text; 75 } 76 77 bool hasParam(T)(const T dec, string name, Decl declObject, bool descend = true) { 78 if(dec is null) 79 return false; 80 81 static if(__traits(compiles, dec.parameters)) { 82 if(dec.parameters && dec.parameters.parameters) 83 foreach(parameter; dec.parameters.parameters) 84 if(parameter.name.text == name) 85 return true; 86 } 87 static if(__traits(compiles, dec.templateParameters)) { 88 if(dec.templateParameters && dec.templateParameters.templateParameterList) 89 foreach(parameter; dec.templateParameters.templateParameterList.items) { 90 if(getIdent(parameter.templateTypeParameter) == name) 91 return true; 92 if(getIdent(parameter.templateValueParameter) == name) 93 return true; 94 if(getIdent(parameter.templateAliasParameter) == name) 95 return true; 96 if(getIdent(parameter.templateTupleParameter) == name) 97 return true; 98 if(parameter.templateThisParameter && getIdent(parameter.templateThisParameter.templateTypeParameter) == name) 99 return true; 100 } 101 } 102 103 if(!descend) 104 return false; 105 106 // need to check parent template parameters in case they are 107 // referenced in a child 108 if(declObject.parent) { 109 bool h; 110 if(auto pdec = cast(TemplateDecl) declObject.parent) 111 h = hasParam(pdec.astNode, name, pdec, false); 112 else 113 {} 114 if(h) 115 return true; 116 } 117 118 // and eponymous ones 119 static if(is(T == TemplateDeclaration)) { 120 auto decl = cast(TemplateDecl) declObject; 121 if(decl) { 122 auto e = decl.eponymousMember(); 123 if(e) { 124 if(auto a = cast(ConstructorDecl) e) 125 return hasParam(a.astNode, name, a, false); 126 if(auto a = cast(FunctionDecl) e) 127 return hasParam(a.astNode, name, a, false); 128 // FIXME: add more 129 } 130 } 131 } 132 133 return false; 134 } 135 136 struct LinkReferenceInfo { 137 enum Type { none, text, link, image } 138 Type type; 139 bool isFootnote; 140 string text; // or alt 141 string link; // or src 142 143 string toHtml(string fun, string rt) { 144 if(rt is null) 145 rt = fun; 146 if(rt is fun && text !is null && type != Type.text) 147 rt = text; 148 149 string display = isFootnote ? ("[" ~ rt ~ "]") : rt; 150 151 Element element; 152 final switch(type) { 153 case Type.none: 154 return null; 155 case Type.text: 156 case Type.link: 157 element = isFootnote ? Element.make("sup", "", "footnote-ref") : Element.make("span"); 158 if(type == Type.text) { 159 element.addChild("abbr", display).setAttribute("title", text); 160 } else { 161 element.addChild("a", display, link); 162 } 163 break; 164 case Type.image: 165 element = Element.make("img", link, text); 166 break; 167 } 168 169 return element.toString(); 170 } 171 } 172 173 __gshared string[string] globalLinkReferences; 174 175 void loadGlobalLinkReferences(string text) { 176 foreach(line; text.splitLines) { 177 line = line.strip; 178 if(line.length == 0) 179 continue; 180 auto idx = line.indexOf("="); 181 if(idx == -1) 182 continue; 183 auto name = line[0 .. idx].strip; 184 auto value = line[idx + 1 .. $].strip; 185 if(name.length == 0 || value.length == 0) 186 continue; 187 globalLinkReferences[name] = value; 188 } 189 } 190 191 Element getSymbolGroupReference(string name, Decl decl, string rt) { 192 auto pm = decl.isModule() ? decl : decl.parentModule; 193 if(pm is null) 194 return null; 195 if(name in pm.parsedDocComment.symbolGroups) { 196 return Element.make("a", rt.length ? rt : name, pm.fullyQualifiedName ~ ".html#group-" ~ name); 197 } 198 199 return null; 200 } 201 202 LinkReferenceInfo getLinkReference(string name, Decl decl) { 203 bool numeric = (name.all!((ch) => ch >= '0' && ch <= '9')); 204 205 bool onGlobal = false; 206 auto refs = decl.parsedDocComment.linkReferences; 207 208 try_again: 209 210 if(name in refs) { 211 auto txt = refs[name]; 212 213 assert(txt.length); 214 215 LinkReferenceInfo lri; 216 217 import std.uri; 218 auto l = uriLength(txt); 219 if(l != -1) { 220 lri.link = txt; 221 lri.type = LinkReferenceInfo.Type.link; 222 } else if(txt[0] == '$') { 223 // should be an image or plain text 224 if(txt.length > 5 && txt[0 .. 6] == "$(IMG " && txt[$-1] == ')') { 225 txt = txt[6 .. $-1]; 226 auto idx = txt.indexOf(","); 227 if(idx == -1) { 228 lri.link = txt.strip; 229 } else { 230 lri.text = txt[idx + 1 .. $].strip; 231 lri.link = txt[0 .. idx].strip; 232 } 233 lri.type = LinkReferenceInfo.Type.image; 234 } else goto plain_text; 235 } else if(txt[0] == '[' && txt[$-1] == ']') { 236 txt = txt[1 .. $-1]; 237 auto idx = txt.indexOf("|"); 238 if(idx == -1) { 239 lri.link = txt; 240 } else { 241 lri.link = txt[0 .. idx].strip; 242 lri.text = txt[idx + 1 .. $].strip; 243 } 244 245 lri.link = getReferenceLink(lri.link, decl).href; 246 247 lri.type = LinkReferenceInfo.Type.link; 248 } else { 249 plain_text: 250 lri.type = LinkReferenceInfo.Type.text; 251 lri.link = null; 252 lri.text = txt; 253 } 254 255 lri.isFootnote = numeric; 256 return lri; 257 } else if(!onGlobal && !numeric) { 258 if(decl.parent !is null) 259 decl = decl.parent; 260 else { 261 onGlobal = true; 262 refs = globalLinkReferences; 263 // decl = null; // so I kinda want it to be null, but that breaks like everything so I can't. 264 } 265 goto try_again; 266 } 267 268 return LinkReferenceInfo.init; 269 } 270 271 struct DocComment { 272 string ddocSummary; 273 string synopsis; 274 275 string details; 276 277 string[] params; // stored as ident=txt. You can split on first index of =. 278 string returns; 279 string examples; 280 string diagnostics; 281 string throws; 282 string bugs; 283 string see_alsos; 284 285 string[string] symbolGroups; // stored as "name" : "raw text" 286 string[] symbolGroupsOrder; 287 string group; // the group this belongs to, if any 288 289 string[string] otherSections; 290 291 string[string] linkReferences; 292 293 string[string] userDefinedMacros; 294 295 Decl decl; 296 297 bool synopsisEndedOnDoubleBlank; 298 299 void writeSynopsis(MyOutputRange output) { 300 output.putTag("<div class=\"documentation-comment synopsis\">"); 301 output.putTag(formatDocumentationComment(synopsis, decl)); 302 if(details !is synopsis && details.strip.length && details.strip != "<div></div>") 303 output.putTag(` <a id="more-link" href="#details">More...</a>`); 304 output.putTag("</div>"); 305 } 306 307 void writeDetails(T = FunctionDeclaration)(MyOutputRange output) { 308 writeDetails!T(output, cast(T) null, null); 309 } 310 311 void writeDetails(T = FunctionDeclaration)(MyOutputRange output, const T functionDec = null, Decl.ProcessedUnittest[] utInfo = null) { 312 auto f = new MyFormatter!(typeof(output))(output, decl); 313 314 string[] params = this.params; 315 316 317 static if(is(T == FunctionDeclaration) || is(T == Constructor)) { 318 if(functionDec !is null) { 319 auto dec = functionDec; 320 if(dec.templateParameters && dec.templateParameters.templateParameterList) 321 foreach(parameter; dec.templateParameters.templateParameterList.items) { 322 if(auto name = getIdent(parameter.templateTypeParameter)) 323 if(name.length && parameter.comment.length) params ~= name ~ "=" ~ preprocessComment(parameter.comment, decl); 324 if(auto name = getIdent(parameter.templateValueParameter)) 325 if(name.length && parameter.comment.length) params ~= name ~ "=" ~ preprocessComment(parameter.comment, decl); 326 if(auto name = getIdent(parameter.templateAliasParameter)) 327 if(name.length && parameter.comment.length) params ~= name ~ "=" ~ preprocessComment(parameter.comment, decl); 328 if(auto name = getIdent(parameter.templateTupleParameter)) 329 if(name.length && parameter.comment.length) params ~= name ~ "=" ~ preprocessComment(parameter.comment, decl); 330 if(parameter.templateThisParameter) 331 if(auto name = getIdent(parameter.templateThisParameter.templateTypeParameter)) 332 if(name.length && parameter.comment.length) params ~= name ~ "=" ~ preprocessComment(parameter.comment, decl); 333 } 334 foreach(p; functionDec.parameters.parameters) { 335 auto name = p.name.text; 336 if(name.length && p.comment.length) 337 params ~= name ~ "=" ~ preprocessComment(p.comment, decl); 338 } 339 } 340 } 341 342 if(params.length) { 343 int count = 0; 344 foreach(param; params) { 345 auto split = param.indexOf("="); 346 auto paramName = param[0 .. split]; 347 348 if(!hasParam(functionDec, paramName, decl)) { 349 //import std.stdio; writeln("no param " ~ paramName, " ", functionDec); 350 continue; 351 } 352 count++; 353 } 354 355 if(count) { 356 output.putTag("<h2 id=\"parameters\">Parameters</h2>"); 357 output.putTag("<dl class=\"parameter-descriptions\">"); 358 foreach(param; params) { 359 auto split = param.indexOf("="); 360 auto paramName = param[0 .. split]; 361 362 if(!hasParam(functionDec, paramName, decl)) 363 continue; 364 365 output.putTag("<dt id=\"param-"~param[0 .. split]~"\">"); 366 output.putTag("<a href=\"#param-"~param[0 .. split]~"\" class=\"parameter-name\" data-ident=\""); 367 output.put(param[0 .. split]); 368 output.putTag("\">"); 369 output.put(param[0 .. split]); 370 output.putTag("</a>"); 371 372 static if(is(T == FunctionDeclaration) || is(T == Constructor)) 373 if(functionDec !is null) { 374 const(Parameter)* paramAst; 375 foreach(ref p; functionDec.parameters.parameters) { 376 if(p.name.type != tok!"") 377 if(p.name.text == param[0 .. split]) { 378 paramAst = &p; 379 break; 380 } 381 } 382 383 if(paramAst) { 384 output.putTag(" <span class=\"parameter-type\">"); 385 f.format(paramAst.type); 386 output.putTag("</span>"); 387 } 388 389 390 if(paramAst && paramAst.atAttributes.length) { 391 output.putTag("<div class=\"parameter-attributes\">"); 392 output.put("Attributes:"); 393 foreach (attribute; paramAst.atAttributes) { 394 output.putTag("<div class=\"parameter-attribute\">"); 395 f.format(attribute); 396 output.putTag("</div>"); 397 } 398 399 output.putTag("</div>"); 400 } 401 402 403 } 404 405 output.putTag("</dt>"); 406 output.putTag("<dd>"); 407 408 output.putTag("<div class=\"documentation-comment\">"); 409 output.putTag(formatDocumentationComment(param[split + 1 .. $], decl)); 410 output.putTag("</div></dd>"); 411 } 412 output.putTag("</dl>"); 413 } 414 } 415 416 if(returns !is null) { 417 output.putTag("<h2 id=\"returns\">Return Value</h2>"); 418 output.putTag("<div>"); 419 static if(is(T == FunctionDeclaration)) 420 if(functionDec !is null) { 421 output.putTag("<div class=\"return-type-holder\">"); 422 output.putTag("Type: "); 423 output.putTag("<span class=\"return-type\">"); 424 if(functionDec.hasAuto && functionDec.hasRef) 425 output.putTag(`<a class="lang-feature" href="http://dpldocs.info/auto-ref-function-return-prototype">auto ref</a> `); 426 else { 427 if (functionDec.hasAuto) 428 output.putTag(`<a class="lang-feature" href="http://dpldocs.info/auto-function-return-prototype">auto</a> `); 429 if (functionDec.hasRef) 430 output.putTag(`<a class="lang-feature" href="http://dpldocs.info/ref-function-return-prototype">ref</a> `); 431 } 432 433 if (functionDec.returnType !is null) 434 f.format(functionDec.returnType); 435 output.putTag("</span>"); 436 output.putTag("</div>"); 437 } 438 output.putTag("<div class=\"documentation-comment returns-description\">"); 439 output.putTag(formatDocumentationComment(returns, decl)); 440 output.putTag("</div>"); 441 output.putTag("</div>"); 442 } 443 444 if(details.strip.length && details.strip != "<div></div>") { 445 output.putTag("<h2 id=\"details\">Detailed Description</h2>"); 446 output.putTag("<div class=\"documentation-comment detailed-description\">"); 447 output.putTag(formatDocumentationComment(details, decl)); 448 output.putTag("</div>"); 449 } 450 451 if(throws.strip.length) { 452 output.putTag("<h2 id=\"throws\">Throws</h2>"); 453 output.putTag("<div class=\"documentation-comment throws-description\">"); 454 output.putTag(formatDocumentationComment(throws, decl)); 455 output.putTag("</div>"); 456 } 457 458 459 if(diagnostics.strip.length) { 460 output.putTag("<h2 id=\"diagnostics\">Common Problems</h2>"); 461 output.putTag("<div class=\"documentation-comment diagnostics\">"); 462 output.putTag(formatDocumentationComment(diagnostics, decl)); 463 output.putTag("</div>"); 464 } 465 466 if(bugs.strip.length) { 467 output.putTag("<h2 id=\"bugs\">Bugs</h2>"); 468 output.putTag("<div class=\"documentation-comment bugs-description\">"); 469 output.putTag(formatDocumentationComment(bugs, decl)); 470 output.putTag("</div>"); 471 } 472 473 bool hasUt; 474 foreach(ut; utInfo) if(ut.embedded == false){hasUt = true; break;} 475 476 if(examples.length || hasUt) { 477 output.putTag("<h2 id=\"examples\"><a href=\"#examples\" class=\"header-anchor\">Examples</a></h2>"); 478 output.putTag("<div class=\"documentation-comment\">"); 479 output.putTag(formatDocumentationComment(examples, decl)); 480 output.putTag("</div>"); 481 482 foreach(example; utInfo) { 483 if(example.embedded) continue; 484 output.putTag(formatUnittestDocTuple(example, decl).toString()); 485 } 486 } 487 488 if(group) 489 see_alsos ~= "\n\n[" ~ group ~ "]"; 490 491 if(see_alsos.length) { 492 output.putTag("<h2 id=\"see-also\">See Also</h2>"); 493 output.putTag("<div class=\"documentation-comment see-also-section\">"); 494 output.putTag(formatDocumentationComment(see_alsos, decl)); 495 output.putTag("</div>"); 496 } 497 498 if(otherSections.keys.length) { 499 output.putTag("<h2 id=\"meta\">Meta</h2>"); 500 } 501 502 foreach(section, content; otherSections) { 503 output.putTag("<div class=\"documentation-comment " ~ section ~ "-section other-section\">"); 504 output.putTag("<h3>"); 505 output.put(section.capitalize); 506 output.putTag("</h3>"); 507 output.putTag(formatDocumentationComment(content, decl)); 508 output.putTag("</div>"); 509 } 510 } 511 } 512 513 Element formatUnittestDocTuple(Decl.ProcessedUnittest example, Decl decl) { 514 auto holder = Element.make("div").addClass("unittest-example-holder"); 515 516 holder.addChild(formatDocumentationComment2(preprocessComment(example.comment, decl), decl).addClass("documentation-comment")); 517 auto pre = holder.addChild("pre").addClass("d_code highlighted"); 518 519 // trim off leading/trailing newlines since they just clutter output 520 auto codeToWrite = example.code; 521 while(codeToWrite.length && (codeToWrite[0] == '\n' || codeToWrite[0] == '\r')) 522 codeToWrite = codeToWrite[1 .. $]; 523 while(codeToWrite.length && (codeToWrite[$-1] == '\n' || codeToWrite[$-1] == '\r')) 524 codeToWrite = codeToWrite[0 .. $ - 1]; 525 526 pre.innerHTML = highlight(outdent(codeToWrite)); 527 528 return holder; 529 } 530 531 string preprocessComment(string comment, Decl decl) { 532 if(comment.length < 3) 533 return comment; 534 535 comment = comment.replace("\r\n", "\n"); 536 537 comment = comment[1 .. $]; // trim off the / 538 539 auto commentType = comment[0]; 540 541 while(comment.length && comment[0] == commentType) 542 comment = comment[1 .. $]; // trim off other opening spam 543 544 string closingSpam; 545 if(commentType == '*' || commentType == '+') { 546 comment = comment[0 .. $-1]; // trim off the closing / 547 bool closingSpamFound; 548 auto tidx = 0; 549 while(comment.length && comment[$-1 - tidx] == commentType) { 550 //comment = comment[0 .. $-1]; // trim off other closing spam 551 tidx++; 552 closingSpamFound = true; 553 } 554 555 if(closingSpamFound) { 556 // if there was closing spam we also want to count the spaces before it 557 // to trim off other line spam. The goal here is to clean up 558 /** 559 * Resolve host name. 560 * Returns: false if unable to resolve. 561 */ 562 // into just "Resolve host name.\n Returns: false if unable to resolve." 563 // give or take some irrelevant whitespace. 564 565 566 // new algorithm in response to github issue # 36 567 568 auto li = lastIndexOf(comment, "\n"); 569 if(li != -1) { 570 // the closing line must be only whitespace and the marker to consider this 571 size_t spot = size_t.max; 572 foreach(idx, ch; comment[li + 1 .. $]) { 573 if(!(ch == ' ' || ch == '\t' || ch == commentType)) { 574 spot = size_t.max; 575 // "foo +/" on the end 576 // need to cut off the + from the end 577 // since the final / was already cut 578 comment = comment[0 .. $-1]; 579 break; 580 } else { 581 if(spot == size_t.max && ch == commentType) 582 spot = idx + 1; 583 } 584 } 585 586 if(spot != size_t.max) { 587 closingSpam = comment[li + 1 .. li + 1 + spot]; 588 comment = comment[0 .. li]; 589 } 590 } else { 591 // single liner, strip off the closing spam 592 // need to cut off the + from the end 593 // since the final / was already cut 594 comment = comment[0 .. $-1]; 595 596 } 597 598 // old algorithm 599 /+ 600 while(comment.length && (comment[$-1] == '\t' || comment[$-1] == ' ')) { 601 closingSpam = comment[$-1] ~ closingSpam; 602 comment = comment[0 .. $-1]; // trim off other closing spam 603 } 604 +/ 605 606 if(closingSpam.length == 0) 607 closingSpam = " "; // some things use the " *" leader, but still end with "*/" on a line of its own 608 } 609 } 610 611 612 string poop; 613 if(commentType == '/') 614 poop = "///"; 615 else 616 poop = closingSpam;//closingSpam ~ commentType ~ " "; 617 618 string newComment; 619 foreach(line; comment.splitter("\n")) { 620 // check for " * some text" 621 if(line.length >= poop.length && line.startsWith(poop)) { 622 if(line.length > poop.length && line[poop.length] == ' ') 623 newComment ~= line[poop.length + 1 .. $]; 624 else if(line.length > poop.length && line[poop.length] == '/' && poop != "/") 625 newComment ~= line; 626 else 627 newComment ~= line[poop.length .. $]; 628 } 629 // check for an empty line with just " *" 630 else if(line.length == poop.length-1 && line[0..poop.length-1] == poop[0..$-1]) 631 {} // this space is intentionally left blank; it is an empty line 632 else if(line.length > 1 && commentType != '/' && line[0] == commentType && line[1] == ' ') 633 newComment ~= line[1 .. $]; // cut off stupid leading * with no space before too 634 else 635 newComment ~= line; 636 newComment ~= "\n"; 637 } 638 639 comment = newComment; 640 return specialPreprocess(comment, decl); 641 } 642 643 DocComment parseDocumentationComment(string comment, Decl decl) { 644 DocComment c; 645 646 if(generatingSource) { 647 if(decl.lineNumber) 648 c.otherSections["source"] ~= "$(LINK2 source/"~decl.parentModule.name~".d.html#L"~to!string(decl.lineNumber)~", See Implementation)$(BR)"; 649 else if(!decl.fakeDecl) 650 c.otherSections["source"] ~= "$(LINK2 source/"~decl.parentModule.name~".d.html, See Source File)$(BR)"; 651 } 652 // FIXME: add links to ddoc and ddox iff std.* or core.* 653 654 c.decl = decl; 655 656 comment = preprocessComment(comment, decl); 657 658 void parseSections(string comment) { 659 string remaining; 660 string section; 661 bool inSynopsis = true; 662 bool justSawBlank = false; 663 bool inCode = false; 664 string inCodeStyle; 665 bool hasAnySynopsis = false; 666 bool inDdocSummary = true; 667 bool synopsisEndedOnDoubleBlank = false; 668 669 string currentMacroName; 670 671 // for symbol_groups 672 string* currentValue; 673 674 bool maybeGroupLine = true; 675 676 677 auto lastLineHelper = comment.strip; 678 auto lastLine = lastLineHelper.lastIndexOf("\n"); 679 if(lastLine != -1) { 680 auto lastLineText = lastLineHelper[lastLine .. $].strip; 681 if(lastLineText.startsWith("Group: ") || lastLineText.startsWith("group: ")) { 682 c.group = lastLineText["Group: ".length .. $]; 683 maybeGroupLine = false; 684 685 comment = lastLineHelper[0 .. lastLine].strip; 686 } 687 } 688 689 foreach(line; comment.splitter("\n")) { 690 691 if(maybeGroupLine) { 692 maybeGroupLine = false; 693 if(line.strip.startsWith("Group: ") || line.strip.startsWith("group: ")) { 694 c.group = line.strip["Group: ".length .. $]; // cut off closing paren 695 continue; 696 } 697 } 698 699 auto maybe = line.strip.toLower; 700 701 if(maybe.startsWith("---") || maybe.startsWith("```")) { 702 justSawBlank = false; 703 704 if(inCode && inCodeStyle == maybe[0 .. 3]) { 705 inCode = false; 706 inCodeStyle = null; 707 } else if(inCode) 708 goto ss; // just still inside code section 709 else { 710 inCodeStyle = maybe[0 .. 3]; 711 inDdocSummary = false; 712 inCode = !inCode; 713 } 714 } 715 if(inCode){ 716 justSawBlank = false; 717 goto ss; // sections never change while in a code example 718 } 719 720 if(inSynopsis && hasAnySynopsis && maybe.length == 0) { 721 // any empty line ends the ddoc summary 722 inDdocSummary = false; 723 // two blank lines in a row ends the synopsis 724 if(justSawBlank) { 725 inSynopsis = false; 726 // synopsis can also end on the presence of another 727 // section, so this is tracked to see how intentional 728 // the break looked (Phobos docs aren't written with 729 // a double-break in mind) 730 synopsisEndedOnDoubleBlank = true; 731 } 732 justSawBlank = true; 733 } else { 734 justSawBlank = false; 735 } 736 737 if(maybe.startsWith("params:")) { 738 section = "params"; 739 inSynopsis = false; 740 } else if(maybe.startsWith("parameters:")) { 741 section = "params"; 742 inSynopsis = false; 743 } else if(maybe.startsWith("returns:")) { 744 section = "returns"; 745 line = line[line.indexOf(":")+1 .. $]; 746 inSynopsis = false; 747 } else if(maybe.startsWith("throws:")) { 748 section = "throws"; 749 inSynopsis = false; 750 line = line[line.indexOf(":")+1 .. $]; 751 } else if(maybe.startsWith("author:")) { 752 section = "authors"; 753 line = line[line.indexOf(":")+1 .. $]; 754 inSynopsis = false; 755 } else if(maybe.startsWith("details:")) { 756 section = "details"; 757 line = line[line.indexOf(":")+1 .. $]; 758 inSynopsis = false; 759 } else if(maybe.startsWith("authors:")) { 760 section = "authors"; 761 line = line[line.indexOf(":")+1 .. $]; 762 inSynopsis = false; 763 } else if(maybe.startsWith("source:")) { 764 section = "source"; 765 line = line[line.indexOf(":")+1 .. $]; 766 inSynopsis = false; 767 } else if(maybe.startsWith("history:")) { 768 section = "history"; 769 line = line[line.indexOf(":")+1 .. $]; 770 inSynopsis = false; 771 } else if(maybe.startsWith("credits:")) { 772 section = "credits"; 773 line = line[line.indexOf(":")+1 .. $]; 774 inSynopsis = false; 775 } else if(maybe.startsWith("version:")) { 776 section = "version"; 777 line = line[line.indexOf(":")+1 .. $]; 778 inSynopsis = false; 779 } else if(maybe.startsWith("since:")) { 780 section = "since"; 781 line = line[line.indexOf(":")+1 .. $]; 782 inSynopsis = false; 783 } else if(maybe.startsWith("license:")) { 784 section = "license"; 785 line = line[line.indexOf(":")+1 .. $]; 786 inSynopsis = false; 787 } else if(maybe.startsWith("copyright:")) { 788 section = "copyright"; 789 line = line[line.indexOf(":")+1 .. $]; 790 inSynopsis = false; 791 } else if(maybe.startsWith("see_also:")) { 792 section = "see_also"; 793 inSynopsis = false; 794 line = line[line.indexOf(":")+1 .. $]; 795 } else if(maybe.startsWith("diagnostics:")) { 796 inSynopsis = false; 797 section = "diagnostics"; 798 line = line[line.indexOf(":")+1 .. $]; 799 } else if(maybe.startsWith("examples:")) { 800 inSynopsis = false; 801 section = "examples"; 802 line = line[line.indexOf(":")+1 .. $]; 803 } else if(maybe.startsWith("example:")) { 804 inSynopsis = false; 805 line = line[line.indexOf(":")+1 .. $]; 806 section = "examples"; // Phobos uses example, the standard is examples. 807 } else if(maybe.startsWith("standards:")) { 808 line = line[line.indexOf(":")+1 .. $]; 809 inSynopsis = false; 810 section = "standards"; 811 } else if(maybe.startsWith("deprecated:")) { 812 inSynopsis = false; 813 section = "deprecated"; 814 } else if(maybe.startsWith("date:")) { 815 inSynopsis = false; 816 section = "date"; 817 } else if(maybe.startsWith("bugs:")) { 818 line = line[line.indexOf(":")+1 .. $]; 819 inSynopsis = false; 820 section = "bugs"; 821 } else if(maybe.startsWith("macros:")) { 822 inSynopsis = false; 823 section = "macros"; 824 currentMacroName = null; 825 } else if(maybe.startsWith("symbol_groups:")) { 826 section = "symbol_groups"; 827 line = line[line.indexOf(":")+1 .. $]; 828 inSynopsis = false; 829 } else if(maybe.startsWith("link_references:")) { 830 inSynopsis = false; 831 section = "link_references"; 832 line = line[line.indexOf(":")+1 .. $].strip; 833 } else if(maybe.isDdocSection()) { 834 inSynopsis = false; 835 836 auto idx2 = line.indexOf(":"); 837 auto name = line[0 .. idx2].replace("_", " "); 838 line = "$(H3 "~name~")\n\n" ~ line[idx2+1 .. $]; 839 } else { 840 // no change to section 841 } 842 843 if(inSynopsis == false) 844 inDdocSummary = false; 845 846 847 ss: switch(section) { 848 case "symbol_groups": 849 // basically a copy/paste of Params but it is coincidental 850 // they might not always share syntax. 851 bool lookingForIdent = true; 852 bool inIdent; 853 bool skippingSpace; 854 size_t space_at; 855 856 auto lol = line.strip; 857 foreach(idx, ch; lol) { 858 import std.uni, std.ascii : isAlphaNum; 859 if(lookingForIdent && !inIdent) { 860 if(!isAlpha(ch) && ch != '_') 861 continue; 862 inIdent = true; 863 lookingForIdent = false; 864 } 865 866 if(inIdent) { 867 if(ch == '_' || isAlphaNum(ch) || isAlpha(ch)) 868 continue; 869 else { 870 skippingSpace = true; 871 inIdent = false; 872 space_at = idx; 873 } 874 } 875 if(skippingSpace) { 876 if(!isWhite(ch)) { 877 if(ch == '=') { 878 // we finally hit a thingy 879 auto currentName = lol[0 .. space_at]; 880 881 c.symbolGroups[currentName] = lol[idx + 1 .. $] ~ "\n"; 882 c.symbolGroupsOrder ~= currentName; 883 currentValue = currentName in c.symbolGroups; 884 break ss; 885 } else 886 // we are expecting whitespace or = and hit 887 // neither.. this can't be ident = desc! 888 break; 889 } 890 } 891 } 892 893 if(currentValue !is null) { 894 *currentValue ~= line ~ "\n"; 895 } 896 break; 897 case "params": 898 bool lookingForIdent = true; 899 bool inIdent; 900 bool skippingSpace; 901 size_t space_at; 902 903 auto lol = line.strip; 904 foreach(idx, ch; lol) { 905 import std.uni, std.ascii : isAlphaNum; 906 if(lookingForIdent && !inIdent) { 907 if(!isAlpha(ch) && ch != '_') 908 continue; 909 inIdent = true; 910 lookingForIdent = false; 911 } 912 913 if(inIdent) { 914 if(ch == '_' || isAlphaNum(ch) || isAlpha(ch)) 915 continue; 916 else { 917 skippingSpace = true; 918 inIdent = false; 919 space_at = idx; 920 } 921 } 922 if(skippingSpace) { 923 if(!isWhite(ch)) { 924 if(ch == '=') { 925 // we finally hit a thingy 926 c.params ~= lol[0 .. space_at] ~ "=" ~ lol[idx + 1 .. $] ~ "\n"; 927 break ss; 928 } else 929 // we are expecting whitespace or = and hit 930 // neither.. this can't be ident = desc! 931 break; 932 } 933 } 934 } 935 936 if(c.params.length) 937 c.params[$-1] ~= line ~ "\n"; 938 break; 939 case "see_also": 940 c.see_alsos ~= line ~ "\n"; 941 break; 942 case "macros": 943 // ignoring for now 944 945 bool lookingForIdent = true; 946 bool inIdent; 947 bool skippingSpace; 948 size_t space_at; 949 950 auto lol = line.strip; 951 foreach(idx, ch; lol) { 952 import std.uni, std.ascii : isAlphaNum; 953 if(lookingForIdent && !inIdent) { 954 if(!isAlpha(ch) && ch != '_') 955 continue; 956 inIdent = true; 957 lookingForIdent = false; 958 } 959 960 if(inIdent) { 961 if(ch == '_' || isAlphaNum(ch) || isAlpha(ch)) 962 continue; 963 else { 964 skippingSpace = true; 965 inIdent = false; 966 space_at = idx; 967 } 968 } 969 if(skippingSpace) { 970 if(!isWhite(ch)) { 971 if(ch == '=') { 972 // we finally hit a thingy 973 currentMacroName = lol[0 .. space_at].strip; 974 auto currentMacroBody = lol[idx + 1 .. $] ~ "\n"; 975 976 c.userDefinedMacros[currentMacroName] = currentMacroBody; 977 break ss; 978 } else 979 // we are expecting whitespace or = and hit 980 // neither.. this can't be ident = desc! 981 break; 982 } 983 } else { 984 if(currentMacroName.length) 985 c.userDefinedMacros[currentMacroName] ~= lol; 986 } 987 } 988 break; 989 case "link_references": 990 auto eql = line.indexOf("="); 991 if(eql != -1) { 992 string name = line[0 .. eql].strip; 993 string value = line[eql + 1 .. $].strip; 994 c.linkReferences[name] = value; 995 } else if(line.strip.length) { 996 section = null; 997 goto case_default; 998 } 999 break; 1000 case "returns": 1001 c.returns ~= line ~ "\n"; 1002 break; 1003 case "diagnostics": 1004 c.diagnostics ~= line ~ "\n"; 1005 break; 1006 case "throws": 1007 c.throws ~= line ~ "\n"; 1008 break; 1009 case "bugs": 1010 c.bugs ~= line ~ "\n"; 1011 break; 1012 case "authors": 1013 case "license": 1014 case "source": 1015 case "history": 1016 case "credits": 1017 case "standards": 1018 case "copyright": 1019 case "version": 1020 case "since": 1021 case "date": 1022 c.otherSections[section] ~= line ~ "\n"; 1023 break; 1024 case "details": 1025 c.details ~= line ~ "\n"; 1026 break; 1027 case "examples": 1028 c.examples ~= line ~ "\n"; 1029 break; 1030 default: 1031 case_default: 1032 if(inSynopsis) { 1033 if(inDdocSummary) 1034 c.ddocSummary ~= line ~ "\n"; 1035 c.synopsis ~= line ~ "\n"; 1036 if(line.length) 1037 hasAnySynopsis = true; 1038 } else 1039 remaining ~= line ~ "\n"; 1040 } 1041 } 1042 1043 c.details ~= remaining; 1044 c.synopsisEndedOnDoubleBlank = synopsisEndedOnDoubleBlank; 1045 } 1046 1047 parseSections(comment); 1048 1049 return c; 1050 } 1051 1052 bool isDdocSection(string s) { 1053 bool hasUnderscore = false; 1054 foreach(idx, char c; s) { 1055 if(c == '_') 1056 hasUnderscore = true; 1057 if(!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) 1058 return hasUnderscore && c == ':'; 1059 } 1060 return false; 1061 } 1062 1063 bool isIdentifierChar(size_t idx, dchar ch) { 1064 import std.uni; 1065 if(!(isAlpha(ch) || (idx != 0 && isNumber(ch)) || ch == '_')) 1066 return false; 1067 return true; 1068 } 1069 1070 bool isIdentifierOrUrl(string text) { 1071 import std.uri; 1072 auto l = uriLength(text); 1073 1074 if(l != -1) 1075 return true; // is url 1076 1077 if(text.length && text[0] == '#') 1078 return true; // is local url link 1079 1080 bool seenHash; 1081 1082 import std.uni; 1083 foreach(idx, dchar ch; text) { 1084 if(!isIdentifierChar(idx, ch) && ch != '.') { 1085 if(!seenHash && ch == '#') { 1086 seenHash = true; 1087 continue; 1088 } else if(seenHash && ch == '-') { 1089 continue; 1090 } 1091 return false; 1092 } 1093 } 1094 1095 // passed the ident test... 1096 if(text.length) 1097 return true; 1098 1099 return false; 1100 1101 } 1102 1103 Element getReferenceLink(string text, Decl decl, string realText = null) { 1104 import std.uri; 1105 auto l = uriLength(text); 1106 string hash; 1107 1108 string className; 1109 1110 if(l != -1) { // a url 1111 text = text; 1112 if(realText is null) 1113 realText = text; 1114 } else if(text.length && text[0] == '#') { 1115 // an anchor. add the link so it works when this is copy/pasted on other pages too (such as in search results) 1116 hash = text; 1117 text = decl.link; 1118 1119 if(realText is null) { 1120 realText = hash[1 .. $].replace("-", " "); 1121 } 1122 } else { 1123 if(realText is null) 1124 realText = text; 1125 1126 auto hashIdx = text.indexOf("#"); 1127 if(hashIdx != -1) { 1128 hash = text[hashIdx .. $]; 1129 text = text[0 .. hashIdx]; 1130 } 1131 1132 auto found = decl.lookupName(text); 1133 1134 className = "xref"; 1135 1136 if(found is null) { 1137 auto lastPieceIdx = text.lastIndexOf("."); 1138 if(lastPieceIdx != -1) { 1139 found = decl.lookupName(text[0 .. lastPieceIdx]); 1140 if(found && hash is null) 1141 hash = "#" ~ text[lastPieceIdx + 1 .. $]; 1142 } 1143 } 1144 1145 if(found !is null) 1146 text = found.link; 1147 else if(auto c = text in allClasses) { 1148 // classes may be thrown and as such can be referenced externally without import 1149 // doing this as kinda a hack. 1150 text = (*c).link; 1151 } else { 1152 text = text.handleCaseSensitivity ~ ".html"; 1153 } 1154 } 1155 1156 auto element = Element.make("a"); 1157 element.className = className; 1158 element.href = getDirectoryForPackage(text) ~ text ~ hash; 1159 element.innerText = realText; 1160 return element; 1161 } 1162 1163 struct DocCommentTerminationCondition { 1164 string[] terminationStrings; 1165 bool mustBeAtStartOfLine; 1166 string remaining; 1167 string terminatedOn; 1168 } 1169 1170 Element formatDocumentationComment2(string comment, Decl decl, string tagName = "div", DocCommentTerminationCondition* termination = null) { 1171 Element div; 1172 if(tagName is null) 1173 div = new DocumentFragment(null); 1174 else 1175 div = Element.make(tagName); 1176 1177 string currentParagraph; 1178 currentParagraph.reserve(2048); 1179 void putch(char c) { 1180 currentParagraph ~= c; 1181 } 1182 void put(in char[] c) { 1183 currentParagraph ~= c; 1184 } 1185 1186 string currentTag = (tagName is null) ? null : "p"; 1187 string currentClass = null; 1188 1189 void commit() { 1190 auto cp = currentParagraph.strip; 1191 if(cp.length) { 1192 if(currentTag is null) 1193 div.appendHtml(cp); 1194 else { 1195 if(currentTag == "p") { 1196 // check for a markdown style header 1197 auto test = cp.strip; 1198 auto lines = test.splitLines(); 1199 if(lines.length == 2) { 1200 auto hdr = lines[0].strip; 1201 auto equals = lines[1].strip; 1202 if(equals.length >= 4) { 1203 foreach(ch; equals) 1204 if(ch != '=') 1205 goto not_special; 1206 } else { 1207 goto not_special; 1208 } 1209 1210 if(hdr.length == 0) 1211 goto not_special; 1212 1213 // passed tests, I'll allow it: 1214 currentTag = "h3"; 1215 currentClass = "user-header"; 1216 cp = hdr; 1217 } else if(lines.length == 1) { 1218 int hashCount = 0; 1219 foreach(idx, ch; test) { 1220 if(ch == '#') 1221 hashCount++; 1222 else if(ch == ' ' && hashCount > 0) { 1223 // it was special! 1224 // there must be text after btw or else strip would have cut the space too 1225 currentTag = "h" ~ to!string(hashCount); 1226 currentClass = "user-header"; 1227 cp = test[idx + 1 .. $]; 1228 1229 break; 1230 } else 1231 break; // not special 1232 } 1233 } 1234 } 1235 1236 not_special: 1237 div.addChild(currentTag, Html(cp), currentClass); 1238 } 1239 } 1240 currentTag = "p"; 1241 currentParagraph = null; 1242 currentParagraph.reserve(2048); 1243 currentClass = null; 1244 } 1245 1246 bool atStartOfLine = true; 1247 bool earlyTermination; 1248 1249 main_loop: 1250 for(size_t idx = 0; idx < comment.length; idx++) { 1251 auto ch = comment[idx]; 1252 auto remaining = comment[idx .. $]; 1253 1254 if(termination !is null) { 1255 if((termination.mustBeAtStartOfLine && atStartOfLine) || !termination.mustBeAtStartOfLine) { 1256 foreach(ts; termination.terminationStrings) { 1257 if(remaining.startsWith(ts)) { 1258 termination.remaining = remaining[ts.length .. $]; 1259 earlyTermination = true; 1260 termination.terminatedOn = ts; 1261 break main_loop; 1262 } 1263 } 1264 } 1265 } 1266 1267 switch(ch) { 1268 case '\r': 1269 continue; // don't care even a little about Windows vs Unix line endings 1270 case ' ', '\t': 1271 goto useCharWithoutTriggeringStartOfLine; 1272 case '\n': 1273 if(atStartOfLine) 1274 commit(); 1275 atStartOfLine = true; 1276 goto useCharWithoutTriggeringStartOfLine; 1277 break; 1278 // these need to be html encoded 1279 case '"': 1280 put("""); 1281 atStartOfLine = false; 1282 break; 1283 case '<': 1284 // FIXME: support just a wee bit of inline html... 1285 put("<"); 1286 atStartOfLine = false; 1287 break; 1288 case '>': 1289 put(">"); 1290 atStartOfLine = false; 1291 break; 1292 case '&': 1293 put("&"); 1294 atStartOfLine = false; 1295 break; 1296 // special syntax 1297 case '`': 1298 // code. ` is inline, ``` is block. 1299 if(atStartOfLine && remaining.startsWith("```")) { 1300 idx += 3; 1301 remaining = remaining[3 .. $]; 1302 bool braced = false; 1303 if(remaining.length && remaining[0] == '{') { 1304 braced = true; 1305 remaining = remaining[1 .. $]; 1306 } 1307 1308 auto line = remaining.indexOf("\n"); 1309 string language; 1310 if(line != -1) { 1311 language = remaining[0 .. line].strip; 1312 remaining = remaining[line + 1 .. $]; 1313 idx += line + 1; 1314 } 1315 1316 size_t ending; 1317 size_t sliceEnding; 1318 if(braced) { 1319 int count = 1; 1320 while(ending < remaining.length) { 1321 if(remaining[ending] == '{') 1322 count++; 1323 if(remaining[ending] == '}') 1324 count--; 1325 1326 if(count == 0) 1327 break; 1328 ending++; 1329 } 1330 1331 sliceEnding = ending; 1332 1333 while(ending < remaining.length) { 1334 if(remaining[ending] == '\n') { 1335 ending++; 1336 break; 1337 } 1338 ending++; 1339 } 1340 1341 } else { 1342 ending = remaining.indexOf("```\n"); 1343 if(ending != -1) { 1344 sliceEnding = ending; 1345 ending += 4; // skip \n 1346 } else { 1347 ending = remaining.indexOf("```\r"); 1348 1349 if(ending != -1) { 1350 sliceEnding = ending; 1351 ending += 5; // skip \r\n 1352 } else { 1353 ending = remaining.length; 1354 sliceEnding = ending; 1355 } 1356 } 1357 } 1358 1359 if(currentTag == "p") 1360 commit(); 1361 // FIXME: we can prolly do more with languages... 1362 string code = outdent(stripRight(remaining[0 .. sliceEnding])); 1363 Element ele; 1364 // all these languages are close enough for hack good enough. 1365 if(language == "javascript" || language == "c" || language == "c++" || language == "java" || language == "php" || language == "c#" || language == "d" || language == "adrscript" || language == "json") 1366 ele = div.addChild("pre", syntaxHighlightCFamily(code, language)); 1367 else if(language == "css") 1368 ele = div.addChild("pre", syntaxHighlightCss(code)); 1369 else if(language == "html" || language == "xml") 1370 ele = div.addChild("pre", syntaxHighlightHtml(code, language)); 1371 else if(language == "python") 1372 ele = div.addChild("pre", syntaxHighlightPython(code)); 1373 else if(language == "ruby") 1374 ele = div.addChild("pre", syntaxHighlightRuby(code)); 1375 else if(language == "sdlang") 1376 ele = div.addChild("pre", syntaxHighlightSdlang(code)); 1377 else 1378 ele = div.addChild("pre", code); 1379 ele.addClass("block-code"); 1380 ele.dataset.language = language; 1381 idx += ending; 1382 } else { 1383 // check for inline `code` style 1384 bool foundIt = false; 1385 size_t foundItWhere = 0; 1386 foreach(i, scout; remaining[1 .. $]) { 1387 if(scout == '\n') 1388 break; 1389 if(scout == '`') { 1390 foundIt = true; 1391 foundItWhere = i; 1392 break; 1393 } 1394 } 1395 1396 if(!foundIt) 1397 goto ordinary; 1398 1399 atStartOfLine = false; 1400 auto slice = remaining[1 .. foundItWhere + 1]; 1401 idx += 1 + foundItWhere; 1402 1403 if(slice.length == 0) { 1404 // empty code block is `` - doubled backtick. Just 1405 // treat that as a literal (escaped) backtick. 1406 putch('`'); 1407 } else { 1408 auto ele = Element.make("tt").addClass("inline-code"); 1409 ele.innerText = slice; 1410 put(ele.toString()); 1411 } 1412 } 1413 break; 1414 case '$': 1415 // ddoc macro. May be magical. 1416 auto info = macroInformation(remaining); 1417 if(info.linesSpanned == 0) { 1418 goto ordinary; 1419 } 1420 1421 atStartOfLine = false; 1422 1423 auto name = remaining[2 .. info.macroNameEndingOffset]; 1424 1425 auto dzeroAdjustment = (info.textBeginningOffset == info.terminatingOffset) ? 0 : 1; 1426 auto text = remaining[info.textBeginningOffset .. info.terminatingOffset - dzeroAdjustment]; 1427 1428 if(name in ddocMacroBlocks) { 1429 commit(); 1430 1431 auto got = expandDdocMacros(remaining, decl); 1432 div.appendChild(got); 1433 } else { 1434 put(expandDdocMacros(remaining, decl).toString()); 1435 } 1436 1437 idx += info.terminatingOffset - 1; 1438 break; 1439 case '-': 1440 // ddoc style code iff --- at start of line. 1441 if(!atStartOfLine) { 1442 goto ordinary; 1443 } 1444 if(remaining.startsWith("---")) { 1445 // a code sample has started 1446 if(currentTag == "p") 1447 commit(); 1448 div.addChild("pre", extractDdocCodeExample(remaining, idx)).addClass("d_code highlighted"); 1449 atStartOfLine = true; 1450 } else 1451 goto ordinary; 1452 break; 1453 case '!': 1454 // possible markdown-style image 1455 if(remaining.length > 1 && remaining[1] == '[') { 1456 auto inside = remaining[1 .. $]; 1457 auto txt = extractBalanceOnSingleLine(inside); 1458 if(txt is null) 1459 goto ordinary; 1460 if(txt.length < inside.length && inside[txt.length] == '(') { 1461 // possible markdown image 1462 auto parens = extractBalanceOnSingleLine(inside[txt.length .. $]); 1463 if(parens.length) { 1464 auto a = Element.make("img"); 1465 a.alt = txt[1 .. $-1]; 1466 a.src = parens[1 .. $-1]; 1467 put(a.toString()); 1468 1469 idx ++; // skip the ! 1470 idx += parens.length; 1471 idx += txt.length; 1472 idx --; // so the ++ in the for loop brings us back to i 1473 break; 1474 } 1475 } 1476 } 1477 goto ordinary; 1478 break; 1479 case '[': 1480 // wiki-style reference iff [text] or [text|other text] 1481 // or possibly markdown style link: [text](url) 1482 // text MUST be either a valid D identifier chain, optionally with a section hash, 1483 // or a fully-encoded http url. 1484 // text may never include ']' or '|' or whitespace or ',' and must always start with '_' or alphabetic (like a D identifier or http url) 1485 // other text may include anything except the string ']' 1486 // it balances [] inside it. 1487 1488 auto txt = extractBalanceOnSingleLine(remaining); 1489 if(txt is null) 1490 goto ordinary; 1491 if(txt.length < remaining.length && remaining[txt.length] == '(') { 1492 // possible markdown link 1493 auto parens = extractBalanceOnSingleLine(remaining[txt.length .. $]); 1494 if(parens.length) { 1495 auto a = Element.make("a", txt[1 .. $-1], parens[1 .. $-1]); 1496 put(a.toString()); 1497 1498 idx += parens.length; 1499 idx += txt.length; 1500 idx --; // so the ++ in the for loop brings us back to i 1501 break; 1502 } 1503 } 1504 1505 auto fun = txt[1 .. $-1]; 1506 string rt; 1507 auto idx2 = fun.indexOf("|"); 1508 if(idx2 != -1) { 1509 rt = fun[idx2 + 1 .. $].strip; 1510 fun = fun[0 .. idx2].strip; 1511 } 1512 1513 if(fun.all!((ch) => ch >= '0' && ch <= '9')) { 1514 // footnote reference 1515 auto lri = getLinkReference(fun, decl); 1516 if(lri.type == LinkReferenceInfo.Type.none) 1517 goto ordinary; 1518 put(lri.toHtml(fun, rt)); 1519 } else if(!fun.isIdentifierOrUrl()) { 1520 goto ordinary; 1521 } else { 1522 auto lri = getLinkReference(fun, decl); 1523 if(lri.type == LinkReferenceInfo.Type.none) { 1524 if(auto l = getSymbolGroupReference(fun, decl, rt)) { 1525 put(l.toString()); 1526 } else { 1527 put(getReferenceLink(fun, decl, rt).toString); 1528 } 1529 } else 1530 put(lri.toHtml(fun, rt)); 1531 } 1532 1533 idx += txt.length; 1534 idx --; // so the ++ in the for loop brings us back to i 1535 break; 1536 case 'h': 1537 // automatically linkify web URLs pasted in 1538 if(tagName is null) 1539 goto ordinary; 1540 import std.uri; 1541 auto l = uriLength(remaining); 1542 if(l == -1) 1543 goto ordinary; 1544 auto url = remaining[0 .. l]; 1545 put("<a href=\"" ~ url ~ "\">" ~ url ~ "</a>"); 1546 idx += url.length; 1547 idx --; // so the ++ puts us at the end 1548 break; 1549 case '_': 1550 // is it that stupid ddocism where a PSYMBOL is prefixed with _? 1551 // still, really, I want to KILL this completely, I hate it a lot 1552 if(idx && !isIdentifierChar(1, comment[idx-1]) && checkStupidDdocIsm(remaining[1..$], decl)) 1553 break; 1554 else 1555 goto ordinary; 1556 break; 1557 /* 1558 /+ I'm just not happy with this yet because it thinks 1559 1560 Paragraph 1561 * list item 1562 After stuff 1563 1564 the After stuff is part of the list item. Blargh. 1565 1566 +/ 1567 case '*': 1568 // potential list item 1569 if(atStartOfLine) { 1570 commit(); 1571 currentTag = "li"; 1572 atStartOfLine = false; 1573 continue; 1574 } else { 1575 goto ordinary; 1576 } 1577 break; 1578 */ 1579 default: 1580 ordinary: 1581 atStartOfLine = false; 1582 useCharWithoutTriggeringStartOfLine: 1583 putch(ch); 1584 } 1585 } 1586 1587 commit(); 1588 1589 if(termination !is null && !earlyTermination) 1590 termination.remaining = null; 1591 1592 /* 1593 if(div.firstChild !is null && div.firstChild.nodeType == NodeType.Text 1594 && div.firstChild.nextSibling !is null && div.firstChild.nextSibling.tagName == "p") 1595 div.firstChild.wrapIn(Element.make("p")); 1596 */ 1597 1598 return div; 1599 } 1600 1601 bool checkStupidDdocIsm(string remaining, Decl decl) { 1602 size_t i; 1603 foreach(idx, dchar ch; remaining) { 1604 if(!isIdentifierChar(idx, ch)) { 1605 i = idx; 1606 break; 1607 } 1608 } 1609 1610 string ident = remaining[0 .. i]; 1611 if(ident.length == 0) 1612 return false; 1613 1614 // check this name 1615 if(ident == decl.name) { 1616 import std.stdio; writeln("stupid ddocism(1): ", ident); 1617 return true; 1618 } 1619 1620 if(decl.isModule) { 1621 auto name = decl.name; 1622 auto idx = name.lastIndexOf("."); 1623 if(idx != -1) { 1624 name = name[idx + 1 .. $]; 1625 } 1626 1627 if(ident == name) { 1628 import std.stdio; writeln("stupid ddocism(2): ", ident); 1629 return true; 1630 } 1631 } 1632 1633 // check the whole scope too, I think dmd does... maybe 1634 if(decl.parentModule && decl.parentModule.lookupName(ident) !is null) { 1635 import std.stdio; writeln("stupid ddocism(3): ", ident); 1636 return true; 1637 } 1638 1639 // FIXME: params? 1640 1641 return false; 1642 } 1643 1644 string extractBalanceOnSingleLine(string txt) { 1645 if(txt.length == 0) return null; 1646 char starter = txt[0]; 1647 char terminator; 1648 switch(starter) { 1649 case '[': 1650 terminator = ']'; 1651 break; 1652 case '(': 1653 terminator = ')'; 1654 break; 1655 case '{': 1656 terminator = '}'; 1657 break; 1658 default: 1659 return null; 1660 } 1661 1662 int count; 1663 foreach(idx, ch; txt) { 1664 if(ch == '\n') 1665 return null; 1666 if(ch == starter) 1667 count++; 1668 else if(ch == terminator) 1669 count--; 1670 1671 if(count == 0) 1672 return txt[0 .. idx + 1]; 1673 } 1674 1675 return null; // unbalanced prolly 1676 } 1677 1678 Html extractDdocCodeExample(string comment, ref size_t idx) { 1679 assert(comment.startsWith("---")); 1680 auto i = comment.indexOf("\n"); 1681 if(i == -1) 1682 return Html(htmlEncode(comment).idup); 1683 comment = comment[i + 1 .. $]; 1684 idx += i + 1; 1685 1686 LexerConfig config; 1687 StringCache stringCache = StringCache(128); 1688 scope(exit) stringCache.freeItAll(); 1689 1690 config.stringBehavior = StringBehavior.source; 1691 config.whitespaceBehavior = WhitespaceBehavior.include; 1692 1693 ubyte[] lies = cast(ubyte[]) comment; 1694 1695 // I'm tokenizing to handle stuff like --- inside strings and 1696 // comments inside the code. If we hit an "operator" -- followed by - 1697 // or -- followed by --, right after some whitespace including a line, 1698 // we're done. 1699 bool justSawNewLine = true; 1700 bool justSawDashDash = false; 1701 size_t terminatingIndex; 1702 lexing: 1703 foreach(token; byToken(lies, config, &stringCache)) { 1704 if(justSawDashDash) { 1705 if(token == tok!"--" || token == tok!"-") { 1706 break lexing; 1707 } 1708 } 1709 1710 if(justSawNewLine) { 1711 if(token == tok!"--") { 1712 justSawDashDash = true; 1713 justSawNewLine = false; 1714 terminatingIndex = token.index; 1715 continue lexing; 1716 } 1717 } 1718 1719 if(token == tok!"whitespace") { 1720 foreach(txt; token.text) 1721 if(txt == '\n') { 1722 justSawNewLine = true; 1723 continue lexing; 1724 } 1725 } 1726 1727 justSawNewLine = false; 1728 } 1729 1730 i = comment.indexOf('\n', terminatingIndex); 1731 if(i == -1) 1732 i = comment.length; 1733 1734 idx += i; 1735 1736 comment = comment[0 .. terminatingIndex]; 1737 1738 return Html(outdent(highlight(comment.stripRight))); 1739 } 1740 1741 immutable string[string] ddocMacros; 1742 immutable int[string] ddocMacroInfo; 1743 immutable int[string] ddocMacroBlocks; 1744 1745 shared static this() { 1746 ddocMacroBlocks = [ 1747 "ADRDOX_SAMPLE" : 1, 1748 "LIST": 1, 1749 "NUMBERED_LIST": 1, 1750 "SMALL_TABLE" : 1, 1751 "TABLE_ROWS" : 1, 1752 "EMBED_UNITTEST": 1, 1753 "UNDOCUMENTED": 1, 1754 1755 "TIP":1, 1756 "NOTE":1, 1757 "WARNING":1, 1758 "PITFALL":1, 1759 "SIDEBAR":1, 1760 "CONSOLE":1, 1761 "H1":1, 1762 "H2":1, 1763 "H3":1, 1764 "H4":1, 1765 "H5":1, 1766 "H5":1, 1767 "HR":1, 1768 "BOOKTABLE":1, 1769 "T2":1, 1770 1771 "TR" : 1, 1772 "TH" : 1, 1773 "TD" : 1, 1774 "TABLE" : 1, 1775 "TDNW" : 1, 1776 "LEADINGROWN" : 1, 1777 1778 "UL" : 1, 1779 "OL" : 1, 1780 "LI" : 1, 1781 1782 "P" : 1, 1783 "PRE" : 1, 1784 "BLOCKQUOTE" : 1, 1785 "CITE" : 1, 1786 "DL" : 1, 1787 "DT" : 1, 1788 "DD" : 1, 1789 "POST" : 1, 1790 "DIV" : 1, 1791 "SIDE_BY_SIDE" : 1, 1792 "COLUMN" : 1, 1793 1794 "DIVC" : 1, 1795 1796 // std.regex 1797 "REG_ROW": 1, 1798 "REG_TITLE": 1, 1799 "REG_TABLE": 1, 1800 "REG_START": 1, 1801 "SECTION": 1, 1802 1803 // std.math 1804 "TABLE_SV" : 1, 1805 "TABLE_DOMRG": 2, 1806 "DOMAIN": 1, 1807 "RANGE": 1, 1808 "SVH": 1, 1809 "SV": 1, 1810 1811 "UDA_USES" : 1, 1812 "C_HEADER_DESCRIPTION": 1, 1813 ]; 1814 1815 ddocMacros = [ 1816 "FULLY_QUALIFIED_NAME" : "MAGIC", // decl.fullyQualifiedName, 1817 "MODULE_NAME" : "MAGIC", // decl.parentModule.fullyQualifiedName, 1818 "D" : "<tt class=\"D\">$0</tt>", // this is magical! D is actually handled in code. 1819 "REF" : `<a href="$0.html">$0</a>`, // this is magical! Handles ref in code 1820 1821 "ALWAYS_DOCUMENT" : "", 1822 1823 "UDA_USES" : "", 1824 1825 "TIP" : "<div class=\"tip\">$0</div>", 1826 "NOTE" : "<div class=\"note\">$0</div>", 1827 "WARNING" : "<div class=\"warning\">$0</div>", 1828 "PITFALL" : "<div class=\"pitfall\">$0</div>", 1829 "SIDEBAR" : "<div class=\"sidebar\"><aside>$0</aside></div>", 1830 "CONSOLE" : "<pre class=\"console\">$0</pre>", 1831 1832 // headers have meaning to the table of contents generator 1833 "H1" : "<h1 class=\"user-header\">$0</h1>", 1834 "H2" : "<h2 class=\"user-header\">$0</h2>", 1835 "H3" : "<h3 class=\"user-header\">$0</h3>", 1836 "H4" : "<h4 class=\"user-header\">$0</h4>", 1837 "H5" : "<h5 class=\"user-header\">$0</h5>", 1838 "H6" : "<h6 class=\"user-header\">$0</h6>", 1839 1840 "BLOCKQUOTE" : "<blockquote>$0</blockquote>", // FIXME? 1841 "CITE" : "<cite>$0</cite>", // FIXME? 1842 1843 "HR" : "<hr />", 1844 1845 "DASH" : "-", 1846 "NOTHING" : "", // used to escape magic syntax sometimes 1847 1848 // DDoc just expects these. 1849 "AMP" : "&", 1850 "LT" : "<", 1851 "GT" : ">", 1852 "LPAREN" : "(", 1853 "RPAREN" : ")", 1854 "DOLLAR" : "$", 1855 "BACKTICK" : "`", 1856 "COMMA": ",", 1857 "COLON": ":", 1858 "ARGS" : "$0", 1859 1860 // support for my docs' legacy components, will be removed before too long. 1861 "DDOC_ANCHOR" : "<a id=\"$0\" href=\"$(FULLY_QUALIFIED_NAME).html#$0\">$0</a>", 1862 1863 "DDOC_COMMENT" : "", 1864 1865 // this is support for the Phobos docs 1866 1867 // useful parts 1868 "BOOKTABLE" : "<table class=\"phobos-booktable\"><caption>$1</caption>$+</table>", 1869 "T2" : "<tr><td>$(LREF $1)</td><td>$+</td></tr>", 1870 1871 "BIGOH" : "<span class=\"big-o\"><i>O</i>($0)</span>", 1872 1873 "TR" : "<tr>$0</tr>", 1874 "TH" : "<th>$0</th>", 1875 "TD" : "<td>$0</td>", 1876 "TABLE": "<table>$0</table>", 1877 "TDNW" : "<td style=\"white-space: nowrap;\">$0</td>", 1878 "LEADINGROWN":"<tr class=\"leading-row\"><th colspan=\"$1\">$2</th></tr>", 1879 "LEADINGROW":"<tr class=\"leading-row\"><th colspan=\"2\">$0</th></tr>", 1880 1881 "UL" : "<ul>$0</ul>", 1882 "OL" : "<ol>$0</ol>", 1883 "LI" : "<li>$0</li>", 1884 1885 "BR" : "<br />", 1886 "I" : "<i>$0</i>", 1887 "EM" : "<em>$0</em>", 1888 "STRONG" : "<strong>$0</strong>", 1889 "TT" : "<tt>$0</tt>", 1890 "B" : "<b>$0</b>", 1891 "STRIKE" : "<s>$0</s>", 1892 "P" : "<p>$0</p>", 1893 "PRE" : "<pre>$0</pre>", 1894 "DL" : "<dl>$0</dl>", 1895 "DT" : "<dt>$0</dt>", 1896 "DD" : "<dd>$0</dd>", 1897 "LINK2" : "<a href=\"$1\">$2</a>", 1898 "LINK" : `<a href="$0">$0</a>`, 1899 1900 "DDLINK": "<a href=\"http://dlang.org/$1\">$3</a>", 1901 "DDSUBLINK": "<a href=\"http://dlang.org/$1#$2\">$3</a>", 1902 1903 "IMG": "<img src=\"$1\" alt=\"$2\" />", 1904 1905 "BLUE" : "<span style=\"color: blue;\">$0</span>", 1906 "RED" : "<span style=\"color: red;\">$0</span>", 1907 1908 "SCRIPT" : "", 1909 1910 "UNDOCUMENTED": "<span class=\"undocumented-note\">$0</span>", 1911 1912 // Useless crap that should just be replaced 1913 "REF_SHORT" : `<a href="$2.$3.$1.html">$1</a>`, 1914 "SHORTREF" : `<a href="$2.$3.$1.html">$1</a>`, 1915 "SUBMODULE" : `<a href="$(FULLY_QUALIFIED_NAME).$2.html">$1</a>`, 1916 "SHORTXREF" : `<a href="std.$1.$2.html">$2</a>`, 1917 "SHORTXREF_PACK" : `<a href="std.$1.$2.$3.html">$3</a>`, 1918 "XREF" : `<a href="std.$1.$2.html">std.$1.$2</a>`, 1919 "CXREF" : `<a href="core.$1.$2.html">core.$1.$2</a>`, 1920 "XREF_PACK" : `<a href="std.$1.$2.$3.html">std.$1.$2.$3</a>`, 1921 "MYREF" : `<a href="$(FULLY_QUALIFIED_NAME).$0.html">$0</a>`, 1922 "LREF" : `<a class="symbol-reference" href="$(MODULE_NAME).$0.html">$0</a>`, 1923 // FIXME: OBJECTREF 1924 "LREF2" : "MAGIC", 1925 "MREF" : `MAGIC`, 1926 "WEB" : `<a href="http://$1">$2</a>`, 1927 "HTTP" : `<a href="http://$1">$2</a>`, 1928 "BUGZILLA": `<a href="https://issues.dlang.org/show_bug.cgi?id=$1">https://issues.dlang.org/show_bug.cgi?id=$1</a>`, 1929 "XREF_PACK_NAMED" : `<a href="std.$1.$2.$3.html">$4</a>`, 1930 1931 "D_INLINECODE" : `<tt>$1</tt>`, 1932 "D_STRING" : `<tt>$1</tt>`, 1933 "D_CODE_STRING" : `<tt>$1</tt>`, 1934 "D_CODE" : `<pre>$0</pre>`, 1935 "HIGHLIGHT" : `<span class="specially-highlighted">$0</span>`, 1936 "HTTPS" : `<a href="https://$1">$2</a>`, 1937 1938 "RES": `<i>result</i>`, 1939 "POST": `<div class="postcondition">$0</div>`, 1940 "COMMENT" : ``, 1941 1942 "DIV" : `<div>$0</div>`, 1943 "SIDE_BY_SIDE" : `<table class="side-by-side"><tbody><tr>$0</tr></tbody></table>`, 1944 "COLUMN" : `<td>$0</td>`, 1945 "DIVC" : `<div class="$1">$+</div>`, 1946 1947 // std.regex 1948 "REG_ROW":`<tr><td><i>$1</i></td><td>$+</td></tr>`, 1949 "REG_TITLE":`<tr><td><b>$1</b></td><td><b>$2</b></td></tr>`, 1950 "REG_TABLE":`<table border="1" cellspacing="0" cellpadding="5" > $0 </table>`, 1951 "REG_START":`<h3><div align="center"> $0 </div></h3>`, 1952 "SECTION":`<h3><a id="$0">$0</a></h3>`, 1953 "S_LINK":`<a href="#$1">$+</a>`, 1954 1955 // std.math 1956 1957 "TABLE_SV": `<table class="std_math special-values"><caption>Special Values</caption>$0</table>`, 1958 "SVH" : `<tr><th>$1</th><th>$2</th></tr>`, 1959 "SV" : `<tr><td>$1</td><td>$2</td></tr>`, 1960 "TABLE_DOMRG": `<table class="std_math domain-and-range">$1 $2</table>`, 1961 "DOMAIN": `<tr><th>Domain</th><td>$0</td></tr>`, 1962 "RANGE": `<tr><th>Range</th><td>$0</td></tr>`, 1963 1964 "PI": "\u03c0", 1965 "INFIN": "\u221e", 1966 "SUB": "<span>$1<sub>$2</sub></span>", 1967 "SQRT" : "\u221a", 1968 "SUPERSCRIPT": "<sup>$1</sup>", 1969 "SUBSCRIPT": "<sub>$1</sub>", 1970 "POWER": "<span>$1<sup>$2</sup></span>", 1971 "NAN": "<span class=\"nan\">NaN</span>", 1972 "PLUSMN": "\u00b1", 1973 "GLOSSARY": `<a href="http://dlang.org/glosary.html#$0">$0</a>`, 1974 "PHOBOSSRC": `<a href="https://github.com/dlang/phobos/blob/master/$0">$0</a>`, 1975 "DRUNTIMESRC": `<a href="https://github.com/dlang/druntime/blob/master/src/$0">$0</a>`, 1976 "C_HEADER_DESCRIPTION": `<p>This module contains bindings to selected types and functions from the standard C header <a href="http://$1"><$2></a>. Note that this is not automatically generated, and may omit some types/functions from the original C header.</p>`, 1977 ]; 1978 1979 ddocMacroInfo = [ // number of arguments expected, if needed 1980 "EMBED_UNITTEST": 1, 1981 "UNDOCUMENTED": 1, 1982 "BOOKTABLE" : 1, 1983 "T2" : 1, 1984 "LEADINGROWN" : 2, 1985 "WEB" : 2, 1986 "HTTP" : 2, 1987 "BUGZILLA" : 1, 1988 "XREF_PACK_NAMED" : 4, 1989 "D_INLINECODE" : 1, 1990 "D_STRING" : 1, 1991 "D_CODE_STRING": 1, 1992 "D_CODE": 1, 1993 "HIGHLIGHT": 1, 1994 "HTTPS": 2, 1995 "DIVC" : 1, 1996 "LINK2" : 2, 1997 "DDLINK" : 3, 1998 "DDSUBLINK" : 3, 1999 "IMG" : 2, 2000 "XREF" : 2, 2001 "CXREF" : 2, 2002 "SHORTXREF" : 2, 2003 "SHORTREF": 3, 2004 "REF_SHORT": 3, 2005 "SUBMODULE" : 2, 2006 "SHORTXREF_PACK" : 3, 2007 "XREF_PACK" : 3, 2008 "MREF" : 1, 2009 "LREF2" : 2, 2010 "C_HEADER_DESCRIPTION": 2, 2011 2012 "REG_ROW" : 1, 2013 "REG_TITLE" : 2, 2014 "S_LINK" : 1, 2015 "PI": 1, 2016 "SUB": 2, 2017 "SQRT" : 1, 2018 "SUPERSCRIPT": 1, 2019 "SUBSCRIPT": 1, 2020 "POWER": 2, 2021 "NAN": 1, 2022 "PLUSMN": 1, 2023 "SVH" : 2, 2024 "SV": 2, 2025 ]; 2026 } 2027 2028 string formatDocumentationComment(string comment, Decl decl) { 2029 // remove that annoying ddocism to suppress its auto-highlight anti-feature 2030 // FIXME 2031 /* 2032 auto psymbols = fullyQualifiedName[$-1].split("."); 2033 // also trim off our .# for overload 2034 import std.uni :isAlpha; 2035 auto psymbol = (psymbols.length && psymbols[$-1].length) ? 2036 ((psymbols[$-1][0].isAlpha || psymbols[$-1][0] == '_') ? psymbols[$-1] : psymbols[$-2]) 2037 : 2038 null; 2039 if(psymbol.length) 2040 comment = comment.replace("_" ~ psymbol, psymbol); 2041 */ 2042 2043 auto data = formatDocumentationComment2(comment, decl).toString(); 2044 2045 return data; 2046 } 2047 2048 2049 // This is kinda deliberately not recursive right now. I might change that later but I want to keep it 2050 // simple to get decent results while keeping the door open to dropping ddoc macros. 2051 Element expandDdocMacros(string txt, Decl decl) { 2052 auto e = expandDdocMacros2(txt, decl); 2053 2054 translateMagicCommands(e); 2055 2056 return e; 2057 } 2058 2059 void translateMagicCommands(Element container, Element applyTo = null) { 2060 foreach(cmd; container.querySelectorAll("magic-command")) { 2061 Element subject; 2062 if(applyTo is null) { 2063 subject = cmd.parentNode; 2064 if(subject is null) 2065 continue; 2066 if(subject.tagName == "caption") 2067 subject = subject.parentNode; 2068 if(subject is null) 2069 continue; 2070 } else { 2071 subject = applyTo; 2072 } 2073 2074 if(cmd.className.length) { 2075 subject.addClass(cmd.className); 2076 } 2077 if(cmd.id.length) 2078 subject.id = cmd.id; 2079 cmd.removeFromTree(); 2080 } 2081 } 2082 2083 Element expandDdocMacros2(string txt, Decl decl) { 2084 auto macros = ddocMacros; 2085 auto numberOfArguments = ddocMacroInfo; 2086 2087 auto userDefinedMacros = decl.parsedDocComment.userDefinedMacros; 2088 2089 string[string] availableMacros; 2090 int[string] availableNumberOfArguments; 2091 2092 if(userDefinedMacros.length) { 2093 foreach(name, value; userDefinedMacros) { 2094 availableMacros[name] = value; 2095 int count; 2096 foreach(idx, ch; value) { 2097 if(ch == '$') { 2098 if(idx + 1 < value.length) { 2099 char n = value[idx + 1]; 2100 if(n >= '0' && n <= '9') { 2101 if(n - '0' > count) 2102 count = n - '0'; 2103 } 2104 } 2105 } 2106 } 2107 if(count) 2108 availableNumberOfArguments[name] = count; 2109 } 2110 2111 foreach(name, value; macros) 2112 availableMacros[name] = value; 2113 foreach(name, n; numberOfArguments) 2114 availableNumberOfArguments[name] = n; 2115 } else { 2116 availableMacros = cast(string[string]) macros; 2117 availableNumberOfArguments = cast(int[string]) numberOfArguments; 2118 } 2119 2120 auto idx = 0; 2121 if(txt[idx] == '$') { 2122 auto info = macroInformation(txt[idx .. $]); 2123 if(info.linesSpanned == 0) 2124 assert(0); 2125 2126 if(idx + 2 > txt.length) 2127 assert(0); 2128 if(idx + info.macroNameEndingOffset > txt.length) 2129 assert(0, txt[idx .. $]); 2130 if(info.macroNameEndingOffset < 2) 2131 assert(0, txt[idx..$]); 2132 2133 auto name = txt[idx + 2 .. idx + info.macroNameEndingOffset]; 2134 2135 auto dzeroAdjustment = (info.textBeginningOffset == info.terminatingOffset) ? 0 : 1; 2136 auto stuff = txt[idx + info.textBeginningOffset .. idx + info.terminatingOffset - dzeroAdjustment]; 2137 auto stuffNonStripped = txt[idx + info.macroNameEndingOffset .. idx + info.terminatingOffset - dzeroAdjustment]; 2138 string replacement; 2139 2140 if(name == "RAW_HTML") { 2141 // magic: user-defined html 2142 auto holder = Element.make("div", "", "user-raw-html"); 2143 holder.innerHTML = stuff; 2144 return holder; 2145 } 2146 2147 if(name == "ID") 2148 return Element.make("magic-command").setAttribute("id", stuff); 2149 if(name == "CLASS") 2150 return Element.make("magic-command").setAttribute("class", stuff); 2151 2152 if(name == "EMBED_UNITTEST") { 2153 // FIXME: use /// Documents: Foo.bar to move a test over to something else 2154 auto id = stuff.strip; 2155 foreach(ref ut; decl.getProcessedUnittests()) { 2156 if(ut.comment.canFind("$(ID " ~ id ~ ")")) { 2157 ut.embedded = true; 2158 return formatUnittestDocTuple(ut, decl); 2159 } 2160 } 2161 } 2162 2163 if(name == "SMALL_TABLE") { 2164 return translateSmallTable(stuff, decl); 2165 } 2166 if(name == "TABLE_ROWS") { 2167 return translateListTable(stuff, decl); 2168 } 2169 if(name == "LIST") { 2170 return translateList(stuff, decl, "ul"); 2171 } 2172 if(name == "NUMBERED_LIST") { 2173 return translateList(stuff, decl, "ol"); 2174 } 2175 2176 if(name == "MATH") { 2177 import adrdox.latex; 2178 import adrdox.jstex; 2179 2180 switch (texMathOpt) with(TexMathOpt) { 2181 case LaTeX: { 2182 auto got = mathToImgHtml(stuff); 2183 if(got is null) 2184 return Element.make("span", stuff, "user-math-render-failed"); 2185 return got; 2186 } 2187 case KaTeX: { 2188 return mathToKaTeXHtml(stuff); 2189 } 2190 default: break; 2191 } 2192 } 2193 2194 if(name == "ADRDOX_SAMPLE") { 2195 // show the original source as code 2196 // then render it for display side-by-side 2197 auto holder = Element.make("div"); 2198 holder.addClass("adrdox-sample"); 2199 2200 auto h = holder.addChild("div"); 2201 2202 h.addChild("pre", outdent(stuffNonStripped)); 2203 h.addChild("div", formatDocumentationComment2(stuff, decl)); 2204 2205 return holder; 2206 } 2207 2208 if(name == "D" || name == "D_PARAM" || name == "D_CODE") { 2209 // this is magic: syntax highlight it 2210 auto holder = Element.make(name == "D_CODE" ? "pre" : "tt"); 2211 holder.className = "D highlighted"; 2212 try { 2213 holder.innerHTML = linkUpHtml(highlight(stuff), decl); 2214 } catch(Throwable t) { 2215 holder.innerText = stuff; 2216 } 2217 return holder; 2218 } 2219 2220 if(name == "UDA_USES") { 2221 Element div = Element.make("dl"); 2222 foreach(d; declsByUda(decl.name, decl.parentModule)) { 2223 if(!d.docsShouldBeOutputted) 2224 continue; 2225 auto dt = div.addChild("dt"); 2226 dt.addChild("a", d.name, d.link); 2227 div.addChild("dd", Html(formatDocumentationComment(d.parsedDocComment.ddocSummary, d))); 2228 } 2229 return div; 2230 } 2231 2232 if(name == "MODULE_NAME") { 2233 return new TextNode(decl.parentModule.fullyQualifiedName); 2234 } 2235 if(name == "FULLY_QUALIFIED_NAME") { 2236 return new TextNode(decl.fullyQualifiedName); 2237 } 2238 if(name == "SUBREF") { 2239 auto cool = stuff.split(","); 2240 if(cool.length == 2) 2241 return getReferenceLink(decl.fullyQualifiedName ~ "." ~ cool[0].strip ~ "." ~ cool[1].strip, decl, cool[1].strip); 2242 } 2243 if(name == "MREF_ALTTEXT") { 2244 auto parts = split(stuff, ","); 2245 string cool; 2246 foreach(indx, p; parts[1 .. $]) { 2247 if(indx) cool ~= "."; 2248 cool ~= p.strip; 2249 } 2250 2251 return getReferenceLink(cool, decl, parts[0].strip); 2252 } 2253 if(name == "LREF2") { 2254 auto parts = split(stuff, ","); 2255 return getReferenceLink(parts[1].strip ~ "." ~ parts[0].strip, decl, parts[0].strip); 2256 } 2257 if(name == "REF_ALTTEXT") { 2258 auto parts = split(stuff, ","); 2259 string cool; 2260 foreach(indx, p; parts[2 .. $]) { 2261 if(indx) cool ~= "."; 2262 cool ~= p.strip; 2263 } 2264 cool ~= "." ~ parts[1].strip; 2265 2266 return getReferenceLink(cool, decl, parts[0].strip); 2267 } 2268 2269 if(name == "NBSP") { 2270 return new TextNode("\ "); 2271 } 2272 2273 if(name == "REF" || name == "MREF" || name == "LREF" || name == "MYREF") { 2274 // this is magic: do commas to dots then link it 2275 string cool; 2276 if(name == "REF") { 2277 auto parts = split(stuff, ","); 2278 parts = parts[1 .. $] ~ parts[0]; 2279 foreach(indx, p; parts) { 2280 if(indx) 2281 cool ~= "."; 2282 cool ~= p.strip; 2283 } 2284 } else 2285 cool = replace(stuff, ",", ".").replace(" ", ""); 2286 return getReferenceLink(cool, decl); 2287 } 2288 2289 2290 if(auto replacementRaw = name in availableMacros) { 2291 if(auto nargsPtr = name in availableNumberOfArguments) { 2292 auto nargs = *nargsPtr; 2293 replacement = *replacementRaw; 2294 2295 auto orig = txt[idx + info.textBeginningOffset .. idx + info.terminatingOffset - dzeroAdjustment]; 2296 2297 foreach(i; 1 .. nargs + 1) { 2298 int blargh = -1; 2299 2300 int parens; 2301 foreach(cidx, ch; stuff) { 2302 if(ch == '(') 2303 parens++; 2304 if(ch == ')') 2305 parens--; 2306 if(parens == 0 && ch == ',') { 2307 blargh = cast(int) cidx; 2308 break; 2309 } 2310 } 2311 if(blargh == -1) 2312 blargh = cast(int) stuff.length; 2313 //break; // insufficient args 2314 //blargh = stuff.length - 1; // trims off the closing paren 2315 2316 auto magic = stuff[0 .. blargh].strip; 2317 if(blargh < stuff.length) 2318 stuff = stuff[blargh + 1 .. $]; 2319 else 2320 stuff = stuff[$..$]; 2321 // FIXME: this should probably not be full replace each time. 2322 //if(magic.length) 2323 magic = formatDocumentationComment2(magic, decl, null).innerHTML; 2324 2325 if(name == "T2" && i == 1) 2326 replacement = replace(replacement, "$(LREF $1)", getReferenceLink(magic, decl).toString); 2327 else 2328 replacement = replace(replacement, "$" ~ cast(char)(i + '0'), magic); 2329 } 2330 2331 // FIXME: recursive replacement doesn't work... 2332 2333 auto awesome = stuff.strip; 2334 if(replacement.indexOf("$+") != -1) { 2335 awesome = formatDocumentationComment2(awesome, decl, null).innerHTML; 2336 replacement = replace(replacement, "$+", awesome); 2337 } 2338 2339 if(replacement.indexOf("$0") != -1) { 2340 awesome = formatDocumentationComment2(orig, decl, null).innerHTML; 2341 replacement = replace(*replacementRaw, "$0", awesome); 2342 } 2343 } else { 2344 // if there is any text, we slice off the ), otherwise, keep the empty string 2345 auto awesome = txt[idx + info.textBeginningOffset .. idx + info.terminatingOffset - dzeroAdjustment]; 2346 2347 if(name == "CONSOLE") { 2348 awesome = outdent(txt[idx + info.macroNameEndingOffset .. idx + info.terminatingOffset - dzeroAdjustment]).strip; 2349 awesome = awesome.replace("\n---", "\n$(NOTHING)---"); // disable ddoc embedded D code as it breaks for D exception messages 2350 } 2351 2352 2353 awesome = formatDocumentationComment2(awesome, decl, null).innerHTML; 2354 replacement = replace(*replacementRaw, "$0", awesome); 2355 } 2356 2357 Element element; 2358 if(replacement != "<" && replacement.strip.startsWith("<")) { 2359 element = Element.make("placeholder"); 2360 2361 element.innerHTML = replacement; 2362 element = element.childNodes[0]; 2363 element.parentNode = null; 2364 2365 foreach(k, v; element.attributes) 2366 element.attrs[k] = v.replace("$(FULLY_QUALIFIED_NAME)", decl.fullyQualifiedName).replace("$(MODULE_NAME)", decl.parentModule.fullyQualifiedName); 2367 } else { 2368 element = new TextNode(replacement); 2369 } 2370 2371 return element; 2372 } else { 2373 idx = idx + cast(int) info.terminatingOffset; 2374 auto textContent = txt[0 .. info.terminatingOffset]; 2375 return new TextNode(textContent); 2376 } 2377 } else assert(0); 2378 2379 2380 return null; 2381 } 2382 2383 struct MacroInformation { 2384 size_t macroNameEndingOffset; // starting is always 2 if it is actually a macro (check linesSpanned > 0) 2385 size_t textBeginningOffset; 2386 size_t terminatingOffset; 2387 int linesSpanned; 2388 } 2389 2390 // Scans the current macro 2391 MacroInformation macroInformation(in char[] str) { 2392 assert(str[0] == '$'); 2393 2394 MacroInformation info; 2395 info.terminatingOffset = str.length; 2396 2397 if (str.length < 2 || (str[1] != '(' && str[1] != '{')) 2398 return info; 2399 2400 auto open = str[1]; 2401 auto close = str[1] == '(' ? ')' : '}'; 2402 2403 bool readingMacroName = true; 2404 bool readingLeadingWhitespace; 2405 int parensCount = 0; 2406 foreach (idx, char ch; str) 2407 { 2408 if (readingMacroName && (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == ',')) 2409 { 2410 readingMacroName = false; 2411 readingLeadingWhitespace = true; 2412 info.macroNameEndingOffset = idx; 2413 } 2414 2415 if (readingLeadingWhitespace && !(ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n')) 2416 { 2417 readingLeadingWhitespace = false; 2418 // This is looking past the macro name 2419 // so the offset of $(FOO bar) ought to be 2420 // the index of "bar" 2421 info.textBeginningOffset = idx; 2422 } 2423 2424 // so i want :) and :( not to count 2425 // also prolly 1) and 2) etc. 2426 2427 if (ch == '\n') 2428 info.linesSpanned++; 2429 if (ch == open) 2430 parensCount++; 2431 if (ch == close) 2432 { 2433 parensCount--; 2434 if (parensCount == 0) 2435 { 2436 if(readingMacroName) 2437 info.macroNameEndingOffset = idx; 2438 info.linesSpanned++; // counts the first line it is on 2439 info.terminatingOffset = idx + 1; 2440 if (info.textBeginningOffset == 0) 2441 info.textBeginningOffset = info.terminatingOffset; 2442 break; 2443 } 2444 } 2445 } 2446 2447 if(parensCount) 2448 info.linesSpanned = 0; // unterminated macro, tell upstream it is plain text 2449 2450 return info; 2451 } 2452 2453 2454 2455 string outdent(string s) { 2456 static import std.string; 2457 try { 2458 return join(std..string.outdent(splitLines(s)), "\n"); 2459 } catch(Exception) { 2460 return s; 2461 } 2462 } 2463 2464 2465 // Original code: 2466 // Copyright Brian Schott (Hackerpilot) 2012. 2467 // Distributed under the Boost Software License, Version 1.0. 2468 // (See accompanying file LICENSE_1_0.txt or copy at 2469 // http://www.boost.org/LICENSE_1_0.txt) 2470 // Adam modified 2471 2472 // http://ethanschoonover.com/solarized 2473 string highlight(string sourceCode) 2474 { 2475 2476 import std.array; 2477 import dparse.lexer; 2478 string ret; 2479 2480 StringCache cache = StringCache(StringCache.defaultBucketCount); 2481 scope(exit) cache.freeItAll(); 2482 LexerConfig config; 2483 config.stringBehavior = StringBehavior.source; 2484 auto tokens = byToken(cast(ubyte[]) sourceCode, config, &cache); 2485 2486 2487 void writeSpan(string cssClass, string value, string dataIdent = "") 2488 { 2489 ret ~= `<span class="` ~ cssClass ~ `" `; 2490 if(dataIdent.length) 2491 ret ~= "data-ident=\""~dataIdent~"\""; 2492 ret ~= `>` ~ value.replace("&", "&").replace("<", "<") ~ `</span>`; 2493 } 2494 2495 2496 while (!tokens.empty) 2497 { 2498 auto t = tokens.front; 2499 tokens.popFront(); 2500 if (isBasicType(t.type)) 2501 writeSpan("type", str(t.type)); 2502 else if(t.text == "string") // string is a de-facto basic type, though it is technically just a user-defined identifier 2503 writeSpan("type", t.text); 2504 else if (isKeyword(t.type)) 2505 writeSpan("kwrd", str(t.type)); 2506 else if (t.type == tok!"comment") { 2507 auto txt = t.text; 2508 if(txt == "/* adrdox_highlight{ */") 2509 ret ~= "<span class=\"specially-highlighted\">"; 2510 else if(txt == "/* }adrdox_highlight */") 2511 ret ~= "</span>"; 2512 else 2513 writeSpan("com", t.text); 2514 } else if (isStringLiteral(t.type) || t.type == tok!"characterLiteral") { 2515 auto txt = t.text; 2516 if(txt.startsWith("q{")) { 2517 ret ~= "<span class=\"token-string-literal\"><span class=\"str\">q{</span>"; 2518 ret ~= highlight(txt[2 .. $ - 1]); 2519 ret ~= "<span class=\"str\">}</span></span>"; 2520 } else { 2521 writeSpan("str", txt); 2522 } 2523 } else if (isNumberLiteral(t.type)) 2524 writeSpan("num", t.text); 2525 else if (isOperator(t.type)) 2526 //writeSpan("op", str(t.type)); 2527 ret ~= htmlEncode(str(t.type)); 2528 else if (t.type == tok!"specialTokenSequence" || t.type == tok!"scriptLine") 2529 writeSpan("cons", t.text); 2530 else if (t.type == tok!"identifier") 2531 writeSpan("hid", t.text); 2532 else 2533 { 2534 ret ~= t.text.replace("<", "<"); 2535 } 2536 2537 } 2538 return ret; 2539 } 2540 2541 2542 2543 2544 import dparse.formatter; 2545 2546 class MyFormatter(Sink) : Formatter!Sink { 2547 Decl context; 2548 this(Sink sink, Decl context = null, bool useTabs = true, IndentStyle style = IndentStyle.otbs, uint indentWidth = 4) 2549 { 2550 this.context = context; 2551 super(sink, useTabs, style, indentWidth); 2552 } 2553 2554 void putTag(in char[] s) { 2555 static if(!__traits(compiles, sink.putTag(""))) 2556 sink.put(s); 2557 else 2558 sink.putTag(s); 2559 } 2560 2561 override void put(string s1) { 2562 auto s = cast(const(char)[]) s1; 2563 static if(!__traits(compiles, sink.putTag(""))) 2564 sink.put(s.htmlEncode); 2565 else 2566 sink.put(s); 2567 } 2568 2569 override void format(const AtAttribute atAttribute) { 2570 if(atAttribute is null || atAttribute.argumentList is null) 2571 super.format(atAttribute); 2572 else { 2573 sink.put("@"); 2574 if(atAttribute.argumentList.items.length == 1) { 2575 format(atAttribute.argumentList.items[0]); 2576 } else { 2577 format(atAttribute.argumentList); 2578 } 2579 } 2580 } 2581 2582 override void format(const TemplateParameterList templateParameterList) 2583 { 2584 putTag("<div class=\"parameters-list\">"); 2585 foreach(i, param; templateParameterList.items) 2586 { 2587 putTag("<div class=\"template-parameter-item parameter-item\">"); 2588 put("\t"); 2589 putTag("<span>"); 2590 format(param); 2591 putTag("</span>"); 2592 putTag("</div>"); 2593 } 2594 putTag("</div>"); 2595 } 2596 2597 override void format(const InStatement inStatement) { 2598 putTag("<a href=\"http://dpldocs.info/in-contract\" class=\"lang-feature\">"); 2599 put("in"); 2600 putTag("</a>"); 2601 //put(" "); 2602 if(inStatement.blockStatement) 2603 format(inStatement.blockStatement); 2604 else if(inStatement.expression) { 2605 put(" ("); 2606 format(inStatement.expression); 2607 put(")"); 2608 } 2609 } 2610 2611 override void format(const OutStatement outStatement) { 2612 putTag("<a href=\"http://dpldocs.info/out-contract\" class=\"lang-feature\">"); 2613 put("out"); 2614 putTag("</a>"); 2615 2616 //put(" "); 2617 if(outStatement.expression) { 2618 put(" ("); 2619 format(outStatement.parameter); 2620 put("; "); 2621 format(outStatement.expression); 2622 put(")"); 2623 } else { 2624 if (outStatement.parameter != tok!"") 2625 { 2626 put(" ("); 2627 format(outStatement.parameter); 2628 put(")"); 2629 } 2630 2631 format(outStatement.blockStatement); 2632 } 2633 } 2634 2635 override void format(const AssertExpression assertExpression) 2636 { 2637 debug(verbose) writeln("AssertExpression"); 2638 2639 /** 2640 AssignExpression assertion; 2641 AssignExpression message; 2642 **/ 2643 2644 with(assertExpression) 2645 { 2646 putTag("<a href=\"http://dpldocs.info/assertion\" class=\"lang-feature\">"); 2647 put("assert"); 2648 putTag("</a> ("); 2649 format(assertion); 2650 if (message) 2651 { 2652 put(", "); 2653 format(message); 2654 } 2655 put(")"); 2656 } 2657 } 2658 2659 2660 override void format(const TemplateAliasParameter templateAliasParameter) 2661 { 2662 debug(verbose) writeln("TemplateAliasParameter"); 2663 2664 /** 2665 Type type; 2666 Token identifier; 2667 Type colonType; 2668 AssignExpression colonExpression; 2669 Type assignType; 2670 AssignExpression assignExpression; 2671 **/ 2672 2673 with(templateAliasParameter) 2674 { 2675 putTag("<a href=\"http://dpldocs.info/template-alias-parameter\" class=\"lang-feature\">"); 2676 put("alias"); 2677 putTag("</a>"); 2678 put(" "); 2679 if (type) 2680 { 2681 format(type); 2682 space(); 2683 } 2684 format(identifier); 2685 if (colonType) 2686 { 2687 put(" : "); 2688 format(colonType); 2689 } 2690 else if (colonExpression) 2691 { 2692 put(" : "); 2693 format(colonExpression); 2694 } 2695 if (assignType) 2696 { 2697 put(" = "); 2698 format(assignType); 2699 } 2700 else if (assignExpression) 2701 { 2702 put(" = "); 2703 format(assignExpression); 2704 } 2705 } 2706 } 2707 2708 2709 override void format(const Constraint constraint) 2710 { 2711 debug(verbose) writeln("Constraint"); 2712 2713 if (constraint.expression) 2714 { 2715 put(" "); 2716 putTag("<a href=\"http://dpldocs.info/template-constraints\" class=\"lang-feature\">"); 2717 put("if"); 2718 putTag("</a>"); 2719 put(" ("); 2720 putTag("<div class=\"template-constraint-expression\">"); 2721 format(constraint.expression); 2722 putTag("</div>"); 2723 put(")"); 2724 } 2725 } 2726 2727 2728 2729 override void format(const PrimaryExpression primaryExpression) 2730 { 2731 debug(verbose) writeln("PrimaryExpression"); 2732 2733 /** 2734 Token dot; 2735 Token primary; 2736 IdentifierOrTemplateInstance identifierOrTemplateInstance; 2737 Token basicType; 2738 TypeofExpression typeofExpression; 2739 TypeidExpression typeidExpression; 2740 ArrayLiteral arrayLiteral; 2741 AssocArrayLiteral assocArrayLiteral; 2742 Expression expression; 2743 IsExpression isExpression; 2744 LambdaExpression lambdaExpression; 2745 FunctionLiteralExpression functionLiteralExpression; 2746 TraitsExpression traitsExpression; 2747 MixinExpression mixinExpression; 2748 ImportExpression importExpression; 2749 Vector vector; 2750 **/ 2751 2752 with(primaryExpression) 2753 { 2754 if (dot != tok!"") put("."); 2755 if (basicType != tok!"") format(basicType); 2756 if (primary != tok!"") 2757 { 2758 if (basicType != tok!"") put("."); // i.e. : uint.max 2759 format(primary); 2760 } 2761 2762 if (expression) 2763 { 2764 putTag("<span class=\"parenthetical-expression\">(<span class=\"parenthetical-expression-contents\">"); 2765 format(expression); 2766 putTag("</span>)</span>"); 2767 } 2768 else if (identifierOrTemplateInstance) 2769 { 2770 format(identifierOrTemplateInstance); 2771 } 2772 else if (typeofExpression) format(typeofExpression); 2773 else if (typeidExpression) format(typeidExpression); 2774 else if (arrayLiteral) format(arrayLiteral); 2775 else if (assocArrayLiteral) format(assocArrayLiteral); 2776 else if (isExpression) format(isExpression); 2777 else if (lambdaExpression) { putTag("<span class=\"lambda-expression\">"); format(lambdaExpression); putTag("</span>"); } 2778 else if (functionLiteralExpression) format(functionLiteralExpression); 2779 else if (traitsExpression) format(traitsExpression); 2780 else if (mixinExpression) format(mixinExpression); 2781 else if (importExpression) format(importExpression); 2782 else if (vector) format(vector); 2783 } 2784 } 2785 2786 override void format(const IsExpression isExpression) { 2787 //Formatter!Sink.format(this); 2788 2789 with(isExpression) { 2790 putTag(`<a href="http://dpldocs.info/is-expression" class="lang-feature">is</a>(`); 2791 if (type) format(type); 2792 if (identifier != tok!"") { 2793 space(); 2794 format(identifier); 2795 } 2796 2797 if (equalsOrColon) { 2798 space(); 2799 put(tokenRep(equalsOrColon)); 2800 space(); 2801 } 2802 2803 if (typeSpecialization) format(typeSpecialization); 2804 if (templateParameterList) { 2805 put(", "); 2806 format(templateParameterList); 2807 } 2808 put(")"); 2809 } 2810 } 2811 2812 override void format(const TypeofExpression typeofExpr) { 2813 debug(verbose) writeln("TypeofExpression"); 2814 2815 /** 2816 Expression expression; 2817 Token return_; 2818 **/ 2819 2820 putTag("<a href=\"http://dpldocs.info/typeof-expression\" class=\"lang-feature\">typeof</a>("); 2821 typeofExpr.expression ? format(typeofExpr.expression) : format(typeofExpr.return_); 2822 put(")"); 2823 } 2824 2825 2826 override void format(const Parameter parameter) 2827 { 2828 debug(verbose) writeln("Parameter"); 2829 2830 /** 2831 IdType[] parameterAttributes; 2832 Type type; 2833 Token name; 2834 bool vararg; 2835 AssignExpression default_; 2836 TypeSuffix[] cstyle; 2837 **/ 2838 2839 putTag("<div class=\"runtime-parameter-item parameter-item\">"); 2840 2841 2842 bool hadAtAttribute; 2843 2844 putTag("<span class=\"parameter-type-holder\">"); 2845 putTag("<span class=\"parameter-type\">"); 2846 foreach (count, attribute; parameter.parameterAttributes) 2847 { 2848 if (count || hadAtAttribute) space(); 2849 putTag("<span class=\"storage-class\">"); 2850 put(tokenRep(attribute)); 2851 putTag("</span>"); 2852 } 2853 2854 if (parameter.parameterAttributes.length > 0) 2855 space(); 2856 2857 if (parameter.type !is null) 2858 format(parameter.type); 2859 2860 putTag(`</span>`); 2861 putTag(`</span>`); 2862 2863 if (parameter.name.type != tok!"") 2864 { 2865 space(); 2866 putTag(`<span class="parameter-name name" data-ident="`~parameter.name.text~`">`); 2867 putTag("<a href=\"#param-" ~ parameter.name.text ~ "\">"); 2868 put(parameter.name.text); 2869 putTag("</a>"); 2870 putTag("</span>"); 2871 } 2872 2873 foreach(suffix; parameter.cstyle) 2874 format(suffix); 2875 2876 if (parameter.default_) 2877 { 2878 putTag(`<span class="parameter-default-value">`); 2879 putTag(" = "); 2880 format(parameter.default_); 2881 putTag(`</span>`); 2882 } 2883 2884 if (parameter.vararg) { 2885 putTag("<a href=\"http://dpldocs.info/typed-variadic-function-arguments\" class=\"lang-feature\">"); 2886 put("..."); 2887 putTag("</a>"); 2888 } 2889 2890 putTag("</div>"); 2891 } 2892 2893 override void format(const Type type) 2894 { 2895 debug(verbose) writeln("Type("); 2896 2897 /** 2898 IdType[] typeConstructors; 2899 TypeSuffix[] typeSuffixes; 2900 Type2 type2; 2901 **/ 2902 2903 foreach (count, constructor; type.typeConstructors) 2904 { 2905 if (count) space(); 2906 put(tokenRep(constructor)); 2907 } 2908 2909 if (type.typeConstructors.length) space(); 2910 2911 //put(`<span class="name" data-ident="`~tokenRep(token)~`">`); 2912 format(type.type2); 2913 //put("</span>"); 2914 2915 foreach (suffix; type.typeSuffixes) 2916 format(suffix); 2917 2918 debug(verbose) writeln(")"); 2919 } 2920 2921 override void format(const Type2 type2) 2922 { 2923 debug(verbose) writeln("Type2"); 2924 2925 /** 2926 IdType builtinType; 2927 Symbol symbol; 2928 TypeofExpression typeofExpression; 2929 IdentifierOrTemplateChain identifierOrTemplateChain; 2930 IdType typeConstructor; 2931 Type type; 2932 **/ 2933 2934 if (type2.symbol !is null) 2935 { 2936 suppressMagic = true; 2937 scope(exit) suppressMagic = false; 2938 2939 Decl link; 2940 if(context) 2941 link = context.lookupName(type2.symbol); 2942 if(link !is null) { 2943 putTag("<a href=\""~link.link~"\" title=\""~link.fullyQualifiedName~"\" class=\"xref\">"); 2944 format(type2.symbol); 2945 putTag("</a>"); 2946 } else if(toText(type2.symbol) == "string") { 2947 putTag("<span class=\"builtin-type\">"); 2948 put("string"); 2949 putTag("</span>"); 2950 } else { 2951 format(type2.symbol); 2952 } 2953 } 2954 else if (type2.typeofExpression !is null) 2955 { 2956 format(type2.typeofExpression); 2957 if (type2.identifierOrTemplateChain) 2958 { 2959 put("."); 2960 format(type2.identifierOrTemplateChain); 2961 } 2962 return; 2963 } 2964 else if (type2.typeConstructor != tok!"") 2965 { 2966 putTag("<span class=\"type-constructor\">"); 2967 put(tokenRep(type2.typeConstructor)); 2968 putTag("</span>"); 2969 put("("); 2970 format(type2.type); 2971 put(")"); 2972 } 2973 else 2974 { 2975 putTag("<span class=\"builtin-type\">"); 2976 put(tokenRep(type2.builtinType)); 2977 putTag("</span>"); 2978 } 2979 } 2980 2981 2982 override void format(const StorageClass storageClass) 2983 { 2984 debug(verbose) writeln("StorageClass"); 2985 2986 /** 2987 AtAttribute atAttribute; 2988 Deprecated deprecated_; 2989 LinkageAttribute linkageAttribute; 2990 Token token; 2991 **/ 2992 2993 with(storageClass) 2994 { 2995 if (atAttribute) format(atAttribute); 2996 else if (deprecated_) format(deprecated_); 2997 else if (linkageAttribute) format(linkageAttribute); 2998 else { 2999 putTag("<span class=\"storage-class\">"); 3000 format(token); 3001 putTag("</span>"); 3002 } 3003 } 3004 } 3005 3006 3007 bool noTag; 3008 override void format(const Token token) 3009 { 3010 debug(verbose) writeln("Token ", tokenRep(token)); 3011 if(!noTag && token == tok!"identifier") { 3012 putTag(`<span class="name" data-ident="`~tokenRep(token)~`">`); 3013 } 3014 auto rep = tokenRep(token); 3015 if(rep == "delegate" || rep == "function") { 3016 putTag(`<span class="lang-feature">`); 3017 put(rep); 3018 putTag(`</span>`); 3019 } else { 3020 put(rep); 3021 } 3022 if(!noTag && token == tok!"identifier") { 3023 putTag("</span>"); 3024 } 3025 } 3026 3027 bool suppressMagic = false; 3028 3029 override void format(const IdentifierOrTemplateInstance identifierOrTemplateInstance) 3030 { 3031 debug(verbose) writeln("IdentifierOrTemplateInstance"); 3032 3033 bool suppressMagicGoingIn = suppressMagic; 3034 3035 //if(!suppressMagicGoingIn) 3036 //putTag("<span class=\"some-ident\">"); 3037 with(identifierOrTemplateInstance) 3038 { 3039 format(identifier); 3040 if (templateInstance) 3041 format(templateInstance); 3042 } 3043 //if(!suppressMagicGoingIn) 3044 //putTag("</span>"); 3045 } 3046 3047 version(none) 3048 override void format(const Symbol symbol) 3049 { 3050 debug(verbose) writeln("Symbol"); 3051 3052 put("GOOD/"); 3053 if (symbol.dot) 3054 put("."); 3055 format(symbol.identifierOrTemplateChain); 3056 put("/GOOD"); 3057 } 3058 3059 override void format(const Parameters parameters) 3060 { 3061 debug(verbose) writeln("Parameters"); 3062 3063 /** 3064 Parameter[] parameters; 3065 bool hasVarargs; 3066 **/ 3067 3068 putTag("<div class=\"parameters-list\">"); 3069 putTag("<span class=\"paren\">(</span>"); 3070 foreach (count, param; parameters.parameters) 3071 { 3072 if (count) putTag("<span class=\"comma\">,</span>"); 3073 format(param); 3074 } 3075 if (parameters.hasVarargs) 3076 { 3077 if (parameters.parameters.length) 3078 putTag("<span class=\"comma\">,</span>"); 3079 putTag("<div class=\"runtime-parameter-item parameter-item\">"); 3080 putTag("<a href=\"http://dpldocs.info/variadic-function-arguments\" class=\"lang-feature\">"); 3081 putTag("..."); 3082 putTag("</a>"); 3083 putTag("</div>"); 3084 } 3085 putTag("<span class=\"paren\">)</span>"); 3086 putTag("</div>"); 3087 } 3088 3089 override void format(const IdentifierChain identifierChain) 3090 { 3091 //put("IDENT"); 3092 foreach(count, ident; identifierChain.identifiers) 3093 { 3094 if (count) put("."); 3095 put(ident.text); 3096 } 3097 //put("/IDENT"); 3098 } 3099 3100 override void format(const AndAndExpression andAndExpression) 3101 { 3102 with(andAndExpression) 3103 { 3104 putTag("<div class=\"andand-left\">"); 3105 format(left); 3106 if (right) 3107 { 3108 putTag(" && </div><div class=\"andand-right\">"); 3109 format(right); 3110 } 3111 putTag("</div>"); 3112 } 3113 } 3114 3115 override void format(const OrOrExpression orOrExpression) 3116 { 3117 with(orOrExpression) 3118 { 3119 putTag("<div class=\"oror-left\">"); 3120 format(left); 3121 if (right) 3122 { 3123 putTag(" || </div><div class=\"oror-right\">"); 3124 format(right); 3125 } 3126 putTag("</div>"); 3127 } 3128 } 3129 3130 alias format = Formatter!Sink.format; 3131 } 3132 3133 3134 /* 3135 The small table looks like this: 3136 3137 3138 Caption 3139 Header | Row 3140 Data | Row 3141 Data | Row 3142 */ 3143 Element translateSmallTable(string text, Decl decl) { 3144 auto holder = Element.make("table"); 3145 holder.addClass("small-table"); 3146 3147 void skipWhitespace() { 3148 while(text.length && (text[0] == ' ' || text[0] == '\t')) 3149 text = text[1 .. $]; 3150 } 3151 3152 string[] extractLine() { 3153 string[] cells; 3154 3155 skipWhitespace(); 3156 size_t i; 3157 3158 while(i < text.length) { 3159 static string lastText; 3160 if(text[i .. $] is lastText) 3161 assert(0, lastText); 3162 3163 lastText = text[i .. $]; 3164 switch(text[i]) { 3165 case '|': 3166 cells ~= text[0 .. i].strip; 3167 text = text[i + 1 .. $]; 3168 i = 0; 3169 break; 3170 case '`': 3171 i++; 3172 while(i < text.length && text[i] != '`') 3173 i++; 3174 if(i < text.length) 3175 i++; // skip closing ` 3176 break; 3177 case '[': 3178 case '(': 3179 case '{': 3180 i++; // FIXME? skip these? 3181 break; 3182 case '$': 3183 auto info = macroInformation(text[i .. $]); 3184 i += info.terminatingOffset + 1; 3185 break; 3186 case '\n': 3187 cells ~= text[0 .. i].strip; 3188 text = text[i + 1 .. $]; 3189 return cells; 3190 break; 3191 default: 3192 i++; 3193 } 3194 } 3195 3196 return cells; 3197 } 3198 3199 bool isDecorative(string[] row) { 3200 foreach(s; row) 3201 foreach(char c; s) 3202 if(c != '+' && c != '-' && c != '|' && c != '=') 3203 return false; 3204 return true; 3205 } 3206 3207 Element tbody; 3208 bool first = true; 3209 bool twodTable; 3210 bool firstLine = true; 3211 string[] headerRow; 3212 while(text.length) { 3213 auto row = extractLine(); 3214 3215 if(row.length == 0) 3216 continue; // should never happen... 3217 3218 if(row.length == 1 && row[0].length == 0) 3219 continue; // blank line 3220 3221 if(isDecorative(row)) 3222 continue; // ascii art is allowed, but ignored 3223 3224 if(firstLine && row.length == 1 && row[0].length) { 3225 firstLine = false; 3226 holder.addChild("caption", row[0]); 3227 continue; 3228 } 3229 3230 firstLine = false; 3231 3232 // empty first and last rows are just surrounding pipes and can be ignored 3233 if(row[0].length == 0) 3234 row = row[1 .. $]; 3235 if(row[$-1].length == 0) 3236 row = row[0 .. $-1]; 3237 3238 if(first) { 3239 auto thead = holder.addChild("thead"); 3240 auto tr = thead.addChild("tr"); 3241 3242 headerRow = row; 3243 3244 if(row[0].length == 0) 3245 twodTable = true; 3246 3247 foreach(cell; row) 3248 tr.addChild("th", formatDocumentationComment2(cell, decl, null)); 3249 first = false; 3250 } else { 3251 if(tbody is null) 3252 tbody = holder.addChild("tbody"); 3253 auto tr = tbody.addChild("tr"); 3254 3255 while(row.length < headerRow.length) 3256 row ~= ""; 3257 3258 foreach(i, cell; row) 3259 tr.addChild((twodTable && i == 0) ? "th" : "td", formatDocumentationComment2(cell, decl, null)); 3260 3261 } 3262 } 3263 3264 // FIXME: format the cells 3265 3266 if(twodTable) 3267 holder.addClass("two-axes"); 3268 3269 3270 return holder; 3271 } 3272 3273 Element translateListTable(string text, Decl decl) { 3274 auto holder = Element.make("table"); 3275 holder.addClass("user-table"); 3276 3277 DocCommentTerminationCondition termination; 3278 termination.terminationStrings = ["*", "-", "+"]; 3279 termination.mustBeAtStartOfLine = true; 3280 3281 string nextTag; 3282 3283 Element row; 3284 3285 do { 3286 auto fmt = formatDocumentationComment2(text, decl, null, &termination); 3287 if(fmt.toString.strip.length) { 3288 if(row is null) 3289 holder.addChild("caption", fmt); 3290 else 3291 row.addChild(nextTag, fmt); 3292 } 3293 text = termination.remaining; 3294 if(termination.terminatedOn == "*") 3295 row = holder.addChild("tr"); 3296 else if(termination.terminatedOn == "+") 3297 nextTag = "th"; 3298 else if(termination.terminatedOn == "-") 3299 nextTag = "td"; 3300 else {} 3301 } while(text.length); 3302 3303 if(holder.querySelector("th:first-child + td")) 3304 holder.addClass("two-axes"); 3305 3306 return holder; 3307 } 3308 3309 Element translateList(string text, Decl decl, string tagName) { 3310 auto holder = Element.make(tagName); 3311 holder.addClass("user-list"); 3312 3313 DocCommentTerminationCondition termination; 3314 termination.terminationStrings = ["*"]; 3315 termination.mustBeAtStartOfLine = true; 3316 3317 auto opening = formatDocumentationComment2(text, decl, null, &termination); 3318 text = termination.remaining; 3319 3320 translateMagicCommands(opening, holder); 3321 3322 while(text.length) { 3323 auto fmt = formatDocumentationComment2(text, decl, null, &termination); 3324 holder.addChild("li", fmt); 3325 text = termination.remaining; 3326 } 3327 3328 return holder; 3329 } 3330 3331 Html syntaxHighlightCss(string code) { 3332 string highlighted; 3333 3334 3335 int pullPiece(string code, bool includeDot) { 3336 int i; 3337 while(i < code.length && ( 3338 (code[i] >= '0' && code[i] <= '9') 3339 || 3340 (code[i] >= 'a' && code[i] <= 'z') 3341 || 3342 (code[i] >= 'A' && code[i] <= 'Z') 3343 || 3344 (includeDot && code[i] == '.') || code[i] == '_' || code[i] == '-' || code[i] == ':' // for css i want to match like :first-letter 3345 )) 3346 { 3347 i++; 3348 } 3349 return i; 3350 } 3351 3352 3353 3354 while(code.length) { 3355 switch(code[0]) { 3356 case '\'': 3357 case '\"': 3358 // string literal 3359 char start = code[0]; 3360 size_t i = 1; 3361 while(i < code.length && code[i] != start && code[i] != '\n') { 3362 if(code[i] == '\\') 3363 i++; // skip escaped char too 3364 i++; 3365 } 3366 3367 i++; // skip closer 3368 highlighted ~= "<span class=\"highlighted-string\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3369 code = code[i .. $]; 3370 break; 3371 case '/': 3372 // check for comment 3373 if(code.length > 1 && code[1] == '*') { 3374 // a star comment 3375 size_t i; 3376 while((i+1) < code.length && !(code[i] == '*' && code[i+1] == '/')) 3377 i++; 3378 3379 if(i < code.length) 3380 i+=2; // skip the */ 3381 3382 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3383 code = code[i .. $]; 3384 } else { 3385 highlighted ~= '/'; 3386 code = code[1 .. $]; 3387 } 3388 break; 3389 case '<': 3390 highlighted ~= "<"; 3391 code = code[1 .. $]; 3392 break; 3393 case '>': 3394 highlighted ~= ">"; 3395 code = code[1 .. $]; 3396 break; 3397 case '&': 3398 highlighted ~= "&"; 3399 code = code[1 .. $]; 3400 break; 3401 case '@': 3402 // @media, @import, @font-face, etc 3403 auto i = pullPiece(code[1 .. $], false); 3404 if(i) 3405 i++; 3406 else 3407 goto plain; 3408 highlighted ~= "<span class=\"highlighted-preprocessor-directive\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3409 code = code[i .. $]; 3410 break; 3411 case '\n', ';': 3412 // check for a new rule (imprecise since we don't actually parse, but meh 3413 // will ignore indent, then see if there is a word-like-this followed by : 3414 int i = 1; 3415 while(i < code.length && (code[i] == ' ' || code[i] == '\t')) 3416 i++; 3417 int start = i; 3418 while(i < code.length && (code[i] == '-' || (code[i] >= 'a' && code[i] <= 'z'))) 3419 i++; 3420 if(start == i || i == code.length || code[i] != ':') 3421 goto plain; 3422 3423 highlighted ~= code[0 .. start]; 3424 highlighted ~= "<span class=\"highlighted-named-constant\">" ~ htmlEntitiesEncode(code[start .. i]) ~ "</span>"; 3425 code = code[i .. $]; 3426 break; 3427 case '[': 3428 // attribute match rule 3429 auto i = pullPiece(code[1 .. $], false); 3430 if(i) 3431 i++; 3432 else 3433 goto plain; 3434 highlighted ~= "<span class=\"highlighted-attribute-name\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3435 code = code[i .. $]; 3436 break; 3437 case '#', '.': // id or class selector 3438 auto i = pullPiece(code[1 .. $], false); 3439 if(i) 3440 i++; 3441 else 3442 goto plain; 3443 highlighted ~= "<span class=\"highlighted-identifier\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3444 code = code[i .. $]; 3445 break; 3446 case ':': 3447 // pseudoclass 3448 auto i = pullPiece(code, false); 3449 highlighted ~= "<span class=\"highlighted-preprocessor-directive\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3450 code = code[i .. $]; 3451 break; 3452 case '(': 3453 // just skipping stuff in parens... 3454 int parenCount = 0; 3455 int pos = 0; 3456 bool inQuote; 3457 bool escaped; 3458 while(pos < code.length) { 3459 if(code[pos] == '\\') 3460 escaped = true; 3461 if(!escaped && code[pos] == '"') 3462 inQuote = !inQuote; 3463 if(!inQuote && code[pos] == '(') 3464 parenCount++; 3465 if(!inQuote && code[pos] == ')') 3466 parenCount--; 3467 pos++; 3468 if(parenCount == 0) 3469 break; 3470 } 3471 3472 highlighted ~= "<span class=\"highlighted-identifier\">" ~ htmlEntitiesEncode(code[0 .. pos]) ~ "</span>"; 3473 code = code[pos .. $]; 3474 break; 3475 case '0': .. case '9': 3476 // number. including text at the end cuz it is probably a unit 3477 auto i = pullPiece(code, true); 3478 highlighted ~= "<span class=\"highlighted-number\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3479 code = code[i .. $]; 3480 break; 3481 default: 3482 plain: 3483 highlighted ~= code[0]; 3484 code = code[1 .. $]; 3485 } 3486 } 3487 3488 return Html(highlighted); 3489 } 3490 3491 string highlightHtmlTag(string tag) { 3492 string highlighted = "<"; 3493 3494 bool isWhite(char c) { 3495 return c == ' ' || c == '\t' || c == '\n' || c == '\r'; 3496 } 3497 3498 tag = tag[1 .. $]; 3499 3500 if(tag[0] == '/') { 3501 highlighted ~= '/'; 3502 tag = tag[1 .. $]; 3503 } 3504 3505 int i = 0; 3506 while(i < tag.length && !isWhite(tag[i]) && tag[i] != '>') { 3507 i++; 3508 } 3509 3510 highlighted ~= "<span class=\"highlighted-tag-name\">" ~ htmlEntitiesEncode(tag[0 .. i]) ~ "</span>"; 3511 tag = tag[i .. $]; 3512 3513 while(tag.length && tag[0] != '>') { 3514 while(isWhite(tag[0])) { 3515 highlighted ~= tag[0]; 3516 tag = tag[1 .. $]; 3517 } 3518 3519 i = 0; 3520 while(i < tag.length && tag[i] != '=' && tag[i] != '>') 3521 i++; 3522 3523 highlighted ~= "<span class=\"highlighted-attribute-name\">" ~ htmlEntitiesEncode(tag[0 .. i]) ~ "</span>"; 3524 tag = tag[i .. $]; 3525 3526 if(tag.length && tag[0] == '=') { 3527 highlighted ~= '='; 3528 tag = tag[1 .. $]; 3529 if(tag.length && (tag[0] == '\'' || tag[0] == '"')) { 3530 char end = tag[0]; 3531 i = 1; 3532 while(i < tag.length && tag[i] != end) 3533 i++; 3534 if(i < tag.length) 3535 i++; 3536 3537 highlighted ~= "<span class=\"highlighted-attribute-value\">" ~ htmlEntitiesEncode(tag[0 .. i]) ~ "</span>"; 3538 tag = tag[i .. $]; 3539 } else if(tag.length) { 3540 i = 0; 3541 while(i < tag.length && !isWhite(tag[i])) 3542 i++; 3543 3544 highlighted ~= "<span class=\"highlighted-attribute-value\">" ~ htmlEntitiesHighlight(tag[0 .. i]) ~ "</span>"; 3545 tag = tag[i .. $]; 3546 } 3547 } 3548 } 3549 3550 3551 highlighted ~= htmlEntitiesEncode(tag); 3552 3553 return highlighted; 3554 } 3555 3556 string htmlEntitiesHighlight(string code) { 3557 string highlighted; 3558 3559 bool isWhite(char c) { 3560 return c == ' ' || c == '\t' || c == '\n' || c == '\r'; 3561 } 3562 3563 while(code.length) 3564 switch(code[0]) { 3565 case '&': 3566 // possibly an entity 3567 int i = 0; 3568 while(i < code.length && code[i] != ';' && !isWhite(code[i])) 3569 i++; 3570 if(i == code.length || isWhite(code[i])) { 3571 highlighted ~= "&"; 3572 code = code[1 .. $]; 3573 } else { 3574 i++; 3575 highlighted ~= 3576 "<abbr class=\"highlighted-entity\" title=\"Entity: "~code[0 .. i].replace("\"", """)~"\">" ~ 3577 htmlEntitiesEncode(code[0 .. i]) ~ 3578 "</abbr>"; 3579 code = code[i .. $]; 3580 } 3581 break; 3582 case '<': 3583 highlighted ~= "<"; 3584 code = code[1 .. $]; 3585 break; 3586 case '"': 3587 highlighted ~= """; 3588 code = code[1 .. $]; 3589 break; 3590 case '>': 3591 highlighted ~= ">"; 3592 code = code[1 .. $]; 3593 break; 3594 default: 3595 highlighted ~= code[0]; 3596 code = code[1 .. $]; 3597 } 3598 3599 return highlighted; 3600 } 3601 3602 Html syntaxHighlightRuby(string code) { 3603 // FIXME this is quite minimal and crappy 3604 // ruby just needs to be parsed to be lexed! 3605 // and i didn't implement its myriad of strings 3606 3607 static immutable string[] rubyKeywords = [ 3608 "BEGIN", "ensure", "self", "when", 3609 "END", "not", "super", "while", 3610 "alias", "defined", "for", "or", "then", "yield", 3611 "and", "do", "if", "redo", 3612 "begin", "else", "in", "rescue", "undef", 3613 "break", "elsif", "retry", "unless", 3614 "case", "end", "next", "return", "until", 3615 ]; 3616 3617 static immutable string[] rubyPreprocessors = [ 3618 "require", "include" 3619 ]; 3620 3621 static immutable string[] rubyTypes = [ 3622 "class", "def", "module" 3623 ]; 3624 3625 static immutable string[] rubyConstants = [ 3626 "nil", "false", "true", 3627 ]; 3628 3629 bool lastWasType; 3630 char lastChar; 3631 int indentLevel; 3632 int[] declarationIndentLevels; 3633 3634 string highlighted; 3635 3636 while(code.length) { 3637 bool thisIterWasType = false; 3638 auto ch = code[0]; 3639 switch(ch) { 3640 case '#': 3641 size_t i; 3642 while(i < code.length && code[i] != '\n') 3643 i++; 3644 3645 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3646 code = code[i .. $]; 3647 break; 3648 case '<': 3649 highlighted ~= "<"; 3650 code = code[1 .. $]; 3651 break; 3652 case '>': 3653 highlighted ~= ">"; 3654 code = code[1 .. $]; 3655 break; 3656 case '&': 3657 highlighted ~= "&"; 3658 code = code[1 .. $]; 3659 break; 3660 case '0': .. case '9': 3661 // number literal 3662 size_t i; 3663 while(i < code.length && ( 3664 (code[i] >= '0' && code[i] <= '9') 3665 || 3666 (code[i] >= 'a' && code[i] <= 'z') 3667 || 3668 (code[i] >= 'A' && code[i] <= 'Z') 3669 || 3670 code[i] == '.' || code[i] == '_' 3671 )) 3672 { 3673 i++; 3674 } 3675 3676 highlighted ~= "<span class=\"highlighted-number\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3677 code = code[i .. $]; 3678 break; 3679 case '"', '\'': 3680 // string 3681 // check for triple-quoted string 3682 if(ch == '"') { 3683 3684 // double quote string 3685 auto idx = 1; 3686 bool escaped = false; 3687 int nestedCount = 0; 3688 bool justSawHash = false; 3689 while(!escaped && nestedCount == 0 && code[idx] != '"') { 3690 if(justSawHash && code[idx] == '{') 3691 nestedCount++; 3692 if(nestedCount && code[idx] == '}') 3693 nestedCount--; 3694 3695 if(!escaped && code[idx] == '#') 3696 justSawHash = true; 3697 3698 if(code[idx] == '\\') 3699 escaped = true; 3700 else { 3701 escaped = false; 3702 } 3703 3704 idx++; 3705 } 3706 idx++; 3707 3708 highlighted ~= "<span class=\"highlighted-string\">" ~ htmlEntitiesEncode(code[0 .. idx]) ~ "</span>"; 3709 code = code[idx .. $]; 3710 } else { 3711 int end = 1; 3712 bool escaped; 3713 while(end < code.length && !escaped && code[end] != ch) { 3714 escaped = (code[end] == '\\'); 3715 end++; 3716 } 3717 3718 if(end < code.length) 3719 end++; 3720 3721 highlighted ~= "<span class=\"highlighted-string\">" ~ htmlEntitiesEncode(code[0 .. end]) ~ "</span>"; 3722 code = code[end .. $]; 3723 } 3724 break; 3725 case '\n': 3726 indentLevel = 0; 3727 int i = 1; 3728 while(i < code.length && (code[i] == ' ' || code[i] == '\t')) { 3729 i++; 3730 indentLevel++; 3731 } 3732 highlighted ~= ch; 3733 code = code[1 .. $]; 3734 break; 3735 case ' ', '\t': 3736 thisIterWasType = lastWasType; // don't change the last counter on just whitespace 3737 highlighted ~= ch; 3738 code = code[1 .. $]; 3739 break; 3740 case ':': 3741 // check for ruby symbol 3742 int nameEnd = 1; 3743 while(nameEnd < code.length && ( 3744 (code[nameEnd] >= 'a' && code[nameEnd] <= 'z') || 3745 (code[nameEnd] >= 'A' && code[nameEnd] <= 'Z') || 3746 (code[nameEnd] >= '0' && code[nameEnd] <= '0') || 3747 code[nameEnd] == '_' 3748 )) 3749 { 3750 nameEnd++; 3751 } 3752 3753 if(nameEnd > 1) { 3754 highlighted ~= "<span class=\"highlighted-string\">" ~ htmlEntitiesEncode(code[0 .. nameEnd]) ~ "</span>"; 3755 code = code[nameEnd .. $]; 3756 } else { 3757 highlighted ~= ch; 3758 code = code[1 .. $]; 3759 } 3760 break; 3761 default: 3762 // check for names 3763 int nameEnd = 0; 3764 while(nameEnd < code.length && ( 3765 (code[nameEnd] >= 'a' && code[nameEnd] <= 'z') || 3766 (code[nameEnd] >= 'A' && code[nameEnd] <= 'Z') || 3767 (code[nameEnd] >= '0' && code[nameEnd] <= '0') || 3768 code[nameEnd] == '_' 3769 )) 3770 { 3771 nameEnd++; 3772 } 3773 3774 if(nameEnd) { 3775 auto name = code[0 .. nameEnd]; 3776 code = code[nameEnd .. $]; 3777 3778 if(rubyTypes.canFind(name)) { 3779 highlighted ~= "<span class=\"highlighted-type\">" ~ name ~ "</span>"; 3780 thisIterWasType = true; 3781 declarationIndentLevels ~= indentLevel; 3782 } else if(rubyConstants.canFind(name)) { 3783 highlighted ~= "<span class=\"highlighted-number\">" ~ name ~ "</span>"; 3784 } else if(rubyPreprocessors.canFind(name)) { 3785 highlighted ~= "<span class=\"highlighted-preprocessor-directive\">" ~ name ~ "</span>"; 3786 } else if(rubyKeywords.canFind(name)) { 3787 if(name == "end") { 3788 if(declarationIndentLevels.length && indentLevel == declarationIndentLevels[$-1]) { 3789 // cheating on matching ends with declarations: if indentation matches... 3790 highlighted ~= "<span class=\"highlighted-type\">" ~ name ~ "</span>"; 3791 declarationIndentLevels = declarationIndentLevels[0 .. $-1]; 3792 } else { 3793 highlighted ~= "<span class=\"highlighted-keyword\">" ~ name ~ "</span>"; 3794 } 3795 } else { 3796 highlighted ~= "<span class=\"highlighted-keyword\">" ~ name ~ "</span>"; 3797 } 3798 } else { 3799 if(lastWasType) { 3800 highlighted ~= "<span class=\"highlighted-identifier\">" ~ name ~ "</span>"; 3801 } else { 3802 if(name.length && name[0] >= 'A' && name[0] <= 'Z') 3803 highlighted ~= "<span class=\"highlighted-named-constant\">" ~ name ~ "</span>"; 3804 else 3805 highlighted ~= name; 3806 } 3807 } 3808 } else { 3809 highlighted ~= ch; 3810 code = code[1 .. $]; 3811 } 3812 break; 3813 } 3814 lastChar = ch; 3815 lastWasType = thisIterWasType; 3816 } 3817 3818 return Html(highlighted); 3819 } 3820 3821 Html syntaxHighlightPython(string code) { 3822 /* 3823 I just want to support: 3824 numbers 3825 anything starting with 0, then going alphanumeric 3826 keywords 3827 from and import are "preprocessor" 3828 None, True, and False are number literal colored 3829 name 3830 an identifier after the "def" keyword, possibly including : if in there. my vim does it light blue 3831 comments 3832 only # .. \n is allowed. 3833 strings 3834 single quote must end on the same line 3835 triple quote span multiple line. 3836 3837 3838 Extracting python definitions: 3839 class foo: 3840 # method baz, arg: bar, doc string attached 3841 def baz(bar): 3842 """ this is the doc string """ 3843 3844 will just have to follow indentation (BARF) 3845 3846 Note: python has default arguments. 3847 3848 3849 Python implicitly does line continuation if parens, brackets, or braces open 3850 you can also explicitly extend with \ at the end 3851 in these cases, the indentation doesn't count. 3852 3853 If there's only comments on a line, the indentation is also exempt. 3854 */ 3855 static immutable string[] pythonKeywords = [ 3856 "and", "del", "not", "while", 3857 "as", "elif", "global", "or", "with", 3858 "assert", "else", "if", "pass", "yield", 3859 "break", "except", "print", 3860 "exec", "in", "raise", 3861 "continue", "finally", "is", "return", 3862 "for", "lambda", "try", 3863 ]; 3864 3865 static immutable string[] pythonPreprocessors = [ 3866 "from", "import" 3867 ]; 3868 3869 static immutable string[] pythonTypes = [ 3870 "class", "def" 3871 ]; 3872 3873 static immutable string[] pythonConstants = [ 3874 "True", "False", "None" 3875 ]; 3876 3877 string[] indentStack; 3878 int openParenCount = 0; 3879 int openBraceCount = 0; 3880 int openBracketCount = 0; 3881 char lastChar; 3882 bool lastWasType; 3883 3884 string highlighted; 3885 3886 // ensure there is one and exactly one new line at the end 3887 // the highlighter uses the final \n as a chance to clean up the 3888 // indent divs 3889 code = code.stripRight; 3890 code ~= "\n"; 3891 3892 int lineCountSpace = 5; // if python is > 1000 lines it can slightly throw off the indent highlight... but meh. 3893 3894 while(code.length) { 3895 bool thisIterWasType = false; 3896 auto ch = code[0]; 3897 switch(ch) { 3898 case '#': 3899 size_t i; 3900 while(i < code.length && code[i] != '\n') 3901 i++; 3902 3903 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3904 code = code[i .. $]; 3905 break; 3906 case '(': 3907 openParenCount++; 3908 highlighted ~= ch; 3909 code = code[1 .. $]; 3910 break; 3911 case '[': 3912 openBracketCount++; 3913 highlighted ~= ch; 3914 code = code[1 .. $]; 3915 break; 3916 case '{': 3917 openBraceCount++; 3918 highlighted ~= ch; 3919 code = code[1 .. $]; 3920 break; 3921 case ')': 3922 openParenCount--; 3923 highlighted ~= ch; 3924 code = code[1 .. $]; 3925 break; 3926 case ']': 3927 openBracketCount--; 3928 highlighted ~= ch; 3929 code = code[1 .. $]; 3930 break; 3931 case '}': 3932 openBraceCount--; 3933 highlighted ~= ch; 3934 code = code[1 .. $]; 3935 break; 3936 case '<': 3937 highlighted ~= "<"; 3938 code = code[1 .. $]; 3939 break; 3940 case '>': 3941 highlighted ~= ">"; 3942 code = code[1 .. $]; 3943 break; 3944 case '&': 3945 highlighted ~= "&"; 3946 code = code[1 .. $]; 3947 break; 3948 case '0': .. case '9': 3949 // number literal 3950 size_t i; 3951 while(i < code.length && ( 3952 (code[i] >= '0' && code[i] <= '9') 3953 || 3954 (code[i] >= 'a' && code[i] <= 'z') 3955 || 3956 (code[i] >= 'A' && code[i] <= 'Z') 3957 || 3958 code[i] == '.' || code[i] == '_' 3959 )) 3960 { 3961 i++; 3962 } 3963 3964 highlighted ~= "<span class=\"highlighted-number\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 3965 code = code[i .. $]; 3966 break; 3967 case '"', '\'': 3968 // string 3969 // check for triple-quoted string 3970 if(ch == '"' && code.length > 2 && code[1] == '"' && code[2] == '"') { 3971 int end = 3; 3972 bool escaped; 3973 while(end + 3 < code.length && !escaped && code[end .. end+ 3] != `"""`) { 3974 escaped = (code[end] == '\\'); 3975 end++; 3976 } 3977 3978 if(end < code.length) 3979 end+= 3; 3980 3981 highlighted ~= "<span class=\"highlighted-string\">" ~ htmlEntitiesEncode(code[0 .. end]) ~ "</span>"; 3982 code = code[end .. $]; 3983 } else { 3984 // otherwise these are limited to one line, though since we 3985 // assume the program is well-formed, I'm not going to bother; 3986 // no need to check for Python errors here, just highlight 3987 int end = 1; 3988 bool escaped; 3989 while(end < code.length && !escaped && code[end] != ch) { 3990 escaped = (code[end] == '\\'); 3991 end++; 3992 } 3993 3994 if(end < code.length) 3995 end++; 3996 3997 highlighted ~= "<span class=\"highlighted-string\">" ~ htmlEntitiesEncode(code[0 .. end]) ~ "</span>"; 3998 code = code[end .. $]; 3999 } 4000 break; 4001 case '\n': 4002 string thisIndent = null; 4003 int thisIndentLength = lineCountSpace; 4004 // figure out indentation and stuff... 4005 if(lastChar == '\\' || openParenCount || openBracketCount || openBraceCount) { 4006 // line continuation, no special processing 4007 } else { 4008 if(code.length == 1) { 4009 // last line in the file, clean up the indentation 4010 int remove; 4011 foreach_reverse(i; indentStack) { 4012 // this isn't actually following the python rule which 4013 // is more like .startsWith but meh 4014 if(i.length <= thisIndent.length) 4015 break; 4016 highlighted ~= "</div>"; 4017 remove++; 4018 } 4019 indentStack = indentStack[0 .. $ - remove]; 4020 // NOT appending the final \n cuz that leads dead space in rendered doc 4021 4022 code = code[1 .. $]; 4023 break; 4024 } else 4025 foreach(idx, cha; code[1 .. $]) { 4026 if(cha == '\n') 4027 break; 4028 if(cha == '#') // comments exempt from indent processing too 4029 break; 4030 if(cha == ' ') 4031 thisIndentLength++; 4032 if(cha == '\t') 4033 thisIndentLength += 8 - (thisIndentLength % 8); 4034 if(cha != ' ' && cha != '\t') { 4035 thisIndent = code[1 .. idx + 1]; 4036 break; 4037 } 4038 } 4039 } 4040 4041 bool changedDiv = false; 4042 4043 if(thisIndent !is null) { // !is null rather than .length is important here. null means skip, "" may need processing 4044 // close open indents if necessary 4045 int remove; 4046 foreach_reverse(i; indentStack) { 4047 // this isn't actually following the python rule which 4048 // is more like .startsWith but meh 4049 if(i.length <= thisIndent.length) 4050 break; 4051 highlighted ~= "</div>"; 4052 changedDiv = true; 4053 remove++; 4054 } 4055 indentStack = indentStack[0 .. $ - remove]; 4056 } 4057 4058 if(thisIndent.length) { // but we only ever open a new one if there was non-zero indent 4059 // open a new one if appropriate 4060 if(indentStack.length == 0 || thisIndent.length > indentStack[$-1].length) { 4061 changedDiv = true; 4062 highlighted ~= "<div class=\"highlighted-python-indent\" style=\"background-position: calc("~to!string(thisIndentLength)~"ch - 2px);\">"; 4063 indentStack ~= thisIndent; 4064 } 4065 } 4066 4067 if(changedDiv) 4068 highlighted ~= "<span style=\"white-space: normal;\">"; 4069 4070 highlighted ~= ch; 4071 4072 if(changedDiv) 4073 highlighted ~= "</span>"; 4074 4075 code = code[1 .. $]; 4076 break; 4077 case ' ', '\t': 4078 thisIterWasType = lastWasType; // don't change the last counter on just whitespace 4079 highlighted ~= ch; 4080 code = code[1 .. $]; 4081 break; 4082 default: 4083 // check for names 4084 int nameEnd = 0; 4085 while(nameEnd < code.length && ( 4086 (code[nameEnd] >= 'a' && code[nameEnd] <= 'z') || 4087 (code[nameEnd] >= 'A' && code[nameEnd] <= 'Z') || 4088 (code[nameEnd] >= '0' && code[nameEnd] <= '0') || 4089 code[nameEnd] == '_' 4090 )) 4091 { 4092 nameEnd++; 4093 } 4094 4095 if(nameEnd) { 4096 auto name = code[0 .. nameEnd]; 4097 code = code[nameEnd .. $]; 4098 4099 if(pythonTypes.canFind(name)) { 4100 highlighted ~= "<span class=\"highlighted-type\">" ~ name ~ "</span>"; 4101 thisIterWasType = true; 4102 } else if(pythonConstants.canFind(name)) { 4103 highlighted ~= "<span class=\"highlighted-number\">" ~ name ~ "</span>"; 4104 } else if(pythonPreprocessors.canFind(name)) { 4105 highlighted ~= "<span class=\"highlighted-preprocessor-directive\">" ~ name ~ "</span>"; 4106 } else if(pythonKeywords.canFind(name)) { 4107 highlighted ~= "<span class=\"highlighted-keyword\">" ~ name ~ "</span>"; 4108 } else { 4109 if(lastWasType) { 4110 highlighted ~= "<span class=\"highlighted-identifier\">" ~ name ~ "</span>"; 4111 } else { 4112 highlighted ~= name; 4113 } 4114 } 4115 } else { 4116 highlighted ~= ch; 4117 code = code[1 .. $]; 4118 } 4119 break; 4120 } 4121 lastChar = ch; 4122 lastWasType = thisIterWasType; 4123 } 4124 4125 return Html(highlighted); 4126 } 4127 4128 Html syntaxHighlightHtml(string code, string language) { 4129 string highlighted; 4130 4131 bool isWhite(char c) { 4132 return c == ' ' || c == '\t' || c == '\n' || c == '\r'; 4133 } 4134 4135 while(code.length) { 4136 switch(code[0]) { 4137 case '<': 4138 if(code.length > 1 && code[1] == '!') { 4139 // comment or processing instruction 4140 4141 int i = 0; 4142 while(i < code.length && code[i] != '>') { 4143 i++; 4144 } 4145 if(i < code.length) 4146 i++; 4147 4148 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(code[0 .. i]) ~ "</span>"; 4149 code = code[i .. $]; 4150 continue; 4151 } else if(code.length > 1 && (code[1] == '/' || (code[1] >= 'a' && code[1] <= 'z') || (code[1] >= 'A' && code[1] <= 'Z'))) { 4152 // possibly a tag 4153 int i = 1; 4154 while(i < code.length && code[i] != '>' && code[i] != '<') 4155 i++; 4156 if(i == code.length || code[i] == '<') 4157 goto plain_lt; 4158 4159 auto tag = code[0 .. i + 1]; 4160 code = code[i + 1 .. $]; 4161 4162 highlighted ~= "<span class=\"highlighted-tag\">" ~ highlightHtmlTag(tag) ~ "</span>"; 4163 4164 if(language == "html" && tag.startsWith("<script")) { 4165 auto end = code.indexOf("</script>"); 4166 if(end == -1) 4167 continue; 4168 4169 auto sCode = code[0 .. end]; 4170 highlighted ~= syntaxHighlightCFamily(sCode, "javascript").source; 4171 code = code[end .. $]; 4172 } else if(language == "html" && tag.startsWith("<style")) { 4173 auto end = code.indexOf("</style>"); 4174 if(end == -1) 4175 continue; 4176 auto sCode = code[0 .. end]; 4177 highlighted ~= syntaxHighlightCss(sCode).source; 4178 code = code[end .. $]; 4179 } 4180 4181 continue; 4182 } 4183 4184 plain_lt: 4185 highlighted ~= "<"; 4186 code = code[1 .. $]; 4187 break; 4188 case '"': 4189 highlighted ~= """; 4190 code = code[1 .. $]; 4191 break; 4192 case '>': 4193 highlighted ~= ">"; 4194 code = code[1 .. $]; 4195 break; 4196 case '&': 4197 // possibly an entity 4198 4199 int i = 0; 4200 while(i < code.length && code[i] != ';' && !isWhite(code[i])) 4201 i++; 4202 if(i == code.length || isWhite(code[i])) { 4203 highlighted ~= "&"; 4204 code = code[1 .. $]; 4205 } else { 4206 i++; 4207 highlighted ~= htmlEntitiesHighlight(code[0 .. i]); 4208 code = code[i .. $]; 4209 } 4210 break; 4211 4212 default: 4213 highlighted ~= code[0]; 4214 code = code[1 .. $]; 4215 } 4216 } 4217 4218 return Html(highlighted); 4219 } 4220 4221 Html syntaxHighlightSdlang(string jsCode) { 4222 string highlighted; 4223 4224 bool isIdentifierChar(char c) { 4225 return 4226 (c >= 'a' && c <= 'z') || 4227 (c >= 'A' && c <= 'Z') || 4228 (c >= '0' && c <= '9') || 4229 (c == '$' || c == '_'); 4230 } 4231 4232 bool startOfLine = true; 4233 4234 while(jsCode.length) { 4235 switch(jsCode[0]) { 4236 case '\'': 4237 case '\"': 4238 // string literal 4239 char start = jsCode[0]; 4240 size_t i = 1; 4241 while(i < jsCode.length && jsCode[i] != start && jsCode[i] != '\n') { 4242 if(jsCode[i] == '\\') 4243 i++; // skip escaped char too 4244 i++; 4245 } 4246 4247 i++; // skip closer 4248 highlighted ~= "<span class=\"highlighted-string\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4249 jsCode = jsCode[i .. $]; 4250 startOfLine = false; 4251 break; 4252 case '-': 4253 if(jsCode.length > 1 && jsCode[1] == '-') { 4254 int i = 0; 4255 while(i < jsCode.length && jsCode[i] != '\n') 4256 i++; 4257 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4258 jsCode = jsCode[i .. $]; 4259 } else { 4260 highlighted ~= '-'; 4261 jsCode = jsCode[1 .. $]; 4262 } 4263 break; 4264 case '#': 4265 int i = 0; 4266 while(i < jsCode.length && jsCode[i] != '\n') 4267 i++; 4268 4269 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4270 jsCode = jsCode[i .. $]; 4271 break; 4272 case '/': 4273 if(jsCode.length > 1 && (jsCode[1] == '*' || jsCode[1] == '/')) { 4274 // it is a comment 4275 if(jsCode[1] == '/') { 4276 size_t i; 4277 while(i < jsCode.length && jsCode[i] != '\n') 4278 i++; 4279 4280 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4281 jsCode = jsCode[i .. $]; 4282 } else { 4283 // a star comment 4284 size_t i; 4285 while((i+1) < jsCode.length && !(jsCode[i] == '*' && jsCode[i+1] == '/')) 4286 i++; 4287 4288 if(i < jsCode.length) 4289 i+=2; // skip the */ 4290 4291 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4292 jsCode = jsCode[i .. $]; 4293 } 4294 } else { 4295 highlighted ~= '/'; 4296 jsCode = jsCode[1 .. $]; 4297 } 4298 break; 4299 case '0': .. case '9': 4300 // number literal 4301 size_t i; 4302 while(i < jsCode.length && ( 4303 (jsCode[i] >= '0' && jsCode[i] <= '9') 4304 || 4305 // really fuzzy but this is meant to highlight hex and exponents too 4306 jsCode[i] == '.' || jsCode[i] == 'e' || jsCode[i] == 'x' || 4307 (jsCode[i] >= 'a' && jsCode[i] <= 'f') || 4308 (jsCode[i] >= 'A' && jsCode[i] <= 'F') 4309 )) 4310 i++; 4311 4312 highlighted ~= "<span class=\"highlighted-number\">" ~ jsCode[0 .. i] ~ "</span>"; 4313 jsCode = jsCode[i .. $]; 4314 startOfLine = false; 4315 break; 4316 case '\t', ' ', '\r': 4317 highlighted ~= jsCode[0]; 4318 jsCode = jsCode[1 .. $]; 4319 break; 4320 case '\n': 4321 startOfLine = true; 4322 highlighted ~= jsCode[0]; 4323 jsCode = jsCode[1 .. $]; 4324 break; 4325 case '\\': 4326 if(jsCode.length > 1 && jsCode[1] == '\n') { 4327 highlighted ~= jsCode[0 .. 1]; 4328 jsCode = jsCode[2 .. $]; 4329 } 4330 break; 4331 case ';': 4332 startOfLine = true; 4333 break; 4334 // escape html chars 4335 case '<': 4336 highlighted ~= "<"; 4337 jsCode = jsCode[1 .. $]; 4338 startOfLine = false; 4339 break; 4340 case '>': 4341 highlighted ~= ">"; 4342 jsCode = jsCode[1 .. $]; 4343 startOfLine = false; 4344 break; 4345 case '&': 4346 highlighted ~= "&"; 4347 jsCode = jsCode[1 .. $]; 4348 startOfLine = false; 4349 break; 4350 default: 4351 if(isIdentifierChar(jsCode[0])) { 4352 size_t i; 4353 while(i < jsCode.length && isIdentifierChar(jsCode[i])) 4354 i++; 4355 auto ident = jsCode[0 .. i]; 4356 jsCode = jsCode[i .. $]; 4357 4358 if(startOfLine) 4359 highlighted ~= "<span class=\"highlighted-type\">" ~ ident ~ "</span>"; 4360 else if(ident == "true" || ident == "false") 4361 highlighted ~= "<span class=\"highlighted-number\">" ~ ident ~ "</span>"; 4362 else 4363 highlighted ~= "<span class=\"highlighted-attribute-name\">" ~ ident ~ "</span>"; 4364 } else { 4365 highlighted ~= jsCode[0]; 4366 jsCode = jsCode[1 .. $]; 4367 } 4368 4369 startOfLine = false; 4370 } 4371 } 4372 4373 return Html(highlighted); 4374 4375 } 4376 4377 Html syntaxHighlightCFamily(string jsCode, string language) { 4378 string highlighted; 4379 4380 bool isJsIdentifierChar(char c) { 4381 return 4382 (c >= 'a' && c <= 'z') || 4383 (c >= 'A' && c <= 'Z') || 4384 (c >= '0' && c <= '9') || 4385 (c == '$' || c == '_'); 4386 } 4387 4388 while(jsCode.length) { 4389 4390 4391 switch(jsCode[0]) { 4392 case '\'': 4393 case '\"': 4394 // string literal 4395 char start = jsCode[0]; 4396 size_t i = 1; 4397 while(i < jsCode.length && jsCode[i] != start && jsCode[i] != '\n') { 4398 if(jsCode[i] == '\\') 4399 i++; // skip escaped char too 4400 i++; 4401 } 4402 4403 i++; // skip closer 4404 highlighted ~= "<span class=\"highlighted-string\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4405 jsCode = jsCode[i .. $]; 4406 break; 4407 case '#': 4408 // preprocessor directive / PHP # comment 4409 size_t i; 4410 while(i < jsCode.length && jsCode[i] != '\n') 4411 i++; 4412 4413 if(language == "php") 4414 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4415 else 4416 highlighted ~= "<span class=\"highlighted-preprocessor-directive\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4417 jsCode = jsCode[i .. $]; 4418 4419 break; 4420 case '/': 4421 // check for comment 4422 // javascript also has that stupid regex literal, but screw it 4423 if(jsCode.length > 1 && (jsCode[1] == '*' || jsCode[1] == '/')) { 4424 // it is a comment 4425 if(jsCode[1] == '/') { 4426 size_t i; 4427 while(i < jsCode.length && jsCode[i] != '\n') 4428 i++; 4429 4430 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4431 jsCode = jsCode[i .. $]; 4432 } else { 4433 // a star comment 4434 size_t i; 4435 while((i+1) < jsCode.length && !(jsCode[i] == '*' && jsCode[i+1] == '/')) 4436 i++; 4437 4438 if(i < jsCode.length) 4439 i+=2; // skip the */ 4440 4441 highlighted ~= "<span class=\"highlighted-comment\">" ~ htmlEntitiesEncode(jsCode[0 .. i]) ~ "</span>"; 4442 jsCode = jsCode[i .. $]; 4443 } 4444 } else { 4445 highlighted ~= '/'; 4446 jsCode = jsCode[1 .. $]; 4447 } 4448 break; 4449 case '0': .. case '9': 4450 // number literal 4451 // FIXME: negative exponents are not done here 4452 // nor are negative numbers, but I think that needs parsing anyway 4453 // it also doesn't do all things right but the assumption is we're highlighting valid code anyway 4454 size_t i; 4455 while(i < jsCode.length && ( 4456 (jsCode[i] >= '0' && jsCode[i] <= '9') 4457 || 4458 // really fuzzy but this is meant to highlight hex and exponents too 4459 jsCode[i] == '.' || jsCode[i] == 'e' || jsCode[i] == 'x' || 4460 (jsCode[i] >= 'a' && jsCode[i] <= 'f') || 4461 (jsCode[i] >= 'A' && jsCode[i] <= 'F') 4462 )) 4463 i++; 4464 4465 highlighted ~= "<span class=\"highlighted-number\">" ~ jsCode[0 .. i] ~ "</span>"; 4466 jsCode = jsCode[i .. $]; 4467 break; 4468 case '\t', ' ', '\n', '\r': 4469 highlighted ~= jsCode[0]; 4470 jsCode = jsCode[1 .. $]; 4471 break; 4472 case '?': 4473 if(language == "php") { 4474 if(jsCode.length >= 2 && jsCode[0 .. 2] == "?>") { 4475 highlighted ~= "<span class=\"highlighted-preprocessor-directive\">?></span>"; 4476 jsCode = jsCode[2 .. $]; 4477 break; 4478 } 4479 } 4480 highlighted ~= jsCode[0]; 4481 jsCode = jsCode[1 .. $]; 4482 break; 4483 // escape html chars 4484 case '<': 4485 if(language == "php") { 4486 if(jsCode.length > 5 && jsCode[0 .. 5] == "<?php") { 4487 highlighted ~= "<span class=\"highlighted-preprocessor-directive\"><?php</span>"; 4488 jsCode = jsCode[5 .. $]; 4489 break; 4490 } 4491 } 4492 highlighted ~= "<"; 4493 jsCode = jsCode[1 .. $]; 4494 break; 4495 case '>': 4496 highlighted ~= ">"; 4497 jsCode = jsCode[1 .. $]; 4498 break; 4499 case '&': 4500 highlighted ~= "&"; 4501 jsCode = jsCode[1 .. $]; 4502 break; 4503 case '@': 4504 // FIXME highlight UDAs 4505 //goto case; 4506 //break; 4507 default: 4508 if(isJsIdentifierChar(jsCode[0])) { 4509 size_t i; 4510 while(i < jsCode.length && isJsIdentifierChar(jsCode[i])) 4511 i++; 4512 auto ident = jsCode[0 .. i]; 4513 jsCode = jsCode[i .. $]; 4514 4515 if(["function", "for", "in", "while", "new", "if", "else", "switch", "return", "break", "do", "delete", "this", "super", "continue", "goto"].canFind(ident)) 4516 highlighted ~= "<span class=\"highlighted-keyword\">" ~ ident ~ "</span>"; 4517 else if(["enum", "final", "virtual", "explicit", "var", "void", "const", "let", "int", "short", "unsigned", "char", "class", "struct", "float", "double", "typedef", "public", "protected", "private", "static"].canFind(ident)) 4518 highlighted ~= "<span class=\"highlighted-type\">" ~ ident ~ "</span>"; 4519 else if(language == "java" && ["extends", "implements"].canFind(ident)) 4520 highlighted ~= "<span class=\"highlighted-type\">" ~ ident ~ "</span>"; 4521 // FIXME: do i want to give using this same color? 4522 else if((language == "c#" || language == "c++") && ["using", "namespace"].canFind(ident)) 4523 highlighted ~= "<span class=\"highlighted-type\">" ~ ident ~ "</span>"; 4524 else if(language == "java" && ["native", "package", "import"].canFind(ident)) 4525 highlighted ~= "<span class=\"highlighted-preprocessor-directive\">" ~ ident ~ "</span>"; 4526 else if(ident[0] == '$') 4527 highlighted ~= "<span class=\"highlighted-identifier\">" ~ ident ~ "</span>"; 4528 else 4529 highlighted ~= ident; // "<span>" ~ ident ~ "</span>"; 4530 4531 } else { 4532 highlighted ~= jsCode[0]; 4533 jsCode = jsCode[1 .. $]; 4534 } 4535 } 4536 } 4537 4538 return Html(highlighted); 4539 }