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;
};