🌳
pt0/sharedF/ansiToHtmlF.mjs
1export const escapeHtml = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
3const ansiMap = {
4 '0': '</span>', '1': '<span style="font-weight:bold">',
5 '31': '<span style="color:var(--red)">', '32': '<span style="color:var(--green)">',
6 '33': '<span style="color:var(--yellow)">', '34': '<span style="color:var(--blue)">',
7 '36': '<span style="color:var(--cyan)">', '37': '<span style="color:var(--white)">',
8 '90': '<span style="color:var(--dim)">',
9 '91': '<span style="color:var(--red)">', '92': '<span style="color:var(--green)">',
10 '93': '<span style="color:var(--yellow)">', '94': '<span style="color:var(--blue)">',
11 '96': '<span style="color:var(--cyan)">', '97': '<span style="color:var(--white)">',
12 '38;5;43': '<span style="color:var(--teal)">', '38;5;212': '<span style="color:var(--pink)">',
13 '38;5;215': '<span style="color:var(--orange)">',
14 '48;5;236': '<span style="background:var(--bg-gray)">',
15 '48;2;17;17;17': '<span style="background:var(--bg-near-black)">',
16 '48;2;157;124;216': '<span style="background:var(--bar-purple)">',
17 '48;2;59;130;246': '<span style="background:var(--bar-blue)">',
18 '48;2;10;14;10': '<span class="diff-add">', '48;2;14;10;10': '<span class="diff-del">',
19 '38;2;140;195;140': '<span style="color:var(--diff-add-text)">',
20 '38;2;220;100;90': '<span style="color:var(--diff-del-text)">',
23export const ansiToHtml = (ansiStr) => {
24 const escaped = escapeHtml(ansiStr)
25 const ansiRegex = /\x1b\[([0-9;]+)m/g
26 let result = '', lastIdx = 0, openSpans = 0, match
27 while ((match = ansiRegex.exec(escaped)) !== null) {
28 result += escaped.slice(lastIdx, match.index)
29 lastIdx = match.index + match[0].length
30 const parts = match[1].split(';')
31 let i = 0
32 while (i < parts.length) {
33 const code = parts[i]
34 if (code === '0' || code === '39' || code === '49') {
35 result += '</span>'.repeat(openSpans)
36 openSpans = 0
37 i++
38 } else if (code === '38' || code === '48') {
39 const fullCode = parts.slice(i, i + (parts[i + 1] === '5' ? 3 : 5)).join(';')
40 const span = ansiMap[fullCode]
41 if (span) { result += span; openSpans++ }
42 i += parts[i + 1] === '5' ? 3 : 5
43 } else {
44 const span = ansiMap[code]
45 if (span) { result += span; openSpans++ }
46 i++
47 }
48 }
49 }
50 result += escaped.slice(lastIdx)
51 result += '</span>'.repeat(openSpans)
52 return result