vterm.js 0.5.0 → 0.6.0
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/README.md +278 -21
- package/dist/index.d.mts +481 -10
- package/dist/index.mjs +2368 -197
- package/package.json +3 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
//#region src/screen.ts
|
|
2
|
+
/** Default retained payload bound for one APC/DCS sequence (16 MiB of ASCII/base64). */
|
|
3
|
+
const DEFAULT_MAX_STRING_SEQUENCE_LENGTH = 16 * 1024 * 1024;
|
|
2
4
|
/** Frozen sentinel for unwritten cells — never mutate, copy-on-write in writeChar(). */
|
|
3
5
|
const EMPTY_CELL = Object.freeze({
|
|
4
6
|
char: "",
|
|
@@ -20,6 +22,248 @@ const EMPTY_CELL = Object.freeze({
|
|
|
20
22
|
function emptyCell() {
|
|
21
23
|
return { ...EMPTY_CELL };
|
|
22
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Drop the palette-origin {@link Color.index} for the public per-cell read
|
|
27
|
+
* boundary. `getCell`/`getRow` return the RESOLVED RGB — their long-standing
|
|
28
|
+
* contract — while the origin index is serialization-only provenance that rides
|
|
29
|
+
* {@link Snapshot} (and thus `serialize()`), not the inspection API.
|
|
30
|
+
* Returns the SAME reference when there is no index (the common truecolor/null
|
|
31
|
+
* case), so the read path allocates nothing there.
|
|
32
|
+
*/
|
|
33
|
+
function stripColorIndex(c) {
|
|
34
|
+
if (c === null || c.index === void 0) return c;
|
|
35
|
+
return {
|
|
36
|
+
r: c.r,
|
|
37
|
+
g: c.g,
|
|
38
|
+
b: c.b
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Shallow cell copy with each color stripped of its palette-origin index. */
|
|
42
|
+
function stripCellColorIndex(cell) {
|
|
43
|
+
return {
|
|
44
|
+
...cell,
|
|
45
|
+
fg: stripColorIndex(cell.fg),
|
|
46
|
+
bg: stripColorIndex(cell.bg),
|
|
47
|
+
underlineColor: stripColorIndex(cell.underlineColor)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const F_BOLD$1 = 1;
|
|
51
|
+
const F_FAINT$1 = 2;
|
|
52
|
+
const F_ITALIC$1 = 4;
|
|
53
|
+
const F_OVERLINE$1 = 8;
|
|
54
|
+
const F_STRIKE$1 = 16;
|
|
55
|
+
const F_INVERSE$1 = 32;
|
|
56
|
+
const F_HIDDEN$1 = 64;
|
|
57
|
+
const F_BLINK$1 = 128;
|
|
58
|
+
const F_WIDE$1 = 256;
|
|
59
|
+
const UL_SHIFT$1 = 9;
|
|
60
|
+
const UL_MASK$1 = 7 << UL_SHIFT$1;
|
|
61
|
+
const F_HAS_FG = 4096;
|
|
62
|
+
const F_HAS_BG = 8192;
|
|
63
|
+
const F_HAS_UL = 16384;
|
|
64
|
+
const F_HAS_URL = 32768;
|
|
65
|
+
const UL_STYLES = [
|
|
66
|
+
"none",
|
|
67
|
+
"single",
|
|
68
|
+
"double",
|
|
69
|
+
"curly",
|
|
70
|
+
"dotted",
|
|
71
|
+
"dashed"
|
|
72
|
+
];
|
|
73
|
+
function underlineToBits(u) {
|
|
74
|
+
const i = UL_STYLES.indexOf(u);
|
|
75
|
+
return (i < 0 ? 0 : i) << UL_SHIFT$1;
|
|
76
|
+
}
|
|
77
|
+
function bitsToUnderline(meta) {
|
|
78
|
+
return UL_STYLES[(meta & UL_MASK$1) >> UL_SHIFT$1] ?? "none";
|
|
79
|
+
}
|
|
80
|
+
/** Pack a color's 8-bit channels into a 24-bit `0xRRGGBB` word. */
|
|
81
|
+
function packRgb(c) {
|
|
82
|
+
return ((c.r & 255) << 16 | (c.g & 255) << 8 | c.b & 255) >>> 0;
|
|
83
|
+
}
|
|
84
|
+
/** Materialize a {@link Color} from a packed RGB word + index (`-1` = no index). */
|
|
85
|
+
function unpackColor(rgb, idx) {
|
|
86
|
+
const r = rgb >> 16 & 255;
|
|
87
|
+
const g = rgb >> 8 & 255;
|
|
88
|
+
const b = rgb & 255;
|
|
89
|
+
return idx >= 0 ? {
|
|
90
|
+
r,
|
|
91
|
+
g,
|
|
92
|
+
b,
|
|
93
|
+
index: idx
|
|
94
|
+
} : {
|
|
95
|
+
r,
|
|
96
|
+
g,
|
|
97
|
+
b
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function makePackedRow(width) {
|
|
101
|
+
const meta = new Uint32Array(width);
|
|
102
|
+
const chars = new Array(width).fill("");
|
|
103
|
+
let fgRgb = null;
|
|
104
|
+
let fgIdx = null;
|
|
105
|
+
let bgRgb = null;
|
|
106
|
+
let bgIdx = null;
|
|
107
|
+
let ulRgb = null;
|
|
108
|
+
let ulIdx = null;
|
|
109
|
+
let urlMap = null;
|
|
110
|
+
function setFg(col, c) {
|
|
111
|
+
if (fgRgb === null) {
|
|
112
|
+
fgRgb = new Uint32Array(width);
|
|
113
|
+
fgIdx = new Int16Array(width);
|
|
114
|
+
}
|
|
115
|
+
fgRgb[col] = packRgb(c);
|
|
116
|
+
fgIdx[col] = c.index ?? -1;
|
|
117
|
+
}
|
|
118
|
+
function setBg(col, c) {
|
|
119
|
+
if (bgRgb === null) {
|
|
120
|
+
bgRgb = new Uint32Array(width);
|
|
121
|
+
bgIdx = new Int16Array(width);
|
|
122
|
+
}
|
|
123
|
+
bgRgb[col] = packRgb(c);
|
|
124
|
+
bgIdx[col] = c.index ?? -1;
|
|
125
|
+
}
|
|
126
|
+
function setUl(col, c) {
|
|
127
|
+
if (ulRgb === null) {
|
|
128
|
+
ulRgb = new Uint32Array(width);
|
|
129
|
+
ulIdx = new Int16Array(width);
|
|
130
|
+
}
|
|
131
|
+
ulRgb[col] = packRgb(c);
|
|
132
|
+
ulIdx[col] = c.index ?? -1;
|
|
133
|
+
}
|
|
134
|
+
function setUrl(col, u) {
|
|
135
|
+
(urlMap ??= /* @__PURE__ */ new Map()).set(col, u);
|
|
136
|
+
}
|
|
137
|
+
function clearUrl(col) {
|
|
138
|
+
if (urlMap !== null && urlMap.size > 0) urlMap.delete(col);
|
|
139
|
+
}
|
|
140
|
+
function packFlags(a, wide) {
|
|
141
|
+
let m = 0;
|
|
142
|
+
if (a.bold) m |= F_BOLD$1;
|
|
143
|
+
if (a.faint) m |= F_FAINT$1;
|
|
144
|
+
if (a.italic) m |= F_ITALIC$1;
|
|
145
|
+
if (a.overline) m |= F_OVERLINE$1;
|
|
146
|
+
if (a.strikethrough) m |= F_STRIKE$1;
|
|
147
|
+
if (a.inverse) m |= F_INVERSE$1;
|
|
148
|
+
if (a.hidden) m |= F_HIDDEN$1;
|
|
149
|
+
if (a.blink) m |= F_BLINK$1;
|
|
150
|
+
if (wide) m |= F_WIDE$1;
|
|
151
|
+
return m | underlineToBits(a.underline);
|
|
152
|
+
}
|
|
153
|
+
const row = {
|
|
154
|
+
length: width,
|
|
155
|
+
writeFromAttrs(col, ch, a, wide) {
|
|
156
|
+
let m = packFlags(a, wide);
|
|
157
|
+
chars[col] = ch;
|
|
158
|
+
if (a.fg) {
|
|
159
|
+
setFg(col, a.fg);
|
|
160
|
+
m |= F_HAS_FG;
|
|
161
|
+
}
|
|
162
|
+
if (a.bg) {
|
|
163
|
+
setBg(col, a.bg);
|
|
164
|
+
m |= F_HAS_BG;
|
|
165
|
+
}
|
|
166
|
+
if (a.underlineColor) {
|
|
167
|
+
setUl(col, a.underlineColor);
|
|
168
|
+
m |= F_HAS_UL;
|
|
169
|
+
}
|
|
170
|
+
if (a.url !== null) {
|
|
171
|
+
setUrl(col, a.url);
|
|
172
|
+
m |= F_HAS_URL;
|
|
173
|
+
} else clearUrl(col);
|
|
174
|
+
meta[col] = m;
|
|
175
|
+
},
|
|
176
|
+
setCellRaw(col, cell) {
|
|
177
|
+
let m = packFlags(cell, cell.wide);
|
|
178
|
+
chars[col] = cell.char;
|
|
179
|
+
if (cell.fg) {
|
|
180
|
+
setFg(col, cell.fg);
|
|
181
|
+
m |= F_HAS_FG;
|
|
182
|
+
}
|
|
183
|
+
if (cell.bg) {
|
|
184
|
+
setBg(col, cell.bg);
|
|
185
|
+
m |= F_HAS_BG;
|
|
186
|
+
}
|
|
187
|
+
if (cell.underlineColor) {
|
|
188
|
+
setUl(col, cell.underlineColor);
|
|
189
|
+
m |= F_HAS_UL;
|
|
190
|
+
}
|
|
191
|
+
if (cell.url !== null) {
|
|
192
|
+
setUrl(col, cell.url);
|
|
193
|
+
m |= F_HAS_URL;
|
|
194
|
+
} else clearUrl(col);
|
|
195
|
+
meta[col] = m;
|
|
196
|
+
},
|
|
197
|
+
setEmpty(col) {
|
|
198
|
+
meta[col] = 0;
|
|
199
|
+
chars[col] = "";
|
|
200
|
+
clearUrl(col);
|
|
201
|
+
},
|
|
202
|
+
eraseWithBg(col, bg) {
|
|
203
|
+
chars[col] = "";
|
|
204
|
+
clearUrl(col);
|
|
205
|
+
if (bg) {
|
|
206
|
+
setBg(col, bg);
|
|
207
|
+
meta[col] = F_HAS_BG;
|
|
208
|
+
} else meta[col] = 0;
|
|
209
|
+
},
|
|
210
|
+
widen(col) {
|
|
211
|
+
meta[col] = (meta[col] | F_WIDE$1) >>> 0;
|
|
212
|
+
if (col + 1 < width) row.setEmpty(col + 1);
|
|
213
|
+
},
|
|
214
|
+
appendChar(col, ch) {
|
|
215
|
+
chars[col] = chars[col] + ch;
|
|
216
|
+
},
|
|
217
|
+
copyCellFrom(col, src, srcCol) {
|
|
218
|
+
row.setCellRaw(col, src.getCellRaw(srcCol));
|
|
219
|
+
},
|
|
220
|
+
getCellRaw(col) {
|
|
221
|
+
const m = meta[col];
|
|
222
|
+
if (m === 0 && chars[col] === "") return emptyCell();
|
|
223
|
+
return {
|
|
224
|
+
char: chars[col],
|
|
225
|
+
fg: m & F_HAS_FG ? unpackColor(fgRgb[col], fgIdx[col]) : null,
|
|
226
|
+
bg: m & F_HAS_BG ? unpackColor(bgRgb[col], bgIdx[col]) : null,
|
|
227
|
+
bold: (m & F_BOLD$1) !== 0,
|
|
228
|
+
faint: (m & F_FAINT$1) !== 0,
|
|
229
|
+
italic: (m & F_ITALIC$1) !== 0,
|
|
230
|
+
underline: bitsToUnderline(m),
|
|
231
|
+
underlineColor: m & F_HAS_UL ? unpackColor(ulRgb[col], ulIdx[col]) : null,
|
|
232
|
+
overline: (m & F_OVERLINE$1) !== 0,
|
|
233
|
+
strikethrough: (m & F_STRIKE$1) !== 0,
|
|
234
|
+
inverse: (m & F_INVERSE$1) !== 0,
|
|
235
|
+
hidden: (m & F_HIDDEN$1) !== 0,
|
|
236
|
+
blink: (m & F_BLINK$1) !== 0,
|
|
237
|
+
wide: (m & F_WIDE$1) !== 0,
|
|
238
|
+
url: m & F_HAS_URL ? urlMap.get(col) ?? null : null
|
|
239
|
+
};
|
|
240
|
+
},
|
|
241
|
+
toCells() {
|
|
242
|
+
const out = new Array(width);
|
|
243
|
+
for (let c = 0; c < width; c++) out[c] = row.getCellRaw(c);
|
|
244
|
+
return out;
|
|
245
|
+
},
|
|
246
|
+
replaceAllFromCells(cells) {
|
|
247
|
+
for (let c = 0; c < width; c++) row.setCellRaw(c, cells[c] ?? EMPTY_CELL);
|
|
248
|
+
},
|
|
249
|
+
isEmpty(col) {
|
|
250
|
+
return meta[col] === 0 && chars[col] === "";
|
|
251
|
+
},
|
|
252
|
+
getChar(col) {
|
|
253
|
+
return chars[col];
|
|
254
|
+
},
|
|
255
|
+
isWide(col) {
|
|
256
|
+
return (meta[col] & F_WIDE$1) !== 0;
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
return row;
|
|
260
|
+
}
|
|
261
|
+
/** Build a packed row from a materialized cell array (reflow / restore output). */
|
|
262
|
+
function packedRowFromCells(cells, width) {
|
|
263
|
+
const r = makePackedRow(width);
|
|
264
|
+
for (let c = 0; c < width; c++) r.setCellRaw(c, cells[c] ?? EMPTY_CELL);
|
|
265
|
+
return r;
|
|
266
|
+
}
|
|
23
267
|
const ANSI_16 = [
|
|
24
268
|
{
|
|
25
269
|
r: 0,
|
|
@@ -217,8 +461,10 @@ function isEmojiModifier(cp) {
|
|
|
217
461
|
function createScreen(options = {}) {
|
|
218
462
|
let cols = options.cols ?? 80;
|
|
219
463
|
let rows = options.rows ?? 24;
|
|
220
|
-
|
|
464
|
+
let scrollbackLimit = options.scrollbackLimit ?? 1e3;
|
|
221
465
|
const onResponse = options.onResponse;
|
|
466
|
+
const maxStringSequenceLength = options.maxStringSequenceLength ?? 16777216;
|
|
467
|
+
if (!Number.isSafeInteger(maxStringSequenceLength) || maxStringSequenceLength < 0) throw new RangeError("maxStringSequenceLength must be a non-negative safe integer");
|
|
222
468
|
let mainGrid = makeGrid(cols, rows);
|
|
223
469
|
let altGrid = makeGrid(cols, rows);
|
|
224
470
|
let grid = mainGrid;
|
|
@@ -303,6 +549,201 @@ function createScreen(options = {}) {
|
|
|
303
549
|
highlightBgColor = cloneColor(snapshot.highlightBgColor);
|
|
304
550
|
highlightFgColor = cloneColor(snapshot.highlightFgColor);
|
|
305
551
|
}
|
|
552
|
+
function cloneColorStateSnapshot(snapshot) {
|
|
553
|
+
return {
|
|
554
|
+
palette256: snapshot.palette256.map((c) => ({ ...c })),
|
|
555
|
+
defaultFgColor: cloneColor(snapshot.defaultFgColor),
|
|
556
|
+
defaultBgColor: cloneColor(snapshot.defaultBgColor),
|
|
557
|
+
cursorColor: cloneColor(snapshot.cursorColor),
|
|
558
|
+
specialColors: snapshot.specialColors.map(([idx, color]) => [idx, { ...color }]),
|
|
559
|
+
pointerFgColor: cloneColor(snapshot.pointerFgColor),
|
|
560
|
+
pointerBgColor: cloneColor(snapshot.pointerBgColor),
|
|
561
|
+
highlightBgColor: cloneColor(snapshot.highlightBgColor),
|
|
562
|
+
highlightFgColor: cloneColor(snapshot.highlightFgColor)
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
function cloneAttrsSnapshot(source) {
|
|
566
|
+
return {
|
|
567
|
+
...source,
|
|
568
|
+
fg: cloneColor(source.fg),
|
|
569
|
+
bg: cloneColor(source.bg),
|
|
570
|
+
underlineColor: cloneColor(source.underlineColor)
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
function cloneCellSnapshot(source) {
|
|
574
|
+
return {
|
|
575
|
+
...source,
|
|
576
|
+
fg: cloneColor(source.fg),
|
|
577
|
+
bg: cloneColor(source.bg),
|
|
578
|
+
underlineColor: cloneColor(source.underlineColor)
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
function isDefaultEmptyCell(cell) {
|
|
582
|
+
return cell.char === "" && cell.fg === null && cell.bg === null && cell.bold === false && cell.faint === false && cell.italic === false && cell.underline === "none" && cell.underlineColor === null && cell.overline === false && cell.strikethrough === false && cell.inverse === false && cell.hidden === false && cell.blink === false && cell.wide === false && cell.url === null;
|
|
583
|
+
}
|
|
584
|
+
function restoreCellSnapshot(source) {
|
|
585
|
+
const cell = cloneCellSnapshot(source);
|
|
586
|
+
return isDefaultEmptyCell(cell) ? EMPTY_CELL : cell;
|
|
587
|
+
}
|
|
588
|
+
/** Materialize a packed grid to plain, deep-cloned Snapshot cells (index preserved). */
|
|
589
|
+
function cloneGridSnapshot(source) {
|
|
590
|
+
return source.map((row) => row.toCells().map(cloneCellSnapshot));
|
|
591
|
+
}
|
|
592
|
+
function restoreGridSnapshot(source, expectedRows, expectedCols) {
|
|
593
|
+
const out = [];
|
|
594
|
+
for (let row = 0; row < expectedRows; row++) {
|
|
595
|
+
const cells = (source[row] ?? []).slice(0, expectedCols).map(restoreCellSnapshot);
|
|
596
|
+
while (cells.length < expectedCols) cells.push(EMPTY_CELL);
|
|
597
|
+
out.push(packedRowFromCells(cells, expectedCols));
|
|
598
|
+
}
|
|
599
|
+
return out;
|
|
600
|
+
}
|
|
601
|
+
function restoreScrollbackSnapshot(source) {
|
|
602
|
+
return source.map((row) => packedRowFromCells(row.map(restoreCellSnapshot), row.length));
|
|
603
|
+
}
|
|
604
|
+
function isRecord(value) {
|
|
605
|
+
return typeof value === "object" && value !== null;
|
|
606
|
+
}
|
|
607
|
+
function assertInteger(name, value, min = 0) {
|
|
608
|
+
if (!Number.isInteger(value) || value < min) throw new TypeError(`Invalid vterm snapshot ${name}`);
|
|
609
|
+
}
|
|
610
|
+
function assertString(name, value) {
|
|
611
|
+
if (typeof value !== "string") throw new TypeError(`Invalid vterm snapshot ${name}`);
|
|
612
|
+
}
|
|
613
|
+
function assertBoolean(name, value) {
|
|
614
|
+
if (typeof value !== "boolean") throw new TypeError(`Invalid vterm snapshot ${name}`);
|
|
615
|
+
}
|
|
616
|
+
function assertRecord(name, value) {
|
|
617
|
+
if (!isRecord(value)) throw new TypeError(`Invalid vterm snapshot ${name}`);
|
|
618
|
+
}
|
|
619
|
+
function assertGrid(name, value, expectedRows, expectedCols) {
|
|
620
|
+
if (!Array.isArray(value) || value.length !== expectedRows) throw new TypeError(`Invalid vterm snapshot ${name}`);
|
|
621
|
+
for (const row of value) if (!Array.isArray(row) || row.length !== expectedCols) throw new TypeError(`Invalid vterm snapshot ${name}`);
|
|
622
|
+
}
|
|
623
|
+
function assertSoftWraps(name, value, expectedRows) {
|
|
624
|
+
if (!Array.isArray(value) || value.length !== expectedRows || value.some((entry) => typeof entry !== "boolean")) throw new TypeError(`Invalid vterm snapshot ${name}`);
|
|
625
|
+
}
|
|
626
|
+
function assertSnapshot(snapshot) {
|
|
627
|
+
const value = snapshot;
|
|
628
|
+
if (!isRecord(value)) throw new TypeError("Invalid vterm snapshot");
|
|
629
|
+
if (value.version !== 1) throw new Error(`Unsupported vterm snapshot version: ${String(value.version)}`);
|
|
630
|
+
assertInteger("cols", value.cols, 1);
|
|
631
|
+
assertInteger("rows", value.rows, 1);
|
|
632
|
+
assertInteger("scrollbackLimit", value.scrollbackLimit, 0);
|
|
633
|
+
if (value.activeBuffer !== "main" && value.activeBuffer !== "alt") throw new TypeError("Invalid vterm snapshot activeBuffer");
|
|
634
|
+
const main = value.main;
|
|
635
|
+
const alt = value.alt;
|
|
636
|
+
assertRecord("main", main);
|
|
637
|
+
assertRecord("alt", alt);
|
|
638
|
+
assertGrid("main.grid", main.grid, value.rows, value.cols);
|
|
639
|
+
assertGrid("alt.grid", alt.grid, value.rows, value.cols);
|
|
640
|
+
assertSoftWraps("main.softWrapped", main.softWrapped, value.rows);
|
|
641
|
+
assertSoftWraps("alt.softWrapped", alt.softWrapped, value.rows);
|
|
642
|
+
if (!Array.isArray(value.scrollback)) throw new TypeError("Invalid vterm snapshot scrollback");
|
|
643
|
+
if (value.scrollbackSoftWrapped !== void 0) assertSoftWraps("scrollbackSoftWrapped", value.scrollbackSoftWrapped, value.scrollback.length);
|
|
644
|
+
const cursor = value.cursor;
|
|
645
|
+
assertRecord("cursor", cursor);
|
|
646
|
+
assertInteger("cursor.x", cursor.x);
|
|
647
|
+
assertInteger("cursor.y", cursor.y);
|
|
648
|
+
assertBoolean("cursor.visible", cursor.visible);
|
|
649
|
+
if (cursor.shape !== "block" && cursor.shape !== "underline" && cursor.shape !== "bar") throw new TypeError("Invalid vterm snapshot cursor.shape");
|
|
650
|
+
assertBoolean("cursor.blinking", cursor.blinking);
|
|
651
|
+
assertInteger("cursor.savedX", cursor.savedX);
|
|
652
|
+
assertInteger("cursor.savedY", cursor.savedY);
|
|
653
|
+
const saved = value.savedState;
|
|
654
|
+
assertRecord("savedState", saved);
|
|
655
|
+
assertInteger("savedState.x", saved.x);
|
|
656
|
+
assertInteger("savedState.y", saved.y);
|
|
657
|
+
assertRecord("savedState.attrs", saved.attrs);
|
|
658
|
+
assertBoolean("savedState.originMode", saved.originMode);
|
|
659
|
+
assertBoolean("savedState.autoWrap", saved.autoWrap);
|
|
660
|
+
assertBoolean("savedState.charsetG0", saved.charsetG0);
|
|
661
|
+
assertRecord("attrs", value.attrs);
|
|
662
|
+
const modes = value.modes;
|
|
663
|
+
assertRecord("modes", modes);
|
|
664
|
+
for (const name of [
|
|
665
|
+
"bracketedPaste",
|
|
666
|
+
"applicationCursor",
|
|
667
|
+
"applicationKeypad",
|
|
668
|
+
"autoWrap",
|
|
669
|
+
"mouseTracking",
|
|
670
|
+
"sgrMouse",
|
|
671
|
+
"focusTracking",
|
|
672
|
+
"origin",
|
|
673
|
+
"insert",
|
|
674
|
+
"reverseVideo",
|
|
675
|
+
"syncOutput",
|
|
676
|
+
"kittyGraphics",
|
|
677
|
+
"colorSchemeReporting",
|
|
678
|
+
"decColumn",
|
|
679
|
+
"altScroll",
|
|
680
|
+
"utf8Mouse"
|
|
681
|
+
]) assertBoolean(`modes.${name}`, modes[name]);
|
|
682
|
+
assertInteger("modes.mouseTrackingMode", modes.mouseTrackingMode);
|
|
683
|
+
assertInteger("modes.kittyKeyboardFlags", modes.kittyKeyboardFlags);
|
|
684
|
+
if (!Array.isArray(modes.kittyKeyboardStack)) throw new TypeError("Invalid vterm snapshot modes.kittyKeyboardStack");
|
|
685
|
+
const margins = value.margins;
|
|
686
|
+
assertRecord("margins", margins);
|
|
687
|
+
assertInteger("margins.scrollTop", margins.scrollTop);
|
|
688
|
+
assertInteger("margins.scrollBottom", margins.scrollBottom);
|
|
689
|
+
assertBoolean("margins.leftRight", margins.leftRight);
|
|
690
|
+
assertInteger("margins.left", margins.left);
|
|
691
|
+
assertInteger("margins.right", margins.right);
|
|
692
|
+
const colors = value.colors;
|
|
693
|
+
assertRecord("colors", colors);
|
|
694
|
+
assertRecord("colors.current", colors.current);
|
|
695
|
+
if (!Array.isArray(colors.stack)) throw new TypeError("Invalid vterm snapshot colors.stack");
|
|
696
|
+
if (!Array.isArray(value.tabStops)) throw new TypeError("Invalid vterm snapshot tabStops");
|
|
697
|
+
for (const stop of value.tabStops) assertInteger("tabStops", stop);
|
|
698
|
+
assertString("title", value.title);
|
|
699
|
+
assertString("clipboard", value.clipboard);
|
|
700
|
+
assertString("cwd", value.cwd);
|
|
701
|
+
if (!Array.isArray(value.notifications) || value.notifications.some((entry) => typeof entry !== "string")) throw new TypeError("Invalid vterm snapshot notifications");
|
|
702
|
+
assertInteger("viewportOffset", value.viewportOffset);
|
|
703
|
+
const parser = value.parser;
|
|
704
|
+
assertRecord("parser", parser);
|
|
705
|
+
if (![
|
|
706
|
+
"ground",
|
|
707
|
+
"escape",
|
|
708
|
+
"escape_charset",
|
|
709
|
+
"escape_hash",
|
|
710
|
+
"csi",
|
|
711
|
+
"osc",
|
|
712
|
+
"dcs",
|
|
713
|
+
"dcs_passthrough",
|
|
714
|
+
"osc_st",
|
|
715
|
+
"dcs_st",
|
|
716
|
+
"apc",
|
|
717
|
+
"apc_st"
|
|
718
|
+
].includes(parser.state)) throw new TypeError("Invalid vterm snapshot parser.state");
|
|
719
|
+
assertString("parser.esc", parser.esc);
|
|
720
|
+
assertString("parser.osc", parser.osc);
|
|
721
|
+
assertString("parser.dcs", parser.dcs);
|
|
722
|
+
assertRecord("parser.dcsStart", parser.dcsStart);
|
|
723
|
+
assertInteger("parser.dcsStart.row", parser.dcsStart.row);
|
|
724
|
+
assertInteger("parser.dcsStart.col", parser.dcsStart.col);
|
|
725
|
+
if (parser.dcsReceivedLength !== void 0) assertInteger("parser.dcsReceivedLength", parser.dcsReceivedLength, 0);
|
|
726
|
+
if (parser.dcsOverflow !== void 0) assertBoolean("parser.dcsOverflow", parser.dcsOverflow);
|
|
727
|
+
assertString("parser.apc", parser.apc);
|
|
728
|
+
if (parser.apcStart !== void 0) {
|
|
729
|
+
assertRecord("parser.apcStart", parser.apcStart);
|
|
730
|
+
assertInteger("parser.apcStart.row", parser.apcStart.row);
|
|
731
|
+
assertInteger("parser.apcStart.col", parser.apcStart.col);
|
|
732
|
+
}
|
|
733
|
+
if (parser.apcReceivedLength !== void 0) assertInteger("parser.apcReceivedLength", parser.apcReceivedLength, 0);
|
|
734
|
+
if (parser.apcOverflow !== void 0) assertBoolean("parser.apcOverflow", parser.apcOverflow);
|
|
735
|
+
if (!Array.isArray(parser.utf8PendingBytes)) throw new TypeError("Invalid vterm snapshot parser.utf8PendingBytes");
|
|
736
|
+
for (const byte of parser.utf8PendingBytes) {
|
|
737
|
+
assertInteger("parser.utf8PendingBytes", byte, 0);
|
|
738
|
+
if (byte > 255) throw new TypeError("Invalid vterm snapshot parser.utf8PendingBytes");
|
|
739
|
+
}
|
|
740
|
+
const unicode = value.unicode;
|
|
741
|
+
assertRecord("unicode", unicode);
|
|
742
|
+
assertBoolean("unicode.charsetG0", unicode.charsetG0);
|
|
743
|
+
assertString("unicode.lastChar", unicode.lastChar);
|
|
744
|
+
if (unicode.pendingRegionalIndicator !== null) assertString("unicode.pendingRegionalIndicator", unicode.pendingRegionalIndicator);
|
|
745
|
+
assertBoolean("unicode.afterZWJ", unicode.afterZWJ);
|
|
746
|
+
}
|
|
306
747
|
let tabStops = defaultTabStops(cols);
|
|
307
748
|
function defaultTabStops(c) {
|
|
308
749
|
const s = /* @__PURE__ */ new Set();
|
|
@@ -337,6 +778,7 @@ function createScreen(options = {}) {
|
|
|
337
778
|
let mainSoftWrapped = new Array(rows).fill(false);
|
|
338
779
|
let altSoftWrapped = new Array(rows).fill(false);
|
|
339
780
|
let softWrapped = mainSoftWrapped;
|
|
781
|
+
let scrollbackSoftWrapped = [];
|
|
340
782
|
let lastChar = "";
|
|
341
783
|
let pendingRegionalIndicator = null;
|
|
342
784
|
let afterZWJ = false;
|
|
@@ -346,17 +788,129 @@ function createScreen(options = {}) {
|
|
|
346
788
|
let dcsBuf = "";
|
|
347
789
|
let dcsStartRow = 0;
|
|
348
790
|
let dcsStartCol = 0;
|
|
791
|
+
let dcsReceivedLength = 0;
|
|
792
|
+
let dcsOverflow = false;
|
|
349
793
|
let apcBuf = "";
|
|
794
|
+
let apcStartRow = 0;
|
|
795
|
+
let apcStartCol = 0;
|
|
796
|
+
let apcReceivedLength = 0;
|
|
797
|
+
let apcOverflow = false;
|
|
798
|
+
const opListeners = /* @__PURE__ */ new Set();
|
|
799
|
+
const parserListeners = /* @__PURE__ */ new Set();
|
|
800
|
+
let printRunParts = [];
|
|
801
|
+
let escIntermediate = "";
|
|
802
|
+
let dirtyAll = false;
|
|
803
|
+
let dirtyRows = /* @__PURE__ */ new Set();
|
|
804
|
+
let dirtyScrolled = 0;
|
|
805
|
+
let dirtyLastAbs = -1;
|
|
806
|
+
let trimmedRowCount = 0;
|
|
807
|
+
let dirtyCursor = {
|
|
808
|
+
x: 0,
|
|
809
|
+
y: 0,
|
|
810
|
+
visible: true,
|
|
811
|
+
shape: "block",
|
|
812
|
+
blinking: true
|
|
813
|
+
};
|
|
814
|
+
let sigDamageActive = false;
|
|
815
|
+
let sigDirtyAll = false;
|
|
816
|
+
let sigDirtyRows = /* @__PURE__ */ new Set();
|
|
817
|
+
let sigDirtyScrolled = 0;
|
|
818
|
+
let sigDirtyLastAbs = -1;
|
|
819
|
+
let sigDirtyCursor = {
|
|
820
|
+
x: 0,
|
|
821
|
+
y: 0,
|
|
822
|
+
visible: true,
|
|
823
|
+
shape: "block",
|
|
824
|
+
blinking: true
|
|
825
|
+
};
|
|
826
|
+
function markScreenRowDirty(screenRow) {
|
|
827
|
+
const abs = scrollback.length + screenRow;
|
|
828
|
+
if (!dirtyAll && abs !== dirtyLastAbs) {
|
|
829
|
+
dirtyLastAbs = abs;
|
|
830
|
+
dirtyRows.add(abs);
|
|
831
|
+
}
|
|
832
|
+
if (sigDamageActive && !sigDirtyAll && abs !== sigDirtyLastAbs) {
|
|
833
|
+
sigDirtyLastAbs = abs;
|
|
834
|
+
sigDirtyRows.add(abs);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function markAbsoluteRowDirty(abs) {
|
|
838
|
+
if (!dirtyAll && abs !== dirtyLastAbs) {
|
|
839
|
+
dirtyLastAbs = abs;
|
|
840
|
+
dirtyRows.add(abs);
|
|
841
|
+
}
|
|
842
|
+
if (sigDamageActive && !sigDirtyAll && abs !== sigDirtyLastAbs) {
|
|
843
|
+
sigDirtyLastAbs = abs;
|
|
844
|
+
sigDirtyRows.add(abs);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
function markScreenRowsDirty(top, bottom) {
|
|
848
|
+
if (dirtyAll && (!sigDamageActive || sigDirtyAll)) return;
|
|
849
|
+
for (let r = top; r <= bottom; r++) markScreenRowDirty(r);
|
|
850
|
+
}
|
|
851
|
+
/** Structural change — the whole visible buffer is damaged. Drops the per-row set (both planes). */
|
|
852
|
+
function markAllDirty() {
|
|
853
|
+
dirtyAll = true;
|
|
854
|
+
dirtyRows.clear();
|
|
855
|
+
dirtyLastAbs = -1;
|
|
856
|
+
if (sigDamageActive) {
|
|
857
|
+
sigDirtyAll = true;
|
|
858
|
+
sigDirtyRows.clear();
|
|
859
|
+
sigDirtyLastAbs = -1;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
350
862
|
const decoder = new TextDecoder();
|
|
863
|
+
const encoder = new TextEncoder();
|
|
864
|
+
let utf8PendingBytes = [];
|
|
865
|
+
function utf8SequenceLength(lead) {
|
|
866
|
+
if (lead <= 127) return 1;
|
|
867
|
+
if (lead >= 194 && lead <= 223) return 2;
|
|
868
|
+
if (lead >= 224 && lead <= 239) return 3;
|
|
869
|
+
if (lead >= 240 && lead <= 244) return 4;
|
|
870
|
+
return 0;
|
|
871
|
+
}
|
|
872
|
+
function utf8CompletePrefixLength(bytes) {
|
|
873
|
+
if (bytes.length === 0) return 0;
|
|
874
|
+
let continuationCount = 0;
|
|
875
|
+
let leadIndex = bytes.length - 1;
|
|
876
|
+
while (leadIndex >= 0 && (bytes[leadIndex] & 192) === 128 && continuationCount < 3) {
|
|
877
|
+
continuationCount++;
|
|
878
|
+
leadIndex--;
|
|
879
|
+
}
|
|
880
|
+
if (leadIndex < 0) return bytes.length;
|
|
881
|
+
const lead = bytes[leadIndex];
|
|
882
|
+
const expectedLength = utf8SequenceLength(lead);
|
|
883
|
+
if (expectedLength === 0) return bytes.length;
|
|
884
|
+
return expectedLength > bytes.length - leadIndex ? leadIndex : bytes.length;
|
|
885
|
+
}
|
|
886
|
+
function decodeInput(data) {
|
|
887
|
+
const bytes = new Uint8Array(utf8PendingBytes.length + data.length);
|
|
888
|
+
bytes.set(utf8PendingBytes, 0);
|
|
889
|
+
bytes.set(data, utf8PendingBytes.length);
|
|
890
|
+
const completeLength = utf8CompletePrefixLength(bytes);
|
|
891
|
+
utf8PendingBytes = Array.from(bytes.slice(completeLength));
|
|
892
|
+
if (completeLength === 0) return "";
|
|
893
|
+
return decoder.decode(bytes.slice(0, completeLength));
|
|
894
|
+
}
|
|
351
895
|
function makeGrid(c, r) {
|
|
352
896
|
const g = [];
|
|
353
897
|
for (let row = 0; row < r; row++) g.push(makeRow(c));
|
|
354
898
|
return g;
|
|
355
899
|
}
|
|
356
900
|
function makeRow(c) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
901
|
+
return makePackedRow(c);
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Edit-path escape hatch for the rare column-splice operations (insert/delete
|
|
905
|
+
* chars & columns, rect fill/copy/attr): materialize the row to a fixed-length
|
|
906
|
+
* cell array, run the ordinary array edit, then repack. Length-preserving edits
|
|
907
|
+
* (splice+push/pop pairs) keep the row exactly `cols` wide. Not the hot path —
|
|
908
|
+
* plain printing goes straight through {@link PackedRow.writeFromAttrs}.
|
|
909
|
+
*/
|
|
910
|
+
function mutateRowAsCells(row, fn) {
|
|
911
|
+
const cells = row.toCells();
|
|
912
|
+
fn(cells);
|
|
913
|
+
row.replaceAllFromCells(cells);
|
|
360
914
|
}
|
|
361
915
|
function resetAttrs() {
|
|
362
916
|
return {
|
|
@@ -388,14 +942,35 @@ function createScreen(options = {}) {
|
|
|
388
942
|
for (let r = top; r < bottom; r++) {
|
|
389
943
|
const srcRow = grid[r + 1];
|
|
390
944
|
const dstRow = grid[r];
|
|
391
|
-
for (let c = lm; c <= rm && c < cols; c++) dstRow
|
|
945
|
+
for (let c = lm; c <= rm && c < cols; c++) dstRow.copyCellFrom(c, srcRow, c);
|
|
392
946
|
}
|
|
393
947
|
const bottomRow = grid[bottom];
|
|
394
|
-
for (let c = lm; c <= rm && c < cols; c++) bottomRow
|
|
948
|
+
for (let c = lm; c <= rm && c < cols; c++) bottomRow.setEmpty(c);
|
|
949
|
+
markScreenRowsDirty(top, bottom);
|
|
395
950
|
} else {
|
|
396
|
-
|
|
951
|
+
const enteredScrollback = grid === mainGrid && top === 0;
|
|
952
|
+
if (enteredScrollback) {
|
|
397
953
|
scrollback.push(grid[0]);
|
|
398
|
-
|
|
954
|
+
scrollbackSoftWrapped.push(softWrapped[0] ?? false);
|
|
955
|
+
markAbsoluteRowDirty(scrollback.length - 1);
|
|
956
|
+
if (scrollback.length > scrollbackLimit * 2) {
|
|
957
|
+
const over = scrollback.length - scrollbackLimit;
|
|
958
|
+
scrollback.splice(0, over);
|
|
959
|
+
scrollbackSoftWrapped.splice(0, over);
|
|
960
|
+
if (!dirtyAll && dirtyRows.size > 0) {
|
|
961
|
+
const shifted = /* @__PURE__ */ new Set();
|
|
962
|
+
for (const r of dirtyRows) if (r >= over) shifted.add(r - over);
|
|
963
|
+
dirtyRows = shifted;
|
|
964
|
+
dirtyLastAbs = -1;
|
|
965
|
+
}
|
|
966
|
+
if (sigDamageActive && !sigDirtyAll && sigDirtyRows.size > 0) {
|
|
967
|
+
const shifted = /* @__PURE__ */ new Set();
|
|
968
|
+
for (const r of sigDirtyRows) if (r >= over) shifted.add(r - over);
|
|
969
|
+
sigDirtyRows = shifted;
|
|
970
|
+
sigDirtyLastAbs = -1;
|
|
971
|
+
}
|
|
972
|
+
trimmedRowCount += over;
|
|
973
|
+
}
|
|
399
974
|
}
|
|
400
975
|
for (let i = top; i < bottom; i++) {
|
|
401
976
|
grid[i] = grid[i + 1];
|
|
@@ -403,6 +978,11 @@ function createScreen(options = {}) {
|
|
|
403
978
|
}
|
|
404
979
|
grid[bottom] = makeRow(cols);
|
|
405
980
|
softWrapped[bottom] = false;
|
|
981
|
+
if (enteredScrollback) {
|
|
982
|
+
dirtyScrolled++;
|
|
983
|
+
if (sigDamageActive) sigDirtyScrolled++;
|
|
984
|
+
markScreenRowDirty(bottom);
|
|
985
|
+
} else markScreenRowsDirty(top, bottom);
|
|
406
986
|
}
|
|
407
987
|
}
|
|
408
988
|
function scrollDown(top, bottom) {
|
|
@@ -412,10 +992,10 @@ function createScreen(options = {}) {
|
|
|
412
992
|
for (let r = bottom; r > top; r--) {
|
|
413
993
|
const srcRow = grid[r - 1];
|
|
414
994
|
const dstRow = grid[r];
|
|
415
|
-
for (let c = lm; c <= rm && c < cols; c++) dstRow
|
|
995
|
+
for (let c = lm; c <= rm && c < cols; c++) dstRow.copyCellFrom(c, srcRow, c);
|
|
416
996
|
}
|
|
417
997
|
const topRow = grid[top];
|
|
418
|
-
for (let c = lm; c <= rm && c < cols; c++) topRow
|
|
998
|
+
for (let c = lm; c <= rm && c < cols; c++) topRow.setEmpty(c);
|
|
419
999
|
} else {
|
|
420
1000
|
for (let i = bottom; i > top; i--) {
|
|
421
1001
|
grid[i] = grid[i - 1];
|
|
@@ -424,11 +1004,17 @@ function createScreen(options = {}) {
|
|
|
424
1004
|
grid[top] = makeRow(cols);
|
|
425
1005
|
softWrapped[top] = false;
|
|
426
1006
|
}
|
|
1007
|
+
markScreenRowsDirty(top, bottom);
|
|
427
1008
|
}
|
|
428
1009
|
function scrollViewport(delta) {
|
|
429
1010
|
viewportOffset = Math.max(0, Math.min(scrollback.length, viewportOffset + delta));
|
|
430
1011
|
}
|
|
431
|
-
/**
|
|
1012
|
+
/**
|
|
1013
|
+
* Locate the previous non-spacer cell position (before curX, skipping a wide-char
|
|
1014
|
+
* spacer). Returns the packed row and column so callers mutate through the row's API
|
|
1015
|
+
* (combining marks append, VS-16 widens). The spacer is detected structurally — a
|
|
1016
|
+
* blank char whose left neighbour is wide — rather than by object identity.
|
|
1017
|
+
*/
|
|
432
1018
|
function getPrevCell() {
|
|
433
1019
|
if (curX === 0 && curY === 0) return null;
|
|
434
1020
|
let prevCol = curX - 1;
|
|
@@ -439,42 +1025,17 @@ function createScreen(options = {}) {
|
|
|
439
1025
|
prevCol = cols - 1;
|
|
440
1026
|
}
|
|
441
1027
|
const row = grid[prevRow];
|
|
442
|
-
|
|
443
|
-
if (
|
|
444
|
-
prevCol--;
|
|
445
|
-
cell = row[prevCol];
|
|
446
|
-
}
|
|
447
|
-
if (cell === EMPTY_CELL) return null;
|
|
1028
|
+
if (prevCol > 0 && row.getChar(prevCol) === "" && row.isWide(prevCol - 1)) prevCol--;
|
|
1029
|
+
if (row.isEmpty(prevCol)) return null;
|
|
448
1030
|
return {
|
|
449
|
-
|
|
450
|
-
col: prevCol
|
|
1031
|
+
row,
|
|
1032
|
+
col: prevCol,
|
|
1033
|
+
rowIdx: prevRow
|
|
451
1034
|
};
|
|
452
1035
|
}
|
|
453
|
-
/** Widen
|
|
454
|
-
function widenCell(row, col
|
|
455
|
-
|
|
456
|
-
if (col + 1 < cols) {
|
|
457
|
-
let spacer = row[col + 1];
|
|
458
|
-
if (spacer === EMPTY_CELL) {
|
|
459
|
-
spacer = { ...EMPTY_CELL };
|
|
460
|
-
row[col + 1] = spacer;
|
|
461
|
-
}
|
|
462
|
-
spacer.char = "";
|
|
463
|
-
spacer.fg = null;
|
|
464
|
-
spacer.bg = null;
|
|
465
|
-
spacer.bold = false;
|
|
466
|
-
spacer.faint = false;
|
|
467
|
-
spacer.italic = false;
|
|
468
|
-
spacer.underline = "none";
|
|
469
|
-
spacer.underlineColor = null;
|
|
470
|
-
spacer.overline = false;
|
|
471
|
-
spacer.strikethrough = false;
|
|
472
|
-
spacer.inverse = false;
|
|
473
|
-
spacer.hidden = false;
|
|
474
|
-
spacer.blink = false;
|
|
475
|
-
spacer.wide = false;
|
|
476
|
-
spacer.url = null;
|
|
477
|
-
}
|
|
1036
|
+
/** Widen the cell at `col` to 2 columns, blanking the trailing spacer at col+1. */
|
|
1037
|
+
function widenCell(row, col) {
|
|
1038
|
+
row.widen(col);
|
|
478
1039
|
}
|
|
479
1040
|
function writeChar(ch) {
|
|
480
1041
|
if (charsetG0 && ch.length === 1) {
|
|
@@ -484,10 +1045,10 @@ function createScreen(options = {}) {
|
|
|
484
1045
|
const codePoint = ch.codePointAt(0) ?? 0;
|
|
485
1046
|
if (isVS16(codePoint)) {
|
|
486
1047
|
const prev = getPrevCell();
|
|
487
|
-
if (prev && !prev.
|
|
488
|
-
prev.
|
|
489
|
-
|
|
490
|
-
|
|
1048
|
+
if (prev && !prev.row.isWide(prev.col)) {
|
|
1049
|
+
prev.row.appendChar(prev.col, ch);
|
|
1050
|
+
widenCell(prev.row, prev.col);
|
|
1051
|
+
markScreenRowDirty(prev.rowIdx);
|
|
491
1052
|
curX = prev.col + 2;
|
|
492
1053
|
if (curX >= cols) curX = cols - 1;
|
|
493
1054
|
}
|
|
@@ -495,18 +1056,25 @@ function createScreen(options = {}) {
|
|
|
495
1056
|
}
|
|
496
1057
|
if (isCombining(codePoint) && !isVS16(codePoint)) {
|
|
497
1058
|
const prev = getPrevCell();
|
|
498
|
-
if (prev)
|
|
1059
|
+
if (prev) {
|
|
1060
|
+
prev.row.appendChar(prev.col, ch);
|
|
1061
|
+
markScreenRowDirty(prev.rowIdx);
|
|
1062
|
+
}
|
|
499
1063
|
return;
|
|
500
1064
|
}
|
|
501
1065
|
if (isEmojiModifier(codePoint)) {
|
|
502
1066
|
const prev = getPrevCell();
|
|
503
|
-
if (prev)
|
|
1067
|
+
if (prev) {
|
|
1068
|
+
prev.row.appendChar(prev.col, ch);
|
|
1069
|
+
markScreenRowDirty(prev.rowIdx);
|
|
1070
|
+
}
|
|
504
1071
|
return;
|
|
505
1072
|
}
|
|
506
1073
|
if (isZWJ(codePoint)) {
|
|
507
1074
|
const prev = getPrevCell();
|
|
508
1075
|
if (prev) {
|
|
509
|
-
prev.
|
|
1076
|
+
prev.row.appendChar(prev.col, ch);
|
|
1077
|
+
markScreenRowDirty(prev.rowIdx);
|
|
510
1078
|
afterZWJ = true;
|
|
511
1079
|
}
|
|
512
1080
|
return;
|
|
@@ -515,7 +1083,8 @@ function createScreen(options = {}) {
|
|
|
515
1083
|
afterZWJ = false;
|
|
516
1084
|
const prev = getPrevCell();
|
|
517
1085
|
if (prev) {
|
|
518
|
-
prev.
|
|
1086
|
+
prev.row.appendChar(prev.col, ch);
|
|
1087
|
+
markScreenRowDirty(prev.rowIdx);
|
|
519
1088
|
return;
|
|
520
1089
|
}
|
|
521
1090
|
}
|
|
@@ -551,35 +1120,18 @@ function createScreen(options = {}) {
|
|
|
551
1120
|
}
|
|
552
1121
|
} else curX = wrapBoundary - charWidth;
|
|
553
1122
|
if (insertMode) {
|
|
554
|
-
const row = grid[curY];
|
|
555
1123
|
const insertEnd = leftRightMarginMode ? rightMargin + 1 : cols;
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
1124
|
+
mutateRowAsCells(grid[curY], (cells) => {
|
|
1125
|
+
for (let i = 0; i < charWidth; i++) {
|
|
1126
|
+
cells.splice(insertEnd - 1, 1);
|
|
1127
|
+
cells.splice(curX, 0, emptyCell());
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
560
1130
|
}
|
|
561
1131
|
const row = grid[curY];
|
|
562
|
-
|
|
563
|
-
if (
|
|
564
|
-
|
|
565
|
-
row[curX] = cell;
|
|
566
|
-
}
|
|
567
|
-
cell.char = ch;
|
|
568
|
-
cell.fg = attrs.fg ? { ...attrs.fg } : null;
|
|
569
|
-
cell.bg = attrs.bg ? { ...attrs.bg } : null;
|
|
570
|
-
cell.bold = attrs.bold;
|
|
571
|
-
cell.faint = attrs.faint;
|
|
572
|
-
cell.italic = attrs.italic;
|
|
573
|
-
cell.underline = attrs.underline;
|
|
574
|
-
cell.underlineColor = attrs.underlineColor ? { ...attrs.underlineColor } : null;
|
|
575
|
-
cell.overline = attrs.overline;
|
|
576
|
-
cell.strikethrough = attrs.strikethrough;
|
|
577
|
-
cell.inverse = attrs.inverse;
|
|
578
|
-
cell.hidden = attrs.hidden;
|
|
579
|
-
cell.blink = attrs.blink;
|
|
580
|
-
cell.wide = wide;
|
|
581
|
-
cell.url = attrs.url;
|
|
582
|
-
if (wide) widenCell(row, curX, cell);
|
|
1132
|
+
row.writeFromAttrs(curX, ch, attrs, wide);
|
|
1133
|
+
if (wide) widenCell(row, curX);
|
|
1134
|
+
markScreenRowDirty(curY);
|
|
583
1135
|
curX += charWidth;
|
|
584
1136
|
lastChar = ch;
|
|
585
1137
|
}
|
|
@@ -634,14 +1186,23 @@ function createScreen(options = {}) {
|
|
|
634
1186
|
const charCode = parts[0] ?? 32;
|
|
635
1187
|
if (charCode < 32 || charCode > 126) return;
|
|
636
1188
|
const { t, l, b, r } = normalizeRect(parts[1] ?? 1, parts[2] ?? 1, parts[3] ?? rows, parts[4] ?? cols);
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
1189
|
+
const fillChar = String.fromCharCode(charCode);
|
|
1190
|
+
for (let row = t; row <= b && row < rows; row++) {
|
|
1191
|
+
const gr = grid[row];
|
|
1192
|
+
for (let col = l; col <= r && col < cols; col++) {
|
|
1193
|
+
const cell = emptyCell();
|
|
1194
|
+
cell.char = fillChar;
|
|
1195
|
+
gr.setCellRaw(col, cell);
|
|
1196
|
+
}
|
|
641
1197
|
}
|
|
1198
|
+
markScreenRowsDirty(t, Math.min(b, rows - 1));
|
|
642
1199
|
} else if (finalByte === "z" || finalByte === "{") {
|
|
643
1200
|
const { t, l, b, r } = normalizeRect(parts[0] ?? 1, parts[1] ?? 1, parts[2] ?? rows, parts[3] ?? cols);
|
|
644
|
-
for (let row = t; row <= b && row < rows; row++)
|
|
1201
|
+
for (let row = t; row <= b && row < rows; row++) {
|
|
1202
|
+
const gr = grid[row];
|
|
1203
|
+
for (let col = l; col <= r && col < cols; col++) gr.setEmpty(col);
|
|
1204
|
+
}
|
|
1205
|
+
markScreenRowsDirty(t, Math.min(b, rows - 1));
|
|
645
1206
|
} else if (finalByte === "v") {
|
|
646
1207
|
const src = normalizeRect(parts[0] ?? 1, parts[1] ?? 1, parts[2] ?? rows, parts[3] ?? cols);
|
|
647
1208
|
const dstTop = Math.max(1, parts[5] ?? 1) - 1;
|
|
@@ -650,34 +1211,35 @@ function createScreen(options = {}) {
|
|
|
650
1211
|
const w = src.r - src.l + 1;
|
|
651
1212
|
const snapshot = [];
|
|
652
1213
|
for (let row = 0; row < h; row++) {
|
|
1214
|
+
const srcRow = grid[src.t + row];
|
|
653
1215
|
const line = [];
|
|
654
|
-
for (let col = 0; col < w; col++)
|
|
655
|
-
const srcCell = grid[src.t + row]?.[src.l + col] ?? EMPTY_CELL;
|
|
656
|
-
line.push(srcCell === EMPTY_CELL ? EMPTY_CELL : { ...srcCell });
|
|
657
|
-
}
|
|
1216
|
+
for (let col = 0; col < w; col++) line.push(srcRow ? srcRow.getCellRaw(src.l + col) : emptyCell());
|
|
658
1217
|
snapshot.push(line);
|
|
659
1218
|
}
|
|
660
1219
|
for (let row = 0; row < h; row++) {
|
|
661
1220
|
const dr = dstTop + row;
|
|
662
1221
|
if (dr < 0 || dr >= rows) continue;
|
|
1222
|
+
const dstRow = grid[dr];
|
|
663
1223
|
for (let col = 0; col < w; col++) {
|
|
664
1224
|
const dc = dstLeft + col;
|
|
665
1225
|
if (dc < 0 || dc >= cols) continue;
|
|
666
|
-
|
|
1226
|
+
dstRow.setCellRaw(dc, snapshot[row][col]);
|
|
667
1227
|
}
|
|
668
1228
|
}
|
|
1229
|
+
markScreenRowsDirty(Math.max(0, dstTop), Math.min(dstTop + h - 1, rows - 1));
|
|
669
1230
|
} else if (finalByte === "r" || finalByte === "t") {
|
|
670
1231
|
const { t, l, b, r } = normalizeRect(parts[0] ?? 1, parts[1] ?? 1, parts[2] ?? rows, parts[3] ?? cols);
|
|
671
1232
|
const sgrParts = parts.slice(4);
|
|
672
1233
|
const reverse = finalByte === "t";
|
|
673
|
-
for (let row = t; row <= b && row < rows; row++)
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
cell =
|
|
677
|
-
|
|
1234
|
+
for (let row = t; row <= b && row < rows; row++) {
|
|
1235
|
+
const gr = grid[row];
|
|
1236
|
+
for (let col = l; col <= r && col < cols; col++) {
|
|
1237
|
+
const cell = gr.getCellRaw(col);
|
|
1238
|
+
applyRectAttrs(cell, sgrParts, reverse);
|
|
1239
|
+
gr.setCellRaw(col, cell);
|
|
678
1240
|
}
|
|
679
|
-
applyRectAttrs(cell, sgrParts, reverse);
|
|
680
1241
|
}
|
|
1242
|
+
markScreenRowsDirty(t, Math.min(b, rows - 1));
|
|
681
1243
|
}
|
|
682
1244
|
return;
|
|
683
1245
|
}
|
|
@@ -693,11 +1255,15 @@ function createScreen(options = {}) {
|
|
|
693
1255
|
const b = Math.min(bArg, rows) - 1;
|
|
694
1256
|
const r = Math.min(rArg, cols) - 1;
|
|
695
1257
|
let sum = 0;
|
|
696
|
-
for (let row = t; row <= b && row < rows; row++)
|
|
697
|
-
const
|
|
698
|
-
if (
|
|
699
|
-
|
|
700
|
-
|
|
1258
|
+
for (let row = t; row <= b && row < rows; row++) {
|
|
1259
|
+
const gr = grid[row];
|
|
1260
|
+
if (!gr) continue;
|
|
1261
|
+
for (let col = l; col <= r && col < cols; col++) {
|
|
1262
|
+
const ch = gr.getChar(col);
|
|
1263
|
+
if (ch) {
|
|
1264
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
1265
|
+
sum = sum + cp & 65535;
|
|
1266
|
+
}
|
|
701
1267
|
}
|
|
702
1268
|
}
|
|
703
1269
|
const hex = sum.toString(16).toUpperCase().padStart(4, "0");
|
|
@@ -916,6 +1482,13 @@ function createScreen(options = {}) {
|
|
|
916
1482
|
if (onResponse) {
|
|
917
1483
|
if (params === "" || params === "0") onResponse("\x1BP!|00000000\x1B\\");
|
|
918
1484
|
}
|
|
1485
|
+
} else if (finalByte === "u") {
|
|
1486
|
+
const parts = params.split(";").map((s) => s === "" ? 0 : parseInt(s, 10));
|
|
1487
|
+
const flags = parts[0] ?? 0;
|
|
1488
|
+
const mode = parts[1] ?? 1;
|
|
1489
|
+
if (mode === 2) kittyKeyboardFlags |= flags;
|
|
1490
|
+
else if (mode === 3) kittyKeyboardFlags &= ~flags;
|
|
1491
|
+
else kittyKeyboardFlags = flags;
|
|
919
1492
|
}
|
|
920
1493
|
}
|
|
921
1494
|
function handleCSIPrivate(params, intermediates, finalByte) {
|
|
@@ -1037,10 +1610,12 @@ function createScreen(options = {}) {
|
|
|
1037
1610
|
useAltScreen = true;
|
|
1038
1611
|
grid = altGrid;
|
|
1039
1612
|
softWrapped = altSoftWrapped;
|
|
1613
|
+
markAllDirty();
|
|
1040
1614
|
} else if (!set && useAltScreen) {
|
|
1041
1615
|
useAltScreen = false;
|
|
1042
1616
|
grid = mainGrid;
|
|
1043
1617
|
softWrapped = mainSoftWrapped;
|
|
1618
|
+
markAllDirty();
|
|
1044
1619
|
}
|
|
1045
1620
|
break;
|
|
1046
1621
|
case 66:
|
|
@@ -1072,6 +1647,7 @@ function createScreen(options = {}) {
|
|
|
1072
1647
|
softWrapped = altSoftWrapped;
|
|
1073
1648
|
curX = 0;
|
|
1074
1649
|
curY = 0;
|
|
1650
|
+
markAllDirty();
|
|
1075
1651
|
} else if (!set && useAltScreen) {
|
|
1076
1652
|
useAltScreen = false;
|
|
1077
1653
|
grid = mainGrid;
|
|
@@ -1079,6 +1655,7 @@ function createScreen(options = {}) {
|
|
|
1079
1655
|
curX = savedCurX;
|
|
1080
1656
|
curY = savedCurY;
|
|
1081
1657
|
clampCursor();
|
|
1658
|
+
markAllDirty();
|
|
1082
1659
|
}
|
|
1083
1660
|
break;
|
|
1084
1661
|
case 69:
|
|
@@ -1135,6 +1712,7 @@ function createScreen(options = {}) {
|
|
|
1135
1712
|
case 3:
|
|
1136
1713
|
for (let row = 0; row < rows; row++) eraseCells(row, 0, row, cols - 1);
|
|
1137
1714
|
if (mode === 3) scrollback.length = 0;
|
|
1715
|
+
markAllDirty();
|
|
1138
1716
|
break;
|
|
1139
1717
|
}
|
|
1140
1718
|
}
|
|
@@ -1156,11 +1734,8 @@ function createScreen(options = {}) {
|
|
|
1156
1734
|
function eraseCells(row, startCol, _endRow, endCol) {
|
|
1157
1735
|
const r = grid[row];
|
|
1158
1736
|
if (!r) return;
|
|
1159
|
-
for (let col = startCol; col <= endCol && col < cols; col++)
|
|
1160
|
-
|
|
1161
|
-
if (attrs.bg) cell.bg = { ...attrs.bg };
|
|
1162
|
-
r[col] = cell;
|
|
1163
|
-
}
|
|
1737
|
+
for (let col = startCol; col <= endCol && col < cols; col++) r.eraseWithBg(col, attrs.bg);
|
|
1738
|
+
markScreenRowDirty(row);
|
|
1164
1739
|
}
|
|
1165
1740
|
function handleInsertLines(count) {
|
|
1166
1741
|
if (curY < scrollTop || curY > scrollBottom) return;
|
|
@@ -1173,32 +1748,36 @@ function createScreen(options = {}) {
|
|
|
1173
1748
|
function handleDeleteChars(count) {
|
|
1174
1749
|
const row = grid[curY];
|
|
1175
1750
|
if (!row) return;
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1751
|
+
mutateRowAsCells(row, (cells) => {
|
|
1752
|
+
if (leftRightMarginMode && (leftMargin > 0 || rightMargin < cols - 1)) {
|
|
1753
|
+
for (let i = 0; i < count; i++) if (curX <= rightMargin) {
|
|
1754
|
+
cells.splice(curX, 1);
|
|
1755
|
+
cells.splice(rightMargin, 0, emptyCell());
|
|
1756
|
+
}
|
|
1757
|
+
} else for (let i = 0; i < count; i++) if (curX < cols) {
|
|
1758
|
+
cells.splice(curX, 1);
|
|
1759
|
+
cells.push(emptyCell());
|
|
1180
1760
|
}
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
row.push(emptyCell());
|
|
1184
|
-
}
|
|
1761
|
+
});
|
|
1762
|
+
markScreenRowDirty(curY);
|
|
1185
1763
|
}
|
|
1186
1764
|
function handleInsertChars(count) {
|
|
1187
1765
|
const row = grid[curY];
|
|
1188
1766
|
if (!row) return;
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1767
|
+
mutateRowAsCells(row, (cells) => {
|
|
1768
|
+
if (leftRightMarginMode && (leftMargin > 0 || rightMargin < cols - 1)) for (let i = 0; i < count; i++) {
|
|
1769
|
+
cells.splice(rightMargin, 1);
|
|
1770
|
+
cells.splice(curX, 0, emptyCell());
|
|
1771
|
+
}
|
|
1772
|
+
else for (let i = 0; i < count; i++) {
|
|
1773
|
+
cells.splice(curX, 0, emptyCell());
|
|
1774
|
+
cells.pop();
|
|
1775
|
+
}
|
|
1776
|
+
});
|
|
1777
|
+
markScreenRowDirty(curY);
|
|
1197
1778
|
}
|
|
1198
1779
|
function handleEraseChars(count) {
|
|
1199
|
-
|
|
1200
|
-
if (!row) return;
|
|
1201
|
-
for (let i = 0; i < count && curX + i < cols; i++) row[curX + i] = emptyCell();
|
|
1780
|
+
eraseCells(curY, curX, curY, Math.min(curX + count - 1, cols - 1));
|
|
1202
1781
|
}
|
|
1203
1782
|
/** Bounds of the active scroll/margin rectangle (inclusive). */
|
|
1204
1783
|
function activeRect() {
|
|
@@ -1216,9 +1795,11 @@ function createScreen(options = {}) {
|
|
|
1216
1795
|
if (!r) continue;
|
|
1217
1796
|
for (let col = left; col <= right; col++) {
|
|
1218
1797
|
const src = col + count;
|
|
1219
|
-
|
|
1798
|
+
if (src <= right) r.copyCellFrom(col, r, src);
|
|
1799
|
+
else r.setEmpty(col);
|
|
1220
1800
|
}
|
|
1221
1801
|
}
|
|
1802
|
+
markScreenRowsDirty(top, bottom);
|
|
1222
1803
|
}
|
|
1223
1804
|
function handleShiftRight(count) {
|
|
1224
1805
|
const { top, left, bottom, right } = activeRect();
|
|
@@ -1227,9 +1808,11 @@ function createScreen(options = {}) {
|
|
|
1227
1808
|
if (!r) continue;
|
|
1228
1809
|
for (let col = right; col >= left; col--) {
|
|
1229
1810
|
const src = col - count;
|
|
1230
|
-
|
|
1811
|
+
if (src >= left) r.copyCellFrom(col, r, src);
|
|
1812
|
+
else r.setEmpty(col);
|
|
1231
1813
|
}
|
|
1232
1814
|
}
|
|
1815
|
+
markScreenRowsDirty(top, bottom);
|
|
1233
1816
|
}
|
|
1234
1817
|
function handleInsertColumn(count) {
|
|
1235
1818
|
const { top, left, bottom, right } = activeRect();
|
|
@@ -1237,9 +1820,10 @@ function createScreen(options = {}) {
|
|
|
1237
1820
|
for (let row = top; row <= bottom; row++) {
|
|
1238
1821
|
const r = grid[row];
|
|
1239
1822
|
if (!r) continue;
|
|
1240
|
-
for (let col = right; col >= curX + count; col--) r
|
|
1241
|
-
for (let col = curX; col < curX + count && col <= right; col++) r
|
|
1823
|
+
for (let col = right; col >= curX + count; col--) r.copyCellFrom(col, r, col - count);
|
|
1824
|
+
for (let col = curX; col < curX + count && col <= right; col++) r.setEmpty(col);
|
|
1242
1825
|
}
|
|
1826
|
+
markScreenRowsDirty(top, bottom);
|
|
1243
1827
|
}
|
|
1244
1828
|
function handleDeleteColumn(count) {
|
|
1245
1829
|
const { top, left, bottom, right } = activeRect();
|
|
@@ -1247,9 +1831,10 @@ function createScreen(options = {}) {
|
|
|
1247
1831
|
for (let row = top; row <= bottom; row++) {
|
|
1248
1832
|
const r = grid[row];
|
|
1249
1833
|
if (!r) continue;
|
|
1250
|
-
for (let col = curX; col + count <= right; col++) r
|
|
1251
|
-
for (let col = right - count + 1; col <= right && col >= 0; col++) r
|
|
1834
|
+
for (let col = curX; col + count <= right; col++) r.copyCellFrom(col, r, col + count);
|
|
1835
|
+
for (let col = right - count + 1; col <= right && col >= 0; col++) r.setEmpty(col);
|
|
1252
1836
|
}
|
|
1837
|
+
markScreenRowsDirty(top, bottom);
|
|
1253
1838
|
}
|
|
1254
1839
|
/**
|
|
1255
1840
|
* Apply (or reverse-toggle) a list of SGR attribute codes to a single cell.
|
|
@@ -1403,7 +1988,10 @@ function createScreen(options = {}) {
|
|
|
1403
1988
|
case 35:
|
|
1404
1989
|
case 36:
|
|
1405
1990
|
case 37:
|
|
1406
|
-
attrs.fg = {
|
|
1991
|
+
attrs.fg = {
|
|
1992
|
+
...palette256[code - 30],
|
|
1993
|
+
index: code - 30
|
|
1994
|
+
};
|
|
1407
1995
|
break;
|
|
1408
1996
|
case 38: {
|
|
1409
1997
|
const subs = subParams.get(i);
|
|
@@ -1430,7 +2018,10 @@ function createScreen(options = {}) {
|
|
|
1430
2018
|
case 45:
|
|
1431
2019
|
case 46:
|
|
1432
2020
|
case 47:
|
|
1433
|
-
attrs.bg = {
|
|
2021
|
+
attrs.bg = {
|
|
2022
|
+
...palette256[code - 40],
|
|
2023
|
+
index: code - 40
|
|
2024
|
+
};
|
|
1434
2025
|
break;
|
|
1435
2026
|
case 48: {
|
|
1436
2027
|
const subs = subParams.get(i);
|
|
@@ -1480,7 +2071,10 @@ function createScreen(options = {}) {
|
|
|
1480
2071
|
case 95:
|
|
1481
2072
|
case 96:
|
|
1482
2073
|
case 97:
|
|
1483
|
-
attrs.fg = {
|
|
2074
|
+
attrs.fg = {
|
|
2075
|
+
...palette256[code - 90 + 8],
|
|
2076
|
+
index: code - 90 + 8
|
|
2077
|
+
};
|
|
1484
2078
|
break;
|
|
1485
2079
|
case 100:
|
|
1486
2080
|
case 101:
|
|
@@ -1490,7 +2084,10 @@ function createScreen(options = {}) {
|
|
|
1490
2084
|
case 105:
|
|
1491
2085
|
case 106:
|
|
1492
2086
|
case 107:
|
|
1493
|
-
attrs.bg = {
|
|
2087
|
+
attrs.bg = {
|
|
2088
|
+
...palette256[code - 100 + 8],
|
|
2089
|
+
index: code - 100 + 8
|
|
2090
|
+
};
|
|
1494
2091
|
break;
|
|
1495
2092
|
}
|
|
1496
2093
|
i++;
|
|
@@ -1501,12 +2098,16 @@ function createScreen(options = {}) {
|
|
|
1501
2098
|
const type = params[startIndex + 1];
|
|
1502
2099
|
if (type === 5 && startIndex + 2 < params.length) {
|
|
1503
2100
|
const idx = params[startIndex + 2];
|
|
2101
|
+
const entry = palette256[idx];
|
|
1504
2102
|
return {
|
|
1505
|
-
color:
|
|
2103
|
+
color: entry ? {
|
|
2104
|
+
...entry,
|
|
2105
|
+
index: idx
|
|
2106
|
+
} : {
|
|
1506
2107
|
r: 0,
|
|
1507
2108
|
g: 0,
|
|
1508
2109
|
b: 0
|
|
1509
|
-
}
|
|
2110
|
+
},
|
|
1510
2111
|
nextIndex: startIndex + 3
|
|
1511
2112
|
};
|
|
1512
2113
|
} else if (type === 2 && startIndex + 4 < params.length) return {
|
|
@@ -1525,11 +2126,15 @@ function createScreen(options = {}) {
|
|
|
1525
2126
|
const type = subs[1];
|
|
1526
2127
|
if (type === 5 && subs.length >= 3) {
|
|
1527
2128
|
const idx = subs[2];
|
|
1528
|
-
|
|
2129
|
+
const entry = palette256[idx];
|
|
2130
|
+
return entry ? {
|
|
2131
|
+
...entry,
|
|
2132
|
+
index: idx
|
|
2133
|
+
} : {
|
|
1529
2134
|
r: 0,
|
|
1530
2135
|
g: 0,
|
|
1531
2136
|
b: 0
|
|
1532
|
-
}
|
|
2137
|
+
};
|
|
1533
2138
|
} else if (type === 2) {
|
|
1534
2139
|
if (subs.length >= 5) {
|
|
1535
2140
|
if (subs.length >= 6) return {
|
|
@@ -1622,7 +2227,10 @@ function createScreen(options = {}) {
|
|
|
1622
2227
|
if (isNaN(idx) || idx < 0 || idx > 4) continue;
|
|
1623
2228
|
if (spec === "?") {
|
|
1624
2229
|
const c = specialColors.get(idx);
|
|
1625
|
-
if (onResponse)
|
|
2230
|
+
if (onResponse) {
|
|
2231
|
+
const payload = c ? formatColorResponse(c) : "rgb:0000/0000/0000";
|
|
2232
|
+
onResponse(`\x1b]5;${idx};${payload}\x1b\\`);
|
|
2233
|
+
}
|
|
1626
2234
|
} else {
|
|
1627
2235
|
const c = parseColorSpec(spec);
|
|
1628
2236
|
if (c) specialColors.set(idx, c);
|
|
@@ -1836,7 +2444,10 @@ function createScreen(options = {}) {
|
|
|
1836
2444
|
if (clipSemi !== -1) {
|
|
1837
2445
|
const data = value.substring(clipSemi + 1);
|
|
1838
2446
|
if (data === "?") {
|
|
1839
|
-
if (onResponse)
|
|
2447
|
+
if (onResponse) {
|
|
2448
|
+
const encoded = btoa(clipboard);
|
|
2449
|
+
onResponse(`\x1b]52;c;${encoded}\x1b\\`);
|
|
2450
|
+
}
|
|
1840
2451
|
} else {
|
|
1841
2452
|
try {
|
|
1842
2453
|
clipboard = atob(data);
|
|
@@ -1938,8 +2549,10 @@ function createScreen(options = {}) {
|
|
|
1938
2549
|
break;
|
|
1939
2550
|
}
|
|
1940
2551
|
case 5522:
|
|
1941
|
-
if (value === "?" && onResponse)
|
|
1942
|
-
|
|
2552
|
+
if (value === "?" && onResponse) {
|
|
2553
|
+
const encoded = btoa(advancedClipboard);
|
|
2554
|
+
onResponse(`\x1b]5522;${encoded}\x1b\\`);
|
|
2555
|
+
} else try {
|
|
1943
2556
|
advancedClipboard = atob(value);
|
|
1944
2557
|
} catch {
|
|
1945
2558
|
advancedClipboard = "";
|
|
@@ -1968,8 +2581,10 @@ function createScreen(options = {}) {
|
|
|
1968
2581
|
return;
|
|
1969
2582
|
}
|
|
1970
2583
|
if (data.startsWith("+q") && onResponse) {
|
|
1971
|
-
if (data.substring(2) === "544e")
|
|
1972
|
-
|
|
2584
|
+
if (data.substring(2) === "544e") {
|
|
2585
|
+
const hexValue = Array.from(new TextEncoder().encode("vterm")).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2586
|
+
onResponse(`\x1bP1+r544e=${hexValue}\x1b\\`);
|
|
2587
|
+
} else onResponse("\x1BP0+r\x1B\\");
|
|
1973
2588
|
return;
|
|
1974
2589
|
}
|
|
1975
2590
|
}
|
|
@@ -2015,6 +2630,7 @@ function createScreen(options = {}) {
|
|
|
2015
2630
|
altGrid = makeGrid(cols, rows);
|
|
2016
2631
|
grid = mainGrid;
|
|
2017
2632
|
scrollback = [];
|
|
2633
|
+
scrollbackSoftWrapped = [];
|
|
2018
2634
|
curX = 0;
|
|
2019
2635
|
curY = 0;
|
|
2020
2636
|
curVisible = true;
|
|
@@ -2086,14 +2702,155 @@ function createScreen(options = {}) {
|
|
|
2086
2702
|
parserState = "ground";
|
|
2087
2703
|
escBuf = "";
|
|
2088
2704
|
oscBuf = "";
|
|
2705
|
+
dcsBuf = "";
|
|
2706
|
+
dcsStartRow = 0;
|
|
2707
|
+
dcsStartCol = 0;
|
|
2708
|
+
dcsReceivedLength = 0;
|
|
2709
|
+
dcsOverflow = false;
|
|
2089
2710
|
apcBuf = "";
|
|
2711
|
+
apcStartRow = 0;
|
|
2712
|
+
apcStartCol = 0;
|
|
2713
|
+
apcReceivedLength = 0;
|
|
2714
|
+
apcOverflow = false;
|
|
2715
|
+
utf8PendingBytes = [];
|
|
2090
2716
|
semanticZones = [];
|
|
2091
2717
|
mainSoftWrapped = new Array(rows).fill(false);
|
|
2092
2718
|
altSoftWrapped = new Array(rows).fill(false);
|
|
2093
2719
|
softWrapped = mainSoftWrapped;
|
|
2720
|
+
markAllDirty();
|
|
2721
|
+
dirtyScrolled = 0;
|
|
2722
|
+
if (sigDamageActive) sigDirtyScrolled = 0;
|
|
2723
|
+
trimmedRowCount = 0;
|
|
2724
|
+
flushSignals();
|
|
2725
|
+
}
|
|
2726
|
+
function emitParserEvent(ev) {
|
|
2727
|
+
for (const listener of parserListeners) listener(ev);
|
|
2728
|
+
}
|
|
2729
|
+
function flushPrintRun() {
|
|
2730
|
+
if (printRunParts.length === 0) return;
|
|
2731
|
+
const text = printRunParts.join("");
|
|
2732
|
+
printRunParts = [];
|
|
2733
|
+
emitParserEvent({
|
|
2734
|
+
kind: "print",
|
|
2735
|
+
text
|
|
2736
|
+
});
|
|
2737
|
+
}
|
|
2738
|
+
function emitExecute(code) {
|
|
2739
|
+
if (parserListeners.size === 0) return;
|
|
2740
|
+
flushPrintRun();
|
|
2741
|
+
emitParserEvent({
|
|
2742
|
+
kind: "execute",
|
|
2743
|
+
code
|
|
2744
|
+
});
|
|
2745
|
+
}
|
|
2746
|
+
function emitCsiEvent(rawParams, intermediates, final) {
|
|
2747
|
+
if (parserListeners.size === 0) return;
|
|
2748
|
+
flushPrintRun();
|
|
2749
|
+
let prefix;
|
|
2750
|
+
let body = rawParams;
|
|
2751
|
+
const marker = body.charCodeAt(0);
|
|
2752
|
+
if (marker === 63 || marker === 62 || marker === 60 || marker === 61) {
|
|
2753
|
+
prefix = body[0];
|
|
2754
|
+
body = body.slice(1);
|
|
2755
|
+
}
|
|
2756
|
+
const ev = {
|
|
2757
|
+
kind: "csi",
|
|
2758
|
+
final,
|
|
2759
|
+
params: body.split(";").map((s) => s === "" ? 0 : parseInt(s, 10))
|
|
2760
|
+
};
|
|
2761
|
+
if (prefix !== void 0) ev.prefix = prefix;
|
|
2762
|
+
if (intermediates !== "") ev.intermediates = intermediates;
|
|
2763
|
+
emitParserEvent(ev);
|
|
2764
|
+
}
|
|
2765
|
+
function emitOscEvent(osc) {
|
|
2766
|
+
if (parserListeners.size === 0) return;
|
|
2767
|
+
const semi = osc.indexOf(";");
|
|
2768
|
+
const code = semi === -1 ? parseInt(osc, 10) : parseInt(osc.slice(0, semi), 10);
|
|
2769
|
+
if (Number.isNaN(code)) return;
|
|
2770
|
+
flushPrintRun();
|
|
2771
|
+
emitParserEvent({
|
|
2772
|
+
kind: "osc",
|
|
2773
|
+
code,
|
|
2774
|
+
data: semi === -1 ? "" : osc.slice(semi + 1)
|
|
2775
|
+
});
|
|
2776
|
+
}
|
|
2777
|
+
function emitEsc(final, intermediates) {
|
|
2778
|
+
if (parserListeners.size === 0) return;
|
|
2779
|
+
flushPrintRun();
|
|
2780
|
+
const ev = {
|
|
2781
|
+
kind: "esc",
|
|
2782
|
+
final
|
|
2783
|
+
};
|
|
2784
|
+
if (intermediates !== "") ev.intermediates = intermediates;
|
|
2785
|
+
emitParserEvent(ev);
|
|
2786
|
+
}
|
|
2787
|
+
function emitStringEvent(sequence, data, row, col) {
|
|
2788
|
+
if (parserListeners.size === 0) return;
|
|
2789
|
+
flushPrintRun();
|
|
2790
|
+
emitParserEvent({
|
|
2791
|
+
kind: sequence,
|
|
2792
|
+
data,
|
|
2793
|
+
row,
|
|
2794
|
+
col
|
|
2795
|
+
});
|
|
2796
|
+
}
|
|
2797
|
+
function emitStringOverflow(sequence, receivedLength, row, col) {
|
|
2798
|
+
if (parserListeners.size === 0) return;
|
|
2799
|
+
flushPrintRun();
|
|
2800
|
+
emitParserEvent({
|
|
2801
|
+
kind: "string-overflow",
|
|
2802
|
+
sequence,
|
|
2803
|
+
maxLength: maxStringSequenceLength,
|
|
2804
|
+
receivedLength,
|
|
2805
|
+
row,
|
|
2806
|
+
col
|
|
2807
|
+
});
|
|
2808
|
+
}
|
|
2809
|
+
function appendDCS(ch) {
|
|
2810
|
+
dcsReceivedLength += ch.length;
|
|
2811
|
+
if (dcsOverflow) return;
|
|
2812
|
+
if (dcsReceivedLength > maxStringSequenceLength) {
|
|
2813
|
+
dcsOverflow = true;
|
|
2814
|
+
dcsBuf = "";
|
|
2815
|
+
return;
|
|
2816
|
+
}
|
|
2817
|
+
dcsBuf += ch;
|
|
2818
|
+
}
|
|
2819
|
+
function appendAPC(ch) {
|
|
2820
|
+
apcReceivedLength += ch.length;
|
|
2821
|
+
if (apcOverflow) return;
|
|
2822
|
+
if (apcReceivedLength > maxStringSequenceLength) {
|
|
2823
|
+
apcOverflow = true;
|
|
2824
|
+
apcBuf = "";
|
|
2825
|
+
return;
|
|
2826
|
+
}
|
|
2827
|
+
apcBuf += ch;
|
|
2828
|
+
}
|
|
2829
|
+
function finishDCS() {
|
|
2830
|
+
if (dcsOverflow) emitStringOverflow("dcs", dcsReceivedLength, dcsStartRow, dcsStartCol);
|
|
2831
|
+
else {
|
|
2832
|
+
handleDCS(dcsBuf);
|
|
2833
|
+
emitStringEvent("dcs", dcsBuf, dcsStartRow, dcsStartCol);
|
|
2834
|
+
}
|
|
2835
|
+
dcsBuf = "";
|
|
2836
|
+
dcsReceivedLength = 0;
|
|
2837
|
+
dcsOverflow = false;
|
|
2838
|
+
}
|
|
2839
|
+
function finishAPC() {
|
|
2840
|
+
if (apcOverflow) emitStringOverflow("apc", apcReceivedLength, apcStartRow, apcStartCol);
|
|
2841
|
+
else {
|
|
2842
|
+
handleAPC(apcBuf);
|
|
2843
|
+
emitStringEvent("apc", apcBuf, apcStartRow, apcStartCol);
|
|
2844
|
+
}
|
|
2845
|
+
apcBuf = "";
|
|
2846
|
+
apcReceivedLength = 0;
|
|
2847
|
+
apcOverflow = false;
|
|
2848
|
+
}
|
|
2849
|
+
function emitOp(op) {
|
|
2850
|
+
for (const listener of opListeners) listener(op);
|
|
2094
2851
|
}
|
|
2095
2852
|
function process(data) {
|
|
2096
|
-
const text =
|
|
2853
|
+
const text = decodeInput(data);
|
|
2097
2854
|
for (let i = 0; i < text.length; i++) {
|
|
2098
2855
|
const ch = text[i];
|
|
2099
2856
|
const code = text.charCodeAt(i);
|
|
@@ -2102,18 +2859,25 @@ function createScreen(options = {}) {
|
|
|
2102
2859
|
if (code === 27) {
|
|
2103
2860
|
parserState = "escape";
|
|
2104
2861
|
escBuf = "";
|
|
2105
|
-
} else if (code === 7)
|
|
2862
|
+
} else if (code === 7) emitExecute(7);
|
|
2863
|
+
else if (code === 8) {
|
|
2106
2864
|
if (curX > 0) curX--;
|
|
2107
|
-
|
|
2108
|
-
else if (code ===
|
|
2865
|
+
emitExecute(8);
|
|
2866
|
+
} else if (code === 9) {
|
|
2867
|
+
curX = nextTabStop(curX);
|
|
2868
|
+
emitExecute(9);
|
|
2869
|
+
} else if (code === 10 || code === 11 || code === 12) {
|
|
2109
2870
|
softWrapped[curY] = false;
|
|
2110
2871
|
curY++;
|
|
2111
2872
|
if (curY > scrollBottom) {
|
|
2112
2873
|
curY = scrollBottom;
|
|
2113
2874
|
scrollUp(scrollTop, scrollBottom);
|
|
2114
2875
|
}
|
|
2115
|
-
|
|
2116
|
-
else if (code
|
|
2876
|
+
emitExecute(code);
|
|
2877
|
+
} else if (code === 13) {
|
|
2878
|
+
curX = 0;
|
|
2879
|
+
emitExecute(13);
|
|
2880
|
+
} else if (code >= 32) {
|
|
2117
2881
|
let char = ch;
|
|
2118
2882
|
if (code >= 55296 && code <= 56319 && i + 1 < text.length) {
|
|
2119
2883
|
const nextCode = text.charCodeAt(i + 1);
|
|
@@ -2123,6 +2887,7 @@ function createScreen(options = {}) {
|
|
|
2123
2887
|
}
|
|
2124
2888
|
}
|
|
2125
2889
|
writeChar(char);
|
|
2890
|
+
if (parserListeners.size > 0) printRunParts.push(char);
|
|
2126
2891
|
}
|
|
2127
2892
|
break;
|
|
2128
2893
|
case "escape":
|
|
@@ -2138,9 +2903,12 @@ function createScreen(options = {}) {
|
|
|
2138
2903
|
dcsBuf = "";
|
|
2139
2904
|
dcsStartRow = curY;
|
|
2140
2905
|
dcsStartCol = curX;
|
|
2906
|
+
dcsReceivedLength = 0;
|
|
2907
|
+
dcsOverflow = false;
|
|
2141
2908
|
} else if (ch === "c") {
|
|
2142
2909
|
fullReset();
|
|
2143
2910
|
parserState = "ground";
|
|
2911
|
+
emitEsc("c", "");
|
|
2144
2912
|
} else if (ch === "D") {
|
|
2145
2913
|
curY++;
|
|
2146
2914
|
if (curY > scrollBottom) {
|
|
@@ -2148,6 +2916,7 @@ function createScreen(options = {}) {
|
|
|
2148
2916
|
scrollUp(scrollTop, scrollBottom);
|
|
2149
2917
|
}
|
|
2150
2918
|
parserState = "ground";
|
|
2919
|
+
emitEsc("D", "");
|
|
2151
2920
|
} else if (ch === "M") {
|
|
2152
2921
|
curY--;
|
|
2153
2922
|
if (curY < scrollTop) {
|
|
@@ -2155,6 +2924,7 @@ function createScreen(options = {}) {
|
|
|
2155
2924
|
scrollDown(scrollTop, scrollBottom);
|
|
2156
2925
|
}
|
|
2157
2926
|
parserState = "ground";
|
|
2927
|
+
emitEsc("M", "");
|
|
2158
2928
|
} else if (ch === "7") {
|
|
2159
2929
|
savedState = {
|
|
2160
2930
|
curX,
|
|
@@ -2170,6 +2940,7 @@ function createScreen(options = {}) {
|
|
|
2170
2940
|
charsetG0
|
|
2171
2941
|
};
|
|
2172
2942
|
parserState = "ground";
|
|
2943
|
+
emitEsc("7", "");
|
|
2173
2944
|
} else if (ch === "8") {
|
|
2174
2945
|
curX = savedState.curX;
|
|
2175
2946
|
curY = savedState.curY;
|
|
@@ -2184,6 +2955,7 @@ function createScreen(options = {}) {
|
|
|
2184
2955
|
charsetG0 = savedState.charsetG0;
|
|
2185
2956
|
clampCursor();
|
|
2186
2957
|
parserState = "ground";
|
|
2958
|
+
emitEsc("8", "");
|
|
2187
2959
|
} else if (ch === "E") {
|
|
2188
2960
|
curX = 0;
|
|
2189
2961
|
curY++;
|
|
@@ -2192,43 +2964,57 @@ function createScreen(options = {}) {
|
|
|
2192
2964
|
scrollUp(scrollTop, scrollBottom);
|
|
2193
2965
|
}
|
|
2194
2966
|
parserState = "ground";
|
|
2967
|
+
emitEsc("E", "");
|
|
2195
2968
|
} else if (ch === "H") {
|
|
2196
2969
|
tabStops.add(curX);
|
|
2197
2970
|
parserState = "ground";
|
|
2198
|
-
|
|
2199
|
-
else if (ch === "
|
|
2200
|
-
|
|
2201
|
-
|
|
2971
|
+
emitEsc("H", "");
|
|
2972
|
+
} else if (ch === "#") {
|
|
2973
|
+
escIntermediate = "#";
|
|
2974
|
+
parserState = "escape_hash";
|
|
2975
|
+
} else if (ch === "(") {
|
|
2976
|
+
escIntermediate = "(";
|
|
2977
|
+
parserState = "escape_charset";
|
|
2978
|
+
} else if (ch === ")") {
|
|
2979
|
+
escIntermediate = ")";
|
|
2980
|
+
parserState = "escape_charset";
|
|
2981
|
+
} else if (ch === "=") {
|
|
2202
2982
|
applicationKeypad = true;
|
|
2203
2983
|
parserState = "ground";
|
|
2984
|
+
emitEsc("=", "");
|
|
2204
2985
|
} else if (ch === ">") {
|
|
2205
2986
|
applicationKeypad = false;
|
|
2206
2987
|
parserState = "ground";
|
|
2988
|
+
emitEsc(">", "");
|
|
2207
2989
|
} else if (ch === "_") {
|
|
2208
2990
|
parserState = "apc";
|
|
2209
2991
|
apcBuf = "";
|
|
2992
|
+
apcStartRow = curY;
|
|
2993
|
+
apcStartCol = curX;
|
|
2994
|
+
apcReceivedLength = 0;
|
|
2995
|
+
apcOverflow = false;
|
|
2210
2996
|
} else parserState = "ground";
|
|
2211
2997
|
break;
|
|
2212
2998
|
case "escape_charset":
|
|
2213
2999
|
if (ch === "0") charsetG0 = true;
|
|
2214
3000
|
else charsetG0 = false;
|
|
2215
3001
|
parserState = "ground";
|
|
3002
|
+
emitEsc(ch, escIntermediate);
|
|
2216
3003
|
break;
|
|
2217
3004
|
case "escape_hash":
|
|
2218
3005
|
if (ch === "8") {
|
|
3006
|
+
const alignCell = emptyCell();
|
|
3007
|
+
alignCell.char = "E";
|
|
2219
3008
|
for (let r = 0; r < rows; r++) {
|
|
2220
3009
|
const row = grid[r];
|
|
2221
|
-
for (let c = 0; c < cols; c++)
|
|
2222
|
-
const cell = emptyCell();
|
|
2223
|
-
cell.char = "E";
|
|
2224
|
-
row[c] = cell;
|
|
2225
|
-
}
|
|
3010
|
+
for (let c = 0; c < cols; c++) row.setCellRaw(c, alignCell);
|
|
2226
3011
|
softWrapped[r] = false;
|
|
2227
3012
|
}
|
|
2228
3013
|
curX = 0;
|
|
2229
3014
|
curY = 0;
|
|
2230
3015
|
}
|
|
2231
3016
|
parserState = "ground";
|
|
3017
|
+
emitEsc(ch, escIntermediate);
|
|
2232
3018
|
break;
|
|
2233
3019
|
case "csi":
|
|
2234
3020
|
if (code >= 64 && code <= 126) {
|
|
@@ -2250,6 +3036,7 @@ function createScreen(options = {}) {
|
|
|
2250
3036
|
else if (paramPart.startsWith("=")) handleCSIEq(paramPart.substring(1), intermediatePart, ch);
|
|
2251
3037
|
else handleCSI(paramPart, intermediatePart, ch);
|
|
2252
3038
|
parserState = "ground";
|
|
3039
|
+
emitCsiEvent(paramPart, intermediatePart, ch);
|
|
2253
3040
|
} else if (escBuf.length >= 256) parserState = "ground";
|
|
2254
3041
|
else escBuf += ch;
|
|
2255
3042
|
break;
|
|
@@ -2257,23 +3044,32 @@ function createScreen(options = {}) {
|
|
|
2257
3044
|
if (code === 7) {
|
|
2258
3045
|
handleOSC(oscBuf);
|
|
2259
3046
|
parserState = "ground";
|
|
3047
|
+
emitOscEvent(oscBuf);
|
|
2260
3048
|
} else if (code === 27) parserState = "osc_st";
|
|
2261
3049
|
else if (oscBuf.length >= 4096) parserState = "ground";
|
|
2262
3050
|
else oscBuf += ch;
|
|
2263
3051
|
break;
|
|
2264
|
-
case "osc_st":
|
|
2265
|
-
|
|
3052
|
+
case "osc_st": {
|
|
3053
|
+
const oscComplete = ch === "\\";
|
|
3054
|
+
if (oscComplete) handleOSC(oscBuf);
|
|
2266
3055
|
parserState = "ground";
|
|
3056
|
+
if (oscComplete) emitOscEvent(oscBuf);
|
|
2267
3057
|
break;
|
|
3058
|
+
}
|
|
2268
3059
|
case "dcs":
|
|
2269
3060
|
if (code === 27) parserState = "dcs_st";
|
|
2270
3061
|
else if (code === 7) {
|
|
2271
|
-
|
|
3062
|
+
finishDCS();
|
|
2272
3063
|
parserState = "ground";
|
|
2273
|
-
} else
|
|
3064
|
+
} else appendDCS(ch);
|
|
2274
3065
|
break;
|
|
2275
3066
|
case "dcs_st":
|
|
2276
|
-
if (ch === "\\")
|
|
3067
|
+
if (ch === "\\") finishDCS();
|
|
3068
|
+
else {
|
|
3069
|
+
dcsBuf = "";
|
|
3070
|
+
dcsReceivedLength = 0;
|
|
3071
|
+
dcsOverflow = false;
|
|
3072
|
+
}
|
|
2277
3073
|
parserState = "ground";
|
|
2278
3074
|
break;
|
|
2279
3075
|
case "dcs_passthrough":
|
|
@@ -2282,16 +3078,27 @@ function createScreen(options = {}) {
|
|
|
2282
3078
|
case "apc":
|
|
2283
3079
|
if (code === 27) parserState = "apc_st";
|
|
2284
3080
|
else if (code === 7) {
|
|
2285
|
-
|
|
3081
|
+
finishAPC();
|
|
2286
3082
|
parserState = "ground";
|
|
2287
|
-
} else
|
|
3083
|
+
} else appendAPC(ch);
|
|
2288
3084
|
break;
|
|
2289
3085
|
case "apc_st":
|
|
2290
|
-
if (ch === "\\")
|
|
3086
|
+
if (ch === "\\") finishAPC();
|
|
3087
|
+
else {
|
|
3088
|
+
apcBuf = "";
|
|
3089
|
+
apcReceivedLength = 0;
|
|
3090
|
+
apcOverflow = false;
|
|
3091
|
+
}
|
|
2291
3092
|
parserState = "ground";
|
|
2292
3093
|
break;
|
|
2293
3094
|
}
|
|
2294
3095
|
}
|
|
3096
|
+
flushPrintRun();
|
|
3097
|
+
if (opListeners.size > 0) emitOp({
|
|
3098
|
+
type: "output",
|
|
3099
|
+
data
|
|
3100
|
+
});
|
|
3101
|
+
flushSignals();
|
|
2295
3102
|
}
|
|
2296
3103
|
/**
|
|
2297
3104
|
* Reconstruct logical lines from a grid, joining rows that were soft-wrapped.
|
|
@@ -2303,7 +3110,7 @@ function createScreen(options = {}) {
|
|
|
2303
3110
|
for (let r = 0; r < srcRows; r++) {
|
|
2304
3111
|
const row = srcGrid[r];
|
|
2305
3112
|
if (!row) continue;
|
|
2306
|
-
for (let c = 0; c < row.length; c++) currentLine.push(row
|
|
3113
|
+
for (let c = 0; c < row.length; c++) currentLine.push(row.getCellRaw(c));
|
|
2307
3114
|
if (srcSoftWrapped[r]) continue;
|
|
2308
3115
|
logical.push(currentLine);
|
|
2309
3116
|
currentLine = [];
|
|
@@ -2313,20 +3120,34 @@ function createScreen(options = {}) {
|
|
|
2313
3120
|
}
|
|
2314
3121
|
/**
|
|
2315
3122
|
* Re-wrap logical lines to a new column width, producing grid rows and soft-wrap flags.
|
|
3123
|
+
*
|
|
3124
|
+
* `track` names one position — a logical line index plus a cell offset
|
|
3125
|
+
* within that line (the cursor, expressed content-relative) — and the
|
|
3126
|
+
* returned `tracked` reports the output row/col it lands on, so `resize()`
|
|
3127
|
+
* can move the cursor WITH its logical line instead of leaving it at its
|
|
3128
|
+
* old absolute row. An offset at/beyond the trimmed line end (cursor in
|
|
3129
|
+
* trailing blanks or one past the last cell) lands after the last emitted
|
|
3130
|
+
* cell, advanced by the leftover distance, clamped to the row.
|
|
2316
3131
|
*/
|
|
2317
|
-
function rewrapLines(logicalLines, newCols) {
|
|
3132
|
+
function rewrapLines(logicalLines, newCols, track) {
|
|
2318
3133
|
const outRows = [];
|
|
2319
3134
|
const outWrapped = [];
|
|
2320
|
-
|
|
3135
|
+
let tracked = null;
|
|
3136
|
+
for (let li = 0; li < logicalLines.length; li++) {
|
|
3137
|
+
const line = logicalLines[li];
|
|
2321
3138
|
let lineLen = line.length;
|
|
2322
3139
|
while (lineLen > 0) {
|
|
2323
3140
|
const cell = line[lineLen - 1];
|
|
2324
|
-
if (cell
|
|
3141
|
+
if (cell.char === "" && !cell.wide) lineLen--;
|
|
2325
3142
|
else break;
|
|
2326
3143
|
}
|
|
2327
3144
|
if (lineLen === 0) {
|
|
2328
3145
|
outRows.push(makeRow(newCols));
|
|
2329
3146
|
outWrapped.push(false);
|
|
3147
|
+
if (track !== void 0 && track.line === li) tracked = {
|
|
3148
|
+
row: outRows.length - 1,
|
|
3149
|
+
col: Math.min(track.offset, newCols - 1)
|
|
3150
|
+
};
|
|
2330
3151
|
continue;
|
|
2331
3152
|
}
|
|
2332
3153
|
let pos = 0;
|
|
@@ -2336,34 +3157,65 @@ function createScreen(options = {}) {
|
|
|
2336
3157
|
while (col < newCols && pos < lineLen) {
|
|
2337
3158
|
const cell = line[pos];
|
|
2338
3159
|
if (cell.wide && col + 2 > newCols) break;
|
|
2339
|
-
|
|
3160
|
+
if (track !== void 0 && track.line === li && pos === track.offset) tracked = {
|
|
3161
|
+
row: outRows.length,
|
|
3162
|
+
col
|
|
3163
|
+
};
|
|
3164
|
+
row.setCellRaw(col, cell);
|
|
2340
3165
|
col++;
|
|
2341
3166
|
pos++;
|
|
2342
3167
|
}
|
|
2343
3168
|
const moreContent = pos < lineLen;
|
|
2344
3169
|
outRows.push(row);
|
|
2345
3170
|
outWrapped.push(moreContent);
|
|
3171
|
+
if (!moreContent && track !== void 0 && track.line === li && tracked === null) {
|
|
3172
|
+
const extra = track.offset - lineLen;
|
|
3173
|
+
tracked = {
|
|
3174
|
+
row: outRows.length - 1,
|
|
3175
|
+
col: Math.min(newCols - 1, col + extra)
|
|
3176
|
+
};
|
|
3177
|
+
}
|
|
2346
3178
|
}
|
|
2347
3179
|
}
|
|
2348
3180
|
return {
|
|
2349
3181
|
rows: outRows,
|
|
2350
|
-
wrapped: outWrapped
|
|
3182
|
+
wrapped: outWrapped,
|
|
3183
|
+
tracked
|
|
2351
3184
|
};
|
|
2352
3185
|
}
|
|
3186
|
+
/** True when a packed row has no printable content (all columns blank). */
|
|
3187
|
+
function packedRowIsBlank(row) {
|
|
3188
|
+
for (let c = 0; c < row.length; c++) if (!(row.getChar(c) === "" && !row.isWide(c))) return false;
|
|
3189
|
+
return true;
|
|
3190
|
+
}
|
|
2353
3191
|
/**
|
|
2354
3192
|
* Trim trailing empty rows from reflowed result, so they don't push content off the top
|
|
2355
3193
|
* when we take the last newRows rows.
|
|
2356
3194
|
*/
|
|
2357
3195
|
function trimTrailingEmptyRows(result) {
|
|
2358
|
-
while (result.rows.length > 1)
|
|
2359
|
-
result.rows.
|
|
2360
|
-
result.wrapped.
|
|
2361
|
-
|
|
3196
|
+
while (result.rows.length > 1) {
|
|
3197
|
+
const lastRow = result.rows[result.rows.length - 1];
|
|
3198
|
+
if (packedRowIsBlank(lastRow) && !result.wrapped[result.rows.length - 2]) {
|
|
3199
|
+
result.rows.pop();
|
|
3200
|
+
result.wrapped.pop();
|
|
3201
|
+
} else break;
|
|
3202
|
+
}
|
|
2362
3203
|
}
|
|
2363
3204
|
function resize(newCols, newRows) {
|
|
2364
|
-
|
|
3205
|
+
if (newCols === cols && newRows === rows) return;
|
|
3206
|
+
const activeWrappedPre = useAltScreen ? altSoftWrapped : mainSoftWrapped;
|
|
3207
|
+
const cursorRowPre = Math.min(Math.max(curY, 0), rows - 1);
|
|
3208
|
+
let cursorLineStart = cursorRowPre;
|
|
3209
|
+
while (cursorLineStart > 0 && activeWrappedPre[cursorLineStart - 1]) cursorLineStart--;
|
|
3210
|
+
let cursorLineIdx = 0;
|
|
3211
|
+
for (let r = 0; r < cursorLineStart; r++) if (!activeWrappedPre[r]) cursorLineIdx++;
|
|
3212
|
+
const cursorTrack = {
|
|
3213
|
+
line: cursorLineIdx,
|
|
3214
|
+
offset: (cursorRowPre - cursorLineStart) * cols + curX
|
|
3215
|
+
};
|
|
3216
|
+
const mainResult = rewrapLines(getLogicalLines(mainGrid, mainSoftWrapped, rows), newCols, useAltScreen ? void 0 : cursorTrack);
|
|
2365
3217
|
trimTrailingEmptyRows(mainResult);
|
|
2366
|
-
const altResult = rewrapLines(getLogicalLines(altGrid, altSoftWrapped, rows), newCols);
|
|
3218
|
+
const altResult = rewrapLines(getLogicalLines(altGrid, altSoftWrapped, rows), newCols, useAltScreen ? cursorTrack : void 0);
|
|
2367
3219
|
trimTrailingEmptyRows(altResult);
|
|
2368
3220
|
const newMain = makeGrid(newCols, newRows);
|
|
2369
3221
|
const newMainWrapped = new Array(newRows).fill(false);
|
|
@@ -2379,6 +3231,12 @@ function createScreen(options = {}) {
|
|
|
2379
3231
|
newAlt[r] = altResult.rows[altStartRow + r];
|
|
2380
3232
|
newAltWrapped[r] = altResult.wrapped[altStartRow + r];
|
|
2381
3233
|
}
|
|
3234
|
+
const activeResult = useAltScreen ? altResult : mainResult;
|
|
3235
|
+
const activeStartRow = useAltScreen ? altStartRow : mainStartRow;
|
|
3236
|
+
if (activeResult.tracked !== null) {
|
|
3237
|
+
curY = Math.min(activeResult.tracked.row, activeResult.rows.length) - activeStartRow;
|
|
3238
|
+
curX = activeResult.tracked.col;
|
|
3239
|
+
}
|
|
2382
3240
|
mainGrid = newMain;
|
|
2383
3241
|
altGrid = newAlt;
|
|
2384
3242
|
mainSoftWrapped = newMainWrapped;
|
|
@@ -2394,31 +3252,279 @@ function createScreen(options = {}) {
|
|
|
2394
3252
|
rightMargin = cols - 1;
|
|
2395
3253
|
if (newCols > oldCols) for (let i = Math.max(8, Math.ceil(oldCols / 8) * 8); i < newCols; i += 8) tabStops.add(i);
|
|
2396
3254
|
clampCursor();
|
|
3255
|
+
markAllDirty();
|
|
3256
|
+
if (opListeners.size > 0) emitOp({
|
|
3257
|
+
type: "resize",
|
|
3258
|
+
cols: newCols,
|
|
3259
|
+
rows: newRows
|
|
3260
|
+
});
|
|
3261
|
+
flushSignals();
|
|
3262
|
+
}
|
|
3263
|
+
function snapshot() {
|
|
3264
|
+
return {
|
|
3265
|
+
version: 1,
|
|
3266
|
+
cols,
|
|
3267
|
+
rows,
|
|
3268
|
+
scrollbackLimit,
|
|
3269
|
+
activeBuffer: useAltScreen ? "alt" : "main",
|
|
3270
|
+
main: {
|
|
3271
|
+
grid: cloneGridSnapshot(mainGrid),
|
|
3272
|
+
softWrapped: [...mainSoftWrapped]
|
|
3273
|
+
},
|
|
3274
|
+
alt: {
|
|
3275
|
+
grid: cloneGridSnapshot(altGrid),
|
|
3276
|
+
softWrapped: [...altSoftWrapped]
|
|
3277
|
+
},
|
|
3278
|
+
scrollback: cloneGridSnapshot(scrollback),
|
|
3279
|
+
scrollbackSoftWrapped: [...scrollbackSoftWrapped],
|
|
3280
|
+
cursor: {
|
|
3281
|
+
x: curX,
|
|
3282
|
+
y: curY,
|
|
3283
|
+
visible: curVisible,
|
|
3284
|
+
shape: cursorShape,
|
|
3285
|
+
blinking: cursorBlinking,
|
|
3286
|
+
savedX: savedCurX,
|
|
3287
|
+
savedY: savedCurY
|
|
3288
|
+
},
|
|
3289
|
+
savedState: {
|
|
3290
|
+
x: savedState.curX,
|
|
3291
|
+
y: savedState.curY,
|
|
3292
|
+
attrs: cloneAttrsSnapshot(savedState.attrs),
|
|
3293
|
+
originMode: savedState.originMode,
|
|
3294
|
+
autoWrap: savedState.autoWrap,
|
|
3295
|
+
charsetG0: savedState.charsetG0
|
|
3296
|
+
},
|
|
3297
|
+
attrs: cloneAttrsSnapshot(attrs),
|
|
3298
|
+
modes: {
|
|
3299
|
+
bracketedPaste,
|
|
3300
|
+
applicationCursor,
|
|
3301
|
+
applicationKeypad,
|
|
3302
|
+
autoWrap,
|
|
3303
|
+
mouseTracking,
|
|
3304
|
+
mouseTrackingMode,
|
|
3305
|
+
sgrMouse,
|
|
3306
|
+
focusTracking,
|
|
3307
|
+
origin: originMode,
|
|
3308
|
+
insert: insertMode,
|
|
3309
|
+
reverseVideo,
|
|
3310
|
+
syncOutput,
|
|
3311
|
+
kittyKeyboardFlags,
|
|
3312
|
+
kittyKeyboardStack: [...kittyKeyboardStack],
|
|
3313
|
+
kittyGraphics: hasKittyGraphics,
|
|
3314
|
+
colorSchemeReporting,
|
|
3315
|
+
decColumn: decColumnMode,
|
|
3316
|
+
altScroll: altScrollMode,
|
|
3317
|
+
utf8Mouse: utf8MouseMode
|
|
3318
|
+
},
|
|
3319
|
+
margins: {
|
|
3320
|
+
scrollTop,
|
|
3321
|
+
scrollBottom,
|
|
3322
|
+
leftRight: leftRightMarginMode,
|
|
3323
|
+
left: leftMargin,
|
|
3324
|
+
right: rightMargin
|
|
3325
|
+
},
|
|
3326
|
+
colors: {
|
|
3327
|
+
current: snapshotColorState(),
|
|
3328
|
+
stack: colorStack.map(cloneColorStateSnapshot)
|
|
3329
|
+
},
|
|
3330
|
+
tabStops: [...tabStops].sort((a, b) => a - b),
|
|
3331
|
+
title,
|
|
3332
|
+
clipboard,
|
|
3333
|
+
cwd,
|
|
3334
|
+
notifications: [...notifications],
|
|
3335
|
+
viewportOffset,
|
|
3336
|
+
parser: {
|
|
3337
|
+
state: parserState,
|
|
3338
|
+
esc: escBuf,
|
|
3339
|
+
osc: oscBuf,
|
|
3340
|
+
dcs: dcsBuf,
|
|
3341
|
+
dcsStart: {
|
|
3342
|
+
row: dcsStartRow,
|
|
3343
|
+
col: dcsStartCol
|
|
3344
|
+
},
|
|
3345
|
+
dcsReceivedLength,
|
|
3346
|
+
dcsOverflow,
|
|
3347
|
+
apc: apcBuf,
|
|
3348
|
+
apcStart: {
|
|
3349
|
+
row: apcStartRow,
|
|
3350
|
+
col: apcStartCol
|
|
3351
|
+
},
|
|
3352
|
+
apcReceivedLength,
|
|
3353
|
+
apcOverflow,
|
|
3354
|
+
utf8PendingBytes: [...utf8PendingBytes]
|
|
3355
|
+
},
|
|
3356
|
+
unicode: {
|
|
3357
|
+
charsetG0,
|
|
3358
|
+
lastChar,
|
|
3359
|
+
pendingRegionalIndicator,
|
|
3360
|
+
afterZWJ
|
|
3361
|
+
}
|
|
3362
|
+
};
|
|
3363
|
+
}
|
|
3364
|
+
function restore(snapshotValue) {
|
|
3365
|
+
assertSnapshot(snapshotValue);
|
|
3366
|
+
cols = snapshotValue.cols;
|
|
3367
|
+
rows = snapshotValue.rows;
|
|
3368
|
+
scrollbackLimit = snapshotValue.scrollbackLimit;
|
|
3369
|
+
mainGrid = restoreGridSnapshot(snapshotValue.main.grid, rows, cols);
|
|
3370
|
+
altGrid = restoreGridSnapshot(snapshotValue.alt.grid, rows, cols);
|
|
3371
|
+
scrollback = restoreScrollbackSnapshot(snapshotValue.scrollback);
|
|
3372
|
+
scrollbackSoftWrapped = snapshotValue.scrollbackSoftWrapped ? [...snapshotValue.scrollbackSoftWrapped] : new Array(scrollback.length).fill(false);
|
|
3373
|
+
mainSoftWrapped = [...snapshotValue.main.softWrapped];
|
|
3374
|
+
altSoftWrapped = [...snapshotValue.alt.softWrapped];
|
|
3375
|
+
useAltScreen = snapshotValue.activeBuffer === "alt";
|
|
3376
|
+
grid = useAltScreen ? altGrid : mainGrid;
|
|
3377
|
+
softWrapped = useAltScreen ? altSoftWrapped : mainSoftWrapped;
|
|
3378
|
+
curX = snapshotValue.cursor.x;
|
|
3379
|
+
curY = snapshotValue.cursor.y;
|
|
3380
|
+
curVisible = snapshotValue.cursor.visible;
|
|
3381
|
+
cursorShape = snapshotValue.cursor.shape;
|
|
3382
|
+
cursorBlinking = snapshotValue.cursor.blinking;
|
|
3383
|
+
savedCurX = snapshotValue.cursor.savedX;
|
|
3384
|
+
savedCurY = snapshotValue.cursor.savedY;
|
|
3385
|
+
savedState = {
|
|
3386
|
+
curX: snapshotValue.savedState.x,
|
|
3387
|
+
curY: snapshotValue.savedState.y,
|
|
3388
|
+
attrs: cloneAttrsSnapshot(snapshotValue.savedState.attrs),
|
|
3389
|
+
originMode: snapshotValue.savedState.originMode,
|
|
3390
|
+
autoWrap: snapshotValue.savedState.autoWrap,
|
|
3391
|
+
charsetG0: snapshotValue.savedState.charsetG0
|
|
3392
|
+
};
|
|
3393
|
+
attrs = cloneAttrsSnapshot(snapshotValue.attrs);
|
|
3394
|
+
bracketedPaste = snapshotValue.modes.bracketedPaste;
|
|
3395
|
+
applicationCursor = snapshotValue.modes.applicationCursor;
|
|
3396
|
+
applicationKeypad = snapshotValue.modes.applicationKeypad;
|
|
3397
|
+
autoWrap = snapshotValue.modes.autoWrap;
|
|
3398
|
+
mouseTracking = snapshotValue.modes.mouseTracking;
|
|
3399
|
+
mouseTrackingMode = snapshotValue.modes.mouseTrackingMode;
|
|
3400
|
+
sgrMouse = snapshotValue.modes.sgrMouse;
|
|
3401
|
+
focusTracking = snapshotValue.modes.focusTracking;
|
|
3402
|
+
originMode = snapshotValue.modes.origin;
|
|
3403
|
+
insertMode = snapshotValue.modes.insert;
|
|
3404
|
+
reverseVideo = snapshotValue.modes.reverseVideo;
|
|
3405
|
+
syncOutput = snapshotValue.modes.syncOutput;
|
|
3406
|
+
kittyKeyboardFlags = snapshotValue.modes.kittyKeyboardFlags;
|
|
3407
|
+
kittyKeyboardStack = [...snapshotValue.modes.kittyKeyboardStack];
|
|
3408
|
+
hasKittyGraphics = snapshotValue.modes.kittyGraphics;
|
|
3409
|
+
colorSchemeReporting = snapshotValue.modes.colorSchemeReporting;
|
|
3410
|
+
decColumnMode = snapshotValue.modes.decColumn;
|
|
3411
|
+
altScrollMode = snapshotValue.modes.altScroll;
|
|
3412
|
+
utf8MouseMode = snapshotValue.modes.utf8Mouse;
|
|
3413
|
+
scrollTop = snapshotValue.margins.scrollTop;
|
|
3414
|
+
scrollBottom = snapshotValue.margins.scrollBottom;
|
|
3415
|
+
leftRightMarginMode = snapshotValue.margins.leftRight;
|
|
3416
|
+
leftMargin = snapshotValue.margins.left;
|
|
3417
|
+
rightMargin = snapshotValue.margins.right;
|
|
3418
|
+
restoreColorState(snapshotValue.colors.current);
|
|
3419
|
+
colorStack.length = 0;
|
|
3420
|
+
colorStack.push(...snapshotValue.colors.stack.map(cloneColorStateSnapshot));
|
|
3421
|
+
tabStops = new Set(snapshotValue.tabStops);
|
|
3422
|
+
title = snapshotValue.title;
|
|
3423
|
+
clipboard = snapshotValue.clipboard;
|
|
3424
|
+
cwd = snapshotValue.cwd;
|
|
3425
|
+
notifications = [...snapshotValue.notifications];
|
|
3426
|
+
viewportOffset = Math.max(0, Math.min(scrollback.length, snapshotValue.viewportOffset));
|
|
3427
|
+
parserState = snapshotValue.parser.state;
|
|
3428
|
+
escBuf = snapshotValue.parser.esc;
|
|
3429
|
+
oscBuf = snapshotValue.parser.osc;
|
|
3430
|
+
dcsBuf = snapshotValue.parser.dcs;
|
|
3431
|
+
dcsStartRow = snapshotValue.parser.dcsStart.row;
|
|
3432
|
+
dcsStartCol = snapshotValue.parser.dcsStart.col;
|
|
3433
|
+
dcsReceivedLength = snapshotValue.parser.dcsReceivedLength ?? dcsBuf.length;
|
|
3434
|
+
dcsOverflow = snapshotValue.parser.dcsOverflow ?? false;
|
|
3435
|
+
apcBuf = snapshotValue.parser.apc;
|
|
3436
|
+
apcStartRow = snapshotValue.parser.apcStart?.row ?? curY;
|
|
3437
|
+
apcStartCol = snapshotValue.parser.apcStart?.col ?? curX;
|
|
3438
|
+
apcReceivedLength = snapshotValue.parser.apcReceivedLength ?? apcBuf.length;
|
|
3439
|
+
apcOverflow = snapshotValue.parser.apcOverflow ?? false;
|
|
3440
|
+
utf8PendingBytes = [...snapshotValue.parser.utf8PendingBytes];
|
|
3441
|
+
charsetG0 = snapshotValue.unicode.charsetG0;
|
|
3442
|
+
lastChar = snapshotValue.unicode.lastChar;
|
|
3443
|
+
pendingRegionalIndicator = snapshotValue.unicode.pendingRegionalIndicator;
|
|
3444
|
+
afterZWJ = snapshotValue.unicode.afterZWJ;
|
|
3445
|
+
textScale = 1;
|
|
3446
|
+
fontSize = 12;
|
|
3447
|
+
fontWindowSize = 12;
|
|
3448
|
+
locale = "en_US.UTF-8";
|
|
3449
|
+
advancedClipboard = "";
|
|
3450
|
+
semanticZones = [];
|
|
3451
|
+
hasSixel = false;
|
|
3452
|
+
sixelImages = [];
|
|
3453
|
+
if (curX < 0) curX = 0;
|
|
3454
|
+
if (curX > cols) curX = cols;
|
|
3455
|
+
if (curY < 0) curY = 0;
|
|
3456
|
+
if (curY >= rows) curY = rows - 1;
|
|
3457
|
+
markAllDirty();
|
|
3458
|
+
dirtyScrolled = 0;
|
|
3459
|
+
if (sigDamageActive) sigDirtyScrolled = 0;
|
|
3460
|
+
trimmedRowCount = 0;
|
|
3461
|
+
flushSignals();
|
|
2397
3462
|
}
|
|
2398
3463
|
function getCell(row, col) {
|
|
2399
3464
|
const r = grid[row];
|
|
2400
3465
|
if (!r || col >= cols) return emptyCell();
|
|
2401
|
-
return
|
|
3466
|
+
return stripCellColorIndex(r.getCellRaw(col));
|
|
2402
3467
|
}
|
|
2403
|
-
function
|
|
3468
|
+
function getRow(row) {
|
|
2404
3469
|
const r = grid[row];
|
|
2405
|
-
if (!r) return makeRow(cols);
|
|
2406
|
-
return r.
|
|
3470
|
+
if (!r) return makeRow(cols).toCells();
|
|
3471
|
+
return r.toCells().map(stripCellColorIndex);
|
|
2407
3472
|
}
|
|
2408
3473
|
function getText() {
|
|
2409
3474
|
const lines = [];
|
|
2410
3475
|
for (let r = 0; r < rows; r++) lines.push(rowToString(grid[r]));
|
|
2411
3476
|
return lines.join("\n");
|
|
2412
3477
|
}
|
|
3478
|
+
function getScrollbackText() {
|
|
3479
|
+
const lines = [];
|
|
3480
|
+
for (const row of scrollback) lines.push(rowToString(row));
|
|
3481
|
+
return lines.join("\n");
|
|
3482
|
+
}
|
|
3483
|
+
function totalRows() {
|
|
3484
|
+
return scrollback.length + rows;
|
|
3485
|
+
}
|
|
3486
|
+
function screenRows() {
|
|
3487
|
+
return rows;
|
|
3488
|
+
}
|
|
3489
|
+
function viewportTop() {
|
|
3490
|
+
return scrollback.length - viewportOffset;
|
|
3491
|
+
}
|
|
3492
|
+
function getRowAbsolute(row) {
|
|
3493
|
+
if (row < 0 || row >= scrollback.length + rows) return makeRow(cols).toCells();
|
|
3494
|
+
return (row < scrollback.length ? scrollback[row] : grid[row - scrollback.length]).toCells().map(stripCellColorIndex);
|
|
3495
|
+
}
|
|
3496
|
+
function firstRetainedRow() {
|
|
3497
|
+
return trimmedRowCount;
|
|
3498
|
+
}
|
|
3499
|
+
function takeDirty() {
|
|
3500
|
+
const cursorChanged = curX !== dirtyCursor.x || curY !== dirtyCursor.y || curVisible !== dirtyCursor.visible || cursorShape !== dirtyCursor.shape || cursorBlinking !== dirtyCursor.blinking;
|
|
3501
|
+
const region = {
|
|
3502
|
+
rows: dirtyAll ? "all" : dirtyRows,
|
|
3503
|
+
cursor: cursorChanged,
|
|
3504
|
+
scrolled: dirtyScrolled
|
|
3505
|
+
};
|
|
3506
|
+
dirtyRows = /* @__PURE__ */ new Set();
|
|
3507
|
+
dirtyAll = false;
|
|
3508
|
+
dirtyScrolled = 0;
|
|
3509
|
+
dirtyLastAbs = -1;
|
|
3510
|
+
dirtyCursor = {
|
|
3511
|
+
x: curX,
|
|
3512
|
+
y: curY,
|
|
3513
|
+
visible: curVisible,
|
|
3514
|
+
shape: cursorShape,
|
|
3515
|
+
blinking: cursorBlinking
|
|
3516
|
+
};
|
|
3517
|
+
return region;
|
|
3518
|
+
}
|
|
2413
3519
|
function rowToString(row) {
|
|
2414
3520
|
let line = "";
|
|
2415
3521
|
for (let i = 0; i < row.length; i++) {
|
|
2416
|
-
const
|
|
2417
|
-
if (
|
|
2418
|
-
else if (
|
|
2419
|
-
if (i > 0 && row
|
|
3522
|
+
const ch = row.getChar(i);
|
|
3523
|
+
if (row.isWide(i)) line += ch;
|
|
3524
|
+
else if (ch === "") {
|
|
3525
|
+
if (i > 0 && row.isWide(i - 1)) continue;
|
|
2420
3526
|
line += " ";
|
|
2421
|
-
} else line +=
|
|
3527
|
+
} else line += ch;
|
|
2422
3528
|
}
|
|
2423
3529
|
return line.replace(/\s+$/, "");
|
|
2424
3530
|
}
|
|
@@ -2431,10 +3537,10 @@ function createScreen(options = {}) {
|
|
|
2431
3537
|
const colEnd = row === endRow ? endCol : cols;
|
|
2432
3538
|
let line = "";
|
|
2433
3539
|
for (let col = colStart; col < colEnd; col++) {
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
if (
|
|
2437
|
-
line +=
|
|
3540
|
+
if (col < 0 || col >= r.length) continue;
|
|
3541
|
+
const ch = r.getChar(col);
|
|
3542
|
+
if (ch === "" && col > 0 && r.isWide(col - 1)) continue;
|
|
3543
|
+
line += ch || " ";
|
|
2438
3544
|
}
|
|
2439
3545
|
parts.push(line.replace(/\s+$/, ""));
|
|
2440
3546
|
}
|
|
@@ -2464,6 +3570,176 @@ function createScreen(options = {}) {
|
|
|
2464
3570
|
default: return false;
|
|
2465
3571
|
}
|
|
2466
3572
|
}
|
|
3573
|
+
function apply(op) {
|
|
3574
|
+
switch (op.type) {
|
|
3575
|
+
case "output":
|
|
3576
|
+
process(typeof op.data === "string" ? encoder.encode(op.data) : op.data);
|
|
3577
|
+
return;
|
|
3578
|
+
case "resize":
|
|
3579
|
+
resize(op.cols, op.rows);
|
|
3580
|
+
return;
|
|
3581
|
+
default: throw new Error(`vterm.apply: unhandled op ${JSON.stringify(op)}`);
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
function tapOps(listener) {
|
|
3585
|
+
opListeners.add(listener);
|
|
3586
|
+
return () => {
|
|
3587
|
+
opListeners.delete(listener);
|
|
3588
|
+
};
|
|
3589
|
+
}
|
|
3590
|
+
function tapParser(listener) {
|
|
3591
|
+
parserListeners.add(listener);
|
|
3592
|
+
return () => {
|
|
3593
|
+
parserListeners.delete(listener);
|
|
3594
|
+
};
|
|
3595
|
+
}
|
|
3596
|
+
const stateFlushSignals = [];
|
|
3597
|
+
/**
|
|
3598
|
+
* A state signal (title/modes/cursor/size): `get()` reads live; `subscribe` captures the current
|
|
3599
|
+
* value as its baseline (so only later changes deliver) and `flush()` delivers once per flush
|
|
3600
|
+
* boundary iff the value changed. Registered in `stateFlushSignals` so `flushSignals` visits it.
|
|
3601
|
+
*/
|
|
3602
|
+
function makeStateSignal(read, eq) {
|
|
3603
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
3604
|
+
let last = read();
|
|
3605
|
+
const signal = {
|
|
3606
|
+
get: read,
|
|
3607
|
+
subscribe(listener) {
|
|
3608
|
+
if (listeners.size === 0) last = read();
|
|
3609
|
+
listeners.add(listener);
|
|
3610
|
+
return () => {
|
|
3611
|
+
listeners.delete(listener);
|
|
3612
|
+
};
|
|
3613
|
+
},
|
|
3614
|
+
flush() {
|
|
3615
|
+
if (listeners.size === 0) return;
|
|
3616
|
+
const current = read();
|
|
3617
|
+
if (eq(current, last)) return;
|
|
3618
|
+
last = current;
|
|
3619
|
+
for (const listener of listeners) listener(current);
|
|
3620
|
+
}
|
|
3621
|
+
};
|
|
3622
|
+
stateFlushSignals.push(signal);
|
|
3623
|
+
return signal;
|
|
3624
|
+
}
|
|
3625
|
+
function modesNow() {
|
|
3626
|
+
return {
|
|
3627
|
+
altScreen: useAltScreen,
|
|
3628
|
+
cursorVisible: curVisible,
|
|
3629
|
+
bracketedPaste,
|
|
3630
|
+
applicationCursor,
|
|
3631
|
+
applicationKeypad,
|
|
3632
|
+
autoWrap,
|
|
3633
|
+
mouseTracking,
|
|
3634
|
+
mouseTrackingMode,
|
|
3635
|
+
sgrMouse,
|
|
3636
|
+
utf8Mouse: utf8MouseMode,
|
|
3637
|
+
focusTracking,
|
|
3638
|
+
originMode,
|
|
3639
|
+
insertMode,
|
|
3640
|
+
reverseVideo,
|
|
3641
|
+
syncOutput,
|
|
3642
|
+
leftRightMargin: leftRightMarginMode,
|
|
3643
|
+
colorSchemeReporting,
|
|
3644
|
+
kittyKeyboard: kittyKeyboardFlags > 0,
|
|
3645
|
+
kittyGraphics: hasKittyGraphics,
|
|
3646
|
+
sixel: hasSixel
|
|
3647
|
+
};
|
|
3648
|
+
}
|
|
3649
|
+
function modesEq(a, b) {
|
|
3650
|
+
return a.altScreen === b.altScreen && a.cursorVisible === b.cursorVisible && a.bracketedPaste === b.bracketedPaste && a.applicationCursor === b.applicationCursor && a.applicationKeypad === b.applicationKeypad && a.autoWrap === b.autoWrap && a.mouseTracking === b.mouseTracking && a.mouseTrackingMode === b.mouseTrackingMode && a.sgrMouse === b.sgrMouse && a.utf8Mouse === b.utf8Mouse && a.focusTracking === b.focusTracking && a.originMode === b.originMode && a.insertMode === b.insertMode && a.reverseVideo === b.reverseVideo && a.syncOutput === b.syncOutput && a.leftRightMargin === b.leftRightMargin && a.colorSchemeReporting === b.colorSchemeReporting && a.kittyKeyboard === b.kittyKeyboard && a.kittyGraphics === b.kittyGraphics && a.sixel === b.sixel;
|
|
3651
|
+
}
|
|
3652
|
+
const damageListeners = /* @__PURE__ */ new Set();
|
|
3653
|
+
let damageLast = {
|
|
3654
|
+
rows: /* @__PURE__ */ new Set(),
|
|
3655
|
+
cursor: false,
|
|
3656
|
+
scrolled: 0
|
|
3657
|
+
};
|
|
3658
|
+
let damageSignal;
|
|
3659
|
+
function getDamageSignal() {
|
|
3660
|
+
return damageSignal ??= {
|
|
3661
|
+
get: () => damageLast,
|
|
3662
|
+
subscribe(listener) {
|
|
3663
|
+
if (damageListeners.size === 0) {
|
|
3664
|
+
sigDamageActive = true;
|
|
3665
|
+
sigDirtyAll = false;
|
|
3666
|
+
sigDirtyRows = /* @__PURE__ */ new Set();
|
|
3667
|
+
sigDirtyScrolled = 0;
|
|
3668
|
+
sigDirtyLastAbs = -1;
|
|
3669
|
+
sigDirtyCursor = {
|
|
3670
|
+
x: curX,
|
|
3671
|
+
y: curY,
|
|
3672
|
+
visible: curVisible,
|
|
3673
|
+
shape: cursorShape,
|
|
3674
|
+
blinking: cursorBlinking
|
|
3675
|
+
};
|
|
3676
|
+
}
|
|
3677
|
+
damageListeners.add(listener);
|
|
3678
|
+
return () => {
|
|
3679
|
+
damageListeners.delete(listener);
|
|
3680
|
+
if (damageListeners.size === 0) sigDamageActive = false;
|
|
3681
|
+
};
|
|
3682
|
+
}
|
|
3683
|
+
};
|
|
3684
|
+
}
|
|
3685
|
+
function emitDamageBatch() {
|
|
3686
|
+
const cursorChanged = curX !== sigDirtyCursor.x || curY !== sigDirtyCursor.y || curVisible !== sigDirtyCursor.visible || cursorShape !== sigDirtyCursor.shape || cursorBlinking !== sigDirtyCursor.blinking;
|
|
3687
|
+
if (!sigDirtyAll && sigDirtyRows.size === 0 && sigDirtyScrolled === 0 && !cursorChanged) return;
|
|
3688
|
+
const region = {
|
|
3689
|
+
rows: sigDirtyAll ? "all" : sigDirtyRows,
|
|
3690
|
+
cursor: cursorChanged,
|
|
3691
|
+
scrolled: sigDirtyScrolled
|
|
3692
|
+
};
|
|
3693
|
+
sigDirtyRows = /* @__PURE__ */ new Set();
|
|
3694
|
+
sigDirtyAll = false;
|
|
3695
|
+
sigDirtyScrolled = 0;
|
|
3696
|
+
sigDirtyLastAbs = -1;
|
|
3697
|
+
sigDirtyCursor = {
|
|
3698
|
+
x: curX,
|
|
3699
|
+
y: curY,
|
|
3700
|
+
visible: curVisible,
|
|
3701
|
+
shape: cursorShape,
|
|
3702
|
+
blinking: cursorBlinking
|
|
3703
|
+
};
|
|
3704
|
+
damageLast = region;
|
|
3705
|
+
for (const listener of damageListeners) listener(region);
|
|
3706
|
+
}
|
|
3707
|
+
let signalsFacade;
|
|
3708
|
+
let titleSignal;
|
|
3709
|
+
let modesSignal;
|
|
3710
|
+
let cursorSignal;
|
|
3711
|
+
let sizeSignal;
|
|
3712
|
+
function getSignals() {
|
|
3713
|
+
return signalsFacade ??= {
|
|
3714
|
+
get title$() {
|
|
3715
|
+
return titleSignal ??= makeStateSignal(() => title, (a, b) => a === b);
|
|
3716
|
+
},
|
|
3717
|
+
get modes$() {
|
|
3718
|
+
return modesSignal ??= makeStateSignal(modesNow, modesEq);
|
|
3719
|
+
},
|
|
3720
|
+
get cursor$() {
|
|
3721
|
+
return cursorSignal ??= makeStateSignal(() => ({
|
|
3722
|
+
col: curX,
|
|
3723
|
+
row: curY
|
|
3724
|
+
}), (a, b) => a.col === b.col && a.row === b.row);
|
|
3725
|
+
},
|
|
3726
|
+
get size$() {
|
|
3727
|
+
return sizeSignal ??= makeStateSignal(() => ({
|
|
3728
|
+
cols,
|
|
3729
|
+
rows
|
|
3730
|
+
}), (a, b) => a.cols === b.cols && a.rows === b.rows);
|
|
3731
|
+
},
|
|
3732
|
+
get damage$() {
|
|
3733
|
+
return getDamageSignal();
|
|
3734
|
+
}
|
|
3735
|
+
};
|
|
3736
|
+
}
|
|
3737
|
+
/** Coalesced end-of-call emission for every accessed signal — the flush boundary (§4). */
|
|
3738
|
+
function flushSignals() {
|
|
3739
|
+
if (signalsFacade === void 0) return;
|
|
3740
|
+
for (const signal of stateFlushSignals) signal.flush();
|
|
3741
|
+
if (sigDamageActive) emitDamageBatch();
|
|
3742
|
+
}
|
|
2467
3743
|
return {
|
|
2468
3744
|
get cols() {
|
|
2469
3745
|
return cols;
|
|
@@ -2471,16 +3747,31 @@ function createScreen(options = {}) {
|
|
|
2471
3747
|
get rows() {
|
|
2472
3748
|
return rows;
|
|
2473
3749
|
},
|
|
3750
|
+
get signals() {
|
|
3751
|
+
return getSignals();
|
|
3752
|
+
},
|
|
2474
3753
|
process,
|
|
2475
3754
|
resize,
|
|
3755
|
+
apply,
|
|
3756
|
+
tapOps,
|
|
3757
|
+
tapParser,
|
|
2476
3758
|
reset: fullReset,
|
|
3759
|
+
snapshot,
|
|
3760
|
+
restore,
|
|
2477
3761
|
getCell,
|
|
2478
|
-
|
|
3762
|
+
getRow,
|
|
2479
3763
|
getText,
|
|
3764
|
+
getScrollbackText,
|
|
2480
3765
|
getTextRange,
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
3766
|
+
totalRows,
|
|
3767
|
+
screenRows,
|
|
3768
|
+
viewportTop,
|
|
3769
|
+
getRowAbsolute,
|
|
3770
|
+
firstRetainedRow,
|
|
3771
|
+
takeDirty,
|
|
3772
|
+
getCursor: () => ({
|
|
3773
|
+
col: curX,
|
|
3774
|
+
row: curY
|
|
2484
3775
|
}),
|
|
2485
3776
|
getCursorVisible: () => curVisible,
|
|
2486
3777
|
getCursorShape: () => cursorShape,
|
|
@@ -2494,8 +3785,888 @@ function createScreen(options = {}) {
|
|
|
2494
3785
|
getViewportOffset: () => viewportOffset,
|
|
2495
3786
|
scrollViewport,
|
|
2496
3787
|
getSemanticZones: () => semanticZones.map((z) => ({ ...z })),
|
|
2497
|
-
getSixelImages: () => sixelImages.map((img) => ({ ...img }))
|
|
3788
|
+
getSixelImages: () => sixelImages.map((img) => ({ ...img })),
|
|
3789
|
+
serialize: (options) => serializeSnapshot(snapshot(), options)
|
|
3790
|
+
};
|
|
3791
|
+
}
|
|
3792
|
+
const SERIALIZE_DEFAULT_PEN = Object.freeze({
|
|
3793
|
+
fg: null,
|
|
3794
|
+
bg: null,
|
|
3795
|
+
bold: false,
|
|
3796
|
+
faint: false,
|
|
3797
|
+
italic: false,
|
|
3798
|
+
underline: "none",
|
|
3799
|
+
underlineColor: null,
|
|
3800
|
+
overline: false,
|
|
3801
|
+
strikethrough: false,
|
|
3802
|
+
inverse: false,
|
|
3803
|
+
hidden: false,
|
|
3804
|
+
blink: false
|
|
3805
|
+
});
|
|
3806
|
+
const SERIALIZE_BOOL_CODES = [
|
|
3807
|
+
["bold", "1"],
|
|
3808
|
+
["faint", "2"],
|
|
3809
|
+
["italic", "3"],
|
|
3810
|
+
["blink", "5"],
|
|
3811
|
+
["inverse", "7"],
|
|
3812
|
+
["hidden", "8"],
|
|
3813
|
+
["strikethrough", "9"],
|
|
3814
|
+
["overline", "53"]
|
|
3815
|
+
];
|
|
3816
|
+
/** Colon-subparam underline forms — the lossless spellings vterm itself parses. */
|
|
3817
|
+
function serializeUnderlineParam(style) {
|
|
3818
|
+
switch (style) {
|
|
3819
|
+
case "single": return "4";
|
|
3820
|
+
case "double": return "4:2";
|
|
3821
|
+
case "curly": return "4:3";
|
|
3822
|
+
case "dotted": return "4:4";
|
|
3823
|
+
case "dashed": return "4:5";
|
|
3824
|
+
case "none": return "24";
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
function serializeColorEq(a, b) {
|
|
3828
|
+
if (a === null || b === null) return a === b;
|
|
3829
|
+
return a.r === b.r && a.g === b.g && a.b === b.b && a.index === b.index;
|
|
3830
|
+
}
|
|
3831
|
+
function serializePenOf(source) {
|
|
3832
|
+
return {
|
|
3833
|
+
fg: source.fg,
|
|
3834
|
+
bg: source.bg,
|
|
3835
|
+
bold: source.bold,
|
|
3836
|
+
faint: source.faint,
|
|
3837
|
+
italic: source.italic,
|
|
3838
|
+
underline: source.underline,
|
|
3839
|
+
underlineColor: source.underlineColor,
|
|
3840
|
+
overline: source.overline,
|
|
3841
|
+
strikethrough: source.strikethrough,
|
|
3842
|
+
inverse: source.inverse,
|
|
3843
|
+
hidden: source.hidden,
|
|
3844
|
+
blink: source.blink
|
|
3845
|
+
};
|
|
3846
|
+
}
|
|
3847
|
+
function serializePenEq(a, b) {
|
|
3848
|
+
return a.bold === b.bold && a.faint === b.faint && a.italic === b.italic && a.blink === b.blink && a.inverse === b.inverse && a.hidden === b.hidden && a.strikethrough === b.strikethrough && a.overline === b.overline && a.underline === b.underline && serializeColorEq(a.fg, b.fg) && serializeColorEq(a.bg, b.bg) && serializeColorEq(a.underlineColor, b.underlineColor);
|
|
3849
|
+
}
|
|
3850
|
+
function serializePenIsDefault(pen) {
|
|
3851
|
+
return serializePenEq(pen, SERIALIZE_DEFAULT_PEN);
|
|
3852
|
+
}
|
|
3853
|
+
function serializeColorParams(prefix, defaultCode, color) {
|
|
3854
|
+
if (color === null) return defaultCode;
|
|
3855
|
+
const idx = color.index;
|
|
3856
|
+
if (idx !== void 0 && idx >= 0 && idx <= 255) {
|
|
3857
|
+
if (prefix === "38") {
|
|
3858
|
+
if (idx <= 7) return String(30 + idx);
|
|
3859
|
+
if (idx <= 15) return String(90 + (idx - 8));
|
|
3860
|
+
return `38;5;${String(idx)}`;
|
|
3861
|
+
}
|
|
3862
|
+
if (prefix === "48") {
|
|
3863
|
+
if (idx <= 7) return String(40 + idx);
|
|
3864
|
+
if (idx <= 15) return String(100 + (idx - 8));
|
|
3865
|
+
return `48;5;${String(idx)}`;
|
|
3866
|
+
}
|
|
3867
|
+
return `${prefix};5;${String(idx)}`;
|
|
3868
|
+
}
|
|
3869
|
+
return `${prefix};2;${String(color.r)};${String(color.g)};${String(color.b)}`;
|
|
3870
|
+
}
|
|
3871
|
+
/**
|
|
3872
|
+
* Reset-on-removal pen diff (the tmux `grid_string_cells` model): emit `SGR 0`
|
|
3873
|
+
* iff a boolean attribute turns off or the underline changes away from a
|
|
3874
|
+
* non-none style, then re-add deltas from the reset base. This makes the
|
|
3875
|
+
* `SGR 22` bold/faint coupling and the underline-off classes structurally
|
|
3876
|
+
* impossible, at a few bytes' cost. The leading `0` is emitted only on an
|
|
3877
|
+
* actual reset (not whenever the previous pen happens to be default) so
|
|
3878
|
+
* output stays byte-deterministic and idempotence-friendly.
|
|
3879
|
+
*/
|
|
3880
|
+
function serializeDiffPen(prev, cur) {
|
|
3881
|
+
let boolOff = false;
|
|
3882
|
+
for (const [key] of SERIALIZE_BOOL_CODES) if (prev[key] === true && cur[key] === false) {
|
|
3883
|
+
boolOff = true;
|
|
3884
|
+
break;
|
|
3885
|
+
}
|
|
3886
|
+
const underlineChanged = prev.underline !== cur.underline && prev.underline !== "none";
|
|
3887
|
+
const reset = boolOff || underlineChanged;
|
|
3888
|
+
const base = reset ? SERIALIZE_DEFAULT_PEN : prev;
|
|
3889
|
+
const out = reset ? ["0"] : [];
|
|
3890
|
+
for (const [key, code] of SERIALIZE_BOOL_CODES) if (cur[key] === true && base[key] === false) out.push(code);
|
|
3891
|
+
if (cur.underline !== base.underline) out.push(serializeUnderlineParam(cur.underline));
|
|
3892
|
+
if (!serializeColorEq(cur.fg, base.fg)) out.push(serializeColorParams("38", "39", cur.fg));
|
|
3893
|
+
if (!serializeColorEq(cur.bg, base.bg)) out.push(serializeColorParams("48", "49", cur.bg));
|
|
3894
|
+
if (!serializeColorEq(cur.underlineColor, base.underlineColor)) out.push(serializeColorParams("58", "59", cur.underlineColor));
|
|
3895
|
+
return out;
|
|
3896
|
+
}
|
|
3897
|
+
/**
|
|
3898
|
+
* Last cell worth emitting: trailing UNWRITTEN cells (`char === ""`, default
|
|
3899
|
+
* pen, no url) are trimmed — the receiver's cleared screen already has them.
|
|
3900
|
+
* Erased-with-bg trailing cells (BCE) are kept and reproduced via ECH.
|
|
3901
|
+
*/
|
|
3902
|
+
function serializeRowEnd(row) {
|
|
3903
|
+
let end = row.length;
|
|
3904
|
+
while (end > 0) {
|
|
3905
|
+
const cell = row[end - 1];
|
|
3906
|
+
if (cell === void 0) {
|
|
3907
|
+
end--;
|
|
3908
|
+
continue;
|
|
3909
|
+
}
|
|
3910
|
+
if (cell.char.length > 0 || cell.wide || cell.url !== null) break;
|
|
3911
|
+
if (!serializePenIsDefault(serializePenOf(cell))) break;
|
|
3912
|
+
end--;
|
|
3913
|
+
}
|
|
3914
|
+
return end;
|
|
3915
|
+
}
|
|
3916
|
+
/**
|
|
3917
|
+
* Encode one row with per-run pen diffing. Invariants:
|
|
3918
|
+
* - the terminal pen is DEFAULT at entry (rows end with `SGR 0`);
|
|
3919
|
+
* - wide-cell spacers (char `""` following a `wide` cell) emit nothing;
|
|
3920
|
+
* - runs of unwritten cells are SKIPPED via CHA (the sink's cells stay
|
|
3921
|
+
* genuinely empty — not painted as spaces), and erased-with-bg runs are
|
|
3922
|
+
* reproduced via SGR+ECH, so the sink grid equals the source grid natively;
|
|
3923
|
+
* - an open OSC 8 hyperlink is always closed by row end.
|
|
3924
|
+
*
|
|
3925
|
+
* `literalWidth > 0` switches to LITERAL mode for soft-wrap-linked history
|
|
3926
|
+
* rows: every one of `literalWidth` columns is WRITTEN (unwritten cells as
|
|
3927
|
+
* pen-carrying spaces — no CHA skips, no ECH, no trailing trim), so the last
|
|
3928
|
+
* column write arms the receiver's pending-wrap and the next row's first
|
|
3929
|
+
* character re-links the logical line. Cell-level cost: the receiver's
|
|
3930
|
+
* scrollback holds written spaces where the source had unwritten cells —
|
|
3931
|
+
* invisible to the text + wrap-bit projections that scrollback serves.
|
|
3932
|
+
*/
|
|
3933
|
+
function serializeEncodeRow(row, hyperlinks, literalWidth = 0) {
|
|
3934
|
+
const literal = literalWidth > 0;
|
|
3935
|
+
const end = literal ? literalWidth : serializeRowEnd(row);
|
|
3936
|
+
let out = "";
|
|
3937
|
+
let pen = SERIALIZE_DEFAULT_PEN;
|
|
3938
|
+
let openUrl = null;
|
|
3939
|
+
let col = 0;
|
|
3940
|
+
while (col < end) {
|
|
3941
|
+
const cell = row[col];
|
|
3942
|
+
if (cell === void 0) {
|
|
3943
|
+
if (!literal) {
|
|
3944
|
+
col++;
|
|
3945
|
+
continue;
|
|
3946
|
+
}
|
|
3947
|
+
const diff = serializeDiffPen(pen, SERIALIZE_DEFAULT_PEN);
|
|
3948
|
+
if (diff.length > 0) {
|
|
3949
|
+
out += `\x1b[${diff.join(";")}m`;
|
|
3950
|
+
pen = SERIALIZE_DEFAULT_PEN;
|
|
3951
|
+
if (diff[0] === "0") openUrl = null;
|
|
3952
|
+
}
|
|
3953
|
+
if (openUrl !== null) {
|
|
3954
|
+
out += "\x1B]8;;\x1B\\";
|
|
3955
|
+
openUrl = null;
|
|
3956
|
+
}
|
|
3957
|
+
out += " ";
|
|
3958
|
+
col++;
|
|
3959
|
+
continue;
|
|
3960
|
+
}
|
|
3961
|
+
if (cell.char.length === 0 && col > 0 && row[col - 1]?.wide === true) {
|
|
3962
|
+
col++;
|
|
3963
|
+
continue;
|
|
3964
|
+
}
|
|
3965
|
+
if (cell.char.length === 0 && cell.url === null && !literal) {
|
|
3966
|
+
const runPen = serializePenOf(cell);
|
|
3967
|
+
const runStart = col;
|
|
3968
|
+
while (col < end) {
|
|
3969
|
+
const next = row[col];
|
|
3970
|
+
if (next === void 0 || next.char.length > 0 || next.url !== null) break;
|
|
3971
|
+
if (!serializePenEq(serializePenOf(next), runPen)) break;
|
|
3972
|
+
col++;
|
|
3973
|
+
}
|
|
3974
|
+
const runLength = col - runStart;
|
|
3975
|
+
if (serializePenIsDefault(runPen)) out += `\x1b[${String(col + 1)}G`;
|
|
3976
|
+
else {
|
|
3977
|
+
const diff = serializeDiffPen(pen, runPen);
|
|
3978
|
+
if (diff.length > 0) {
|
|
3979
|
+
out += `\x1b[${diff.join(";")}m`;
|
|
3980
|
+
pen = runPen;
|
|
3981
|
+
if (diff[0] === "0") openUrl = null;
|
|
3982
|
+
}
|
|
3983
|
+
out += `\x1b[${String(runLength)}X\x1b[${String(col + 1)}G`;
|
|
3984
|
+
}
|
|
3985
|
+
continue;
|
|
3986
|
+
}
|
|
3987
|
+
const cellPen = serializePenOf(cell);
|
|
3988
|
+
const diff = serializeDiffPen(pen, cellPen);
|
|
3989
|
+
if (diff.length > 0) {
|
|
3990
|
+
out += `\x1b[${diff.join(";")}m`;
|
|
3991
|
+
pen = cellPen;
|
|
3992
|
+
if (diff[0] === "0") openUrl = null;
|
|
3993
|
+
}
|
|
3994
|
+
const url = hyperlinks ? cell.url : null;
|
|
3995
|
+
if (url !== openUrl) {
|
|
3996
|
+
out += url === null ? "\x1B]8;;\x1B\\" : `\x1b]8;;${url}\x1b\\`;
|
|
3997
|
+
openUrl = url;
|
|
3998
|
+
}
|
|
3999
|
+
out += cell.char.length > 0 ? cell.char : " ";
|
|
4000
|
+
col++;
|
|
4001
|
+
}
|
|
4002
|
+
if (openUrl !== null) out += "\x1B]8;;\x1B\\";
|
|
4003
|
+
return out;
|
|
4004
|
+
}
|
|
4005
|
+
/**
|
|
4006
|
+
* Paint-safe DECSET boolean flags emitted in phase 3 — set-forms only, emitted
|
|
4007
|
+
* only when true (the receiver is fresh: every flag defaults to false). Origin
|
|
4008
|
+
* and insert are deliberately NOT here — they corrupt a positioned paint and
|
|
4009
|
+
* are restored in finalize instead.
|
|
4010
|
+
*/
|
|
4011
|
+
const SERIALIZE_DECSET_FLAGS = [
|
|
4012
|
+
["applicationCursor", 1],
|
|
4013
|
+
["reverseVideo", 5],
|
|
4014
|
+
["focusTracking", 1004],
|
|
4015
|
+
["utf8Mouse", 1005],
|
|
4016
|
+
["sgrMouse", 1006],
|
|
4017
|
+
["altScroll", 1007],
|
|
4018
|
+
["bracketedPaste", 2004],
|
|
4019
|
+
["colorSchemeReporting", 2031]
|
|
4020
|
+
];
|
|
4021
|
+
/** Default palette shared by every serialize call (read-only baseline for OSC 4 diffs). */
|
|
4022
|
+
const SERIALIZE_DEFAULT_PALETTE = buildPalette256();
|
|
4023
|
+
/** `rgb:RR/GG/BB` — the XParseColor spelling vterm's own OSC parser round-trips exactly. */
|
|
4024
|
+
function serializeColorSpec(color) {
|
|
4025
|
+
const hex = (v) => v.toString(16).padStart(2, "0");
|
|
4026
|
+
return `rgb:${hex(color.r)}/${hex(color.g)}/${hex(color.b)}`;
|
|
4027
|
+
}
|
|
4028
|
+
/** DECSCUSR code for (shape, blinking); 1 (blinking block) is the fresh default. */
|
|
4029
|
+
function serializeCursorStyleCode(shape, blinking) {
|
|
4030
|
+
const base = shape === "block" ? 1 : shape === "underline" ? 3 : 5;
|
|
4031
|
+
return blinking ? base : base + 1;
|
|
4032
|
+
}
|
|
4033
|
+
/** True when `stops` (sorted) is exactly the fresh default — a stop every 8 columns. */
|
|
4034
|
+
function serializeTabStopsAreDefault(stops, cols) {
|
|
4035
|
+
let expected = 8;
|
|
4036
|
+
for (const stop of stops) {
|
|
4037
|
+
if (stop !== expected) return false;
|
|
4038
|
+
expected += 8;
|
|
4039
|
+
}
|
|
4040
|
+
return expected >= cols;
|
|
4041
|
+
}
|
|
4042
|
+
/**
|
|
4043
|
+
* Serialize a snapshot to minimal ANSI a FRESH same-size terminal can replay.
|
|
4044
|
+
* Phase order + the hard exclusion set are documented on the section header
|
|
4045
|
+
* above; every ordering constraint is pinned by the mode round-trip matrix.
|
|
4046
|
+
*
|
|
4047
|
+
* Intended divergences (documented, not silent): syncOutput/decColumn are
|
|
4048
|
+
* never emitted; the inactive buffer, SCP saved cursor, DECSC saved state,
|
|
4049
|
+
* color stack, and parser/pending-wrap/mid-parse state are unserializable to
|
|
4050
|
+
* a VT byte stream by design — the binary snapshot carries them.
|
|
4051
|
+
*/
|
|
4052
|
+
function serializeSnapshot(snapshot, options = {}) {
|
|
4053
|
+
const hyperlinks = options.hyperlinks !== false;
|
|
4054
|
+
const excluded = new Set(options.excludeModes ?? []);
|
|
4055
|
+
const rows = snapshot.rows;
|
|
4056
|
+
const m = snapshot.modes;
|
|
4057
|
+
const out = [];
|
|
4058
|
+
if (options.includeScrollback !== false && snapshot.scrollback.length > 0) {
|
|
4059
|
+
const wraps = snapshot.scrollbackSoftWrapped;
|
|
4060
|
+
const last = snapshot.scrollback.length - 1;
|
|
4061
|
+
for (let i = 0; i < snapshot.scrollback.length; i++) {
|
|
4062
|
+
const row = snapshot.scrollback[i];
|
|
4063
|
+
if (wraps?.[i] === true && i < last) out.push(serializeEncodeRow(row, hyperlinks, snapshot.cols), "\x1B[0m");
|
|
4064
|
+
else out.push(serializeEncodeRow(row, hyperlinks), "\x1B[0m\r\n");
|
|
4065
|
+
}
|
|
4066
|
+
if (rows > 1) out.push("\n".repeat(rows - 1));
|
|
4067
|
+
}
|
|
4068
|
+
if (snapshot.activeBuffer === "alt") out.push("\x1B[?1049h");
|
|
4069
|
+
if (snapshot.margins.scrollTop !== 0 || snapshot.margins.scrollBottom !== rows - 1) out.push(`\x1b[${String(snapshot.margins.scrollTop + 1)};${String(snapshot.margins.scrollBottom + 1)}r`);
|
|
4070
|
+
for (const [key, code] of SERIALIZE_DECSET_FLAGS) if (m[key] === true && !excluded.has(key)) out.push(`\x1b[?${String(code)}h`);
|
|
4071
|
+
if (m.mouseTracking && m.mouseTrackingMode > 0 && !excluded.has("mouseTracking")) out.push(`\x1b[?${String(m.mouseTrackingMode)}h`);
|
|
4072
|
+
if (m.applicationKeypad && !excluded.has("applicationKeypad")) out.push("\x1B=");
|
|
4073
|
+
if (!excluded.has("kittyKeyboardFlags") && (m.kittyKeyboardFlags !== 0 || m.kittyKeyboardStack.length > 0)) {
|
|
4074
|
+
const stack = m.kittyKeyboardStack;
|
|
4075
|
+
if (stack.length === 0) out.push(`\x1b[=${String(m.kittyKeyboardFlags)}u`);
|
|
4076
|
+
else {
|
|
4077
|
+
out.push(`\x1b[=${String(stack[0])}u`);
|
|
4078
|
+
for (let i = 1; i < stack.length; i++) out.push(`\x1b[>${String(stack[i])}u`);
|
|
4079
|
+
out.push(`\x1b[>${String(m.kittyKeyboardFlags)}u`);
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
const colors = snapshot.colors.current;
|
|
4083
|
+
if (colors.defaultFgColor) out.push(`\x1b]10;${serializeColorSpec(colors.defaultFgColor)}\x1b\\`);
|
|
4084
|
+
if (colors.defaultBgColor) out.push(`\x1b]11;${serializeColorSpec(colors.defaultBgColor)}\x1b\\`);
|
|
4085
|
+
for (let i = 0; i < colors.palette256.length; i++) {
|
|
4086
|
+
const cur = colors.palette256[i];
|
|
4087
|
+
const def = SERIALIZE_DEFAULT_PALETTE[i];
|
|
4088
|
+
if (cur !== void 0 && def !== void 0 && !serializeColorEq(cur, def)) out.push(`\x1b]4;${String(i)};${serializeColorSpec(cur)}\x1b\\`);
|
|
4089
|
+
}
|
|
4090
|
+
if (options.includeTitle === true && snapshot.title.length > 0) out.push(`\x1b]0;${snapshot.title}\x1b\\`);
|
|
4091
|
+
out.push("\x1B[?7l", "\x1B[H\x1B[2J");
|
|
4092
|
+
const grid = snapshot.activeBuffer === "alt" ? snapshot.alt.grid : snapshot.main.grid;
|
|
4093
|
+
for (let r = 0; r < rows; r++) {
|
|
4094
|
+
const line = serializeEncodeRow(grid[r] ?? [], hyperlinks);
|
|
4095
|
+
if (line.length === 0) continue;
|
|
4096
|
+
out.push(`\x1b[${String(r + 1)};1H`, line, "\x1B[0m");
|
|
4097
|
+
}
|
|
4098
|
+
out.push(m.autoWrap ? "\x1B[?7h" : "\x1B[?7l");
|
|
4099
|
+
if (snapshot.cursor.shape !== "block" || !snapshot.cursor.blinking) out.push(`\x1b[${String(serializeCursorStyleCode(snapshot.cursor.shape, snapshot.cursor.blinking))} q`);
|
|
4100
|
+
if (snapshot.unicode.charsetG0) out.push("\x1B(0");
|
|
4101
|
+
if (m.insert && !excluded.has("insert")) out.push("\x1B[4h");
|
|
4102
|
+
if (!serializeTabStopsAreDefault(snapshot.tabStops, snapshot.cols)) {
|
|
4103
|
+
out.push("\x1B[3g");
|
|
4104
|
+
for (const stop of snapshot.tabStops) if (stop < snapshot.cols) out.push(`\x1b[${String(stop + 1)}G\x1bH`);
|
|
4105
|
+
}
|
|
4106
|
+
if (snapshot.margins.leftRight) {
|
|
4107
|
+
out.push("\x1B[?69h");
|
|
4108
|
+
if (snapshot.margins.left !== 0 || snapshot.margins.right !== snapshot.cols - 1) out.push(`\x1b[${String(snapshot.margins.left + 1)};${String(snapshot.margins.right + 1)}s`);
|
|
4109
|
+
}
|
|
4110
|
+
const penParams = serializeDiffPen(SERIALIZE_DEFAULT_PEN, serializePenOf(snapshot.attrs));
|
|
4111
|
+
if (penParams.length > 0) out.push(`\x1b[${penParams.join(";")}m`);
|
|
4112
|
+
if (hyperlinks && snapshot.attrs.url !== null) out.push(`\x1b]8;;${snapshot.attrs.url}\x1b\\`);
|
|
4113
|
+
out.push(snapshot.cursor.visible ? "\x1B[?25h" : "\x1B[?25l");
|
|
4114
|
+
const origin = m.origin && !excluded.has("origin");
|
|
4115
|
+
const inRegion = snapshot.cursor.y >= snapshot.margins.scrollTop && snapshot.cursor.y <= snapshot.margins.scrollBottom;
|
|
4116
|
+
if (origin && inRegion) {
|
|
4117
|
+
out.push("\x1B[?6h");
|
|
4118
|
+
out.push(`\x1b[${String(snapshot.cursor.y - snapshot.margins.scrollTop + 1)};${String(snapshot.cursor.x + 1)}H`);
|
|
4119
|
+
} else {
|
|
4120
|
+
out.push(`\x1b[${String(snapshot.cursor.y + 1)};${String(snapshot.cursor.x + 1)}H`);
|
|
4121
|
+
if (origin) out.push("\x1B[?6h");
|
|
4122
|
+
}
|
|
4123
|
+
return out.join("");
|
|
4124
|
+
}
|
|
4125
|
+
//#endregion
|
|
4126
|
+
//#region src/snapshot-codec.ts
|
|
4127
|
+
const VERSION = 1;
|
|
4128
|
+
const encoder = new TextEncoder();
|
|
4129
|
+
const decoder = new TextDecoder();
|
|
4130
|
+
const F_BOLD = 1;
|
|
4131
|
+
const F_FAINT = 2;
|
|
4132
|
+
const F_ITALIC = 4;
|
|
4133
|
+
const F_OVERLINE = 8;
|
|
4134
|
+
const F_STRIKE = 16;
|
|
4135
|
+
const F_INVERSE = 32;
|
|
4136
|
+
const F_HIDDEN = 64;
|
|
4137
|
+
const F_BLINK = 128;
|
|
4138
|
+
const F_WIDE = 256;
|
|
4139
|
+
const UL_SHIFT = 9;
|
|
4140
|
+
const UL_MASK = 7;
|
|
4141
|
+
const UNDERLINE_STYLES = [
|
|
4142
|
+
"none",
|
|
4143
|
+
"single",
|
|
4144
|
+
"double",
|
|
4145
|
+
"curly",
|
|
4146
|
+
"dotted",
|
|
4147
|
+
"dashed"
|
|
4148
|
+
];
|
|
4149
|
+
const UNDERLINE_INDEX = {
|
|
4150
|
+
none: 0,
|
|
4151
|
+
single: 1,
|
|
4152
|
+
double: 2,
|
|
4153
|
+
curly: 3,
|
|
4154
|
+
dotted: 4,
|
|
4155
|
+
dashed: 5
|
|
4156
|
+
};
|
|
4157
|
+
function createWriter() {
|
|
4158
|
+
let buf = /* @__PURE__ */ new Uint8Array(1024);
|
|
4159
|
+
let len = 0;
|
|
4160
|
+
function ensure(extra) {
|
|
4161
|
+
if (len + extra <= buf.length) return;
|
|
4162
|
+
let cap = buf.length;
|
|
4163
|
+
while (cap < len + extra) cap *= 2;
|
|
4164
|
+
const next = new Uint8Array(cap);
|
|
4165
|
+
next.set(buf.subarray(0, len));
|
|
4166
|
+
buf = next;
|
|
4167
|
+
}
|
|
4168
|
+
return {
|
|
4169
|
+
u8(byte) {
|
|
4170
|
+
ensure(1);
|
|
4171
|
+
buf[len++] = byte & 255;
|
|
4172
|
+
},
|
|
4173
|
+
varint(value) {
|
|
4174
|
+
ensure(10);
|
|
4175
|
+
let v = value;
|
|
4176
|
+
while (v >= 128) {
|
|
4177
|
+
buf[len++] = v % 128 + 128;
|
|
4178
|
+
v = Math.floor(v / 128);
|
|
4179
|
+
}
|
|
4180
|
+
buf[len++] = v;
|
|
4181
|
+
},
|
|
4182
|
+
bytes(src) {
|
|
4183
|
+
ensure(src.length);
|
|
4184
|
+
buf.set(src, len);
|
|
4185
|
+
len += src.length;
|
|
4186
|
+
},
|
|
4187
|
+
finish() {
|
|
4188
|
+
return buf.slice(0, len);
|
|
4189
|
+
}
|
|
4190
|
+
};
|
|
4191
|
+
}
|
|
4192
|
+
function createReader(bytes) {
|
|
4193
|
+
let pos = 0;
|
|
4194
|
+
function u8() {
|
|
4195
|
+
if (pos >= bytes.length) throw new Error("vterm snapshot codec: unexpected end of input");
|
|
4196
|
+
return bytes[pos++];
|
|
4197
|
+
}
|
|
4198
|
+
return {
|
|
4199
|
+
u8,
|
|
4200
|
+
varint() {
|
|
4201
|
+
let result = 0;
|
|
4202
|
+
let shift = 1;
|
|
4203
|
+
for (;;) {
|
|
4204
|
+
const byte = u8();
|
|
4205
|
+
result += (byte & 127) * shift;
|
|
4206
|
+
if ((byte & 128) === 0) break;
|
|
4207
|
+
shift *= 128;
|
|
4208
|
+
if (shift > 2 ** 53) throw new Error("vterm snapshot codec: varint too large");
|
|
4209
|
+
}
|
|
4210
|
+
return result;
|
|
4211
|
+
},
|
|
4212
|
+
take(n) {
|
|
4213
|
+
if (pos + n > bytes.length) throw new Error("vterm snapshot codec: unexpected end of input");
|
|
4214
|
+
const slice = bytes.subarray(pos, pos + n);
|
|
4215
|
+
pos += n;
|
|
4216
|
+
return slice;
|
|
4217
|
+
}
|
|
4218
|
+
};
|
|
4219
|
+
}
|
|
4220
|
+
function createRunAccumulator() {
|
|
4221
|
+
const runs = [];
|
|
4222
|
+
let hasLast = false;
|
|
4223
|
+
let lastValue = 0;
|
|
4224
|
+
return {
|
|
4225
|
+
runs,
|
|
4226
|
+
push(value) {
|
|
4227
|
+
if (hasLast && value === lastValue) runs[runs.length - 2] += 1;
|
|
4228
|
+
else {
|
|
4229
|
+
runs.push(1, value);
|
|
4230
|
+
lastValue = value;
|
|
4231
|
+
hasLast = true;
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4234
|
+
};
|
|
4235
|
+
}
|
|
4236
|
+
function writeRuns(w, runs) {
|
|
4237
|
+
w.varint(runs.length / 2);
|
|
4238
|
+
for (let i = 0; i < runs.length; i += 2) {
|
|
4239
|
+
w.varint(runs[i]);
|
|
4240
|
+
w.varint(runs[i + 1]);
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
function readRuns(r) {
|
|
4244
|
+
const runCount = r.varint();
|
|
4245
|
+
const runs = [];
|
|
4246
|
+
for (let i = 0; i < runCount; i++) {
|
|
4247
|
+
const count = r.varint();
|
|
4248
|
+
const value = r.varint();
|
|
4249
|
+
runs.push(count, value);
|
|
4250
|
+
}
|
|
4251
|
+
return runs;
|
|
4252
|
+
}
|
|
4253
|
+
function createRunCursor(runs) {
|
|
4254
|
+
let total = 0;
|
|
4255
|
+
for (let i = 0; i < runs.length; i += 2) total += runs[i];
|
|
4256
|
+
let idx = 0;
|
|
4257
|
+
let remaining = runs.length > 0 ? runs[0] : 0;
|
|
4258
|
+
let value = runs.length > 1 ? runs[1] : 0;
|
|
4259
|
+
return {
|
|
4260
|
+
total,
|
|
4261
|
+
next() {
|
|
4262
|
+
while (remaining === 0) {
|
|
4263
|
+
idx += 2;
|
|
4264
|
+
if (idx >= runs.length) throw new Error("vterm snapshot codec: run-length underflow");
|
|
4265
|
+
remaining = runs[idx];
|
|
4266
|
+
value = runs[idx + 1];
|
|
4267
|
+
}
|
|
4268
|
+
remaining -= 1;
|
|
4269
|
+
return value;
|
|
4270
|
+
}
|
|
4271
|
+
};
|
|
4272
|
+
}
|
|
4273
|
+
function expandLengths(runs) {
|
|
4274
|
+
const out = [];
|
|
4275
|
+
for (let i = 0; i < runs.length; i += 2) {
|
|
4276
|
+
const count = runs[i];
|
|
4277
|
+
const value = runs[i + 1];
|
|
4278
|
+
for (let k = 0; k < count; k++) out.push(value);
|
|
4279
|
+
}
|
|
4280
|
+
return out;
|
|
4281
|
+
}
|
|
4282
|
+
function writeBits(w, bits) {
|
|
4283
|
+
let acc = 0;
|
|
4284
|
+
let n = 0;
|
|
4285
|
+
for (const bit of bits) {
|
|
4286
|
+
if (bit) acc |= 1 << n;
|
|
4287
|
+
n++;
|
|
4288
|
+
if (n === 8) {
|
|
4289
|
+
w.u8(acc);
|
|
4290
|
+
acc = 0;
|
|
4291
|
+
n = 0;
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
if (n > 0) w.u8(acc);
|
|
4295
|
+
}
|
|
4296
|
+
function readBits(r, count) {
|
|
4297
|
+
const out = new Array(count);
|
|
4298
|
+
let acc = 0;
|
|
4299
|
+
let have = 0;
|
|
4300
|
+
for (let i = 0; i < count; i++) {
|
|
4301
|
+
if (have === 0) {
|
|
4302
|
+
acc = r.u8();
|
|
4303
|
+
have = 8;
|
|
4304
|
+
}
|
|
4305
|
+
out[i] = (acc & 1) === 1;
|
|
4306
|
+
acc >>= 1;
|
|
4307
|
+
have--;
|
|
4308
|
+
}
|
|
4309
|
+
return out;
|
|
4310
|
+
}
|
|
4311
|
+
function checkByte(name, value) {
|
|
4312
|
+
if (!Number.isInteger(value) || value < 0 || value > 255) throw new Error(`vterm snapshot codec: ${name} out of 0-255 range: ${String(value)}`);
|
|
4313
|
+
}
|
|
4314
|
+
function createInterner() {
|
|
4315
|
+
const stringList = [""];
|
|
4316
|
+
const stringMap = /* @__PURE__ */ new Map([["", 0]]);
|
|
4317
|
+
const colorList = [];
|
|
4318
|
+
const colorMap = /* @__PURE__ */ new Map();
|
|
4319
|
+
const styleList = [];
|
|
4320
|
+
const styleMap = /* @__PURE__ */ new Map();
|
|
4321
|
+
function internString(s) {
|
|
4322
|
+
let idx = stringMap.get(s);
|
|
4323
|
+
if (idx === void 0) {
|
|
4324
|
+
idx = stringList.length;
|
|
4325
|
+
stringList.push(s);
|
|
4326
|
+
stringMap.set(s, idx);
|
|
4327
|
+
}
|
|
4328
|
+
return idx;
|
|
4329
|
+
}
|
|
4330
|
+
/** null → 0; otherwise color-table index + 1. */
|
|
4331
|
+
function colorRef(c) {
|
|
4332
|
+
if (c === null) return 0;
|
|
4333
|
+
checkByte("color.r", c.r);
|
|
4334
|
+
checkByte("color.g", c.g);
|
|
4335
|
+
checkByte("color.b", c.b);
|
|
4336
|
+
if (c.index !== void 0) checkByte("color.index", c.index);
|
|
4337
|
+
const key = c.index === void 0 ? `${c.r},${c.g},${c.b}` : `${c.r},${c.g},${c.b},${c.index}`;
|
|
4338
|
+
let idx = colorMap.get(key);
|
|
4339
|
+
if (idx === void 0) {
|
|
4340
|
+
idx = colorList.length;
|
|
4341
|
+
colorList.push(c.index === void 0 ? {
|
|
4342
|
+
r: c.r,
|
|
4343
|
+
g: c.g,
|
|
4344
|
+
b: c.b
|
|
4345
|
+
} : {
|
|
4346
|
+
r: c.r,
|
|
4347
|
+
g: c.g,
|
|
4348
|
+
b: c.b,
|
|
4349
|
+
index: c.index
|
|
4350
|
+
});
|
|
4351
|
+
colorMap.set(key, idx);
|
|
4352
|
+
}
|
|
4353
|
+
return idx + 1;
|
|
4354
|
+
}
|
|
4355
|
+
function internStyle(cell) {
|
|
4356
|
+
const fgRef = colorRef(cell.fg);
|
|
4357
|
+
const bgRef = colorRef(cell.bg);
|
|
4358
|
+
const ulRef = colorRef(cell.underlineColor);
|
|
4359
|
+
const urlRef = cell.url === null ? 0 : internString(cell.url) + 1;
|
|
4360
|
+
const ulIdx = UNDERLINE_INDEX[cell.underline];
|
|
4361
|
+
if (ulIdx === void 0) throw new Error(`vterm snapshot codec: unknown underline style ${String(cell.underline)}`);
|
|
4362
|
+
let flags = 0;
|
|
4363
|
+
if (cell.bold) flags |= F_BOLD;
|
|
4364
|
+
if (cell.faint) flags |= F_FAINT;
|
|
4365
|
+
if (cell.italic) flags |= F_ITALIC;
|
|
4366
|
+
if (cell.overline) flags |= F_OVERLINE;
|
|
4367
|
+
if (cell.strikethrough) flags |= F_STRIKE;
|
|
4368
|
+
if (cell.inverse) flags |= F_INVERSE;
|
|
4369
|
+
if (cell.hidden) flags |= F_HIDDEN;
|
|
4370
|
+
if (cell.blink) flags |= F_BLINK;
|
|
4371
|
+
if (cell.wide) flags |= F_WIDE;
|
|
4372
|
+
flags |= ulIdx << UL_SHIFT;
|
|
4373
|
+
const key = `${flags}|${fgRef}|${bgRef}|${ulRef}|${urlRef}`;
|
|
4374
|
+
let idx = styleMap.get(key);
|
|
4375
|
+
if (idx === void 0) {
|
|
4376
|
+
idx = styleList.length;
|
|
4377
|
+
styleList.push({
|
|
4378
|
+
flags,
|
|
4379
|
+
fgRef,
|
|
4380
|
+
bgRef,
|
|
4381
|
+
ulRef,
|
|
4382
|
+
urlRef
|
|
4383
|
+
});
|
|
4384
|
+
styleMap.set(key, idx);
|
|
4385
|
+
}
|
|
4386
|
+
return idx;
|
|
4387
|
+
}
|
|
4388
|
+
return {
|
|
4389
|
+
stringList,
|
|
4390
|
+
colorList,
|
|
4391
|
+
styleList,
|
|
4392
|
+
internString,
|
|
4393
|
+
internStyle
|
|
4394
|
+
};
|
|
4395
|
+
}
|
|
4396
|
+
function buildGridColumns(grid, interner) {
|
|
4397
|
+
const charAcc = createRunAccumulator();
|
|
4398
|
+
const styleAcc = createRunAccumulator();
|
|
4399
|
+
const lenAcc = createRunAccumulator();
|
|
4400
|
+
for (const row of grid) {
|
|
4401
|
+
lenAcc.push(row.length);
|
|
4402
|
+
for (const cell of row) {
|
|
4403
|
+
charAcc.push(interner.internString(cell.char));
|
|
4404
|
+
styleAcc.push(interner.internStyle(cell));
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
return {
|
|
4408
|
+
rowCount: grid.length,
|
|
4409
|
+
lenRuns: lenAcc.runs,
|
|
4410
|
+
charRuns: charAcc.runs,
|
|
4411
|
+
styleRuns: styleAcc.runs
|
|
4412
|
+
};
|
|
4413
|
+
}
|
|
4414
|
+
function writeGridSection(w, columns, softWrapped, softWrapNullable) {
|
|
4415
|
+
w.varint(columns.rowCount);
|
|
4416
|
+
if (softWrapNullable && softWrapped === void 0) w.u8(0);
|
|
4417
|
+
else {
|
|
4418
|
+
if (softWrapNullable) w.u8(1);
|
|
4419
|
+
if (softWrapped === void 0) throw new Error("vterm snapshot codec: missing soft-wrap array for a required grid");
|
|
4420
|
+
if (softWrapped.length !== columns.rowCount) throw new Error(`vterm snapshot codec: soft-wrap length ${softWrapped.length} != rowCount ${columns.rowCount}`);
|
|
4421
|
+
writeBits(w, softWrapped);
|
|
4422
|
+
}
|
|
4423
|
+
writeRuns(w, columns.lenRuns);
|
|
4424
|
+
writeRuns(w, columns.charRuns);
|
|
4425
|
+
writeRuns(w, columns.styleRuns);
|
|
4426
|
+
}
|
|
4427
|
+
function writeStringTable(w, list) {
|
|
4428
|
+
w.varint(list.length);
|
|
4429
|
+
for (const s of list) {
|
|
4430
|
+
const b = encoder.encode(s);
|
|
4431
|
+
w.varint(b.length);
|
|
4432
|
+
w.bytes(b);
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
function writeColorTable(w, list) {
|
|
4436
|
+
w.varint(list.length);
|
|
4437
|
+
for (const c of list) {
|
|
4438
|
+
w.u8(c.r);
|
|
4439
|
+
w.u8(c.g);
|
|
4440
|
+
w.u8(c.b);
|
|
4441
|
+
w.varint(c.index === void 0 ? 0 : c.index + 1);
|
|
4442
|
+
}
|
|
4443
|
+
}
|
|
4444
|
+
function writeStyleTable(w, list) {
|
|
4445
|
+
w.varint(list.length);
|
|
4446
|
+
for (const st of list) {
|
|
4447
|
+
w.varint(st.flags);
|
|
4448
|
+
w.varint(st.fgRef);
|
|
4449
|
+
w.varint(st.bgRef);
|
|
4450
|
+
w.varint(st.ulRef);
|
|
4451
|
+
w.varint(st.urlRef);
|
|
4452
|
+
}
|
|
4453
|
+
}
|
|
4454
|
+
function readStringTable(r) {
|
|
4455
|
+
const count = r.varint();
|
|
4456
|
+
const list = new Array(count);
|
|
4457
|
+
for (let i = 0; i < count; i++) {
|
|
4458
|
+
const len = r.varint();
|
|
4459
|
+
list[i] = decoder.decode(r.take(len));
|
|
4460
|
+
}
|
|
4461
|
+
return list;
|
|
4462
|
+
}
|
|
4463
|
+
function readColorTable(r) {
|
|
4464
|
+
const count = r.varint();
|
|
4465
|
+
const list = new Array(count);
|
|
4466
|
+
for (let i = 0; i < count; i++) {
|
|
4467
|
+
const cr = r.u8();
|
|
4468
|
+
const cg = r.u8();
|
|
4469
|
+
const cb = r.u8();
|
|
4470
|
+
const idxPlus = r.varint();
|
|
4471
|
+
list[i] = idxPlus === 0 ? {
|
|
4472
|
+
r: cr,
|
|
4473
|
+
g: cg,
|
|
4474
|
+
b: cb
|
|
4475
|
+
} : {
|
|
4476
|
+
r: cr,
|
|
4477
|
+
g: cg,
|
|
4478
|
+
b: cb,
|
|
4479
|
+
index: idxPlus - 1
|
|
4480
|
+
};
|
|
4481
|
+
}
|
|
4482
|
+
return list;
|
|
4483
|
+
}
|
|
4484
|
+
function readStyleTable(r) {
|
|
4485
|
+
const count = r.varint();
|
|
4486
|
+
const list = new Array(count);
|
|
4487
|
+
for (let i = 0; i < count; i++) list[i] = {
|
|
4488
|
+
flags: r.varint(),
|
|
4489
|
+
fgRef: r.varint(),
|
|
4490
|
+
bgRef: r.varint(),
|
|
4491
|
+
ulRef: r.varint(),
|
|
4492
|
+
urlRef: r.varint()
|
|
4493
|
+
};
|
|
4494
|
+
return list;
|
|
4495
|
+
}
|
|
4496
|
+
function colorFromRef(ref, table) {
|
|
4497
|
+
if (ref === 0) return null;
|
|
4498
|
+
const c = table[ref - 1];
|
|
4499
|
+
if (c === void 0) throw new Error(`vterm snapshot codec: color ref ${ref} out of range (table size ${table.length})`);
|
|
4500
|
+
return c.index === void 0 ? {
|
|
4501
|
+
r: c.r,
|
|
4502
|
+
g: c.g,
|
|
4503
|
+
b: c.b
|
|
4504
|
+
} : {
|
|
4505
|
+
r: c.r,
|
|
4506
|
+
g: c.g,
|
|
4507
|
+
b: c.b,
|
|
4508
|
+
index: c.index
|
|
4509
|
+
};
|
|
4510
|
+
}
|
|
4511
|
+
function makeCell(charIdx, styleIdx, strings, colors, styles) {
|
|
4512
|
+
const char = strings[charIdx];
|
|
4513
|
+
if (char === void 0) throw new Error(`vterm snapshot codec: char ref ${charIdx} out of range (table size ${strings.length})`);
|
|
4514
|
+
const style = styles[styleIdx];
|
|
4515
|
+
if (style === void 0) throw new Error(`vterm snapshot codec: style ref ${styleIdx} out of range (table size ${styles.length})`);
|
|
4516
|
+
const underline = UNDERLINE_STYLES[style.flags >> UL_SHIFT & UL_MASK];
|
|
4517
|
+
if (underline === void 0) throw new Error(`vterm snapshot codec: invalid underline bits in style flags ${style.flags}`);
|
|
4518
|
+
let url;
|
|
4519
|
+
if (style.urlRef === 0) url = null;
|
|
4520
|
+
else {
|
|
4521
|
+
const u = strings[style.urlRef - 1];
|
|
4522
|
+
if (u === void 0) throw new Error(`vterm snapshot codec: url ref ${style.urlRef} out of range (table size ${strings.length})`);
|
|
4523
|
+
url = u;
|
|
4524
|
+
}
|
|
4525
|
+
return {
|
|
4526
|
+
char,
|
|
4527
|
+
fg: colorFromRef(style.fgRef, colors),
|
|
4528
|
+
bg: colorFromRef(style.bgRef, colors),
|
|
4529
|
+
bold: (style.flags & F_BOLD) !== 0,
|
|
4530
|
+
faint: (style.flags & F_FAINT) !== 0,
|
|
4531
|
+
italic: (style.flags & F_ITALIC) !== 0,
|
|
4532
|
+
underline,
|
|
4533
|
+
underlineColor: colorFromRef(style.ulRef, colors),
|
|
4534
|
+
overline: (style.flags & F_OVERLINE) !== 0,
|
|
4535
|
+
strikethrough: (style.flags & F_STRIKE) !== 0,
|
|
4536
|
+
inverse: (style.flags & F_INVERSE) !== 0,
|
|
4537
|
+
hidden: (style.flags & F_HIDDEN) !== 0,
|
|
4538
|
+
blink: (style.flags & F_BLINK) !== 0,
|
|
4539
|
+
wide: (style.flags & F_WIDE) !== 0,
|
|
4540
|
+
url
|
|
4541
|
+
};
|
|
4542
|
+
}
|
|
4543
|
+
function readGridSection(r, softWrapNullable, expected, strings, colors, styles) {
|
|
4544
|
+
const rowCount = r.varint();
|
|
4545
|
+
let softWrapped;
|
|
4546
|
+
if (softWrapNullable) {
|
|
4547
|
+
const present = r.u8();
|
|
4548
|
+
if (present === 0) softWrapped = void 0;
|
|
4549
|
+
else if (present === 1) softWrapped = readBits(r, rowCount);
|
|
4550
|
+
else throw new Error(`vterm snapshot codec: invalid soft-wrap presence byte ${present}`);
|
|
4551
|
+
} else softWrapped = readBits(r, rowCount);
|
|
4552
|
+
const rowLengths = expandLengths(readRuns(r));
|
|
4553
|
+
if (rowLengths.length !== rowCount) throw new Error(`vterm snapshot codec: decoded ${rowLengths.length} row lengths but rowCount is ${rowCount}`);
|
|
4554
|
+
if (expected) {
|
|
4555
|
+
if (rowCount !== expected.rows) throw new Error(`vterm snapshot codec: grid rowCount ${rowCount} != expected ${expected.rows}`);
|
|
4556
|
+
for (const len of rowLengths) if (len !== expected.cols) throw new Error(`vterm snapshot codec: grid row length ${len} != expected ${expected.cols}`);
|
|
4557
|
+
}
|
|
4558
|
+
let totalCells = 0;
|
|
4559
|
+
for (const len of rowLengths) totalCells += len;
|
|
4560
|
+
const charCursor = createRunCursor(readRuns(r));
|
|
4561
|
+
const styleCursor = createRunCursor(readRuns(r));
|
|
4562
|
+
if (charCursor.total !== totalCells) throw new Error(`vterm snapshot codec: char column has ${charCursor.total} cells, grid needs ${totalCells}`);
|
|
4563
|
+
if (styleCursor.total !== totalCells) throw new Error(`vterm snapshot codec: style column has ${styleCursor.total} cells, grid needs ${totalCells}`);
|
|
4564
|
+
const grid = new Array(rowCount);
|
|
4565
|
+
for (let row = 0; row < rowCount; row++) {
|
|
4566
|
+
const len = rowLengths[row];
|
|
4567
|
+
const cells = new Array(len);
|
|
4568
|
+
for (let col = 0; col < len; col++) cells[col] = makeCell(charCursor.next(), styleCursor.next(), strings, colors, styles);
|
|
4569
|
+
grid[row] = cells;
|
|
4570
|
+
}
|
|
4571
|
+
return {
|
|
4572
|
+
grid,
|
|
4573
|
+
softWrapped
|
|
4574
|
+
};
|
|
4575
|
+
}
|
|
4576
|
+
/**
|
|
4577
|
+
* Encode a {@link Snapshot} to a compact binary buffer. Output is
|
|
4578
|
+
* deterministic for identical input and decodes back to a deep-equal snapshot.
|
|
4579
|
+
*/
|
|
4580
|
+
function encodeScreenSnapshotBinary(snapshot) {
|
|
4581
|
+
const w = createWriter();
|
|
4582
|
+
w.u8(VERSION);
|
|
4583
|
+
const header = {
|
|
4584
|
+
cols: snapshot.cols,
|
|
4585
|
+
rows: snapshot.rows,
|
|
4586
|
+
scrollbackLimit: snapshot.scrollbackLimit,
|
|
4587
|
+
activeBuffer: snapshot.activeBuffer,
|
|
4588
|
+
cursor: snapshot.cursor,
|
|
4589
|
+
savedState: snapshot.savedState,
|
|
4590
|
+
attrs: snapshot.attrs,
|
|
4591
|
+
modes: snapshot.modes,
|
|
4592
|
+
margins: snapshot.margins,
|
|
4593
|
+
colors: snapshot.colors,
|
|
4594
|
+
tabStops: snapshot.tabStops,
|
|
4595
|
+
title: snapshot.title,
|
|
4596
|
+
clipboard: snapshot.clipboard,
|
|
4597
|
+
cwd: snapshot.cwd,
|
|
4598
|
+
notifications: snapshot.notifications,
|
|
4599
|
+
viewportOffset: snapshot.viewportOffset,
|
|
4600
|
+
parser: snapshot.parser,
|
|
4601
|
+
unicode: snapshot.unicode
|
|
4602
|
+
};
|
|
4603
|
+
const headerBytes = encoder.encode(JSON.stringify(header));
|
|
4604
|
+
w.varint(headerBytes.length);
|
|
4605
|
+
w.bytes(headerBytes);
|
|
4606
|
+
const interner = createInterner();
|
|
4607
|
+
const mainColumns = buildGridColumns(snapshot.main.grid, interner);
|
|
4608
|
+
const altColumns = buildGridColumns(snapshot.alt.grid, interner);
|
|
4609
|
+
const scrollbackColumns = buildGridColumns(snapshot.scrollback, interner);
|
|
4610
|
+
writeStringTable(w, interner.stringList);
|
|
4611
|
+
writeColorTable(w, interner.colorList);
|
|
4612
|
+
writeStyleTable(w, interner.styleList);
|
|
4613
|
+
writeGridSection(w, mainColumns, snapshot.main.softWrapped, false);
|
|
4614
|
+
writeGridSection(w, altColumns, snapshot.alt.softWrapped, false);
|
|
4615
|
+
writeGridSection(w, scrollbackColumns, snapshot.scrollbackSoftWrapped, true);
|
|
4616
|
+
return w.finish();
|
|
4617
|
+
}
|
|
4618
|
+
/**
|
|
4619
|
+
* Decode a buffer produced by {@link encodeScreenSnapshotBinary} back into a
|
|
4620
|
+
* {@link Snapshot}. Throws loudly on an unknown version byte, truncated
|
|
4621
|
+
* input, or any internal inconsistency — never returns a partial/normalized result.
|
|
4622
|
+
*/
|
|
4623
|
+
function decodeScreenSnapshotBinary(bytes) {
|
|
4624
|
+
const r = createReader(bytes);
|
|
4625
|
+
const version = r.u8();
|
|
4626
|
+
if (version !== VERSION) throw new Error(`vterm snapshot codec: unsupported version byte ${version} (expected ${VERSION})`);
|
|
4627
|
+
const headerLen = r.varint();
|
|
4628
|
+
const header = JSON.parse(decoder.decode(r.take(headerLen)));
|
|
4629
|
+
const strings = readStringTable(r);
|
|
4630
|
+
const colors = readColorTable(r);
|
|
4631
|
+
const styles = readStyleTable(r);
|
|
4632
|
+
const geom = {
|
|
4633
|
+
rows: header.rows,
|
|
4634
|
+
cols: header.cols
|
|
4635
|
+
};
|
|
4636
|
+
const main = readGridSection(r, false, geom, strings, colors, styles);
|
|
4637
|
+
const alt = readGridSection(r, false, geom, strings, colors, styles);
|
|
4638
|
+
const scrollback = readGridSection(r, true, null, strings, colors, styles);
|
|
4639
|
+
return {
|
|
4640
|
+
version: 1,
|
|
4641
|
+
cols: header.cols,
|
|
4642
|
+
rows: header.rows,
|
|
4643
|
+
scrollbackLimit: header.scrollbackLimit,
|
|
4644
|
+
activeBuffer: header.activeBuffer,
|
|
4645
|
+
main: {
|
|
4646
|
+
grid: main.grid,
|
|
4647
|
+
softWrapped: main.softWrapped
|
|
4648
|
+
},
|
|
4649
|
+
alt: {
|
|
4650
|
+
grid: alt.grid,
|
|
4651
|
+
softWrapped: alt.softWrapped
|
|
4652
|
+
},
|
|
4653
|
+
scrollback: scrollback.grid,
|
|
4654
|
+
scrollbackSoftWrapped: scrollback.softWrapped,
|
|
4655
|
+
cursor: header.cursor,
|
|
4656
|
+
savedState: header.savedState,
|
|
4657
|
+
attrs: header.attrs,
|
|
4658
|
+
modes: header.modes,
|
|
4659
|
+
margins: header.margins,
|
|
4660
|
+
colors: header.colors,
|
|
4661
|
+
tabStops: header.tabStops,
|
|
4662
|
+
title: header.title,
|
|
4663
|
+
clipboard: header.clipboard,
|
|
4664
|
+
cwd: header.cwd,
|
|
4665
|
+
notifications: header.notifications,
|
|
4666
|
+
viewportOffset: header.viewportOffset,
|
|
4667
|
+
parser: header.parser,
|
|
4668
|
+
unicode: header.unicode
|
|
2498
4669
|
};
|
|
2499
4670
|
}
|
|
2500
4671
|
//#endregion
|
|
2501
|
-
export { createScreen as createVtermScreen };
|
|
4672
|
+
export { DEFAULT_MAX_STRING_SEQUENCE_LENGTH, createScreen as createVtermScreen, decodeScreenSnapshotBinary, encodeScreenSnapshotBinary, serializeSnapshot };
|