presentation/renderers/syntax.renderer.ts
presentation/renderers/syntax.renderer.ts is a file in Bane's Lab Site. 50 lines of code and 10 definitions.
import { CODE_LINE_ATTRIBUTE, CODE_LINE_CLASS, SYNTAX_CLASSES } from "#configuration/constants/syntax.constants";
import { LINE_BREAK } from "#configuration/constants/code.constants";
import type { Token } from "#types/syntax.types";
import { createElement } from "#core/factories/element.factory";
import { isStaticRender } from "#core/predicates/document.predicate";
import { tokenize } from "#core/analyzers/syntax.analyzer";
interface Piece {
readonly kind: Token["kind"];
readonly text: string;
}
const renderToken = function renderToken(token: Piece): Node {
const className = SYNTAX_CLASSES.get(token.kind);
if (className === undefined) {
return document.createTextNode(token.text);
}
return createElement("span", { className, text: token.text });
};
const lineSpan = function lineSpan(number: number): HTMLElement {
return createElement("span", { attributes: { [CODE_LINE_ATTRIBUTE]: String(number) }, className: CODE_LINE_CLASS });
};
const splitLines = function splitLines(tokens: readonly Token[]): Piece[][] {
const lines: Piece[][] = [[]];
for (const token of tokens) {
const pieces = token.text.split(LINE_BREAK);
pieces.forEach((text, index) => {
if (index > 0) {
lines.push([]);
}
if (text.length > 0) {
lines.at(-1)?.push({ kind: token.kind, text });
}
});
}
return lines;
};
export const renderSyntax = function renderSyntax(code: string, language: string): HTMLElement {
if (isStaticRender()) {
return createElement("code", { text: code });
}
const element = createElement("code");
splitLines(tokenize(code, language)).forEach((pieces, index) => {
if (index > 0) {
element.append(document.createTextNode(LINE_BREAK));
}
const line = lineSpan(index + 1);
line.append(...pieces.map(renderToken));
element.append(line);
});
return element;
};