(text: string)
| 51 | * - 24-bit true color (38;2;r;g;b) |
| 52 | */ |
| 53 | export function parseAnsi(text: string): ParsedLine[] { |
| 54 | const lines: ParsedLine[] = [] |
| 55 | const rawLines = text.split('\n') |
| 56 | |
| 57 | for (const line of rawLines) { |
| 58 | const spans: TextSpan[] = [] |
| 59 | let currentColor = DEFAULT_FG |
| 60 | let bold = false |
| 61 | let i = 0 |
| 62 | |
| 63 | while (i < line.length) { |
| 64 | // Check for ANSI escape sequence |
| 65 | if (line[i] === '\x1b' && line[i + 1] === '[') { |
| 66 | // Find the end of the escape sequence |
| 67 | let j = i + 2 |
| 68 | while (j < line.length && !/[A-Za-z]/.test(line[j]!)) { |
| 69 | j++ |
| 70 | } |
| 71 | |
| 72 | if (line[j] === 'm') { |
| 73 | // Color/style code |
| 74 | const codes = line |
| 75 | .slice(i + 2, j) |
| 76 | .split(';') |
| 77 | .map(Number) |
| 78 | |
| 79 | let k = 0 |
| 80 | while (k < codes.length) { |
| 81 | const code = codes[k]! |
| 82 | if (code === 0) { |
| 83 | // Reset |
| 84 | currentColor = DEFAULT_FG |
| 85 | bold = false |
| 86 | } else if (code === 1) { |
| 87 | bold = true |
| 88 | } else if (code >= 30 && code <= 37) { |
| 89 | currentColor = ANSI_COLORS[code] || DEFAULT_FG |
| 90 | } else if (code >= 90 && code <= 97) { |
| 91 | currentColor = ANSI_COLORS[code] || DEFAULT_FG |
| 92 | } else if (code === 39) { |
| 93 | currentColor = DEFAULT_FG |
| 94 | } else if (code === 38) { |
| 95 | // Extended color - check next code |
| 96 | if (codes[k + 1] === 5 && codes[k + 2] !== undefined) { |
| 97 | // 256-color mode: 38;5;n |
| 98 | const colorIndex = codes[k + 2]! |
| 99 | currentColor = get256Color(colorIndex) |
| 100 | k += 2 |
| 101 | } else if ( |
| 102 | codes[k + 1] === 2 && |
| 103 | codes[k + 2] !== undefined && |
| 104 | codes[k + 3] !== undefined && |
| 105 | codes[k + 4] !== undefined |
| 106 | ) { |
| 107 | // 24-bit true color: 38;2;r;g;b |
| 108 | currentColor = { |
| 109 | r: codes[k + 2]!, |
| 110 | g: codes[k + 3]!, |
no test coverage detected