Something went wrong. Try again.
fork of prime agent with changes i want but are not upstreamable probqbly (and some fixes)
Something went wrong. Try again.
5.8 kB · 230 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231/** * ANSI escape code to HTML converter. * * Converts terminal ANSI color/style codes to HTML with inline styles. * Supports: * - Standard foreground colors (30-37) and bright variants (90-97) * - Standard background colors (40-47) and bright variants (100-107) * - 256-color palette (38;5;N and 48;5;N) * - RGB true color (38;2;R;G;B and 48;2;R;G;B) * - Text styles: bold (1), dim (2), italic (3), underline (4) * - Reset (0) */
const ANSI_COLORS = [ "#000000", // 0: black "#800000", // 1: red "#008000", // 2: green "#808000", // 3: yellow "#000080", // 4: blue "#800080", // 5: magenta "#008080", // 6: cyan "#c0c0c0", // 7: white "#808080", // 8: bright black "#ff0000", // 9: bright red "#00ff00", // 10: bright green "#ffff00", // 11: bright yellow "#0000ff", // 12: bright blue "#ff00ff", // 13: bright magenta "#00ffff", // 14: bright cyan "#ffffff", // 15: bright white];
/** * Convert 256-color index to hex. */function color256ToHex(index: number): string { if (index < 16) { return ANSI_COLORS[index]; }
if (index < 232) { const cubeIndex = index - 16; const r = Math.floor(cubeIndex / 36); const g = Math.floor((cubeIndex % 36) / 6); const b = cubeIndex % 6; const toComponent = (n: number) => (n === 0 ? 0 : 55 + n * 40); const toHex = (n: number) => toComponent(n).toString(16).padStart(2, "0"); return `#${toHex(r)}${toHex(g)}${toHex(b)}`; }
const gray = 8 + (index - 232) * 10; const grayHex = gray.toString(16).padStart(2, "0"); return `#${grayHex}${grayHex}${grayHex}`;}
/** * Escape HTML special characters. */function escapeHtml(text: string): string { return text .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'");}
interface TextStyle { fg: string | null; bg: string | null; bold: boolean; dim: boolean; italic: boolean; underline: boolean;}
function createEmptyStyle(): TextStyle { return { fg: null, bg: null, bold: false, dim: false, italic: false, underline: false, };}
function styleToInlineCSS(style: TextStyle): string { const parts: string[] = []; if (style.fg) parts.push(`color:${style.fg}`); if (style.bg) parts.push(`background-color:${style.bg}`); if (style.bold) parts.push("font-weight:bold"); if (style.dim) parts.push("opacity:0.6"); if (style.italic) parts.push("font-style:italic"); if (style.underline) parts.push("text-decoration:underline"); return parts.join(";");}
function hasStyle(style: TextStyle): boolean { return style.fg !== null || style.bg !== null || style.bold || style.dim || style.italic || style.underline;}
/** * Parse ANSI SGR (Select Graphic Rendition) codes and update style. */function applySgrCode(params: number[], style: TextStyle): void { let i = 0; while (i < params.length) { const code = params[i];
if (code === 0) { style.fg = null; style.bg = null; style.bold = false; style.dim = false; style.italic = false; style.underline = false; } else if (code === 1) { style.bold = true; } else if (code === 2) { style.dim = true; } else if (code === 3) { style.italic = true; } else if (code === 4) { style.underline = true; } else if (code === 22) { style.bold = false; style.dim = false; } else if (code === 23) { style.italic = false; } else if (code === 24) { style.underline = false; } else if (code >= 30 && code <= 37) { style.fg = ANSI_COLORS[code - 30]; } else if (code === 38) { if (params[i + 1] === 5 && params.length > i + 2) { style.fg = color256ToHex(params[i + 2]); i += 2; } else if (params[i + 1] === 2 && params.length > i + 4) { const r = params[i + 2]; const g = params[i + 3]; const b = params[i + 4]; style.fg = `rgb(${r},${g},${b})`; i += 4; } } else if (code === 39) { style.fg = null; } else if (code >= 40 && code <= 47) { style.bg = ANSI_COLORS[code - 40]; } else if (code === 48) { if (params[i + 1] === 5 && params.length > i + 2) { style.bg = color256ToHex(params[i + 2]); i += 2; } else if (params[i + 1] === 2 && params.length > i + 4) { const r = params[i + 2]; const g = params[i + 3]; const b = params[i + 4]; style.bg = `rgb(${r},${g},${b})`; i += 4; } } else if (code === 49) { style.bg = null; } else if (code >= 90 && code <= 97) { style.fg = ANSI_COLORS[code - 90 + 8]; } else if (code >= 100 && code <= 107) { style.bg = ANSI_COLORS[code - 100 + 8]; }
i++; }}
const ANSI_REGEX = /\x1b\[([\d;]*)m/g;
/** * Convert ANSI-escaped text to HTML with inline styles. */export function ansiToHtml(text: string): string { const style = createEmptyStyle(); let result = ""; let lastIndex = 0; let inSpan = false;
ANSI_REGEX.lastIndex = 0;
let match = ANSI_REGEX.exec(text); while (match !== null) { const beforeText = text.slice(lastIndex, match.index); if (beforeText) { result += escapeHtml(beforeText); }
const paramStr = match[1]; const params = paramStr ? paramStr.split(";").map((p) => parseInt(p, 10) || 0) : [0];
if (inSpan) { result += "</span>"; inSpan = false; }
applySgrCode(params, style);
if (hasStyle(style)) { result += `<span style="${styleToInlineCSS(style)}">`; inSpan = true; }
lastIndex = match.index + match[0].length; match = ANSI_REGEX.exec(text); }
const remainingText = text.slice(lastIndex); if (remainingText) { result += escapeHtml(remainingText); }
if (inSpan) { result += "</span>"; }
return result;}
/** * Convert array of ANSI-escaped lines to HTML. * Each line is wrapped in a div element. */export function ansiLinesToHtml(lines: string[]): string { return lines.map((line) => `<div class="ansi-line">${ansiToHtml(line) || " "}</div>`).join("");}