@effect-tui/emulator 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Kit Langton
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # @effect-tui/emulator
2
+
3
+ Lightweight ANSI terminal emulator for testing render output.
4
+
5
+ Features:
6
+ - Cursor movement (CUU/CUD/CUF/CUB, CUP, CHA)
7
+ - SGR styles (fg/bg + basic attributes)
8
+ - Screen/line erase (ED/EL)
9
+ - In-memory cell grid for diffing against `CellBuffer`
10
+
11
+ ## Usage
12
+ ```ts
13
+ import { AnsiEmulator } from "@effect-tui/emulator"
14
+
15
+ const emu = new AnsiEmulator(80, 24)
16
+ emu.apply(ansiOutput)
17
+ const diff = emu.diffBuffer(nextBuffer)
18
+ ```
@@ -0,0 +1,69 @@
1
+ import { CellBuffer, Palette, type Wcwidth } from "@effect-tui/core";
2
+ export interface EmulatorBufferSnapshot {
3
+ width: number;
4
+ height: number;
5
+ g: Uint32Array;
6
+ s: Uint32Array;
7
+ cw: Uint8Array;
8
+ }
9
+ export interface BufferDiff {
10
+ index: number;
11
+ x: number;
12
+ y: number;
13
+ actual: {
14
+ g: number;
15
+ s: number;
16
+ cw: number;
17
+ };
18
+ expected: {
19
+ g: number;
20
+ s: number;
21
+ cw: number;
22
+ };
23
+ }
24
+ export interface AnsiEmulatorOptions {
25
+ palette?: Palette;
26
+ wcwidth?: Wcwidth;
27
+ }
28
+ /**
29
+ * Lightweight ANSI terminal emulator focused on correctness for renderer tests.
30
+ * - Tracks cursor position
31
+ * - Applies SGR (fg/bg/bold/italic/underline/inverse)
32
+ * - Handles CUU/CUD/CUF/CUB, CUP, CHA, EL, ED
33
+ * - Maintains a grid of cells with glyph + style + width
34
+ */
35
+ export declare class AnsiEmulator {
36
+ width: number;
37
+ height: number;
38
+ cursorX: number;
39
+ cursorY: number;
40
+ private readonly palette;
41
+ private readonly wcwidth;
42
+ private styleSpec;
43
+ private styleId;
44
+ private g;
45
+ private s;
46
+ private cw;
47
+ constructor(width: number, height: number, options?: AnsiEmulatorOptions);
48
+ reset(): void;
49
+ resize(width: number, height: number): void;
50
+ apply(input: string): void;
51
+ snapshot(): EmulatorBufferSnapshot;
52
+ toCellBuffer(): CellBuffer;
53
+ diffBuffer(buffer: CellBuffer): BufferDiff | null;
54
+ matchesBuffer(buffer: CellBuffer): boolean;
55
+ private findCsiEnd;
56
+ private findOscEnd;
57
+ private handleCsi;
58
+ private parseParams;
59
+ private applySgr;
60
+ private writeCodePoint;
61
+ private lineFeed;
62
+ private scrollUp;
63
+ private clearScreen;
64
+ private clearLine;
65
+ private clearRow;
66
+ private clampRow;
67
+ private clampCol;
68
+ }
69
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,OAAO,EAAkC,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAEpG,MAAM,WAAW,sBAAsB;IACtC,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,CAAC,EAAE,WAAW,CAAA;IACd,CAAC,EAAE,WAAW,CAAA;IACd,EAAE,EAAE,UAAU,CAAA;CACd;AAED,MAAM,WAAW,UAAU;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CAC9C;AAED,MAAM,WAAW,mBAAmB;IACnC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;GAMG;AACH,qBAAa,YAAY;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,SAAI;IACX,OAAO,SAAI;IAEX,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,OAAO,CAAI;IAEnB,OAAO,CAAC,CAAC,CAAa;IACtB,OAAO,CAAC,CAAC,CAAa;IACtB,OAAO,CAAC,EAAE,CAAY;gBAEV,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB;IAY5E,KAAK,IAAI,IAAI;IAQb,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAY3C,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IA8D1B,QAAQ,IAAI,sBAAsB;IAUlC,YAAY,IAAI,UAAU;IAQ1B,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,IAAI;IAkBjD,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO;IAI1C,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,SAAS;IAuDjB,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,QAAQ;IA0DhB,OAAO,CAAC,cAAc;IAsBtB,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,QAAQ;IAgBhB,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,QAAQ;CAGhB"}
package/dist/index.js ADDED
@@ -0,0 +1,386 @@
1
+ import { CellBuffer, Palette, defaultWcwidth } from "@effect-tui/core";
2
+ /**
3
+ * Lightweight ANSI terminal emulator focused on correctness for renderer tests.
4
+ * - Tracks cursor position
5
+ * - Applies SGR (fg/bg/bold/italic/underline/inverse)
6
+ * - Handles CUU/CUD/CUF/CUB, CUP, CHA, EL, ED
7
+ * - Maintains a grid of cells with glyph + style + width
8
+ */
9
+ export class AnsiEmulator {
10
+ width;
11
+ height;
12
+ cursorX = 0;
13
+ cursorY = 0;
14
+ palette;
15
+ wcwidth;
16
+ styleSpec;
17
+ styleId = 0;
18
+ g;
19
+ s;
20
+ cw;
21
+ constructor(width, height, options = {}) {
22
+ this.width = Math.max(1, width | 0);
23
+ this.height = Math.max(1, height | 0);
24
+ this.palette = options.palette ?? new Palette();
25
+ this.wcwidth = options.wcwidth ?? defaultWcwidth;
26
+ const size = this.width * this.height;
27
+ this.g = new Uint32Array(size);
28
+ this.s = new Uint32Array(size);
29
+ this.cw = new Uint8Array(size);
30
+ this.clearScreen(2);
31
+ }
32
+ reset() {
33
+ this.cursorX = 0;
34
+ this.cursorY = 0;
35
+ this.styleSpec = undefined;
36
+ this.styleId = 0;
37
+ this.clearScreen(2);
38
+ }
39
+ resize(width, height) {
40
+ this.width = Math.max(1, width | 0);
41
+ this.height = Math.max(1, height | 0);
42
+ const size = this.width * this.height;
43
+ this.g = new Uint32Array(size);
44
+ this.s = new Uint32Array(size);
45
+ this.cw = new Uint8Array(size);
46
+ this.cursorX = 0;
47
+ this.cursorY = 0;
48
+ this.clearScreen(2);
49
+ }
50
+ apply(input) {
51
+ let i = 0;
52
+ while (i < input.length) {
53
+ const ch = input[i];
54
+ if (ch === "\u001b") {
55
+ const next = input[i + 1];
56
+ if (next === "[") {
57
+ const end = this.findCsiEnd(input, i + 2);
58
+ if (end < 0)
59
+ break;
60
+ const paramsRaw = input.slice(i + 2, end);
61
+ const final = input[end];
62
+ this.handleCsi(paramsRaw, final);
63
+ i = end + 1;
64
+ continue;
65
+ }
66
+ if (next === "]") {
67
+ const end = this.findOscEnd(input, i + 2);
68
+ i = end >= 0 ? end : input.length;
69
+ continue;
70
+ }
71
+ if (next === "7") {
72
+ i += 2;
73
+ continue;
74
+ }
75
+ if (next === "8") {
76
+ i += 2;
77
+ continue;
78
+ }
79
+ i += 1;
80
+ continue;
81
+ }
82
+ const cp = input.codePointAt(i) ?? 0;
83
+ const size = cp > 0xffff ? 2 : 1;
84
+ if (cp === 10) {
85
+ this.lineFeed();
86
+ i += size;
87
+ continue;
88
+ }
89
+ if (cp === 13) {
90
+ this.cursorX = 0;
91
+ i += size;
92
+ continue;
93
+ }
94
+ if (cp === 8) {
95
+ this.cursorX = Math.max(0, this.cursorX - 1);
96
+ i += size;
97
+ continue;
98
+ }
99
+ if (cp === 9) {
100
+ const nextTab = Math.min(this.width, ((this.cursorX >> 3) + 1) << 3);
101
+ this.cursorX = nextTab;
102
+ i += size;
103
+ continue;
104
+ }
105
+ this.writeCodePoint(cp);
106
+ i += size;
107
+ }
108
+ }
109
+ snapshot() {
110
+ return {
111
+ width: this.width,
112
+ height: this.height,
113
+ g: this.g,
114
+ s: this.s,
115
+ cw: this.cw,
116
+ };
117
+ }
118
+ toCellBuffer() {
119
+ const buf = new CellBuffer(this.width, this.height, this.wcwidth);
120
+ buf.g.set(this.g);
121
+ buf.s.set(this.s);
122
+ buf.cw.set(this.cw);
123
+ return buf;
124
+ }
125
+ diffBuffer(buffer) {
126
+ const total = this.width * this.height;
127
+ for (let i = 0; i < total; i++) {
128
+ if (buffer.g[i] !== this.g[i] || buffer.s[i] !== this.s[i] || buffer.cw[i] !== this.cw[i]) {
129
+ const x = i % this.width;
130
+ const y = Math.floor(i / this.width);
131
+ return {
132
+ index: i,
133
+ x,
134
+ y,
135
+ actual: { g: this.g[i], s: this.s[i], cw: this.cw[i] },
136
+ expected: { g: buffer.g[i], s: buffer.s[i], cw: buffer.cw[i] },
137
+ };
138
+ }
139
+ }
140
+ return null;
141
+ }
142
+ matchesBuffer(buffer) {
143
+ return this.diffBuffer(buffer) === null;
144
+ }
145
+ findCsiEnd(input, start) {
146
+ for (let i = start; i < input.length; i++) {
147
+ const code = input.charCodeAt(i);
148
+ if (code >= 0x40 && code <= 0x7e)
149
+ return i;
150
+ }
151
+ return -1;
152
+ }
153
+ findOscEnd(input, start) {
154
+ for (let i = start; i < input.length; i++) {
155
+ if (input[i] === "\u0007")
156
+ return i + 1;
157
+ if (input[i] === "\u001b" && input[i + 1] === "\\")
158
+ return i + 2;
159
+ }
160
+ return -1;
161
+ }
162
+ handleCsi(paramsRaw, final) {
163
+ const params = this.parseParams(paramsRaw);
164
+ switch (final) {
165
+ case "A": {
166
+ const n = params[0] || 1;
167
+ this.cursorY = Math.max(0, this.cursorY - n);
168
+ break;
169
+ }
170
+ case "B": {
171
+ const n = params[0] || 1;
172
+ this.cursorY = Math.min(this.height - 1, this.cursorY + n);
173
+ break;
174
+ }
175
+ case "C": {
176
+ const n = params[0] || 1;
177
+ this.cursorX = Math.min(this.width - 1, this.cursorX + n);
178
+ break;
179
+ }
180
+ case "D": {
181
+ const n = params[0] || 1;
182
+ this.cursorX = Math.max(0, this.cursorX - n);
183
+ break;
184
+ }
185
+ case "G": {
186
+ const col = params[0] || 1;
187
+ this.cursorX = this.clampCol(col - 1);
188
+ break;
189
+ }
190
+ case "H":
191
+ case "f": {
192
+ const row = params[0] || 1;
193
+ const col = params[1] || 1;
194
+ this.cursorY = this.clampRow(row - 1);
195
+ this.cursorX = this.clampCol(col - 1);
196
+ break;
197
+ }
198
+ case "J": {
199
+ const mode = params[0] ?? 0;
200
+ this.clearScreen(mode);
201
+ break;
202
+ }
203
+ case "K": {
204
+ const mode = params[0] ?? 0;
205
+ this.clearLine(mode);
206
+ break;
207
+ }
208
+ case "m": {
209
+ this.applySgr(params);
210
+ break;
211
+ }
212
+ default:
213
+ break;
214
+ }
215
+ }
216
+ parseParams(paramsRaw) {
217
+ if (paramsRaw.length === 0)
218
+ return [0];
219
+ const parts = paramsRaw.split(";");
220
+ return parts.map((part) => {
221
+ const cleaned = part.replace(/[^\d-]/g, "");
222
+ if (cleaned === "")
223
+ return 0;
224
+ const num = Number(cleaned);
225
+ return Number.isFinite(num) ? num : 0;
226
+ });
227
+ }
228
+ applySgr(params) {
229
+ if (params.length === 0) {
230
+ this.styleSpec = undefined;
231
+ this.styleId = 0;
232
+ return;
233
+ }
234
+ let spec = this.styleSpec ? { ...this.styleSpec } : {};
235
+ for (let i = 0; i < params.length; i++) {
236
+ const code = params[i] ?? 0;
237
+ if (code === 0) {
238
+ spec = {};
239
+ continue;
240
+ }
241
+ if (code === 1)
242
+ spec.bold = true;
243
+ else if (code === 3)
244
+ spec.italic = true;
245
+ else if (code === 4)
246
+ spec.underline = true;
247
+ else if (code === 7)
248
+ spec.inverse = true;
249
+ else if (code === 22)
250
+ spec.bold = false;
251
+ else if (code === 23)
252
+ spec.italic = false;
253
+ else if (code === 24)
254
+ spec.underline = false;
255
+ else if (code === 27)
256
+ spec.inverse = false;
257
+ else if (code === 39)
258
+ spec.fg = undefined;
259
+ else if (code === 49)
260
+ spec.bg = undefined;
261
+ else if (code === 38 || code === 48) {
262
+ const isBg = code === 48;
263
+ const mode = params[i + 1];
264
+ if (mode === 5) {
265
+ const value = params[i + 2];
266
+ if (typeof value === "number") {
267
+ if (isBg)
268
+ spec.bg = value;
269
+ else
270
+ spec.fg = value;
271
+ }
272
+ i += 2;
273
+ }
274
+ else if (mode === 2) {
275
+ const r = params[i + 2];
276
+ const g = params[i + 3];
277
+ const b = params[i + 4];
278
+ if (r !== undefined && g !== undefined && b !== undefined) {
279
+ const rgb = { r, g, b };
280
+ if (isBg)
281
+ spec.bg = rgb;
282
+ else
283
+ spec.fg = rgb;
284
+ }
285
+ i += 4;
286
+ }
287
+ }
288
+ }
289
+ if (Object.keys(spec).length === 0) {
290
+ this.styleSpec = undefined;
291
+ this.styleId = 0;
292
+ return;
293
+ }
294
+ this.styleSpec = spec;
295
+ this.styleId = this.palette.id(spec);
296
+ }
297
+ writeCodePoint(cp) {
298
+ if (this.cursorY < 0 || this.cursorY >= this.height)
299
+ return;
300
+ if (this.cursorX < 0 || this.cursorX >= this.width)
301
+ return;
302
+ const width = this.wcwidth(cp) || 1;
303
+ if (this.cursorX + width > this.width)
304
+ return;
305
+ const idx = this.cursorY * this.width + this.cursorX;
306
+ this.g[idx] = cp;
307
+ this.s[idx] = this.styleId;
308
+ this.cw[idx] = width;
309
+ if (width === 2 && this.cursorX + 1 < this.width) {
310
+ const nextIdx = idx + 1;
311
+ this.g[nextIdx] = 0;
312
+ this.s[nextIdx] = this.styleId;
313
+ this.cw[nextIdx] = 0;
314
+ }
315
+ this.cursorX += width;
316
+ }
317
+ lineFeed() {
318
+ if (this.cursorY === this.height - 1) {
319
+ this.scrollUp(1);
320
+ }
321
+ else {
322
+ this.cursorY += 1;
323
+ }
324
+ }
325
+ scrollUp(lines) {
326
+ const count = Math.max(1, lines | 0);
327
+ const rowSize = this.width;
328
+ for (let y = 0; y < this.height - count; y++) {
329
+ const dst = y * rowSize;
330
+ const src = (y + count) * rowSize;
331
+ this.g.copyWithin(dst, src, src + rowSize);
332
+ this.s.copyWithin(dst, src, src + rowSize);
333
+ this.cw.copyWithin(dst, src, src + rowSize);
334
+ }
335
+ for (let y = this.height - count; y < this.height; y++) {
336
+ this.clearRow(y, 0, this.width);
337
+ }
338
+ this.cursorY = this.height - 1;
339
+ }
340
+ clearScreen(mode) {
341
+ if (mode === 2) {
342
+ for (let y = 0; y < this.height; y++) {
343
+ this.clearRow(y, 0, this.width);
344
+ }
345
+ return;
346
+ }
347
+ if (mode === 1) {
348
+ for (let y = 0; y < this.cursorY; y++) {
349
+ this.clearRow(y, 0, this.width);
350
+ }
351
+ this.clearRow(this.cursorY, 0, this.cursorX + 1);
352
+ return;
353
+ }
354
+ for (let y = this.cursorY; y < this.height; y++) {
355
+ const start = y === this.cursorY ? this.cursorX : 0;
356
+ this.clearRow(y, start, this.width);
357
+ }
358
+ }
359
+ clearLine(mode) {
360
+ if (mode === 2) {
361
+ this.clearRow(this.cursorY, 0, this.width);
362
+ return;
363
+ }
364
+ if (mode === 1) {
365
+ this.clearRow(this.cursorY, 0, this.cursorX + 1);
366
+ return;
367
+ }
368
+ this.clearRow(this.cursorY, this.cursorX, this.width);
369
+ }
370
+ clearRow(y, startX, endX) {
371
+ const rowStart = y * this.width;
372
+ for (let x = Math.max(0, startX); x < Math.min(this.width, endX); x++) {
373
+ const idx = rowStart + x;
374
+ this.g[idx] = 32;
375
+ this.s[idx] = this.styleId;
376
+ this.cw[idx] = 1;
377
+ }
378
+ }
379
+ clampRow(row) {
380
+ return Math.max(0, Math.min(this.height - 1, row));
381
+ }
382
+ clampCol(col) {
383
+ return Math.max(0, Math.min(this.width - 1, col));
384
+ }
385
+ }
386
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@effect-tui/emulator",
3
+ "version": "0.1.7",
4
+ "description": "Lightweight ANSI terminal emulator for effect-tui tests",
5
+ "type": "module",
6
+ "files": [
7
+ "dist",
8
+ "src",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "bun": "./src/index.ts",
15
+ "import": "./src/index.ts",
16
+ "types": "./src/index.ts"
17
+ }
18
+ },
19
+ "types": "./src/index.ts",
20
+ "keywords": [
21
+ "terminal",
22
+ "tui",
23
+ "ansi",
24
+ "emulator",
25
+ "testing"
26
+ ],
27
+ "author": "Kit Langton",
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/kitlangton/effect-tui.git",
32
+ "directory": "packages/effect-tui-emulator"
33
+ },
34
+ "homepage": "https://github.com/kitlangton/effect-tui",
35
+ "bugs": "https://github.com/kitlangton/effect-tui/issues",
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc -p .",
41
+ "typecheck": "tsc -p . --noEmit",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest",
44
+ "format": "biome format --write .",
45
+ "format:check": "biome format .",
46
+ "prepublishOnly": "bun run typecheck && bun run build"
47
+ },
48
+ "dependencies": {
49
+ "@effect-tui/core": "workspace:^"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^25.0.3",
53
+ "typescript": "^5.9.3",
54
+ "vitest": "^4.0.16"
55
+ }
56
+ }
package/src/index.ts ADDED
@@ -0,0 +1,415 @@
1
+ import { CellBuffer, Palette, type StyleSpec, defaultWcwidth, type Wcwidth } from "@effect-tui/core"
2
+
3
+ export interface EmulatorBufferSnapshot {
4
+ width: number
5
+ height: number
6
+ g: Uint32Array
7
+ s: Uint32Array
8
+ cw: Uint8Array
9
+ }
10
+
11
+ export interface BufferDiff {
12
+ index: number
13
+ x: number
14
+ y: number
15
+ actual: { g: number; s: number; cw: number }
16
+ expected: { g: number; s: number; cw: number }
17
+ }
18
+
19
+ export interface AnsiEmulatorOptions {
20
+ palette?: Palette
21
+ wcwidth?: Wcwidth
22
+ }
23
+
24
+ /**
25
+ * Lightweight ANSI terminal emulator focused on correctness for renderer tests.
26
+ * - Tracks cursor position
27
+ * - Applies SGR (fg/bg/bold/italic/underline/inverse)
28
+ * - Handles CUU/CUD/CUF/CUB, CUP, CHA, EL, ED
29
+ * - Maintains a grid of cells with glyph + style + width
30
+ */
31
+ export class AnsiEmulator {
32
+ width: number
33
+ height: number
34
+ cursorX = 0
35
+ cursorY = 0
36
+
37
+ private readonly palette: Palette
38
+ private readonly wcwidth: Wcwidth
39
+ private styleSpec: StyleSpec | undefined
40
+ private styleId = 0
41
+
42
+ private g: Uint32Array
43
+ private s: Uint32Array
44
+ private cw: Uint8Array
45
+
46
+ constructor(width: number, height: number, options: AnsiEmulatorOptions = {}) {
47
+ this.width = Math.max(1, width | 0)
48
+ this.height = Math.max(1, height | 0)
49
+ this.palette = options.palette ?? new Palette()
50
+ this.wcwidth = options.wcwidth ?? defaultWcwidth
51
+ const size = this.width * this.height
52
+ this.g = new Uint32Array(size)
53
+ this.s = new Uint32Array(size)
54
+ this.cw = new Uint8Array(size)
55
+ this.clearScreen(2)
56
+ }
57
+
58
+ reset(): void {
59
+ this.cursorX = 0
60
+ this.cursorY = 0
61
+ this.styleSpec = undefined
62
+ this.styleId = 0
63
+ this.clearScreen(2)
64
+ }
65
+
66
+ resize(width: number, height: number): void {
67
+ this.width = Math.max(1, width | 0)
68
+ this.height = Math.max(1, height | 0)
69
+ const size = this.width * this.height
70
+ this.g = new Uint32Array(size)
71
+ this.s = new Uint32Array(size)
72
+ this.cw = new Uint8Array(size)
73
+ this.cursorX = 0
74
+ this.cursorY = 0
75
+ this.clearScreen(2)
76
+ }
77
+
78
+ apply(input: string): void {
79
+ let i = 0
80
+ while (i < input.length) {
81
+ const ch = input[i]
82
+ if (ch === "\u001b") {
83
+ const next = input[i + 1]
84
+ if (next === "[") {
85
+ const end = this.findCsiEnd(input, i + 2)
86
+ if (end < 0) break
87
+ const paramsRaw = input.slice(i + 2, end)
88
+ const final = input[end]
89
+ this.handleCsi(paramsRaw, final)
90
+ i = end + 1
91
+ continue
92
+ }
93
+ if (next === "]") {
94
+ const end = this.findOscEnd(input, i + 2)
95
+ i = end >= 0 ? end : input.length
96
+ continue
97
+ }
98
+ if (next === "7") {
99
+ i += 2
100
+ continue
101
+ }
102
+ if (next === "8") {
103
+ i += 2
104
+ continue
105
+ }
106
+ i += 1
107
+ continue
108
+ }
109
+
110
+ const cp = input.codePointAt(i) ?? 0
111
+ const size = cp > 0xffff ? 2 : 1
112
+
113
+ if (cp === 10) {
114
+ this.lineFeed()
115
+ i += size
116
+ continue
117
+ }
118
+ if (cp === 13) {
119
+ this.cursorX = 0
120
+ i += size
121
+ continue
122
+ }
123
+ if (cp === 8) {
124
+ this.cursorX = Math.max(0, this.cursorX - 1)
125
+ i += size
126
+ continue
127
+ }
128
+ if (cp === 9) {
129
+ const nextTab = Math.min(this.width, ((this.cursorX >> 3) + 1) << 3)
130
+ this.cursorX = nextTab
131
+ i += size
132
+ continue
133
+ }
134
+
135
+ this.writeCodePoint(cp)
136
+ i += size
137
+ }
138
+ }
139
+
140
+ snapshot(): EmulatorBufferSnapshot {
141
+ return {
142
+ width: this.width,
143
+ height: this.height,
144
+ g: this.g,
145
+ s: this.s,
146
+ cw: this.cw,
147
+ }
148
+ }
149
+
150
+ toCellBuffer(): CellBuffer {
151
+ const buf = new CellBuffer(this.width, this.height, this.wcwidth)
152
+ buf.g.set(this.g)
153
+ buf.s.set(this.s)
154
+ buf.cw.set(this.cw)
155
+ return buf
156
+ }
157
+
158
+ diffBuffer(buffer: CellBuffer): BufferDiff | null {
159
+ const total = this.width * this.height
160
+ for (let i = 0; i < total; i++) {
161
+ if (buffer.g[i] !== this.g[i] || buffer.s[i] !== this.s[i] || buffer.cw[i] !== this.cw[i]) {
162
+ const x = i % this.width
163
+ const y = Math.floor(i / this.width)
164
+ return {
165
+ index: i,
166
+ x,
167
+ y,
168
+ actual: { g: this.g[i], s: this.s[i], cw: this.cw[i] },
169
+ expected: { g: buffer.g[i], s: buffer.s[i], cw: buffer.cw[i] },
170
+ }
171
+ }
172
+ }
173
+ return null
174
+ }
175
+
176
+ matchesBuffer(buffer: CellBuffer): boolean {
177
+ return this.diffBuffer(buffer) === null
178
+ }
179
+
180
+ private findCsiEnd(input: string, start: number): number {
181
+ for (let i = start; i < input.length; i++) {
182
+ const code = input.charCodeAt(i)
183
+ if (code >= 0x40 && code <= 0x7e) return i
184
+ }
185
+ return -1
186
+ }
187
+
188
+ private findOscEnd(input: string, start: number): number {
189
+ for (let i = start; i < input.length; i++) {
190
+ if (input[i] === "\u0007") return i + 1
191
+ if (input[i] === "\u001b" && input[i + 1] === "\\") return i + 2
192
+ }
193
+ return -1
194
+ }
195
+
196
+ private handleCsi(paramsRaw: string, final: string): void {
197
+ const params = this.parseParams(paramsRaw)
198
+ switch (final) {
199
+ case "A": {
200
+ const n = params[0] || 1
201
+ this.cursorY = Math.max(0, this.cursorY - n)
202
+ break
203
+ }
204
+ case "B": {
205
+ const n = params[0] || 1
206
+ this.cursorY = Math.min(this.height - 1, this.cursorY + n)
207
+ break
208
+ }
209
+ case "C": {
210
+ const n = params[0] || 1
211
+ this.cursorX = Math.min(this.width - 1, this.cursorX + n)
212
+ break
213
+ }
214
+ case "D": {
215
+ const n = params[0] || 1
216
+ this.cursorX = Math.max(0, this.cursorX - n)
217
+ break
218
+ }
219
+ case "G": {
220
+ const col = params[0] || 1
221
+ this.cursorX = this.clampCol(col - 1)
222
+ break
223
+ }
224
+ case "H":
225
+ case "f": {
226
+ const row = params[0] || 1
227
+ const col = params[1] || 1
228
+ this.cursorY = this.clampRow(row - 1)
229
+ this.cursorX = this.clampCol(col - 1)
230
+ break
231
+ }
232
+ case "J": {
233
+ const mode = params[0] ?? 0
234
+ this.clearScreen(mode)
235
+ break
236
+ }
237
+ case "K": {
238
+ const mode = params[0] ?? 0
239
+ this.clearLine(mode)
240
+ break
241
+ }
242
+ case "m": {
243
+ this.applySgr(params)
244
+ break
245
+ }
246
+ default:
247
+ break
248
+ }
249
+ }
250
+
251
+ private parseParams(paramsRaw: string): number[] {
252
+ if (paramsRaw.length === 0) return [0]
253
+ const parts = paramsRaw.split(";")
254
+ return parts.map((part) => {
255
+ const cleaned = part.replace(/[^\d-]/g, "")
256
+ if (cleaned === "") return 0
257
+ const num = Number(cleaned)
258
+ return Number.isFinite(num) ? num : 0
259
+ })
260
+ }
261
+
262
+ private applySgr(params: number[]): void {
263
+ if (params.length === 0) {
264
+ this.styleSpec = undefined
265
+ this.styleId = 0
266
+ return
267
+ }
268
+
269
+ let spec: StyleSpec = this.styleSpec ? { ...this.styleSpec } : {}
270
+ for (let i = 0; i < params.length; i++) {
271
+ const code = params[i] ?? 0
272
+ if (code === 0) {
273
+ spec = {}
274
+ continue
275
+ }
276
+ if (code === 1) spec.bold = true
277
+ else if (code === 3) spec.italic = true
278
+ else if (code === 4) spec.underline = true
279
+ else if (code === 7) spec.inverse = true
280
+ else if (code === 22) spec.bold = false
281
+ else if (code === 23) spec.italic = false
282
+ else if (code === 24) spec.underline = false
283
+ else if (code === 27) spec.inverse = false
284
+ else if (code === 39) spec.fg = undefined
285
+ else if (code === 49) spec.bg = undefined
286
+ else if (code === 38 || code === 48) {
287
+ const isBg = code === 48
288
+ const mode = params[i + 1]
289
+ if (mode === 5) {
290
+ const value = params[i + 2]
291
+ if (typeof value === "number") {
292
+ if (isBg) spec.bg = value
293
+ else spec.fg = value
294
+ }
295
+ i += 2
296
+ } else if (mode === 2) {
297
+ const r = params[i + 2]
298
+ const g = params[i + 3]
299
+ const b = params[i + 4]
300
+ if (r !== undefined && g !== undefined && b !== undefined) {
301
+ const rgb = { r, g, b }
302
+ if (isBg) spec.bg = rgb
303
+ else spec.fg = rgb
304
+ }
305
+ i += 4
306
+ }
307
+ }
308
+ }
309
+
310
+ if (Object.keys(spec).length === 0) {
311
+ this.styleSpec = undefined
312
+ this.styleId = 0
313
+ return
314
+ }
315
+
316
+ this.styleSpec = spec
317
+ this.styleId = this.palette.id(spec)
318
+ }
319
+
320
+ private writeCodePoint(cp: number): void {
321
+ if (this.cursorY < 0 || this.cursorY >= this.height) return
322
+ if (this.cursorX < 0 || this.cursorX >= this.width) return
323
+
324
+ const width = this.wcwidth(cp) || 1
325
+ if (this.cursorX + width > this.width) return
326
+
327
+ const idx = this.cursorY * this.width + this.cursorX
328
+ this.g[idx] = cp
329
+ this.s[idx] = this.styleId
330
+ this.cw[idx] = width
331
+
332
+ if (width === 2 && this.cursorX + 1 < this.width) {
333
+ const nextIdx = idx + 1
334
+ this.g[nextIdx] = 0
335
+ this.s[nextIdx] = this.styleId
336
+ this.cw[nextIdx] = 0
337
+ }
338
+
339
+ this.cursorX += width
340
+ }
341
+
342
+ private lineFeed(): void {
343
+ if (this.cursorY === this.height - 1) {
344
+ this.scrollUp(1)
345
+ } else {
346
+ this.cursorY += 1
347
+ }
348
+ }
349
+
350
+ private scrollUp(lines: number): void {
351
+ const count = Math.max(1, lines | 0)
352
+ const rowSize = this.width
353
+ for (let y = 0; y < this.height - count; y++) {
354
+ const dst = y * rowSize
355
+ const src = (y + count) * rowSize
356
+ this.g.copyWithin(dst, src, src + rowSize)
357
+ this.s.copyWithin(dst, src, src + rowSize)
358
+ this.cw.copyWithin(dst, src, src + rowSize)
359
+ }
360
+ for (let y = this.height - count; y < this.height; y++) {
361
+ this.clearRow(y, 0, this.width)
362
+ }
363
+ this.cursorY = this.height - 1
364
+ }
365
+
366
+ private clearScreen(mode: number): void {
367
+ if (mode === 2) {
368
+ for (let y = 0; y < this.height; y++) {
369
+ this.clearRow(y, 0, this.width)
370
+ }
371
+ return
372
+ }
373
+ if (mode === 1) {
374
+ for (let y = 0; y < this.cursorY; y++) {
375
+ this.clearRow(y, 0, this.width)
376
+ }
377
+ this.clearRow(this.cursorY, 0, this.cursorX + 1)
378
+ return
379
+ }
380
+ for (let y = this.cursorY; y < this.height; y++) {
381
+ const start = y === this.cursorY ? this.cursorX : 0
382
+ this.clearRow(y, start, this.width)
383
+ }
384
+ }
385
+
386
+ private clearLine(mode: number): void {
387
+ if (mode === 2) {
388
+ this.clearRow(this.cursorY, 0, this.width)
389
+ return
390
+ }
391
+ if (mode === 1) {
392
+ this.clearRow(this.cursorY, 0, this.cursorX + 1)
393
+ return
394
+ }
395
+ this.clearRow(this.cursorY, this.cursorX, this.width)
396
+ }
397
+
398
+ private clearRow(y: number, startX: number, endX: number): void {
399
+ const rowStart = y * this.width
400
+ for (let x = Math.max(0, startX); x < Math.min(this.width, endX); x++) {
401
+ const idx = rowStart + x
402
+ this.g[idx] = 32
403
+ this.s[idx] = this.styleId
404
+ this.cw[idx] = 1
405
+ }
406
+ }
407
+
408
+ private clampRow(row: number): number {
409
+ return Math.max(0, Math.min(this.height - 1, row))
410
+ }
411
+
412
+ private clampCol(col: number): number {
413
+ return Math.max(0, Math.min(this.width - 1, col))
414
+ }
415
+ }