aboutsummaryrefslogtreecommitdiff
path: root/bundle/src/ansi.js
blob: 66173ea40af6aba91a6c12495470632f714f23a5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

// Splits terminal text into runs styled by SGR escape sequences, keeping the
// style and any sequence cut off at a chunk boundary for the next chunk.
// Handles reset, bold and the 16 foreground colors; drops other sequences.
const names = ['Black', 'Red', 'Green', 'Yellow', 'Blue', 'Magenta', 'Cyan', 'White'];
const sequence = /\x1b\[([0-?]*)[ -/]*([@-~])/g;
const unfinished = /\x1b(\[[0-?]*[ -/]*)?$/;

export function createAnsiParser() {
	let bold = false;
	let color = null;
	let rest = '';
	return text => {
		text = rest + text;
		const cut = text.match(unfinished);
		rest = cut ? cut[0] : '';
		if (cut) text = text.slice(0, cut.index);
		const runs = [];
		let start = 0;
		for (const match of text.matchAll(sequence)) {
			if (match.index > start) runs.push({ text: text.slice(start, match.index), bold, color });
			start = match.index + match[0].length;
			if (match[2] !== 'm') continue;
			for (const parameter of match[1].split(';')) {
				const code = Number(parameter || 0);
				if (code === 0) { bold = false; color = null; }
				else if (code === 1) bold = true;
				else if (code === 22) bold = false;
				else if (code === 39) color = null;
				else if (code >= 30 && code <= 37) color = `terminal.ansi${names[code - 30]}`;
				else if (code >= 90 && code <= 97) color = `terminal.ansiBright${names[code - 90]}`;
			}
		}
		if (start < text.length) runs.push({ text: text.slice(start), bold, color });
		return runs;
	};
}