// Minimal ANSI(SGR) -> HTML. Stödjer 16/256-färg, truecolor, bold/dim/ // invers/understruket. Allt annat (cursorstyrning m.m.) strippas — // helmd skickar capture-pane-text, så bara färgkoder förekommer. const BASE = [ "#1c1f26", "#e5605c", "#3fca7c", "#e0a63f", "#4f9cf9", "#b877d9", "#3fc4c4", "#cfd6e4", "#5b6472", "#ff7b76", "#5fe09a", "#ffc65c", "#7bc4ff", "#d49df0", "#6ee0e0", "#ffffff", ]; function color256(n) { n = Number(n); if (n < 16) return BASE[n]; if (n < 232) { const c = n - 16, v = [0, 95, 135, 175, 215, 255]; return `rgb(${v[(c / 36) | 0]},${v[((c / 6) | 0) % 6]},${v[c % 6]})`; } const g = 8 + (n - 232) * 10; return `rgb(${g},${g},${g})`; } const esc = (s) => s.replace(/&/g, "&").replace(//g, ">"); export function ansiToHtml(text) { let out = "", open = false; const st = { fg: null, bg: null, bold: false, dim: false, ul: false, inv: false }; const flushSpan = () => { if (open) { out += ""; open = false; } }; const openSpan = () => { const css = []; let fg = st.fg, bg = st.bg; if (st.inv) [fg, bg] = [bg || "#cfd6e4", fg || "#05070b"]; if (fg) css.push(`color:${fg}`); if (bg) css.push(`background:${bg}`); if (st.bold) css.push("font-weight:700"); if (st.dim) css.push("opacity:.6"); if (st.ul) css.push("text-decoration:underline"); if (css.length) { out += ``; open = true; } }; // dela på escape-sekvenser; behåll SGR, släng övriga const parts = text.split(/(\x1b\[[0-9;:]*[A-Za-z])/); for (const p of parts) { if (!p) continue; if (p.startsWith("\x1b[")) { if (!p.endsWith("m")) continue; // icke-SGR: ignorera const codes = p.slice(2, -1).split(/[;:]/).map((x) => (x === "" ? 0 : Number(x))); for (let i = 0; i < codes.length; i++) { const c = codes[i]; if (c === 0) { st.fg = st.bg = null; st.bold = st.dim = st.ul = st.inv = false; } else if (c === 1) st.bold = true; else if (c === 2) st.dim = true; else if (c === 4) st.ul = true; else if (c === 7) st.inv = true; else if (c === 22) { st.bold = st.dim = false; } else if (c === 24) st.ul = false; else if (c === 27) st.inv = false; else if (c >= 30 && c <= 37) st.fg = BASE[c - 30]; else if (c === 38 && codes[i + 1] === 5) { st.fg = color256(codes[i + 2]); i += 2; } else if (c === 38 && codes[i + 1] === 2) { st.fg = `rgb(${codes[i + 2]},${codes[i + 3]},${codes[i + 4]})`; i += 4; } else if (c === 39) st.fg = null; else if (c >= 40 && c <= 47) st.bg = BASE[c - 40]; else if (c === 48 && codes[i + 1] === 5) { st.bg = color256(codes[i + 2]); i += 2; } else if (c === 48 && codes[i + 1] === 2) { st.bg = `rgb(${codes[i + 2]},${codes[i + 3]},${codes[i + 4]})`; i += 4; } else if (c === 49) st.bg = null; else if (c >= 90 && c <= 97) st.fg = BASE[c - 90 + 8]; else if (c >= 100 && c <= 107) st.bg = BASE[c - 100 + 8]; } flushSpan(); openSpan(); } else { out += esc(p.replace(/\x1b./g, "")); } } flushSpan(); return out; }