@oh-my-pi/pi-utils 17.2.8 → 17.2.10

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.
Files changed (89) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/types/acp/connection.d.ts +118 -0
  3. package/dist/types/acp/protocol.d.ts +526 -0
  4. package/dist/types/acp/schema.d.ts +41 -0
  5. package/dist/types/acp/stream.d.ts +8 -0
  6. package/dist/types/acp/transport.d.ts +81 -0
  7. package/dist/types/acp.d.ts +6 -0
  8. package/dist/types/browsers.d.ts +68 -0
  9. package/dist/types/chalk.d.ts +125 -0
  10. package/dist/types/dates.d.ts +7 -0
  11. package/dist/types/docx/converter.d.ts +46 -0
  12. package/dist/types/docx/xml.d.ts +26 -0
  13. package/dist/types/docx/zip.d.ts +6 -0
  14. package/dist/types/docx.d.ts +11 -0
  15. package/dist/types/dom/core.d.ts +431 -0
  16. package/dist/types/dom/parser.d.ts +7 -0
  17. package/dist/types/dom/selector.d.ts +5 -0
  18. package/dist/types/dom.d.ts +5 -0
  19. package/dist/types/headers.d.ts +34 -0
  20. package/dist/types/index.d.ts +1 -0
  21. package/dist/types/logger/rotating-file.d.ts +18 -0
  22. package/dist/types/lru.d.ts +46 -0
  23. package/dist/types/marked/core.d.ts +445 -0
  24. package/dist/types/marked.d.ts +2 -0
  25. package/dist/types/postmortem.d.ts +14 -0
  26. package/dist/types/procmgr.d.ts +16 -0
  27. package/dist/types/prompt.d.ts +2 -2
  28. package/dist/types/readability/readability.d.ts +9 -0
  29. package/dist/types/readability/readerable.d.ts +10 -0
  30. package/dist/types/readability/types.d.ts +70 -0
  31. package/dist/types/readability.d.ts +4 -0
  32. package/dist/types/template.d.ts +62 -0
  33. package/dist/types/turndown/gfm.d.ts +11 -0
  34. package/dist/types/turndown/html.d.ts +5 -0
  35. package/dist/types/turndown/service.d.ts +21 -0
  36. package/dist/types/turndown/types.d.ts +70 -0
  37. package/dist/types/turndown.d.ts +4 -0
  38. package/dist/types/version.d.ts +18 -0
  39. package/dist/types/vterm/buffer.d.ts +99 -0
  40. package/dist/types/vterm/terminal.d.ts +44 -0
  41. package/dist/types/vterm.d.ts +8 -0
  42. package/dist/types/xml.d.ts +31 -0
  43. package/package.json +2 -5
  44. package/src/acp/connection.ts +344 -0
  45. package/src/acp/protocol.ts +466 -0
  46. package/src/acp/schema.ts +160 -0
  47. package/src/acp/stream.ts +82 -0
  48. package/src/acp/transport.ts +213 -0
  49. package/src/acp.ts +6 -0
  50. package/src/browsers.ts +501 -0
  51. package/src/chalk.ts +312 -0
  52. package/src/dates.ts +194 -0
  53. package/src/docx/converter.ts +681 -0
  54. package/src/docx/xml.ts +166 -0
  55. package/src/docx/zip.ts +87 -0
  56. package/src/docx.ts +20 -0
  57. package/src/dom/core.ts +1254 -0
  58. package/src/dom/parser.ts +370 -0
  59. package/src/dom/selector.ts +290 -0
  60. package/src/dom.ts +33 -0
  61. package/src/fetch-retry.ts +7 -1
  62. package/src/frontmatter.ts +1 -1
  63. package/src/headers.ts +167 -0
  64. package/src/index.ts +1 -0
  65. package/src/logger/rotating-file.ts +149 -0
  66. package/src/logger.ts +12 -28
  67. package/src/lru.ts +185 -0
  68. package/src/marked/core.ts +1576 -0
  69. package/src/marked.ts +2 -0
  70. package/src/postmortem.ts +44 -1
  71. package/src/procmgr.ts +21 -4
  72. package/src/prompt.ts +5 -22
  73. package/src/readability/readability.ts +533 -0
  74. package/src/readability/readerable.ts +51 -0
  75. package/src/readability/types.ts +72 -0
  76. package/src/readability.ts +11 -0
  77. package/src/template.ts +586 -0
  78. package/src/turndown/gfm.ts +106 -0
  79. package/src/turndown/html.ts +257 -0
  80. package/src/turndown/service.ts +334 -0
  81. package/src/turndown/types.ts +81 -0
  82. package/src/turndown.ts +5 -0
  83. package/src/version.ts +99 -0
  84. package/src/vterm/buffer.ts +218 -0
  85. package/src/vterm/terminal.ts +773 -0
  86. package/src/vterm.ts +8 -0
  87. package/src/which.ts +6 -4
  88. package/src/xml.ts +313 -0
  89. package/src/winston-daily-rotate-file.d.ts +0 -6
@@ -0,0 +1,773 @@
1
+ import {
2
+ BufferLine,
3
+ type BufferState,
4
+ BufferView,
5
+ blankCell,
6
+ type CellAttributes,
7
+ type CellData,
8
+ defaultAttributes,
9
+ } from "./buffer";
10
+
11
+ const segmenter = new Intl.Segmenter();
12
+
13
+ /** Construction options supported by the headless virtual terminal. */
14
+ export interface TerminalOptions {
15
+ cols?: number;
16
+ rows?: number;
17
+ scrollback?: number;
18
+ allowProposedApi?: boolean;
19
+ disableStdin?: boolean;
20
+ }
21
+
22
+ /** Disposable event subscription. */
23
+ export interface Disposable {
24
+ dispose(): void;
25
+ }
26
+
27
+ /** Observable terminal modes used by interactive input normalization. */
28
+ export interface TerminalModes {
29
+ applicationCursorKeysMode: boolean;
30
+ }
31
+
32
+ /** Buffer collection exposed by the virtual terminal. */
33
+ export interface TerminalBuffers {
34
+ readonly active: BufferView;
35
+ readonly normal: BufferView;
36
+ readonly alternate: BufferView;
37
+ }
38
+
39
+ interface SavedCursor {
40
+ x: number;
41
+ y: number;
42
+ attrs: CellAttributes;
43
+ }
44
+
45
+ function createState(columns: number, rows: number): BufferState {
46
+ return {
47
+ lines: Array.from({ length: rows }, () => new BufferLine(columns)),
48
+ baseY: 0,
49
+ viewportY: 0,
50
+ cursorX: 0,
51
+ cursorY: 0,
52
+ };
53
+ }
54
+
55
+ function cloneCell(cell: CellData): CellData {
56
+ return { chars: cell.chars, width: cell.width, attrs: { ...cell.attrs } };
57
+ }
58
+
59
+ /** Behavior-compatible reimplementation of @xterm/headless's used surface. */
60
+ export class Terminal {
61
+ /** Current terminal column count. */
62
+ cols: number;
63
+ /** Current terminal row count. */
64
+ rows: number;
65
+ /** Normal, alternate, and active buffer views. */
66
+ readonly buffer: TerminalBuffers;
67
+ /** Currently active input modes. */
68
+ readonly modes: TerminalModes = { applicationCursorKeysMode: false };
69
+
70
+ #scrollback: number;
71
+ #normal: BufferState;
72
+ #alternate: BufferState;
73
+ #active: BufferState;
74
+ #usingAlternate = false;
75
+ #attrs = defaultAttributes();
76
+ #scrollTop = 0;
77
+ #scrollBottom: number;
78
+ #originMode = false;
79
+ #autowrap = true;
80
+ #insertMode = false;
81
+ #pendingWrap = false;
82
+ #saved: SavedCursor | undefined;
83
+ #alternateSaved: SavedCursor | undefined;
84
+ #state: "ground" | "escape" | "csi" | "string" = "ground";
85
+ #sequence = "";
86
+ #stringEscape = false;
87
+ #decoder = new TextDecoder();
88
+ #dataListeners = new Set<(data: string) => void>();
89
+ #disposed = false;
90
+
91
+ constructor(options: TerminalOptions = {}) {
92
+ this.cols = Math.max(2, Math.floor(options.cols ?? 80));
93
+ this.rows = Math.max(1, Math.floor(options.rows ?? 24));
94
+ this.#scrollback = Math.max(0, Math.floor(options.scrollback ?? 1_000));
95
+ this.#scrollBottom = this.rows - 1;
96
+ this.#normal = createState(this.cols, this.rows);
97
+ this.#alternate = createState(this.cols, this.rows);
98
+ this.#active = this.#normal;
99
+ const normal = new BufferView(() => this.#normal);
100
+ const alternate = new BufferView(() => this.#alternate);
101
+ const thisTerminal = this;
102
+ this.buffer = {
103
+ get active() {
104
+ return thisTerminal.#usingAlternate ? alternate : normal;
105
+ },
106
+ normal,
107
+ alternate,
108
+ };
109
+ }
110
+
111
+ /** Parses terminal output and invokes the callback after the write is committed. */
112
+ write(data: string | Uint8Array, callback?: () => void): void {
113
+ if (this.#disposed) return;
114
+ const text = typeof data === "string" ? data : this.#decoder.decode(data, { stream: true });
115
+ this.#parse(text);
116
+ if (callback) queueMicrotask(callback);
117
+ }
118
+
119
+ /** Changes the terminal grid and reflows wrapped normal-buffer lines. */
120
+ resize(columns: number, rows: number): void {
121
+ if (this.#disposed) return;
122
+ const nextColumns = Math.max(2, Math.floor(columns));
123
+ const nextRows = Math.max(1, Math.floor(rows));
124
+ if (nextColumns === this.cols && nextRows === this.rows) return;
125
+ this.#normal = this.#reflow(this.#normal, nextColumns, nextRows, true);
126
+ this.#alternate = this.#reflow(this.#alternate, nextColumns, nextRows, false);
127
+ this.cols = nextColumns;
128
+ this.rows = nextRows;
129
+ this.#scrollTop = 0;
130
+ this.#scrollBottom = nextRows - 1;
131
+ this.#active = this.#usingAlternate ? this.#alternate : this.#normal;
132
+ this.#pendingWrap = this.#active.cursorX >= this.cols;
133
+ }
134
+
135
+ /** Subscribes to terminal-generated input replies. */
136
+ onData(listener: (data: string) => void): Disposable {
137
+ this.#dataListeners.add(listener);
138
+ return { dispose: () => this.#dataListeners.delete(listener) };
139
+ }
140
+
141
+ /** Releases parser state and event listeners. */
142
+ dispose(): void {
143
+ this.#disposed = true;
144
+ this.#dataListeners.clear();
145
+ }
146
+
147
+ #emitData(data: string): void {
148
+ for (const listener of this.#dataListeners) listener(data);
149
+ }
150
+
151
+ #parse(text: string): void {
152
+ let printable = "";
153
+ const flush = () => {
154
+ if (!printable) return;
155
+ for (const part of segmenter.segment(printable)) this.#print(part.segment);
156
+ printable = "";
157
+ };
158
+ for (const char of text) {
159
+ if (this.#state === "string") {
160
+ if (this.#stringEscape && char === "\\") {
161
+ this.#state = "ground";
162
+ this.#stringEscape = false;
163
+ } else if (char === "\x07") {
164
+ this.#state = "ground";
165
+ this.#stringEscape = false;
166
+ } else {
167
+ this.#stringEscape = char === "\x1b";
168
+ }
169
+ continue;
170
+ }
171
+ if (this.#state === "escape") {
172
+ this.#handleEscape(char);
173
+ continue;
174
+ }
175
+ if (this.#state === "csi") {
176
+ if (char >= "@" && char <= "~") {
177
+ this.#handleCsi(this.#sequence, char);
178
+ this.#sequence = "";
179
+ this.#state = "ground";
180
+ } else if (char === "\x1b") {
181
+ this.#state = "escape";
182
+ this.#sequence = "";
183
+ } else {
184
+ this.#sequence += char;
185
+ }
186
+ continue;
187
+ }
188
+ if (char === "\x1b") {
189
+ flush();
190
+ this.#state = "escape";
191
+ } else if (char < " " || char === "\x7f") {
192
+ flush();
193
+ this.#handleControl(char);
194
+ } else {
195
+ printable += char;
196
+ }
197
+ }
198
+ flush();
199
+ }
200
+
201
+ #handleControl(char: string): void {
202
+ switch (char) {
203
+ case "\b":
204
+ this.#active.cursorX = Math.max(0, this.#active.cursorX - 1);
205
+ this.#pendingWrap = false;
206
+ break;
207
+ case "\t": {
208
+ const stop = Math.min(this.cols - 1, (Math.floor(this.#active.cursorX / 8) + 1) * 8);
209
+ this.#active.cursorX = stop;
210
+ this.#pendingWrap = false;
211
+ break;
212
+ }
213
+ case "\n":
214
+ case "\v":
215
+ case "\f":
216
+ this.#lineFeed(false);
217
+ break;
218
+ case "\r":
219
+ this.#active.cursorX = 0;
220
+ this.#pendingWrap = false;
221
+ break;
222
+ }
223
+ }
224
+
225
+ #handleEscape(char: string): void {
226
+ this.#state = "ground";
227
+ switch (char) {
228
+ case "[":
229
+ this.#state = "csi";
230
+ this.#sequence = "";
231
+ break;
232
+ case "]":
233
+ case "P":
234
+ case "_":
235
+ case "^":
236
+ this.#state = "string";
237
+ this.#stringEscape = false;
238
+ break;
239
+ case "7":
240
+ this.#saveCursor();
241
+ break;
242
+ case "8":
243
+ this.#restoreCursor();
244
+ break;
245
+ case "D":
246
+ this.#lineFeed(false);
247
+ break;
248
+ case "E":
249
+ this.#active.cursorX = 0;
250
+ this.#lineFeed(false);
251
+ break;
252
+ case "M":
253
+ this.#reverseIndex();
254
+ break;
255
+ case "c":
256
+ this.#reset();
257
+ break;
258
+ }
259
+ }
260
+
261
+ #handleCsi(raw: string, final: string): void {
262
+ const privateMode = raw.startsWith("?");
263
+ const greater = raw.startsWith(">");
264
+ const body = privateMode || greater ? raw.slice(1) : raw;
265
+ const params = body
266
+ .replaceAll(":", ";")
267
+ .split(";")
268
+ .map(value => (value === "" ? 0 : Number(value)));
269
+ const first = params[0] ?? 0;
270
+ const amount = Math.max(1, first);
271
+ switch (final) {
272
+ case "A":
273
+ this.#moveVertical(-amount);
274
+ break;
275
+ case "B":
276
+ case "e":
277
+ this.#moveVertical(amount);
278
+ break;
279
+ case "C":
280
+ case "a":
281
+ this.#active.cursorX = Math.min(this.cols - 1, this.#active.cursorX + amount);
282
+ this.#pendingWrap = false;
283
+ break;
284
+ case "D":
285
+ this.#active.cursorX = Math.max(0, this.#active.cursorX - amount);
286
+ this.#pendingWrap = false;
287
+ break;
288
+ case "E":
289
+ this.#moveVertical(amount);
290
+ this.#active.cursorX = 0;
291
+ break;
292
+ case "F":
293
+ this.#moveVertical(-amount);
294
+ this.#active.cursorX = 0;
295
+ break;
296
+ case "G":
297
+ case "`":
298
+ this.#active.cursorX = Math.min(this.cols - 1, Math.max(0, amount - 1));
299
+ this.#pendingWrap = false;
300
+ break;
301
+ case "H":
302
+ case "f":
303
+ this.#setCursor((params[0] || 1) - 1, (params[1] || 1) - 1);
304
+ break;
305
+ case "d":
306
+ this.#setCursor((first || 1) - 1, this.#active.cursorX);
307
+ break;
308
+ case "J":
309
+ this.#eraseDisplay(first);
310
+ break;
311
+ case "K":
312
+ this.#eraseLine(first);
313
+ break;
314
+ case "L":
315
+ this.#insertLines(amount);
316
+ break;
317
+ case "M":
318
+ this.#deleteLines(amount);
319
+ break;
320
+ case "@":
321
+ this.#insertCells(amount);
322
+ break;
323
+ case "P":
324
+ this.#deleteCells(amount);
325
+ break;
326
+ case "X":
327
+ this.#eraseCells(amount);
328
+ break;
329
+ case "S":
330
+ for (let index = 0; index < amount; index++) this.#scrollUp();
331
+ break;
332
+ case "T":
333
+ for (let index = 0; index < amount; index++) this.#scrollDown();
334
+ break;
335
+ case "m":
336
+ this.#setRendition(params);
337
+ break;
338
+ case "r":
339
+ if (!privateMode) this.#setScrollRegion(params);
340
+ break;
341
+ case "s":
342
+ this.#saveCursor();
343
+ break;
344
+ case "u":
345
+ this.#restoreCursor();
346
+ break;
347
+ case "h":
348
+ case "l":
349
+ this.#setModes(params, privateMode, final === "h");
350
+ break;
351
+ case "n":
352
+ if (!privateMode && first === 6)
353
+ this.#emitData(`\x1b[${this.#active.cursorY + 1};${this.#active.cursorX + 1}R`);
354
+ break;
355
+ case "c":
356
+ if (!greater) this.#emitData("\x1b[?1;2c");
357
+ break;
358
+ }
359
+ }
360
+
361
+ #print(grapheme: string): void {
362
+ let width = Bun.stringWidth(grapheme);
363
+ if (width === 0) {
364
+ this.#appendCombining(grapheme);
365
+ return;
366
+ }
367
+ width = Math.min(2, width);
368
+ if (this.#pendingWrap || (width === 2 && this.#active.cursorX === this.cols - 1)) {
369
+ if (!this.#autowrap) {
370
+ this.#active.cursorX = this.cols - width;
371
+ } else {
372
+ this.#lineFeed(true);
373
+ this.#active.cursorX = 0;
374
+ }
375
+ }
376
+ const line = this.#currentLine();
377
+ const column = Math.min(this.#active.cursorX, this.cols - 1);
378
+ if (this.#insertMode) this.#shiftCells(line, column, width);
379
+ this.#clearWideAt(line, column);
380
+ line.cells[column] = { chars: grapheme, width, attrs: { ...this.#attrs } };
381
+ if (width === 2 && column + 1 < this.cols) {
382
+ line.cells[column + 1] = { chars: "", width: 0, attrs: { ...this.#attrs } };
383
+ }
384
+ this.#active.cursorX = column + width;
385
+ this.#pendingWrap = this.#active.cursorX >= this.cols;
386
+ }
387
+
388
+ #appendCombining(mark: string): void {
389
+ let column = this.#active.cursorX - 1;
390
+ let row = this.#active.baseY + this.#active.cursorY;
391
+ if (column < 0 && row > 0) {
392
+ row -= 1;
393
+ column = this.cols - 1;
394
+ }
395
+ const line = this.#active.lines[row];
396
+ if (!line) return;
397
+ while (column >= 0 && line.cells[column]?.width === 0) column--;
398
+ const cell = line.cells[column];
399
+ if (cell?.chars) cell.chars += mark;
400
+ }
401
+
402
+ #lineFeed(wrapped: boolean): void {
403
+ this.#pendingWrap = false;
404
+ if (this.#active.cursorY === this.#scrollBottom) {
405
+ this.#scrollUp();
406
+ } else if (this.#active.cursorY < this.rows - 1) {
407
+ this.#active.cursorY += 1;
408
+ }
409
+ if (wrapped) this.#currentLine().isWrapped = true;
410
+ }
411
+
412
+ #scrollUp(): void {
413
+ const top = this.#active.baseY + this.#scrollTop;
414
+ const bottom = this.#active.baseY + this.#scrollBottom;
415
+ if (this.#scrollTop === 0 && this.#scrollBottom === this.rows - 1 && !this.#usingAlternate) {
416
+ const followedBottom = this.#active.viewportY === this.#active.baseY;
417
+ this.#active.lines.push(new BufferLine(this.cols, this.#attrs));
418
+ const capacity = this.rows + this.#scrollback;
419
+ if (this.#active.lines.length > capacity) this.#active.lines.splice(0, this.#active.lines.length - capacity);
420
+ this.#active.baseY = Math.max(0, this.#active.lines.length - this.rows);
421
+ if (followedBottom) this.#active.viewportY = this.#active.baseY;
422
+ else this.#active.viewportY = Math.min(this.#active.viewportY, this.#active.baseY);
423
+ return;
424
+ }
425
+ this.#active.lines.splice(top, 1);
426
+ this.#active.lines.splice(bottom, 0, new BufferLine(this.cols, this.#attrs));
427
+ }
428
+
429
+ #scrollDown(): void {
430
+ const top = this.#active.baseY + this.#scrollTop;
431
+ const bottom = this.#active.baseY + this.#scrollBottom;
432
+ this.#active.lines.splice(bottom, 1);
433
+ this.#active.lines.splice(top, 0, new BufferLine(this.cols, this.#attrs));
434
+ }
435
+
436
+ #reverseIndex(): void {
437
+ if (this.#active.cursorY === this.#scrollTop) this.#scrollDown();
438
+ else this.#active.cursorY = Math.max(this.#scrollTop, this.#active.cursorY - 1);
439
+ this.#pendingWrap = false;
440
+ }
441
+
442
+ #currentLine(): BufferLine {
443
+ return this.#active.lines[this.#active.baseY + this.#active.cursorY]!;
444
+ }
445
+
446
+ #moveVertical(delta: number): void {
447
+ const top = this.#originMode ? this.#scrollTop : 0;
448
+ const bottom = this.#originMode ? this.#scrollBottom : this.rows - 1;
449
+ this.#active.cursorY = Math.min(bottom, Math.max(top, this.#active.cursorY + delta));
450
+ this.#pendingWrap = false;
451
+ }
452
+
453
+ #setCursor(row: number, column: number): void {
454
+ const top = this.#originMode ? this.#scrollTop : 0;
455
+ const bottom = this.#originMode ? this.#scrollBottom : this.rows - 1;
456
+ this.#active.cursorY = Math.min(bottom, Math.max(top, row + top));
457
+ this.#active.cursorX = Math.min(this.cols - 1, Math.max(0, column));
458
+ this.#pendingWrap = false;
459
+ }
460
+
461
+ #eraseDisplay(mode: number): void {
462
+ if (mode === 3 && !this.#usingAlternate) {
463
+ this.#active.lines = this.#active.lines.slice(this.#active.baseY);
464
+ this.#active.baseY = 0;
465
+ this.#active.viewportY = 0;
466
+ return;
467
+ }
468
+ if (mode === 2) {
469
+ for (let row = 0; row < this.rows; row++)
470
+ this.#active.lines[this.#active.baseY + row] = new BufferLine(this.cols, this.#attrs);
471
+ return;
472
+ }
473
+ if (mode === 0) {
474
+ this.#eraseRange(this.#currentLine(), this.#active.cursorX, this.cols);
475
+ for (let row = this.#active.cursorY + 1; row < this.rows; row++)
476
+ this.#active.lines[this.#active.baseY + row] = new BufferLine(this.cols, this.#attrs);
477
+ } else if (mode === 1) {
478
+ for (let row = 0; row < this.#active.cursorY; row++)
479
+ this.#active.lines[this.#active.baseY + row] = new BufferLine(this.cols, this.#attrs);
480
+ this.#eraseRange(this.#currentLine(), 0, this.#active.cursorX + 1);
481
+ }
482
+ }
483
+
484
+ #eraseLine(mode: number): void {
485
+ const line = this.#currentLine();
486
+ if (mode === 0) this.#eraseRange(line, this.#active.cursorX, this.cols);
487
+ else if (mode === 1) this.#eraseRange(line, 0, this.#active.cursorX + 1);
488
+ else if (mode === 2) this.#eraseRange(line, 0, this.cols);
489
+ }
490
+
491
+ #eraseRange(line: BufferLine, start: number, end: number): void {
492
+ for (let column = Math.max(0, start); column < Math.min(this.cols, end); column++)
493
+ line.cells[column] = blankCell(this.#attrs);
494
+ this.#repairWideCells(line);
495
+ }
496
+
497
+ #insertLines(count: number): void {
498
+ if (this.#active.cursorY < this.#scrollTop || this.#active.cursorY > this.#scrollBottom) return;
499
+ const start = this.#active.baseY + this.#active.cursorY;
500
+ const bottom = this.#active.baseY + this.#scrollBottom;
501
+ for (let index = 0; index < Math.min(count, bottom - start + 1); index++) {
502
+ this.#active.lines.splice(bottom, 1);
503
+ this.#active.lines.splice(start, 0, new BufferLine(this.cols, this.#attrs));
504
+ }
505
+ }
506
+
507
+ #deleteLines(count: number): void {
508
+ if (this.#active.cursorY < this.#scrollTop || this.#active.cursorY > this.#scrollBottom) return;
509
+ const start = this.#active.baseY + this.#active.cursorY;
510
+ const bottom = this.#active.baseY + this.#scrollBottom;
511
+ for (let index = 0; index < Math.min(count, bottom - start + 1); index++) {
512
+ this.#active.lines.splice(start, 1);
513
+ this.#active.lines.splice(bottom, 0, new BufferLine(this.cols, this.#attrs));
514
+ }
515
+ }
516
+
517
+ #insertCells(count: number): void {
518
+ const line = this.#currentLine();
519
+ this.#shiftCells(line, this.#active.cursorX, count);
520
+ this.#repairWideCells(line);
521
+ }
522
+
523
+ #shiftCells(line: BufferLine, column: number, count: number): void {
524
+ line.cells.splice(column, 0, ...Array.from({ length: count }, () => blankCell(this.#attrs)));
525
+ line.cells.length = this.cols;
526
+ }
527
+
528
+ #deleteCells(count: number): void {
529
+ const line = this.#currentLine();
530
+ line.cells.splice(this.#active.cursorX, count);
531
+ while (line.cells.length < this.cols) line.cells.push(blankCell(this.#attrs));
532
+ this.#repairWideCells(line);
533
+ }
534
+
535
+ #eraseCells(count: number): void {
536
+ this.#eraseRange(this.#currentLine(), this.#active.cursorX, this.#active.cursorX + count);
537
+ }
538
+
539
+ #clearWideAt(line: BufferLine, column: number): void {
540
+ if (line.cells[column]?.width === 0 && column > 0) line.cells[column - 1] = blankCell(this.#attrs);
541
+ if (line.cells[column]?.width === 2 && column + 1 < this.cols) line.cells[column + 1] = blankCell(this.#attrs);
542
+ }
543
+
544
+ #repairWideCells(line: BufferLine): void {
545
+ for (let column = 0; column < this.cols; column++) {
546
+ const cell = line.cells[column]!;
547
+ if (cell.width === 0 && (column === 0 || line.cells[column - 1]?.width !== 2))
548
+ line.cells[column] = blankCell(this.#attrs);
549
+ if (cell.width === 2 && (column === this.cols - 1 || line.cells[column + 1]?.width !== 0))
550
+ line.cells[column] = blankCell(this.#attrs);
551
+ }
552
+ }
553
+
554
+ #setRendition(values: number[]): void {
555
+ const params = values.length === 0 ? [0] : values;
556
+ for (let index = 0; index < params.length; index++) {
557
+ const code = params[index] ?? 0;
558
+ if (code === 0) this.#attrs = defaultAttributes();
559
+ else if (code === 1) this.#attrs.bold = true;
560
+ else if (code === 2) this.#attrs.dim = true;
561
+ else if (code === 3) this.#attrs.italic = true;
562
+ else if (code === 4 || code === 21) this.#attrs.underline = true;
563
+ else if (code === 7) this.#attrs.inverse = true;
564
+ else if (code === 9) this.#attrs.strikethrough = true;
565
+ else if (code === 22) {
566
+ this.#attrs.bold = false;
567
+ this.#attrs.dim = false;
568
+ } else if (code === 23) this.#attrs.italic = false;
569
+ else if (code === 24) this.#attrs.underline = false;
570
+ else if (code === 27) this.#attrs.inverse = false;
571
+ else if (code === 29) this.#attrs.strikethrough = false;
572
+ else if (code === 53) this.#attrs.overline = true;
573
+ else if (code === 55) this.#attrs.overline = false;
574
+ else if (code >= 30 && code <= 37) {
575
+ this.#attrs.fgMode = 1;
576
+ this.#attrs.fg = code - 30;
577
+ } else if (code >= 40 && code <= 47) {
578
+ this.#attrs.bgMode = 1;
579
+ this.#attrs.bg = code - 40;
580
+ } else if (code >= 90 && code <= 97) {
581
+ this.#attrs.fgMode = 1;
582
+ this.#attrs.fg = code - 82;
583
+ } else if (code >= 100 && code <= 107) {
584
+ this.#attrs.bgMode = 1;
585
+ this.#attrs.bg = code - 92;
586
+ } else if (code === 39) this.#attrs.fgMode = 0;
587
+ else if (code === 49) this.#attrs.bgMode = 0;
588
+ else if (code === 38 || code === 48) {
589
+ const foreground = code === 38;
590
+ const mode = params[index + 1];
591
+ if (mode === 5 && params[index + 2] !== undefined) {
592
+ if (foreground) {
593
+ this.#attrs.fgMode = 1;
594
+ this.#attrs.fg = params[index + 2]! & 0xff;
595
+ } else {
596
+ this.#attrs.bgMode = 1;
597
+ this.#attrs.bg = params[index + 2]! & 0xff;
598
+ }
599
+ index += 2;
600
+ } else if (mode === 2 && params[index + 4] !== undefined) {
601
+ const color =
602
+ ((params[index + 2]! & 0xff) << 16) |
603
+ ((params[index + 3]! & 0xff) << 8) |
604
+ (params[index + 4]! & 0xff);
605
+ if (foreground) {
606
+ this.#attrs.fgMode = 2;
607
+ this.#attrs.fg = color;
608
+ } else {
609
+ this.#attrs.bgMode = 2;
610
+ this.#attrs.bg = color;
611
+ }
612
+ index += 4;
613
+ }
614
+ }
615
+ }
616
+ }
617
+
618
+ #setScrollRegion(params: number[]): void {
619
+ const top = Math.max(0, (params[0] || 1) - 1);
620
+ const bottom = Math.min(this.rows - 1, (params[1] || this.rows) - 1);
621
+ if (top >= bottom) return;
622
+ this.#scrollTop = top;
623
+ this.#scrollBottom = bottom;
624
+ this.#setCursor(0, 0);
625
+ }
626
+
627
+ #setModes(params: number[], privateMode: boolean, enabled: boolean): void {
628
+ for (const mode of params) {
629
+ if (!privateMode && mode === 4) this.#insertMode = enabled;
630
+ else if (privateMode && mode === 1) this.modes.applicationCursorKeysMode = enabled;
631
+ else if (privateMode && mode === 6) {
632
+ this.#originMode = enabled;
633
+ this.#setCursor(0, 0);
634
+ } else if (privateMode && mode === 7) this.#autowrap = enabled;
635
+ else if (privateMode && (mode === 47 || mode === 1047 || mode === 1049)) {
636
+ if (enabled) this.#enterAlternate(mode === 1049);
637
+ else this.#leaveAlternate(mode === 1049);
638
+ } else if (privateMode && mode === 1048) {
639
+ if (enabled) this.#saveCursor();
640
+ else this.#restoreCursor();
641
+ }
642
+ }
643
+ }
644
+
645
+ #saveCursor(): void {
646
+ this.#saved = { x: this.#active.cursorX, y: this.#active.cursorY, attrs: { ...this.#attrs } };
647
+ }
648
+
649
+ #restoreCursor(): void {
650
+ if (!this.#saved) return;
651
+ this.#active.cursorX = Math.min(this.cols - 1, this.#saved.x);
652
+ this.#active.cursorY = Math.min(this.rows - 1, this.#saved.y);
653
+ this.#attrs = { ...this.#saved.attrs };
654
+ this.#pendingWrap = false;
655
+ }
656
+
657
+ #enterAlternate(saveCursor: boolean): void {
658
+ if (this.#usingAlternate) return;
659
+ if (saveCursor)
660
+ this.#alternateSaved = { x: this.#normal.cursorX, y: this.#normal.cursorY, attrs: { ...this.#attrs } };
661
+ this.#alternate = createState(this.cols, this.rows);
662
+ this.#active = this.#alternate;
663
+ this.#usingAlternate = true;
664
+ this.#scrollTop = 0;
665
+ this.#scrollBottom = this.rows - 1;
666
+ this.#pendingWrap = false;
667
+ }
668
+
669
+ #leaveAlternate(restoreCursor: boolean): void {
670
+ if (!this.#usingAlternate) return;
671
+ this.#active = this.#normal;
672
+ this.#usingAlternate = false;
673
+ if (restoreCursor && this.#alternateSaved) {
674
+ this.#normal.cursorX = this.#alternateSaved.x;
675
+ this.#normal.cursorY = this.#alternateSaved.y;
676
+ this.#attrs = { ...this.#alternateSaved.attrs };
677
+ }
678
+ this.#scrollTop = 0;
679
+ this.#scrollBottom = this.rows - 1;
680
+ this.#pendingWrap = false;
681
+ }
682
+
683
+ #reset(): void {
684
+ this.#attrs = defaultAttributes();
685
+ this.#normal = createState(this.cols, this.rows);
686
+ this.#alternate = createState(this.cols, this.rows);
687
+ this.#usingAlternate = false;
688
+ this.#active = this.#normal;
689
+ this.#scrollTop = 0;
690
+ this.#scrollBottom = this.rows - 1;
691
+ this.#originMode = false;
692
+ this.#autowrap = true;
693
+ this.#insertMode = false;
694
+ this.#pendingWrap = false;
695
+ this.modes.applicationCursorKeysMode = false;
696
+ }
697
+
698
+ #reflow(state: BufferState, columns: number, rows: number, retainHistory: boolean): BufferState {
699
+ const absoluteCursor = state.baseY + state.cursorY;
700
+ const groups: Array<{ cells: CellData[]; cursorOffset?: number }> = [];
701
+ for (let row = 0; row < state.lines.length; row++) {
702
+ const line = state.lines[row]!;
703
+ if (!line.isWrapped || groups.length === 0) groups.push({ cells: [] });
704
+ const group = groups.at(-1)!;
705
+ const used = line.isWrapped || state.lines[row + 1]?.isWrapped ? line.cells.length : this.#usedColumns(line);
706
+ if (row === absoluteCursor) group.cursorOffset = group.cells.length + state.cursorX;
707
+ for (let column = 0; column < used; column++) group.cells.push(cloneCell(line.cells[column]!));
708
+ }
709
+ const lines: BufferLine[] = [];
710
+ let cursorAbsolute = 0;
711
+ let cursorX = 0;
712
+ for (const group of groups) {
713
+ const start = lines.length;
714
+ if (group.cells.length === 0) lines.push(new BufferLine(columns));
715
+ else {
716
+ let source = 0;
717
+ while (source < group.cells.length) {
718
+ const line = new BufferLine(columns);
719
+ line.isWrapped = source > 0;
720
+ let target = 0;
721
+ while (source < group.cells.length && target < columns) {
722
+ const cell = group.cells[source]!;
723
+ if (cell.width === 0) {
724
+ source++;
725
+ continue;
726
+ }
727
+ if (cell.width === 2 && target === columns - 1) break;
728
+ line.cells[target] = cloneCell(cell);
729
+ if (cell.width === 2) line.cells[target + 1] = { chars: "", width: 0, attrs: { ...cell.attrs } };
730
+ target += cell.width;
731
+ source += cell.width;
732
+ }
733
+ lines.push(line);
734
+ }
735
+ }
736
+ if (group.cursorOffset !== undefined) {
737
+ const offset = group.cursorOffset;
738
+ const onBoundary = offset > 0 && offset % columns === 0;
739
+ cursorAbsolute = start + Math.floor(offset / columns) - Number(onBoundary);
740
+ cursorX = onBoundary ? columns - 1 : offset % columns;
741
+ }
742
+ }
743
+ while (
744
+ lines.length > rows &&
745
+ cursorAbsolute < lines.length - 1 &&
746
+ !lines.at(-1)!.isWrapped &&
747
+ this.#usedColumns(lines.at(-1)!) === 0
748
+ ) {
749
+ lines.pop();
750
+ }
751
+ while (lines.length < rows) lines.push(new BufferLine(columns));
752
+ const capacity = rows + (retainHistory ? this.#scrollback : 0);
753
+ const removed = Math.max(0, lines.length - capacity);
754
+ if (removed > 0) lines.splice(0, removed);
755
+ cursorAbsolute = Math.max(0, cursorAbsolute - removed);
756
+ const baseY = Math.max(0, lines.length - rows);
757
+ return {
758
+ lines,
759
+ baseY,
760
+ viewportY: baseY,
761
+ cursorX,
762
+ cursorY: Math.min(rows - 1, Math.max(0, cursorAbsolute - baseY)),
763
+ };
764
+ }
765
+
766
+ #usedColumns(line: BufferLine): number {
767
+ for (let column = line.cells.length - 1; column >= 0; column--) {
768
+ const cell = line.cells[column]!;
769
+ if (cell.chars || cell.width === 0) return column + 1;
770
+ }
771
+ return 0;
772
+ }
773
+ }