autodoc: use commonmark.js for Markdown rendering

This commit is contained in:
Ian Johnson 2023-06-25 21:15:12 -04:00 committed by Loris Cro
parent ba6e5e65a0
commit d3eaa75c07

View File

@ -482,40 +482,40 @@ const NAV_MODES = {
const root_file_idx = zigAnalysis.modules[zigAnalysis.rootMod].file;
const root_file_name = getFile(root_file_idx).name;
domActiveGuide.innerHTML = markdown(`
# Zig Guides
These autodocs don't contain any guide.
# Zig Guides
These autodocs don't contain any guide.
While the API section is a reference guide autogenerated from Zig source code,
guides are meant to be handwritten explanations that provide for example:
While the API section is a reference guide autogenerated from Zig source code,
guides are meant to be handwritten explanations that provide for example:
- how-to explanations for common use-cases
- technical documentation
- information about advanced usage patterns
You can add guides by specifying which markdown files to include
in the top level doc comment of your root file, like so:
- how-to explanations for common use-cases
- technical documentation
- information about advanced usage patterns
(At the top of *${root_file_name}*)
\`\`\`
//!zig-autodoc-guide: intro.md
//!zig-autodoc-guide: quickstart.md
//!zig-autodoc-guide: advanced-docs/advanced-stuff.md
\`\`\`
You can add guides by specifying which markdown files to include
in the top level doc comment of your root file, like so:
You can also create sections to group guides together:
(At the top of *${root_file_name}*)
\`\`\`
//!zig-autodoc-guide: intro.md
//!zig-autodoc-guide: quickstart.md
//!zig-autodoc-guide: advanced-docs/advanced-stuff.md
\`\`\`
\`\`\`
//!zig-autodoc-section: CLI Usage
//!zig-autodoc-guide: cli-basics.md
//!zig-autodoc-guide: cli-advanced.md
\`\`\`
**Note that this feature is still under heavy development so expect bugs**
**and missing features!**
You can also create sections to group guides together:
Happy writing!
`);
\`\`\`
//!zig-autodoc-section: CLI Usage
//!zig-autodoc-guide: cli-basics.md
//!zig-autodoc-guide: cli-advanced.md
\`\`\`
**Note that this feature is still under heavy development so expect bugs**
**and missing features!**
Happy writing!
`);
} else {
domActiveGuide.innerHTML = markdown(activeGuide.body);
}
@ -3586,231 +3586,25 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) {
function markdown(input, contextType) {
const raw_lines = input.split("\n"); // zig allows no '\r', so we don't need to split on CR
const parsed = new commonmark.Parser({ smart: true }).parse(input);
const lines = [];
// PHASE 1:
// Dissect lines and determine the type for each line.
// Also computes indentation level and removes unnecessary whitespace
let is_reading_code = false;
let code_indent = 0;
for (let line_no = 0; line_no < raw_lines.length; line_no++) {
const raw_line = raw_lines[line_no];
const line = {
indent: 0,
raw_text: raw_line,
text: raw_line.trim(),
type: "p", // p, h1 … h6, code, ul, ol, blockquote, skip, empty
ordered_number: -1, // NOTE: hack to make the type checker happy
};
if (!is_reading_code) {
while (
line.indent < line.raw_text.length &&
line.raw_text[line.indent] == " "
) {
line.indent += 1;
// Look for decl references in inline code (`ref`)
const walker = parsed.walker();
let event;
while ((event = walker.next())) {
const node = event.node;
if (node.type === "code") {
const declHash = detectDeclPath(node.literal, contextType);
if (declHash) {
const link = new commonmark.Node("link");
link.destination = declHash;
node.insertBefore(link);
link.appendChild(node);
}
if (line.text.startsWith("######")) {
line.type = "h6";
line.text = line.text.substr(6);
} else if (line.text.startsWith("#####")) {
line.type = "h5";
line.text = line.text.substr(5);
} else if (line.text.startsWith("####")) {
line.type = "h4";
line.text = line.text.substr(4);
} else if (line.text.startsWith("###")) {
line.type = "h3";
line.text = line.text.substr(3);
} else if (line.text.startsWith("##")) {
line.type = "h2";
line.text = line.text.substr(2);
} else if (line.text.startsWith("#")) {
line.type = "h1";
line.text = line.text.substr(1);
} else if (line.text.match(/^-[ \t]+.*$/)) {
// line starts with a hyphen, followed by spaces or tabs
const match = line.text.match(/^-[ \t]+/);
line.type = "ul";
line.text = line.text.substr(match[0].length);
} else if (line.text.match(/^\d+\.[ \t]+.*$/)) {
// line starts with {number}{dot}{spaces or tabs}
const match = line.text.match(/(\d+)\.[ \t]+/);
line.type = "ol";
line.text = line.text.substr(match[0].length);
line.ordered_number = Number(match[1].length);
} else if (line.text == "```") {
line.type = "skip";
is_reading_code = true;
code_indent = line.indent;
} else if (line.text == "") {
line.type = "empty";
}
} else {
if (line.text == "```") {
is_reading_code = false;
line.type = "skip";
} else {
line.type = "code";
line.text = line.raw_text.substr(code_indent); // remove the indent of the ``` from all the code block
}
}
if (line.type != "skip") {
lines.push(line);
}
}
// PHASE 2:
// Render HTML from markdown lines.
// Look at each line and emit fitting HTML code
function markdownInlines(innerText, contextType) {
// inline types:
// **{INLINE}** : <strong>
// __{INLINE}__ : <u>
// ~~{INLINE}~~ : <s>
// *{INLINE}* : <emph>
// _{INLINE}_ : <emph>
// `{TEXT}` : <code>
// [{INLINE}]({URL}) : <a>
// ![{TEXT}]({URL}) : <img>
// [[std;format.fmt]] : <a> (inner link)
const formats = [
{
marker: "**",
tag: "strong",
},
{
marker: "~~",
tag: "s",
},
{
marker: "__",
tag: "u",
},
{
marker: "*",
tag: "em",
},
];
const stack = [];
let innerHTML = "";
let currentRun = "";
function flushRun() {
if (currentRun != "") {
innerHTML += escapeHtml(currentRun);
}
currentRun = "";
}
let parsing_code = false;
let codetag = "";
let in_code = false;
// state used to link decl references
let quote_start = undefined;
let quote_start_html = undefined;
for (let i = 0; i < innerText.length; i++) {
if (parsing_code && in_code) {
if (innerText.substr(i, codetag.length) == codetag) {
// remove leading and trailing whitespace if string both starts and ends with one.
if (
currentRun[0] == " " &&
currentRun[currentRun.length - 1] == " "
) {
currentRun = currentRun.substr(1, currentRun.length - 2);
}
flushRun();
i += codetag.length - 1;
in_code = false;
parsing_code = false;
innerHTML += "</code>";
codetag = "";
// find out if this is a decl that should be linked
const maybe_decl_path = innerText.substr(quote_start, i-quote_start);
const decl_hash = detectDeclPath(maybe_decl_path, contextType);
if (decl_hash) {
const anchor_opening_tag = "<a href='"+ decl_hash +"'>";
innerHTML = innerHTML.slice(0, quote_start_html)
+ anchor_opening_tag
+ innerHTML.slice(quote_start_html) + "</a>";
}
} else {
currentRun += innerText[i];
}
continue;
}
if (innerText[i] == "`") {
flushRun();
if (!parsing_code) {
quote_start = i + 1;
quote_start_html = innerHTML.length;
innerHTML += "<code>";
}
parsing_code = true;
codetag += "`";
continue;
}
if (parsing_code) {
currentRun += innerText[i];
in_code = true;
} else {
let any = false;
for (
let idx = stack.length > 0 ? -1 : 0;
idx < formats.length;
idx++
) {
const fmt = idx >= 0 ? formats[idx] : stack[stack.length - 1];
if (innerText.substr(i, fmt.marker.length) == fmt.marker) {
flushRun();
if (stack[stack.length - 1] == fmt) {
stack.pop();
innerHTML += "</" + fmt.tag + ">";
} else {
stack.push(fmt);
innerHTML += "<" + fmt.tag + ">";
}
i += fmt.marker.length - 1;
any = true;
break;
}
}
if (!any) {
currentRun += innerText[i];
}
}
}
flushRun();
if (in_code) {
in_code = false;
parsing_code = false;
innerHTML += "</code>";
codetag = "";
}
while (stack.length > 0) {
const fmt = stack.pop();
innerHTML += "</" + fmt.tag + ">";
}
return innerHTML;
}
return new commonmark.HtmlRenderer({ safe: true }).render(parsed);
function detectDeclPath(text, context) {
let result = "";
@ -3876,102 +3670,6 @@ function addDeclToSearchResults(decl, declIndex, modNames, item, list, stack) {
return result;
}
function previousLineIs(type, line_no) {
if (line_no > 0) {
return lines[line_no - 1].type == type;
} else {
return false;
}
}
function nextLineIs(type, line_no) {
if (line_no < lines.length - 1) {
return lines[line_no + 1].type == type;
} else {
return false;
}
}
function getPreviousLineIndent(line_no) {
if (line_no > 0) {
return lines[line_no - 1].indent;
} else {
return 0;
}
}
function getNextLineIndent(line_no) {
if (line_no < lines.length - 1) {
return lines[line_no + 1].indent;
} else {
return 0;
}
}
let html = "";
for (let line_no = 0; line_no < lines.length; line_no++) {
const line = lines[line_no];
switch (line.type) {
case "h1":
case "h2":
case "h3":
case "h4":
case "h5":
case "h6":
html +=
"<" +
line.type +
">" +
markdownInlines(line.text, contextType) +
"</" +
line.type +
">\n";
break;
case "ul":
case "ol":
if (
!previousLineIs(line.type, line_no) ||
getPreviousLineIndent(line_no) < line.indent
) {
html += "<" + line.type + ">\n";
}
html += "<li>" + markdownInlines(line.text, contextType) + "</li>\n";
if (
!nextLineIs(line.type, line_no) ||
getNextLineIndent(line_no) < line.indent
) {
html += "</" + line.type + ">\n";
}
break;
case "p":
if (!previousLineIs("p", line_no)) {
html += "<p>\n";
}
html += markdownInlines(line.text, contextType) + "\n";
if (!nextLineIs("p", line_no)) {
html += "</p>\n";
}
break;
case "code":
if (!previousLineIs("code", line_no)) {
html += "<pre><code>";
}
html += escapeHtml(line.text) + "\n";
if (!nextLineIs("code", line_no)) {
html += "</code></pre>\n";
}
break;
}
}
return html;
}
function activateSelectedResult() {