import { ansiParser } from "./ansi.ts"; /** Append coloured output snapshots, restarting when the result replaces or truncates the stream. */ export function ansiOutput(element: HTMLElement): (source: string) => void { let parser = ansiParser(); let previous = ""; let tail: Text | null = null; let tailStyle = ""; element.hidden = true; return (source) => { if (source === previous) return; if (!source.startsWith(previous)) { parser = ansiParser(); previous = ""; tail = null; element.replaceChildren(); } const fragment = document.createDocumentFragment(); for (const { text, style } of parser.write(source.slice(previous.length))) { const key = JSON.stringify(style); let offset = 0; while (offset < text.length) { // bound text-node edits without making a new span for every streaming chunk if (tail === null || key !== tailStyle || tail.length >= 16384) { const span = document.createElement("span"); for (const [property, value] of Object.entries(style)) { span.style.setProperty(property, value); } tail = document.createTextNode(""); tailStyle = key; span.append(tail); fragment.append(span); } let end = Math.min(text.length, offset + 16384 - tail.length); if ( end < text.length && text.charCodeAt(end - 1) >= 0xd800 && text.charCodeAt(end - 1) <= 0xdbff && text.charCodeAt(end) >= 0xdc00 && text.charCodeAt(end) <= 0xdfff ) end++; tail.appendData(text.slice(offset, end)); offset = end; } } element.append(fragment); element.hidden = source === ""; previous = source; }; }