import { CODE_LINE_ATTRIBUTE, CODE_LINE_CLASS, LINE_BREAK, SYNTAX_CLASS_PREFIX, } from "#configuration/constants/syntax.constants"; import type { Token } from "#types/syntax.types"; import { createElement } from "#core/factories/element.factory"; import { tokenize } from "#core/analyzers/syntax.analyzer"; interface Piece { readonly kind: Token["kind"]; readonly text: string; } const renderToken = function renderToken(token: Piece): Node { if (token.kind === "plain") { return document.createTextNode(token.text); } return createElement("span", { className: SYNTAX_CLASS_PREFIX + token.kind, 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 { 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; };