From 5456eb11078a630afc21d52ebb515ac753764a84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Quei=C3=9Fner?= Date: Sun, 20 Oct 2019 11:49:28 +0200 Subject: [PATCH] Starts to implement markdown parser. Implemented: strong, emphasis, strikethrough, underline, code blocks, ulist, olist, paragraphs, headings. Planned: Links, Images, internal references. --- lib/std/special/docs/main.js | 286 ++++++++++++++++++++++++++++++++++- 1 file changed, 283 insertions(+), 3 deletions(-) diff --git a/lib/std/special/docs/main.js b/lib/std/special/docs/main.js index a709c4a7a8..74049f8546 100644 --- a/lib/std/special/docs/main.js +++ b/lib/std/special/docs/main.js @@ -1315,9 +1315,289 @@ return markdown(firstLine); } - function markdown(mdText) { - // TODO implement more - return escapeHtml(mdText); + function markdown(input) { + const raw_lines = input.split('\n'); // zig allows no '\r', so we don't need to split on CR + const lines = []; + + // PHASE 1: + // Dissect lines and determine the type for each line. + // Also computes indentation level and removes unnecessary whitespace + + var is_reading_code = false; + var code_indent = 0; + for (var 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 + }; + + if (!is_reading_code) { + while ((line.indent < line.raw_text.length) && line.raw_text[line.indent] == ' ') { + line.indent += 1; + } + + 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.startsWith("-")) { + line.type = "ul"; + line.text = line.text.substr(1); + } + else if (line.text.match(/\d+\./)) { + const match = line.match(/(\d+)\./); + line.type = "ul"; + 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, stopChar) { + + // inline types: + // **{INLINE}** : + // __{INLINE}__ : + // ~~{INLINE}~~ : + // *{INLINE}* : + // _{INLINE}_ : + // `{TEXT}` : + // [{INLINE}]({URL}) : + // ![{TEXT}]({URL}) : + // [[std;format.fmt]] : (inner link) + + const formats = [ + { + marker: "**", + tag: "strong", + }, + { + marker: "~~", + tag: "s", + }, + { + marker: "__", + tag: "u", + }, + { + marker: "*", + tag: "em", + } + ]; + + const stack = []; + + var innerHTML = ""; + var currentRun = ""; + + function flushRun() { + if (currentRun != "") { + innerHTML += escapeHtml(currentRun); + } + currentRun = ""; + } + + var parsing_code = false; + var codetag = ""; + var in_code = false; + + for (var 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 += ""; + codetag = ""; + } else { + currentRun += innerText[i]; + } + continue; + } + + if (innerText[i] == "`") { + flushRun(); + if (!parsing_code) { + innerHTML += ""; + } + parsing_code = true; + codetag += "`"; + continue; + } + + if (parsing_code) { + currentRun += innerText[i]; + in_code = true; + } else { + var any = false; + for (var 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 += ""; + } else { + stack.push(fmt); + innerHTML += "<" + fmt.tag + ">"; + } + i += fmt.marker.length - 1; + any = true; + break; + } + } + if (!any) { + currentRun += innerText[i]; + } + } + } + flushRun(); + + while (stack.length > 0) { + const fmt = stack.pop(); + innerHTML += ""; + } + + return innerHTML; + } + + var html = ""; + for (var line_no = 0; line_no < lines.length; line_no++) { + const line = lines[line_no]; + + function previousLineIs(type) { + if (line_no > 0) { + return (lines[line_no - 1].type == type); + } else { + return false; + } + } + + function nextLineIs(type) { + if (line_no < (lines.length - 1)) { + return (lines[line_no + 1].type == type); + } else { + return false; + } + } + + function getPreviousLineIndent() { + if (line_no > 0) { + return lines[line_no - 1].indent; + } else { + return 0; + } + } + + function getNextLineIndent() { + if (line_no < (lines.length - 1)) { + return lines[line_no + 1].indent; + } else { + return 0; + } + } + + switch (line.type) { + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + html += "<" + line.type + ">" + markdownInlines(line.text) + "\n"; + break; + + case "ul": + case "ol": + if (!previousLineIs("ul") || getPreviousLineIndent() < line.indent) { + html += "<" + line.type + ">\n"; + } + + html += "
  • " + markdownInlines(line.text) + "
  • \n"; + + if (!nextLineIs("ul") || getNextLineIndent() < line.indent) { + html += "\n"; + } + break; + + case "p": + if (!previousLineIs("p")) { + html += "

    \n"; + } + html += markdownInlines(line.text) + "\n"; + if (!nextLineIs("p")) { + html += "

    \n"; + } + break; + + case "code": + if (!previousLineIs("code")) { + html += "
    ";
    +                    }
    +                    html += escapeHtml(line.text) + "\n";
    +                    if (!nextLineIs("code")) {
    +                        html += "
    \n"; + } + break; + } + } + + return html; } function activateSelectedResult() {