authorgravatar for ian@ianjohnson.devIan Johnson <ian@ianjohnson.dev> 2023-06-25 21:15:12-04:00
committergravatar for kappaloris@gmail.comLoris Cro <kappaloris@gmail.com> 2023-07-04 17:08:46+02:00
logd3eaa75c07aaac09d9dd74aa2a175ba799159faa
treea9bcb1c20d9432f1b7ea63f432478eff19a444e6
parentba6e5e65a0f84b803fffe4dd26b6e71417cab616

autodoc: use commonmark.js for Markdown rendering


1 files changed, 46 insertions(+), 348 deletions(-)

lib/docs/main.js+46-348
......@@ -482,40 +482,40 @@ const NAV_MODES = {
482482 const root_file_idx = zigAnalysis.modules[zigAnalysis.rootMod].file;
483483 const root_file_name = getFile(root_file_idx).name;
484484 domActiveGuide.innerHTML = markdown(`
485 # Zig Guides
486 These autodocs don't contain any guide.
485# Zig Guides
486These autodocs don't contain any guide.
487487
488 While the API section is a reference guide autogenerated from Zig source code,
489 guides are meant to be handwritten explanations that provide for example:
488While the API section is a reference guide autogenerated from Zig source code,
489guides are meant to be handwritten explanations that provide for example:
490490
491 - how-to explanations for common use-cases
492 - technical documentation
493 - information about advanced usage patterns
494
495 You can add guides by specifying which markdown files to include
496 in the top level doc comment of your root file, like so:
497
498 (At the top of *${root_file_name}*)
499 \`\`\`
500 //!zig-autodoc-guide: intro.md
501 //!zig-autodoc-guide: quickstart.md
502 //!zig-autodoc-guide: advanced-docs/advanced-stuff.md
503 \`\`\`
504
505 You can also create sections to group guides together:
506
507 \`\`\`
508 //!zig-autodoc-section: CLI Usage
509 //!zig-autodoc-guide: cli-basics.md
510 //!zig-autodoc-guide: cli-advanced.md
511 \`\`\`
512
513
514 **Note that this feature is still under heavy development so expect bugs**
515 **and missing features!**
491- how-to explanations for common use-cases
492- technical documentation
493- information about advanced usage patterns
494
495You can add guides by specifying which markdown files to include
496in the top level doc comment of your root file, like so:
497
498(At the top of *${root_file_name}*)
499\`\`\`
500//!zig-autodoc-guide: intro.md
501//!zig-autodoc-guide: quickstart.md
502//!zig-autodoc-guide: advanced-docs/advanced-stuff.md
503\`\`\`
504
505You can also create sections to group guides together:
516506
517 Happy writing!
518 `);
507\`\`\`
508//!zig-autodoc-section: CLI Usage
509//!zig-autodoc-guide: cli-basics.md
510//!zig-autodoc-guide: cli-advanced.md
511\`\`\`
512
513
514**Note that this feature is still under heavy development so expect bugs**
515**and missing features!**
516
517Happy writing!
518`);
519519 } else {
520520 domActiveGuide.innerHTML = markdown(activeGuide.body);
521521 }
......@@ -3586,231 +3586,25 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) {
35863586
35873587
35883588 function markdown(input, contextType) {
3589 const raw_lines = input.split("\n"); // zig allows no '\r', so we don't need to split on CR
3590
3591 const lines = [];
3592
3593 // PHASE 1:
3594 // Dissect lines and determine the type for each line.
3595 // Also computes indentation level and removes unnecessary whitespace
3596
3597 let is_reading_code = false;
3598 let code_indent = 0;
3599 for (let line_no = 0; line_no < raw_lines.length; line_no++) {
3600 const raw_line = raw_lines[line_no];
3601
3602 const line = {
3603 indent: 0,
3604 raw_text: raw_line,
3605 text: raw_line.trim(),
3606 type: "p", // p, h1 … h6, code, ul, ol, blockquote, skip, empty
3607 ordered_number: -1, // NOTE: hack to make the type checker happy
3608 };
3609
3610 if (!is_reading_code) {
3611 while (
3612 line.indent < line.raw_text.length &&
3613 line.raw_text[line.indent] == " "
3614 ) {
3615 line.indent += 1;
3616 }
3617
3618 if (line.text.startsWith("######")) {
3619 line.type = "h6";
3620 line.text = line.text.substr(6);
3621 } else if (line.text.startsWith("#####")) {
3622 line.type = "h5";
3623 line.text = line.text.substr(5);
3624 } else if (line.text.startsWith("####")) {
3625 line.type = "h4";
3626 line.text = line.text.substr(4);
3627 } else if (line.text.startsWith("###")) {
3628 line.type = "h3";
3629 line.text = line.text.substr(3);
3630 } else if (line.text.startsWith("##")) {
3631 line.type = "h2";
3632 line.text = line.text.substr(2);
3633 } else if (line.text.startsWith("#")) {
3634 line.type = "h1";
3635 line.text = line.text.substr(1);
3636 } else if (line.text.match(/^-[ \t]+.*$/)) {
3637 // line starts with a hyphen, followed by spaces or tabs
3638 const match = line.text.match(/^-[ \t]+/);
3639 line.type = "ul";
3640 line.text = line.text.substr(match[0].length);
3641 } else if (line.text.match(/^\d+\.[ \t]+.*$/)) {
3642 // line starts with {number}{dot}{spaces or tabs}
3643 const match = line.text.match(/(\d+)\.[ \t]+/);
3644 line.type = "ol";
3645 line.text = line.text.substr(match[0].length);
3646 line.ordered_number = Number(match[1].length);
3647 } else if (line.text == "```") {
3648 line.type = "skip";
3649 is_reading_code = true;
3650 code_indent = line.indent;
3651 } else if (line.text == "") {
3652 line.type = "empty";
3589 const parsed = new commonmark.Parser({ smart: true }).parse(input);
3590
3591 // Look for decl references in inline code (`ref`)
3592 const walker = parsed.walker();
3593 let event;
3594 while ((event = walker.next())) {
3595 const node = event.node;
3596 if (node.type === "code") {
3597 const declHash = detectDeclPath(node.literal, contextType);
3598 if (declHash) {
3599 const link = new commonmark.Node("link");
3600 link.destination = declHash;
3601 node.insertBefore(link);
3602 link.appendChild(node);
36533603 }
3654 } else {
3655 if (line.text == "```") {
3656 is_reading_code = false;
3657 line.type = "skip";
3658 } else {
3659 line.type = "code";
3660 line.text = line.raw_text.substr(code_indent); // remove the indent of the ``` from all the code block
3661 }
3662 }
3663
3664 if (line.type != "skip") {
3665 lines.push(line);
36663604 }
36673605 }
36683606
3669 // PHASE 2:
3670 // Render HTML from markdown lines.
3671 // Look at each line and emit fitting HTML code
3672
3673 function markdownInlines(innerText, contextType) {
3674 // inline types:
3675 // **{INLINE}** : <strong>
3676 // __{INLINE}__ : <u>
3677 // ~~{INLINE}~~ : <s>
3678 // *{INLINE}* : <emph>
3679 // _{INLINE}_ : <emph>
3680 // `{TEXT}` : <code>
3681 // [{INLINE}]({URL}) : <a>
3682 // ![{TEXT}]({URL}) : <img>
3683 // [[std;format.fmt]] : <a> (inner link)
3684
3685 const formats = [
3686 {
3687 marker: "**",
3688 tag: "strong",
3689 },
3690 {
3691 marker: "~~",
3692 tag: "s",
3693 },
3694 {
3695 marker: "__",
3696 tag: "u",
3697 },
3698 {
3699 marker: "*",
3700 tag: "em",
3701 },
3702 ];
3703
3704 const stack = [];
3705
3706 let innerHTML = "";
3707 let currentRun = "";
3708
3709 function flushRun() {
3710 if (currentRun != "") {
3711 innerHTML += escapeHtml(currentRun);
3712 }
3713 currentRun = "";
3714 }
3715
3716 let parsing_code = false;
3717 let codetag = "";
3718 let in_code = false;
3719
3720 // state used to link decl references
3721 let quote_start = undefined;
3722 let quote_start_html = undefined;
3723
3724 for (let i = 0; i < innerText.length; i++) {
3725 if (parsing_code && in_code) {
3726 if (innerText.substr(i, codetag.length) == codetag) {
3727 // remove leading and trailing whitespace if string both starts and ends with one.
3728 if (
3729 currentRun[0] == " " &&
3730 currentRun[currentRun.length - 1] == " "
3731 ) {
3732 currentRun = currentRun.substr(1, currentRun.length - 2);
3733 }
3734 flushRun();
3735 i += codetag.length - 1;
3736 in_code = false;
3737 parsing_code = false;
3738 innerHTML += "</code>";
3739 codetag = "";
3740
3741 // find out if this is a decl that should be linked
3742 const maybe_decl_path = innerText.substr(quote_start, i-quote_start);
3743 const decl_hash = detectDeclPath(maybe_decl_path, contextType);
3744 if (decl_hash) {
3745 const anchor_opening_tag = "<a href='"+ decl_hash +"'>";
3746 innerHTML = innerHTML.slice(0, quote_start_html)
3747 + anchor_opening_tag
3748 + innerHTML.slice(quote_start_html) + "</a>";
3749 }
3750 } else {
3751 currentRun += innerText[i];
3752 }
3753 continue;
3754 }
3755
3756 if (innerText[i] == "`") {
3757 flushRun();
3758 if (!parsing_code) {
3759 quote_start = i + 1;
3760 quote_start_html = innerHTML.length;
3761 innerHTML += "<code>";
3762 }
3763 parsing_code = true;
3764 codetag += "`";
3765 continue;
3766 }
3767
3768 if (parsing_code) {
3769 currentRun += innerText[i];
3770 in_code = true;
3771 } else {
3772 let any = false;
3773 for (
3774 let idx = stack.length > 0 ? -1 : 0;
3775 idx < formats.length;
3776 idx++
3777 ) {
3778 const fmt = idx >= 0 ? formats[idx] : stack[stack.length - 1];
3779 if (innerText.substr(i, fmt.marker.length) == fmt.marker) {
3780 flushRun();
3781 if (stack[stack.length - 1] == fmt) {
3782 stack.pop();
3783 innerHTML += "</" + fmt.tag + ">";
3784 } else {
3785 stack.push(fmt);
3786 innerHTML += "<" + fmt.tag + ">";
3787 }
3788 i += fmt.marker.length - 1;
3789 any = true;
3790 break;
3791 }
3792 }
3793 if (!any) {
3794 currentRun += innerText[i];
3795 }
3796 }
3797 }
3798 flushRun();
3799
3800 if (in_code) {
3801 in_code = false;
3802 parsing_code = false;
3803 innerHTML += "</code>";
3804 codetag = "";
3805 }
3806
3807 while (stack.length > 0) {
3808 const fmt = stack.pop();
3809 innerHTML += "</" + fmt.tag + ">";
3810 }
3811
3812 return innerHTML;
3813 }
3607 return new commonmark.HtmlRenderer({ safe: true }).render(parsed);
38143608
38153609 function detectDeclPath(text, context) {
38163610 let result = "";
......@@ -3876,102 +3670,6 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) {
38763670 return result;
38773671
38783672 }
3879
3880 function previousLineIs(type, line_no) {
3881 if (line_no > 0) {
3882 return lines[line_no - 1].type == type;
3883 } else {
3884 return false;
3885 }
3886 }
3887
3888 function nextLineIs(type, line_no) {
3889 if (line_no < lines.length - 1) {
3890 return lines[line_no + 1].type == type;
3891 } else {
3892 return false;
3893 }
3894 }
3895
3896 function getPreviousLineIndent(line_no) {
3897 if (line_no > 0) {
3898 return lines[line_no - 1].indent;
3899 } else {
3900 return 0;
3901 }
3902 }
3903
3904 function getNextLineIndent(line_no) {
3905 if (line_no < lines.length - 1) {
3906 return lines[line_no + 1].indent;
3907 } else {
3908 return 0;
3909 }
3910 }
3911
3912 let html = "";
3913 for (let line_no = 0; line_no < lines.length; line_no++) {
3914 const line = lines[line_no];
3915
3916 switch (line.type) {
3917 case "h1":
3918 case "h2":
3919 case "h3":
3920 case "h4":
3921 case "h5":
3922 case "h6":
3923 html +=
3924 "<" +
3925 line.type +
3926 ">" +
3927 markdownInlines(line.text, contextType) +
3928 "</" +
3929 line.type +
3930 ">\n";
3931 break;
3932
3933 case "ul":
3934 case "ol":
3935 if (
3936 !previousLineIs(line.type, line_no) ||
3937 getPreviousLineIndent(line_no) < line.indent
3938 ) {
3939 html += "<" + line.type + ">\n";
3940 }
3941
3942 html += "<li>" + markdownInlines(line.text, contextType) + "</li>\n";
3943
3944 if (
3945 !nextLineIs(line.type, line_no) ||
3946 getNextLineIndent(line_no) < line.indent
3947 ) {
3948 html += "</" + line.type + ">\n";
3949 }
3950 break;
3951
3952 case "p":
3953 if (!previousLineIs("p", line_no)) {
3954 html += "<p>\n";
3955 }
3956 html += markdownInlines(line.text, contextType) + "\n";
3957 if (!nextLineIs("p", line_no)) {
3958 html += "</p>\n";
3959 }
3960 break;
3961
3962 case "code":
3963 if (!previousLineIs("code", line_no)) {
3964 html += "<pre><code>";
3965 }
3966 html += escapeHtml(line.text) + "\n";
3967 if (!nextLineIs("code", line_no)) {
3968 html += "</code></pre>\n";
3969 }
3970 break;
3971 }
3972 }
3973
3974 return html;
39753673 }
39763674
39773675 function activateSelectedResult() {