vt220.js 0.1.0 → 0.1.2

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/dist/index.mjs ADDED
@@ -0,0 +1,756 @@
1
+ //#region src/screen.ts
2
+ /** Frozen sentinel for unwritten cells — never mutate, copy-on-write in writeChar(). */
3
+ const EMPTY_CELL = Object.freeze({
4
+ char: "",
5
+ fg: null,
6
+ bg: null,
7
+ bold: false,
8
+ underline: false,
9
+ blink: false,
10
+ inverse: false,
11
+ hidden: false
12
+ });
13
+ function emptyCell() {
14
+ return { ...EMPTY_CELL };
15
+ }
16
+ const ANSI_8 = [
17
+ {
18
+ r: 0,
19
+ g: 0,
20
+ b: 0
21
+ },
22
+ {
23
+ r: 128,
24
+ g: 0,
25
+ b: 0
26
+ },
27
+ {
28
+ r: 0,
29
+ g: 128,
30
+ b: 0
31
+ },
32
+ {
33
+ r: 128,
34
+ g: 128,
35
+ b: 0
36
+ },
37
+ {
38
+ r: 0,
39
+ g: 0,
40
+ b: 128
41
+ },
42
+ {
43
+ r: 128,
44
+ g: 0,
45
+ b: 128
46
+ },
47
+ {
48
+ r: 0,
49
+ g: 128,
50
+ b: 128
51
+ },
52
+ {
53
+ r: 192,
54
+ g: 192,
55
+ b: 192
56
+ }
57
+ ];
58
+ function createScreen(opts) {
59
+ let cols = opts.cols;
60
+ let rows = opts.rows;
61
+ const scrollbackLimit = opts.scrollbackLimit ?? 1e3;
62
+ const onResponse = opts.onResponse;
63
+ let grid = makeGrid(cols, rows);
64
+ let scrollback = [];
65
+ let curX = 0;
66
+ let curY = 0;
67
+ let curVisible = true;
68
+ let savedCurX = 0;
69
+ let savedCurY = 0;
70
+ let savedState = {
71
+ curX: 0,
72
+ curY: 0,
73
+ attrs: resetAttrs(),
74
+ originMode: false,
75
+ autoWrap: true
76
+ };
77
+ let attrs = resetAttrs();
78
+ let title = "";
79
+ let applicationCursor = false;
80
+ let applicationKeypad = false;
81
+ let autoWrap = true;
82
+ let originMode = false;
83
+ let insertMode = false;
84
+ let reverseVideo = false;
85
+ let scrollTop = 0;
86
+ let scrollBottom = rows - 1;
87
+ let viewportOffset = 0;
88
+ let parserState = "ground";
89
+ let escBuf = "";
90
+ let oscBuf = "";
91
+ const decoder = new TextDecoder();
92
+ function makeGrid(c, r) {
93
+ const g = [];
94
+ for (let row = 0; row < r; row++) g.push(makeRow(c));
95
+ return g;
96
+ }
97
+ function makeRow(c) {
98
+ const row = [];
99
+ for (let col = 0; col < c; col++) row.push(EMPTY_CELL);
100
+ return row;
101
+ }
102
+ function resetAttrs() {
103
+ return {
104
+ fg: null,
105
+ bg: null,
106
+ bold: false,
107
+ underline: false,
108
+ blink: false,
109
+ inverse: false,
110
+ hidden: false
111
+ };
112
+ }
113
+ function clampCursor() {
114
+ if (curX < 0) curX = 0;
115
+ if (curX >= cols) curX = cols - 1;
116
+ if (curY < 0) curY = 0;
117
+ if (curY >= rows) curY = rows - 1;
118
+ }
119
+ function scrollUp(top, bottom) {
120
+ if (top === 0) {
121
+ scrollback.push(grid[0]);
122
+ if (scrollback.length > scrollbackLimit * 2) scrollback.splice(0, scrollback.length - scrollbackLimit);
123
+ }
124
+ for (let i = top; i < bottom; i++) grid[i] = grid[i + 1];
125
+ grid[bottom] = makeRow(cols);
126
+ }
127
+ function scrollDown(top, bottom) {
128
+ for (let i = bottom; i > top; i--) grid[i] = grid[i - 1];
129
+ grid[top] = makeRow(cols);
130
+ }
131
+ function writeChar(ch) {
132
+ if (curX >= cols) if (autoWrap) {
133
+ curX = 0;
134
+ curY++;
135
+ if (curY > scrollBottom) {
136
+ curY = scrollBottom;
137
+ scrollUp(scrollTop, scrollBottom);
138
+ }
139
+ } else curX = cols - 1;
140
+ if (insertMode) {
141
+ const row = grid[curY];
142
+ row.splice(curX, 0, EMPTY_CELL);
143
+ row.pop();
144
+ }
145
+ const row = grid[curY];
146
+ let cell = row[curX];
147
+ if (cell === EMPTY_CELL) {
148
+ cell = { ...EMPTY_CELL };
149
+ row[curX] = cell;
150
+ }
151
+ cell.char = ch;
152
+ cell.fg = attrs.fg ? { ...attrs.fg } : null;
153
+ cell.bg = attrs.bg ? { ...attrs.bg } : null;
154
+ cell.bold = attrs.bold;
155
+ cell.underline = attrs.underline;
156
+ cell.blink = attrs.blink;
157
+ cell.inverse = attrs.inverse;
158
+ cell.hidden = attrs.hidden;
159
+ curX++;
160
+ }
161
+ function handleCSI(params, finalByte) {
162
+ const parts = params.split(";").map((s) => s === "" ? 0 : parseInt(s, 10));
163
+ switch (finalByte) {
164
+ case "A":
165
+ curY -= Math.max(parts[0] ?? 1, 1);
166
+ clampCursor();
167
+ break;
168
+ case "B":
169
+ curY += Math.max(parts[0] ?? 1, 1);
170
+ clampCursor();
171
+ break;
172
+ case "C":
173
+ curX += Math.max(parts[0] ?? 1, 1);
174
+ clampCursor();
175
+ break;
176
+ case "D":
177
+ curX -= Math.max(parts[0] ?? 1, 1);
178
+ clampCursor();
179
+ break;
180
+ case "E":
181
+ curY += Math.max(parts[0] ?? 1, 1);
182
+ curX = 0;
183
+ clampCursor();
184
+ break;
185
+ case "F":
186
+ curY -= Math.max(parts[0] ?? 1, 1);
187
+ curX = 0;
188
+ clampCursor();
189
+ break;
190
+ case "G":
191
+ curX = (parts[0] ?? 1) - 1;
192
+ clampCursor();
193
+ break;
194
+ case "H":
195
+ case "f":
196
+ curY = (parts[0] ?? 1) - 1;
197
+ curX = (parts[1] ?? 1) - 1;
198
+ clampCursor();
199
+ break;
200
+ case "J":
201
+ handleEraseDisplay(parts[0] ?? 0);
202
+ break;
203
+ case "K":
204
+ handleEraseLine(parts[0] ?? 0);
205
+ break;
206
+ case "L":
207
+ handleInsertLines(Math.max(parts[0] ?? 1, 1));
208
+ break;
209
+ case "M":
210
+ handleDeleteLines(Math.max(parts[0] ?? 1, 1));
211
+ break;
212
+ case "P":
213
+ handleDeleteChars(Math.max(parts[0] ?? 1, 1));
214
+ break;
215
+ case "@":
216
+ handleInsertChars(Math.max(parts[0] ?? 1, 1));
217
+ break;
218
+ case "X":
219
+ handleEraseChars(Math.max(parts[0] ?? 1, 1));
220
+ break;
221
+ case "S":
222
+ for (let i = 0; i < Math.max(parts[0] ?? 1, 1); i++) scrollUp(scrollTop, scrollBottom);
223
+ break;
224
+ case "T":
225
+ for (let i = 0; i < Math.max(parts[0] ?? 1, 1); i++) scrollDown(scrollTop, scrollBottom);
226
+ break;
227
+ case "d":
228
+ curY = (parts[0] ?? 1) - 1;
229
+ clampCursor();
230
+ break;
231
+ case "m":
232
+ handleSGR(params);
233
+ break;
234
+ case "r":
235
+ scrollTop = (parts[0] ?? 1) - 1;
236
+ scrollBottom = (parts[1] ?? rows) - 1;
237
+ if (scrollTop < 0) scrollTop = 0;
238
+ if (scrollBottom >= rows) scrollBottom = rows - 1;
239
+ if (scrollTop > scrollBottom) {
240
+ scrollTop = 0;
241
+ scrollBottom = rows - 1;
242
+ }
243
+ curX = 0;
244
+ curY = originMode ? scrollTop : 0;
245
+ break;
246
+ case "n":
247
+ if (onResponse) {
248
+ if (parts[0] === 5) onResponse("\x1B[0n");
249
+ else if (parts[0] === 6) onResponse(`\x1b[${curY + 1};${curX + 1}R`);
250
+ }
251
+ break;
252
+ case "c":
253
+ if (onResponse) {
254
+ if (params === "" || params === "0") onResponse("\x1B[?62;1;2;6;7;8;9c");
255
+ }
256
+ break;
257
+ case "s":
258
+ savedCurX = curX;
259
+ savedCurY = curY;
260
+ break;
261
+ case "u":
262
+ curX = savedCurX;
263
+ curY = savedCurY;
264
+ clampCursor();
265
+ break;
266
+ default: break;
267
+ }
268
+ }
269
+ function handleCSIWithIntermediate(params, intermediate, finalByte) {
270
+ if (intermediate === "!" && finalByte === "p") softReset();
271
+ else if (intermediate === ">" && finalByte === "c") {
272
+ if (onResponse) onResponse("\x1B[>1;10;0c");
273
+ }
274
+ }
275
+ function handleCSIPrivate(params, finalByte) {
276
+ const parts = params.split(";").map((s) => s === "" ? 0 : parseInt(s, 10));
277
+ if (finalByte === "J") {
278
+ handleSelectiveEraseDisplay(parts[0] ?? 0);
279
+ return;
280
+ }
281
+ if (finalByte === "K") {
282
+ handleSelectiveEraseLine(parts[0] ?? 0);
283
+ return;
284
+ }
285
+ const set = finalByte === "h";
286
+ for (const code of parts) switch (code) {
287
+ case 1:
288
+ applicationCursor = set;
289
+ break;
290
+ case 4:
291
+ insertMode = set;
292
+ break;
293
+ case 5:
294
+ reverseVideo = set;
295
+ break;
296
+ case 6:
297
+ originMode = set;
298
+ break;
299
+ case 7:
300
+ autoWrap = set;
301
+ break;
302
+ case 25:
303
+ curVisible = set;
304
+ break;
305
+ case 66:
306
+ applicationKeypad = set;
307
+ break;
308
+ }
309
+ }
310
+ function handleSetResetMode(params, finalByte) {
311
+ const parts = params.split(";").map((s) => s === "" ? 0 : parseInt(s, 10));
312
+ const set = finalByte === "h";
313
+ for (const code of parts) switch (code) {
314
+ case 4:
315
+ insertMode = set;
316
+ break;
317
+ }
318
+ }
319
+ function handleEraseDisplay(mode) {
320
+ switch (mode) {
321
+ case 0:
322
+ eraseCells(curY, curX, curY, cols - 1);
323
+ for (let row = curY + 1; row < rows; row++) eraseCells(row, 0, row, cols - 1);
324
+ break;
325
+ case 1:
326
+ for (let row = 0; row < curY; row++) eraseCells(row, 0, row, cols - 1);
327
+ eraseCells(curY, 0, curY, curX);
328
+ break;
329
+ case 2:
330
+ case 3:
331
+ for (let row = 0; row < rows; row++) eraseCells(row, 0, row, cols - 1);
332
+ if (mode === 3) scrollback.length = 0;
333
+ break;
334
+ }
335
+ }
336
+ function handleEraseLine(mode) {
337
+ switch (mode) {
338
+ case 0:
339
+ eraseCells(curY, curX, curY, cols - 1);
340
+ break;
341
+ case 1:
342
+ eraseCells(curY, 0, curY, curX);
343
+ break;
344
+ case 2:
345
+ eraseCells(curY, 0, curY, cols - 1);
346
+ break;
347
+ }
348
+ }
349
+ function handleSelectiveEraseDisplay(mode) {
350
+ handleEraseDisplay(mode);
351
+ }
352
+ function handleSelectiveEraseLine(mode) {
353
+ handleEraseLine(mode);
354
+ }
355
+ function eraseCells(row, startCol, _endRow, endCol) {
356
+ const r = grid[row];
357
+ if (!r) return;
358
+ for (let col = startCol; col <= endCol && col < cols; col++) r[col] = emptyCell();
359
+ }
360
+ function handleInsertLines(count) {
361
+ if (curY < scrollTop || curY > scrollBottom) return;
362
+ for (let i = 0; i < count; i++) scrollDown(curY, scrollBottom);
363
+ }
364
+ function handleDeleteLines(count) {
365
+ if (curY < scrollTop || curY > scrollBottom) return;
366
+ for (let i = 0; i < count; i++) scrollUp(curY, scrollBottom);
367
+ }
368
+ function handleDeleteChars(count) {
369
+ const row = grid[curY];
370
+ if (!row) return;
371
+ for (let i = 0; i < count; i++) if (curX < cols) {
372
+ row.splice(curX, 1);
373
+ row.push(emptyCell());
374
+ }
375
+ }
376
+ function handleInsertChars(count) {
377
+ const row = grid[curY];
378
+ if (!row) return;
379
+ for (let i = 0; i < count; i++) {
380
+ row.splice(curX, 0, emptyCell());
381
+ row.pop();
382
+ }
383
+ }
384
+ function handleEraseChars(count) {
385
+ const row = grid[curY];
386
+ if (!row) return;
387
+ for (let i = 0; i < count && curX + i < cols; i++) row[curX + i] = emptyCell();
388
+ }
389
+ function handleSGR(rawParams) {
390
+ const params = rawParams.split(";").map((s) => s === "" ? 0 : parseInt(s, 10));
391
+ if (params.length === 0 || params.length === 1 && params[0] === 0) {
392
+ attrs = resetAttrs();
393
+ return;
394
+ }
395
+ let i = 0;
396
+ while (i < params.length) {
397
+ const code = params[i];
398
+ switch (code) {
399
+ case 0:
400
+ attrs = resetAttrs();
401
+ break;
402
+ case 1:
403
+ attrs.bold = true;
404
+ break;
405
+ case 4:
406
+ attrs.underline = true;
407
+ break;
408
+ case 5:
409
+ attrs.blink = true;
410
+ break;
411
+ case 7:
412
+ attrs.inverse = true;
413
+ break;
414
+ case 8:
415
+ attrs.hidden = true;
416
+ break;
417
+ case 22:
418
+ attrs.bold = false;
419
+ break;
420
+ case 24:
421
+ attrs.underline = false;
422
+ break;
423
+ case 25:
424
+ attrs.blink = false;
425
+ break;
426
+ case 27:
427
+ attrs.inverse = false;
428
+ break;
429
+ case 28:
430
+ attrs.hidden = false;
431
+ break;
432
+ case 30:
433
+ case 31:
434
+ case 32:
435
+ case 33:
436
+ case 34:
437
+ case 35:
438
+ case 36:
439
+ case 37:
440
+ attrs.fg = { ...ANSI_8[code - 30] };
441
+ break;
442
+ case 39:
443
+ attrs.fg = null;
444
+ break;
445
+ case 40:
446
+ case 41:
447
+ case 42:
448
+ case 43:
449
+ case 44:
450
+ case 45:
451
+ case 46:
452
+ case 47:
453
+ attrs.bg = { ...ANSI_8[code - 40] };
454
+ break;
455
+ case 49:
456
+ attrs.bg = null;
457
+ break;
458
+ case 38:
459
+ case 48:
460
+ if (i + 1 < params.length && params[i + 1] === 2) i += 4;
461
+ else if (i + 1 < params.length && params[i + 1] === 5) i += 2;
462
+ break;
463
+ }
464
+ i++;
465
+ }
466
+ }
467
+ function handleOSC(oscString) {
468
+ const semicolonIdx = oscString.indexOf(";");
469
+ if (semicolonIdx === -1) return;
470
+ const code = parseInt(oscString.substring(0, semicolonIdx), 10);
471
+ const value = oscString.substring(semicolonIdx + 1);
472
+ switch (code) {
473
+ case 0:
474
+ case 2:
475
+ title = value;
476
+ break;
477
+ case 1: break;
478
+ }
479
+ }
480
+ function process(data) {
481
+ const text = decoder.decode(data, { stream: true });
482
+ for (let i = 0; i < text.length; i++) {
483
+ const ch = text[i];
484
+ const code = text.charCodeAt(i);
485
+ switch (parserState) {
486
+ case "ground":
487
+ if (code === 27) {
488
+ parserState = "escape";
489
+ escBuf = "";
490
+ } else if (code === 7) {} else if (code === 8) {
491
+ if (curX > 0) curX--;
492
+ } else if (code === 9) curX = Math.min((Math.floor(curX / 8) + 1) * 8, cols - 1);
493
+ else if (code === 10 || code === 11 || code === 12) {
494
+ curY++;
495
+ if (curY > scrollBottom) {
496
+ curY = scrollBottom;
497
+ scrollUp(scrollTop, scrollBottom);
498
+ }
499
+ } else if (code === 13) curX = 0;
500
+ else if (code >= 32) writeChar(ch);
501
+ break;
502
+ case "escape":
503
+ if (ch === "[") {
504
+ parserState = "csi";
505
+ escBuf = "";
506
+ } else if (ch === "]") {
507
+ parserState = "osc";
508
+ oscBuf = "";
509
+ } else if (ch === "P") {
510
+ parserState = "dcs";
511
+ escBuf = "";
512
+ } else if (ch === "c") fullReset();
513
+ else if (ch === "D") {
514
+ curY++;
515
+ if (curY > scrollBottom) {
516
+ curY = scrollBottom;
517
+ scrollUp(scrollTop, scrollBottom);
518
+ }
519
+ parserState = "ground";
520
+ } else if (ch === "M") {
521
+ curY--;
522
+ if (curY < scrollTop) {
523
+ curY = scrollTop;
524
+ scrollDown(scrollTop, scrollBottom);
525
+ }
526
+ parserState = "ground";
527
+ } else if (ch === "7") {
528
+ savedState = {
529
+ curX,
530
+ curY,
531
+ attrs: {
532
+ ...attrs,
533
+ fg: attrs.fg ? { ...attrs.fg } : null,
534
+ bg: attrs.bg ? { ...attrs.bg } : null
535
+ },
536
+ originMode,
537
+ autoWrap
538
+ };
539
+ parserState = "ground";
540
+ } else if (ch === "8") {
541
+ curX = savedState.curX;
542
+ curY = savedState.curY;
543
+ attrs = {
544
+ ...savedState.attrs,
545
+ fg: savedState.attrs.fg ? { ...savedState.attrs.fg } : null,
546
+ bg: savedState.attrs.bg ? { ...savedState.attrs.bg } : null
547
+ };
548
+ originMode = savedState.originMode;
549
+ autoWrap = savedState.autoWrap;
550
+ clampCursor();
551
+ parserState = "ground";
552
+ } else if (ch === "E") {
553
+ curX = 0;
554
+ curY++;
555
+ if (curY > scrollBottom) {
556
+ curY = scrollBottom;
557
+ scrollUp(scrollTop, scrollBottom);
558
+ }
559
+ parserState = "ground";
560
+ } else if (ch === "=") {
561
+ applicationKeypad = true;
562
+ parserState = "ground";
563
+ } else if (ch === ">") {
564
+ applicationKeypad = false;
565
+ parserState = "ground";
566
+ } else parserState = "ground";
567
+ break;
568
+ case "csi":
569
+ if (code >= 64 && code <= 126) {
570
+ if (escBuf.startsWith("?")) handleCSIPrivate(escBuf.substring(1), ch);
571
+ else if (ch === "h" || ch === "l") handleSetResetMode(escBuf, ch);
572
+ else {
573
+ let intermediateIdx = -1;
574
+ for (let j = 0; j < escBuf.length; j++) {
575
+ const c = escBuf.charCodeAt(j);
576
+ if (c >= 32 && c <= 47 || c === 62) {
577
+ intermediateIdx = j;
578
+ break;
579
+ }
580
+ }
581
+ if (intermediateIdx >= 0) handleCSIWithIntermediate(escBuf.substring(0, intermediateIdx), escBuf.substring(intermediateIdx), ch);
582
+ else handleCSI(escBuf, ch);
583
+ }
584
+ parserState = "ground";
585
+ } else if (escBuf.length >= 256) parserState = "ground";
586
+ else escBuf += ch;
587
+ break;
588
+ case "osc":
589
+ if (code === 7) {
590
+ handleOSC(oscBuf);
591
+ parserState = "ground";
592
+ } else if (code === 27) parserState = "oscString";
593
+ else if (oscBuf.length >= 4096) parserState = "ground";
594
+ else oscBuf += ch;
595
+ break;
596
+ case "oscString":
597
+ if (ch === "\\") handleOSC(oscBuf);
598
+ parserState = "ground";
599
+ break;
600
+ case "dcs":
601
+ if (code === 27) parserState = "oscString";
602
+ break;
603
+ }
604
+ }
605
+ }
606
+ function fullReset() {
607
+ grid = makeGrid(cols, rows);
608
+ scrollback = [];
609
+ curX = 0;
610
+ curY = 0;
611
+ curVisible = true;
612
+ savedCurX = 0;
613
+ savedCurY = 0;
614
+ savedState = {
615
+ curX: 0,
616
+ curY: 0,
617
+ attrs: resetAttrs(),
618
+ originMode: false,
619
+ autoWrap: true
620
+ };
621
+ attrs = resetAttrs();
622
+ title = "";
623
+ applicationCursor = false;
624
+ applicationKeypad = false;
625
+ autoWrap = true;
626
+ originMode = false;
627
+ insertMode = false;
628
+ reverseVideo = false;
629
+ scrollTop = 0;
630
+ scrollBottom = rows - 1;
631
+ viewportOffset = 0;
632
+ parserState = "ground";
633
+ escBuf = "";
634
+ oscBuf = "";
635
+ }
636
+ function softReset() {
637
+ attrs = resetAttrs();
638
+ applicationCursor = false;
639
+ applicationKeypad = false;
640
+ autoWrap = true;
641
+ originMode = false;
642
+ insertMode = false;
643
+ reverseVideo = false;
644
+ curVisible = true;
645
+ scrollTop = 0;
646
+ scrollBottom = rows - 1;
647
+ savedState = {
648
+ curX: 0,
649
+ curY: 0,
650
+ attrs: resetAttrs(),
651
+ originMode: false,
652
+ autoWrap: true
653
+ };
654
+ savedCurX = 0;
655
+ savedCurY = 0;
656
+ }
657
+ function resize(newCols, newRows) {
658
+ const newGrid = makeGrid(newCols, newRows);
659
+ copyGrid(grid, newGrid, Math.min(cols, newCols), Math.min(rows, newRows));
660
+ grid = newGrid;
661
+ cols = newCols;
662
+ rows = newRows;
663
+ scrollTop = 0;
664
+ scrollBottom = rows - 1;
665
+ clampCursor();
666
+ }
667
+ function copyGrid(src, dst, copyCols, copyRows) {
668
+ for (let row = 0; row < copyRows; row++) for (let col = 0; col < copyCols; col++) {
669
+ const srcCell = src[row]?.[col];
670
+ if (srcCell) dst[row][col] = { ...srcCell };
671
+ }
672
+ }
673
+ function getCell(row, col) {
674
+ const r = grid[row];
675
+ if (!r || col >= cols) return emptyCell();
676
+ return { ...r[col] };
677
+ }
678
+ function getLine(row) {
679
+ const r = grid[row];
680
+ if (!r) return makeRow(cols);
681
+ return r.map((cell) => ({ ...cell }));
682
+ }
683
+ function getText() {
684
+ const lines = [];
685
+ for (const row of scrollback) lines.push(rowToString(row));
686
+ for (let r = 0; r < rows; r++) lines.push(rowToString(grid[r]));
687
+ return lines.join("\n");
688
+ }
689
+ function rowToString(row) {
690
+ let line = "";
691
+ for (let i = 0; i < row.length; i++) {
692
+ const cell = row[i];
693
+ if (cell.char === "") line += " ";
694
+ else line += cell.char;
695
+ }
696
+ return line.replace(/\s+$/, "");
697
+ }
698
+ function getTextRange(startRow, startCol, endRow, endCol) {
699
+ const parts = [];
700
+ for (let row = startRow; row <= endRow; row++) {
701
+ const r = grid[row];
702
+ if (!r) continue;
703
+ const colStart = row === startRow ? startCol : 0;
704
+ const colEnd = row === endRow ? endCol : cols;
705
+ let line = "";
706
+ for (let col = colStart; col < colEnd; col++) {
707
+ const cell = r[col];
708
+ if (!cell) continue;
709
+ line += cell.char || " ";
710
+ }
711
+ parts.push(line.replace(/\s+$/, ""));
712
+ }
713
+ return parts.join("\n");
714
+ }
715
+ function getMode(mode) {
716
+ switch (mode) {
717
+ case "cursorVisible": return curVisible;
718
+ case "applicationCursor": return applicationCursor;
719
+ case "applicationKeypad": return applicationKeypad;
720
+ case "autoWrap": return autoWrap;
721
+ case "originMode": return originMode;
722
+ case "insertMode": return insertMode;
723
+ case "reverseVideo": return reverseVideo;
724
+ default: return false;
725
+ }
726
+ }
727
+ return {
728
+ get cols() {
729
+ return cols;
730
+ },
731
+ get rows() {
732
+ return rows;
733
+ },
734
+ process,
735
+ resize,
736
+ reset: fullReset,
737
+ getCell,
738
+ getLine,
739
+ getText,
740
+ getTextRange,
741
+ getCursorPosition: () => ({
742
+ x: curX,
743
+ y: curY
744
+ }),
745
+ getCursorVisible: () => curVisible,
746
+ getTitle: () => title,
747
+ getMode,
748
+ getScrollbackLength: () => scrollback.length,
749
+ getViewportOffset: () => viewportOffset,
750
+ scrollViewport: (delta) => {
751
+ viewportOffset = Math.max(0, Math.min(scrollback.length, viewportOffset + delta));
752
+ }
753
+ };
754
+ }
755
+ //#endregion
756
+ export { createScreen as createVt220Screen };