Markdown
Markdown as code, for a md fence in a doc. It's read with markz's own parser, the one that
renders the docs, so what's coloured as a heading or a link is exactly what markz reads as one.
There's no second, guessing reader of the same dialect.
Each node paints its range, and a node inside another paints over it. Headings and bold are
bold, emphasis is italic and inline code a string. List markers stay plain, as shiki leaves them. A link is a link, with its text a keyword,
as shiki shows them. A fence inside is highlighted in its own language, through embed.
import { parse, walk } from "@amitkaps/markz";
import type { Colour, Span } from "./scan.js";
export function markdown(source: string, embed: (code: string, lang: string) => Span[]): Span[] {
const doc = parse(source);
const paint = Array.from<Colour | null>({ length: source.length }).fill(null);
const fill = (start: number, end: number, colour: Colour | null): void => {
paint.fill(colour, start, end);
};
walk(doc, {
enter(node) {
const start = doc.start(node);
const end = doc.end(node);
switch (doc.type(node)) {
case "comment":
fill(start, end, "comment");
break;
case "heading":
case "strong":
fill(start, end, "strong");
break;
case "emphasis":
fill(start, end, "emphasis");
break;
case "inlineCode":
fill(start, end, "string");
break;
case "link":
case "image":
fill(start, end, "link");
for (const child of doc.children(node)) {
fill(doc.start(child), doc.end(child), "keyword");
}
break;
case "code": {
const { lang, body } = doc.data(node, "code");
fill(start, end, "string");
fill(body.start, body.end, null);
for (const s of embed(source.slice(body.start, body.end), lang ?? "text")) {
fill(body.start + s.start, body.start + s.end, s.colour);
}
return false;
}
}
return true;
},
});
const spans: Span[] = [];
for (let i = 0; i < paint.length;) {
const colour = paint[i];
let end = i + 1;
while (end < paint.length && paint[end] === colour) end++;
if (colour) spans.push({ colour, start: i, end });
i = end;
}
return spans;
}