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