vterm.js 0.4.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 +2369 -189
- 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);
|
|
@@ -270,8 +512,225 @@ function createScreen(options = {}) {
|
|
|
270
512
|
let defaultBgColor = null;
|
|
271
513
|
let cursorColor = null;
|
|
272
514
|
const specialColors = /* @__PURE__ */ new Map();
|
|
515
|
+
let pointerFgColor = null;
|
|
516
|
+
let pointerBgColor = null;
|
|
273
517
|
let highlightBgColor = null;
|
|
274
518
|
let highlightFgColor = null;
|
|
519
|
+
const colorStack = [];
|
|
520
|
+
function cloneColor(c) {
|
|
521
|
+
return c ? { ...c } : null;
|
|
522
|
+
}
|
|
523
|
+
function snapshotColorState() {
|
|
524
|
+
return {
|
|
525
|
+
palette256: palette256.map((c) => ({ ...c })),
|
|
526
|
+
defaultFgColor: cloneColor(defaultFgColor),
|
|
527
|
+
defaultBgColor: cloneColor(defaultBgColor),
|
|
528
|
+
cursorColor: cloneColor(cursorColor),
|
|
529
|
+
specialColors: [...specialColors].map(([idx, color]) => [idx, { ...color }]),
|
|
530
|
+
pointerFgColor: cloneColor(pointerFgColor),
|
|
531
|
+
pointerBgColor: cloneColor(pointerBgColor),
|
|
532
|
+
highlightBgColor: cloneColor(highlightBgColor),
|
|
533
|
+
highlightFgColor: cloneColor(highlightFgColor)
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
function restoreColorState(snapshot) {
|
|
537
|
+
palette256 = snapshot.palette256.map((c) => ({ ...c }));
|
|
538
|
+
defaultFgColor = cloneColor(snapshot.defaultFgColor);
|
|
539
|
+
defaultBgColor = cloneColor(snapshot.defaultBgColor);
|
|
540
|
+
cursorColor = cloneColor(snapshot.cursorColor);
|
|
541
|
+
specialColors.clear();
|
|
542
|
+
for (const [idx, color] of snapshot.specialColors) specialColors.set(idx, { ...color });
|
|
543
|
+
pointerFgColor = cloneColor(snapshot.pointerFgColor);
|
|
544
|
+
pointerBgColor = cloneColor(snapshot.pointerBgColor);
|
|
545
|
+
highlightBgColor = cloneColor(snapshot.highlightBgColor);
|
|
546
|
+
highlightFgColor = cloneColor(snapshot.highlightFgColor);
|
|
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
|
+
}
|
|
275
734
|
let tabStops = defaultTabStops(cols);
|
|
276
735
|
function defaultTabStops(c) {
|
|
277
736
|
const s = /* @__PURE__ */ new Set();
|
|
@@ -288,6 +747,12 @@ function createScreen(options = {}) {
|
|
|
288
747
|
return 0;
|
|
289
748
|
}
|
|
290
749
|
let textScale = 1;
|
|
750
|
+
const CELL_W_PX = 8;
|
|
751
|
+
const CELL_H_PX = 17;
|
|
752
|
+
const FONT_ASCENT_PX = 14;
|
|
753
|
+
let fontSize = 12;
|
|
754
|
+
let fontWindowSize = 12;
|
|
755
|
+
let locale = "en_US.UTF-8";
|
|
291
756
|
let advancedClipboard = "";
|
|
292
757
|
let viewportOffset = 0;
|
|
293
758
|
let charsetG0 = false;
|
|
@@ -300,6 +765,7 @@ function createScreen(options = {}) {
|
|
|
300
765
|
let mainSoftWrapped = new Array(rows).fill(false);
|
|
301
766
|
let altSoftWrapped = new Array(rows).fill(false);
|
|
302
767
|
let softWrapped = mainSoftWrapped;
|
|
768
|
+
let scrollbackSoftWrapped = [];
|
|
303
769
|
let lastChar = "";
|
|
304
770
|
let pendingRegionalIndicator = null;
|
|
305
771
|
let afterZWJ = false;
|
|
@@ -310,16 +776,122 @@ function createScreen(options = {}) {
|
|
|
310
776
|
let dcsStartRow = 0;
|
|
311
777
|
let dcsStartCol = 0;
|
|
312
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
|
+
}
|
|
313
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
|
+
}
|
|
314
876
|
function makeGrid(c, r) {
|
|
315
877
|
const g = [];
|
|
316
878
|
for (let row = 0; row < r; row++) g.push(makeRow(c));
|
|
317
879
|
return g;
|
|
318
880
|
}
|
|
319
881
|
function makeRow(c) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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);
|
|
323
895
|
}
|
|
324
896
|
function resetAttrs() {
|
|
325
897
|
return {
|
|
@@ -351,14 +923,35 @@ function createScreen(options = {}) {
|
|
|
351
923
|
for (let r = top; r < bottom; r++) {
|
|
352
924
|
const srcRow = grid[r + 1];
|
|
353
925
|
const dstRow = grid[r];
|
|
354
|
-
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);
|
|
355
927
|
}
|
|
356
928
|
const bottomRow = grid[bottom];
|
|
357
|
-
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);
|
|
358
931
|
} else {
|
|
359
|
-
|
|
932
|
+
const enteredScrollback = grid === mainGrid && top === 0;
|
|
933
|
+
if (enteredScrollback) {
|
|
360
934
|
scrollback.push(grid[0]);
|
|
361
|
-
|
|
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
|
+
}
|
|
362
955
|
}
|
|
363
956
|
for (let i = top; i < bottom; i++) {
|
|
364
957
|
grid[i] = grid[i + 1];
|
|
@@ -366,6 +959,11 @@ function createScreen(options = {}) {
|
|
|
366
959
|
}
|
|
367
960
|
grid[bottom] = makeRow(cols);
|
|
368
961
|
softWrapped[bottom] = false;
|
|
962
|
+
if (enteredScrollback) {
|
|
963
|
+
dirtyScrolled++;
|
|
964
|
+
if (sigDamageActive) sigDirtyScrolled++;
|
|
965
|
+
markScreenRowDirty(bottom);
|
|
966
|
+
} else markScreenRowsDirty(top, bottom);
|
|
369
967
|
}
|
|
370
968
|
}
|
|
371
969
|
function scrollDown(top, bottom) {
|
|
@@ -375,10 +973,10 @@ function createScreen(options = {}) {
|
|
|
375
973
|
for (let r = bottom; r > top; r--) {
|
|
376
974
|
const srcRow = grid[r - 1];
|
|
377
975
|
const dstRow = grid[r];
|
|
378
|
-
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);
|
|
379
977
|
}
|
|
380
978
|
const topRow = grid[top];
|
|
381
|
-
for (let c = lm; c <= rm && c < cols; c++) topRow
|
|
979
|
+
for (let c = lm; c <= rm && c < cols; c++) topRow.setEmpty(c);
|
|
382
980
|
} else {
|
|
383
981
|
for (let i = bottom; i > top; i--) {
|
|
384
982
|
grid[i] = grid[i - 1];
|
|
@@ -387,8 +985,17 @@ function createScreen(options = {}) {
|
|
|
387
985
|
grid[top] = makeRow(cols);
|
|
388
986
|
softWrapped[top] = false;
|
|
389
987
|
}
|
|
988
|
+
markScreenRowsDirty(top, bottom);
|
|
390
989
|
}
|
|
391
|
-
|
|
990
|
+
function scrollViewport(delta) {
|
|
991
|
+
viewportOffset = Math.max(0, Math.min(scrollback.length, viewportOffset + delta));
|
|
992
|
+
}
|
|
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
|
+
*/
|
|
392
999
|
function getPrevCell() {
|
|
393
1000
|
if (curX === 0 && curY === 0) return null;
|
|
394
1001
|
let prevCol = curX - 1;
|
|
@@ -399,42 +1006,17 @@ function createScreen(options = {}) {
|
|
|
399
1006
|
prevCol = cols - 1;
|
|
400
1007
|
}
|
|
401
1008
|
const row = grid[prevRow];
|
|
402
|
-
|
|
403
|
-
if (
|
|
404
|
-
prevCol--;
|
|
405
|
-
cell = row[prevCol];
|
|
406
|
-
}
|
|
407
|
-
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;
|
|
408
1011
|
return {
|
|
409
|
-
|
|
410
|
-
col: prevCol
|
|
1012
|
+
row,
|
|
1013
|
+
col: prevCol,
|
|
1014
|
+
rowIdx: prevRow
|
|
411
1015
|
};
|
|
412
1016
|
}
|
|
413
|
-
/** Widen
|
|
414
|
-
function widenCell(row, col
|
|
415
|
-
|
|
416
|
-
if (col + 1 < cols) {
|
|
417
|
-
let spacer = row[col + 1];
|
|
418
|
-
if (spacer === EMPTY_CELL) {
|
|
419
|
-
spacer = { ...EMPTY_CELL };
|
|
420
|
-
row[col + 1] = spacer;
|
|
421
|
-
}
|
|
422
|
-
spacer.char = "";
|
|
423
|
-
spacer.fg = null;
|
|
424
|
-
spacer.bg = null;
|
|
425
|
-
spacer.bold = false;
|
|
426
|
-
spacer.faint = false;
|
|
427
|
-
spacer.italic = false;
|
|
428
|
-
spacer.underline = "none";
|
|
429
|
-
spacer.underlineColor = null;
|
|
430
|
-
spacer.overline = false;
|
|
431
|
-
spacer.strikethrough = false;
|
|
432
|
-
spacer.inverse = false;
|
|
433
|
-
spacer.hidden = false;
|
|
434
|
-
spacer.blink = false;
|
|
435
|
-
spacer.wide = false;
|
|
436
|
-
spacer.url = null;
|
|
437
|
-
}
|
|
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);
|
|
438
1020
|
}
|
|
439
1021
|
function writeChar(ch) {
|
|
440
1022
|
if (charsetG0 && ch.length === 1) {
|
|
@@ -444,10 +1026,10 @@ function createScreen(options = {}) {
|
|
|
444
1026
|
const codePoint = ch.codePointAt(0) ?? 0;
|
|
445
1027
|
if (isVS16(codePoint)) {
|
|
446
1028
|
const prev = getPrevCell();
|
|
447
|
-
if (prev && !prev.
|
|
448
|
-
prev.
|
|
449
|
-
|
|
450
|
-
|
|
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);
|
|
451
1033
|
curX = prev.col + 2;
|
|
452
1034
|
if (curX >= cols) curX = cols - 1;
|
|
453
1035
|
}
|
|
@@ -455,18 +1037,25 @@ function createScreen(options = {}) {
|
|
|
455
1037
|
}
|
|
456
1038
|
if (isCombining(codePoint) && !isVS16(codePoint)) {
|
|
457
1039
|
const prev = getPrevCell();
|
|
458
|
-
if (prev)
|
|
1040
|
+
if (prev) {
|
|
1041
|
+
prev.row.appendChar(prev.col, ch);
|
|
1042
|
+
markScreenRowDirty(prev.rowIdx);
|
|
1043
|
+
}
|
|
459
1044
|
return;
|
|
460
1045
|
}
|
|
461
1046
|
if (isEmojiModifier(codePoint)) {
|
|
462
1047
|
const prev = getPrevCell();
|
|
463
|
-
if (prev)
|
|
1048
|
+
if (prev) {
|
|
1049
|
+
prev.row.appendChar(prev.col, ch);
|
|
1050
|
+
markScreenRowDirty(prev.rowIdx);
|
|
1051
|
+
}
|
|
464
1052
|
return;
|
|
465
1053
|
}
|
|
466
1054
|
if (isZWJ(codePoint)) {
|
|
467
1055
|
const prev = getPrevCell();
|
|
468
1056
|
if (prev) {
|
|
469
|
-
prev.
|
|
1057
|
+
prev.row.appendChar(prev.col, ch);
|
|
1058
|
+
markScreenRowDirty(prev.rowIdx);
|
|
470
1059
|
afterZWJ = true;
|
|
471
1060
|
}
|
|
472
1061
|
return;
|
|
@@ -475,7 +1064,8 @@ function createScreen(options = {}) {
|
|
|
475
1064
|
afterZWJ = false;
|
|
476
1065
|
const prev = getPrevCell();
|
|
477
1066
|
if (prev) {
|
|
478
|
-
prev.
|
|
1067
|
+
prev.row.appendChar(prev.col, ch);
|
|
1068
|
+
markScreenRowDirty(prev.rowIdx);
|
|
479
1069
|
return;
|
|
480
1070
|
}
|
|
481
1071
|
}
|
|
@@ -511,35 +1101,18 @@ function createScreen(options = {}) {
|
|
|
511
1101
|
}
|
|
512
1102
|
} else curX = wrapBoundary - charWidth;
|
|
513
1103
|
if (insertMode) {
|
|
514
|
-
const row = grid[curY];
|
|
515
1104
|
const insertEnd = leftRightMarginMode ? rightMargin + 1 : cols;
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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
|
+
});
|
|
520
1111
|
}
|
|
521
1112
|
const row = grid[curY];
|
|
522
|
-
|
|
523
|
-
if (
|
|
524
|
-
|
|
525
|
-
row[curX] = cell;
|
|
526
|
-
}
|
|
527
|
-
cell.char = ch;
|
|
528
|
-
cell.fg = attrs.fg ? { ...attrs.fg } : null;
|
|
529
|
-
cell.bg = attrs.bg ? { ...attrs.bg } : null;
|
|
530
|
-
cell.bold = attrs.bold;
|
|
531
|
-
cell.faint = attrs.faint;
|
|
532
|
-
cell.italic = attrs.italic;
|
|
533
|
-
cell.underline = attrs.underline;
|
|
534
|
-
cell.underlineColor = attrs.underlineColor ? { ...attrs.underlineColor } : null;
|
|
535
|
-
cell.overline = attrs.overline;
|
|
536
|
-
cell.strikethrough = attrs.strikethrough;
|
|
537
|
-
cell.inverse = attrs.inverse;
|
|
538
|
-
cell.hidden = attrs.hidden;
|
|
539
|
-
cell.blink = attrs.blink;
|
|
540
|
-
cell.wide = wide;
|
|
541
|
-
cell.url = attrs.url;
|
|
542
|
-
if (wide) widenCell(row, curX, cell);
|
|
1113
|
+
row.writeFromAttrs(curX, ch, attrs, wide);
|
|
1114
|
+
if (wide) widenCell(row, curX);
|
|
1115
|
+
markScreenRowDirty(curY);
|
|
543
1116
|
curX += charWidth;
|
|
544
1117
|
lastChar = ch;
|
|
545
1118
|
}
|
|
@@ -594,14 +1167,23 @@ function createScreen(options = {}) {
|
|
|
594
1167
|
const charCode = parts[0] ?? 32;
|
|
595
1168
|
if (charCode < 32 || charCode > 126) return;
|
|
596
1169
|
const { t, l, b, r } = normalizeRect(parts[1] ?? 1, parts[2] ?? 1, parts[3] ?? rows, parts[4] ?? cols);
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|
+
}
|
|
601
1178
|
}
|
|
1179
|
+
markScreenRowsDirty(t, Math.min(b, rows - 1));
|
|
602
1180
|
} else if (finalByte === "z" || finalByte === "{") {
|
|
603
1181
|
const { t, l, b, r } = normalizeRect(parts[0] ?? 1, parts[1] ?? 1, parts[2] ?? rows, parts[3] ?? cols);
|
|
604
|
-
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));
|
|
605
1187
|
} else if (finalByte === "v") {
|
|
606
1188
|
const src = normalizeRect(parts[0] ?? 1, parts[1] ?? 1, parts[2] ?? rows, parts[3] ?? cols);
|
|
607
1189
|
const dstTop = Math.max(1, parts[5] ?? 1) - 1;
|
|
@@ -610,34 +1192,35 @@ function createScreen(options = {}) {
|
|
|
610
1192
|
const w = src.r - src.l + 1;
|
|
611
1193
|
const snapshot = [];
|
|
612
1194
|
for (let row = 0; row < h; row++) {
|
|
1195
|
+
const srcRow = grid[src.t + row];
|
|
613
1196
|
const line = [];
|
|
614
|
-
for (let col = 0; col < w; col++)
|
|
615
|
-
const srcCell = grid[src.t + row]?.[src.l + col] ?? EMPTY_CELL;
|
|
616
|
-
line.push(srcCell === EMPTY_CELL ? EMPTY_CELL : { ...srcCell });
|
|
617
|
-
}
|
|
1197
|
+
for (let col = 0; col < w; col++) line.push(srcRow ? srcRow.getCellRaw(src.l + col) : emptyCell());
|
|
618
1198
|
snapshot.push(line);
|
|
619
1199
|
}
|
|
620
1200
|
for (let row = 0; row < h; row++) {
|
|
621
1201
|
const dr = dstTop + row;
|
|
622
1202
|
if (dr < 0 || dr >= rows) continue;
|
|
1203
|
+
const dstRow = grid[dr];
|
|
623
1204
|
for (let col = 0; col < w; col++) {
|
|
624
1205
|
const dc = dstLeft + col;
|
|
625
1206
|
if (dc < 0 || dc >= cols) continue;
|
|
626
|
-
|
|
1207
|
+
dstRow.setCellRaw(dc, snapshot[row][col]);
|
|
627
1208
|
}
|
|
628
1209
|
}
|
|
1210
|
+
markScreenRowsDirty(Math.max(0, dstTop), Math.min(dstTop + h - 1, rows - 1));
|
|
629
1211
|
} else if (finalByte === "r" || finalByte === "t") {
|
|
630
1212
|
const { t, l, b, r } = normalizeRect(parts[0] ?? 1, parts[1] ?? 1, parts[2] ?? rows, parts[3] ?? cols);
|
|
631
1213
|
const sgrParts = parts.slice(4);
|
|
632
1214
|
const reverse = finalByte === "t";
|
|
633
|
-
for (let row = t; row <= b && row < rows; row++)
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
cell =
|
|
637
|
-
|
|
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);
|
|
638
1221
|
}
|
|
639
|
-
applyRectAttrs(cell, sgrParts, reverse);
|
|
640
1222
|
}
|
|
1223
|
+
markScreenRowsDirty(t, Math.min(b, rows - 1));
|
|
641
1224
|
}
|
|
642
1225
|
return;
|
|
643
1226
|
}
|
|
@@ -653,11 +1236,15 @@ function createScreen(options = {}) {
|
|
|
653
1236
|
const b = Math.min(bArg, rows) - 1;
|
|
654
1237
|
const r = Math.min(rArg, cols) - 1;
|
|
655
1238
|
let sum = 0;
|
|
656
|
-
for (let row = t; row <= b && row < rows; row++)
|
|
657
|
-
const
|
|
658
|
-
if (
|
|
659
|
-
|
|
660
|
-
|
|
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
|
+
}
|
|
661
1248
|
}
|
|
662
1249
|
}
|
|
663
1250
|
const hex = sum.toString(16).toUpperCase().padStart(4, "0");
|
|
@@ -876,6 +1463,13 @@ function createScreen(options = {}) {
|
|
|
876
1463
|
if (onResponse) {
|
|
877
1464
|
if (params === "" || params === "0") onResponse("\x1BP!|00000000\x1B\\");
|
|
878
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;
|
|
879
1473
|
}
|
|
880
1474
|
}
|
|
881
1475
|
function handleCSIPrivate(params, intermediates, finalByte) {
|
|
@@ -997,10 +1591,12 @@ function createScreen(options = {}) {
|
|
|
997
1591
|
useAltScreen = true;
|
|
998
1592
|
grid = altGrid;
|
|
999
1593
|
softWrapped = altSoftWrapped;
|
|
1594
|
+
markAllDirty();
|
|
1000
1595
|
} else if (!set && useAltScreen) {
|
|
1001
1596
|
useAltScreen = false;
|
|
1002
1597
|
grid = mainGrid;
|
|
1003
1598
|
softWrapped = mainSoftWrapped;
|
|
1599
|
+
markAllDirty();
|
|
1004
1600
|
}
|
|
1005
1601
|
break;
|
|
1006
1602
|
case 66:
|
|
@@ -1032,6 +1628,7 @@ function createScreen(options = {}) {
|
|
|
1032
1628
|
softWrapped = altSoftWrapped;
|
|
1033
1629
|
curX = 0;
|
|
1034
1630
|
curY = 0;
|
|
1631
|
+
markAllDirty();
|
|
1035
1632
|
} else if (!set && useAltScreen) {
|
|
1036
1633
|
useAltScreen = false;
|
|
1037
1634
|
grid = mainGrid;
|
|
@@ -1039,6 +1636,7 @@ function createScreen(options = {}) {
|
|
|
1039
1636
|
curX = savedCurX;
|
|
1040
1637
|
curY = savedCurY;
|
|
1041
1638
|
clampCursor();
|
|
1639
|
+
markAllDirty();
|
|
1042
1640
|
}
|
|
1043
1641
|
break;
|
|
1044
1642
|
case 69:
|
|
@@ -1095,6 +1693,7 @@ function createScreen(options = {}) {
|
|
|
1095
1693
|
case 3:
|
|
1096
1694
|
for (let row = 0; row < rows; row++) eraseCells(row, 0, row, cols - 1);
|
|
1097
1695
|
if (mode === 3) scrollback.length = 0;
|
|
1696
|
+
markAllDirty();
|
|
1098
1697
|
break;
|
|
1099
1698
|
}
|
|
1100
1699
|
}
|
|
@@ -1116,11 +1715,8 @@ function createScreen(options = {}) {
|
|
|
1116
1715
|
function eraseCells(row, startCol, _endRow, endCol) {
|
|
1117
1716
|
const r = grid[row];
|
|
1118
1717
|
if (!r) return;
|
|
1119
|
-
for (let col = startCol; col <= endCol && col < cols; col++)
|
|
1120
|
-
|
|
1121
|
-
if (attrs.bg) cell.bg = { ...attrs.bg };
|
|
1122
|
-
r[col] = cell;
|
|
1123
|
-
}
|
|
1718
|
+
for (let col = startCol; col <= endCol && col < cols; col++) r.eraseWithBg(col, attrs.bg);
|
|
1719
|
+
markScreenRowDirty(row);
|
|
1124
1720
|
}
|
|
1125
1721
|
function handleInsertLines(count) {
|
|
1126
1722
|
if (curY < scrollTop || curY > scrollBottom) return;
|
|
@@ -1133,32 +1729,36 @@ function createScreen(options = {}) {
|
|
|
1133
1729
|
function handleDeleteChars(count) {
|
|
1134
1730
|
const row = grid[curY];
|
|
1135
1731
|
if (!row) return;
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
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());
|
|
1140
1741
|
}
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
row.push(emptyCell());
|
|
1144
|
-
}
|
|
1742
|
+
});
|
|
1743
|
+
markScreenRowDirty(curY);
|
|
1145
1744
|
}
|
|
1146
1745
|
function handleInsertChars(count) {
|
|
1147
1746
|
const row = grid[curY];
|
|
1148
1747
|
if (!row) return;
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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);
|
|
1157
1759
|
}
|
|
1158
1760
|
function handleEraseChars(count) {
|
|
1159
|
-
|
|
1160
|
-
if (!row) return;
|
|
1161
|
-
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));
|
|
1162
1762
|
}
|
|
1163
1763
|
/** Bounds of the active scroll/margin rectangle (inclusive). */
|
|
1164
1764
|
function activeRect() {
|
|
@@ -1176,9 +1776,11 @@ function createScreen(options = {}) {
|
|
|
1176
1776
|
if (!r) continue;
|
|
1177
1777
|
for (let col = left; col <= right; col++) {
|
|
1178
1778
|
const src = col + count;
|
|
1179
|
-
|
|
1779
|
+
if (src <= right) r.copyCellFrom(col, r, src);
|
|
1780
|
+
else r.setEmpty(col);
|
|
1180
1781
|
}
|
|
1181
1782
|
}
|
|
1783
|
+
markScreenRowsDirty(top, bottom);
|
|
1182
1784
|
}
|
|
1183
1785
|
function handleShiftRight(count) {
|
|
1184
1786
|
const { top, left, bottom, right } = activeRect();
|
|
@@ -1187,9 +1789,11 @@ function createScreen(options = {}) {
|
|
|
1187
1789
|
if (!r) continue;
|
|
1188
1790
|
for (let col = right; col >= left; col--) {
|
|
1189
1791
|
const src = col - count;
|
|
1190
|
-
|
|
1792
|
+
if (src >= left) r.copyCellFrom(col, r, src);
|
|
1793
|
+
else r.setEmpty(col);
|
|
1191
1794
|
}
|
|
1192
1795
|
}
|
|
1796
|
+
markScreenRowsDirty(top, bottom);
|
|
1193
1797
|
}
|
|
1194
1798
|
function handleInsertColumn(count) {
|
|
1195
1799
|
const { top, left, bottom, right } = activeRect();
|
|
@@ -1197,9 +1801,10 @@ function createScreen(options = {}) {
|
|
|
1197
1801
|
for (let row = top; row <= bottom; row++) {
|
|
1198
1802
|
const r = grid[row];
|
|
1199
1803
|
if (!r) continue;
|
|
1200
|
-
for (let col = right; col >= curX + count; col--) r
|
|
1201
|
-
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);
|
|
1202
1806
|
}
|
|
1807
|
+
markScreenRowsDirty(top, bottom);
|
|
1203
1808
|
}
|
|
1204
1809
|
function handleDeleteColumn(count) {
|
|
1205
1810
|
const { top, left, bottom, right } = activeRect();
|
|
@@ -1207,9 +1812,10 @@ function createScreen(options = {}) {
|
|
|
1207
1812
|
for (let row = top; row <= bottom; row++) {
|
|
1208
1813
|
const r = grid[row];
|
|
1209
1814
|
if (!r) continue;
|
|
1210
|
-
for (let col = curX; col + count <= right; col++) r
|
|
1211
|
-
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);
|
|
1212
1817
|
}
|
|
1818
|
+
markScreenRowsDirty(top, bottom);
|
|
1213
1819
|
}
|
|
1214
1820
|
/**
|
|
1215
1821
|
* Apply (or reverse-toggle) a list of SGR attribute codes to a single cell.
|
|
@@ -1363,7 +1969,10 @@ function createScreen(options = {}) {
|
|
|
1363
1969
|
case 35:
|
|
1364
1970
|
case 36:
|
|
1365
1971
|
case 37:
|
|
1366
|
-
attrs.fg = {
|
|
1972
|
+
attrs.fg = {
|
|
1973
|
+
...palette256[code - 30],
|
|
1974
|
+
index: code - 30
|
|
1975
|
+
};
|
|
1367
1976
|
break;
|
|
1368
1977
|
case 38: {
|
|
1369
1978
|
const subs = subParams.get(i);
|
|
@@ -1390,7 +1999,10 @@ function createScreen(options = {}) {
|
|
|
1390
1999
|
case 45:
|
|
1391
2000
|
case 46:
|
|
1392
2001
|
case 47:
|
|
1393
|
-
attrs.bg = {
|
|
2002
|
+
attrs.bg = {
|
|
2003
|
+
...palette256[code - 40],
|
|
2004
|
+
index: code - 40
|
|
2005
|
+
};
|
|
1394
2006
|
break;
|
|
1395
2007
|
case 48: {
|
|
1396
2008
|
const subs = subParams.get(i);
|
|
@@ -1440,7 +2052,10 @@ function createScreen(options = {}) {
|
|
|
1440
2052
|
case 95:
|
|
1441
2053
|
case 96:
|
|
1442
2054
|
case 97:
|
|
1443
|
-
attrs.fg = {
|
|
2055
|
+
attrs.fg = {
|
|
2056
|
+
...palette256[code - 90 + 8],
|
|
2057
|
+
index: code - 90 + 8
|
|
2058
|
+
};
|
|
1444
2059
|
break;
|
|
1445
2060
|
case 100:
|
|
1446
2061
|
case 101:
|
|
@@ -1450,7 +2065,10 @@ function createScreen(options = {}) {
|
|
|
1450
2065
|
case 105:
|
|
1451
2066
|
case 106:
|
|
1452
2067
|
case 107:
|
|
1453
|
-
attrs.bg = {
|
|
2068
|
+
attrs.bg = {
|
|
2069
|
+
...palette256[code - 100 + 8],
|
|
2070
|
+
index: code - 100 + 8
|
|
2071
|
+
};
|
|
1454
2072
|
break;
|
|
1455
2073
|
}
|
|
1456
2074
|
i++;
|
|
@@ -1461,12 +2079,16 @@ function createScreen(options = {}) {
|
|
|
1461
2079
|
const type = params[startIndex + 1];
|
|
1462
2080
|
if (type === 5 && startIndex + 2 < params.length) {
|
|
1463
2081
|
const idx = params[startIndex + 2];
|
|
2082
|
+
const entry = palette256[idx];
|
|
1464
2083
|
return {
|
|
1465
|
-
color:
|
|
2084
|
+
color: entry ? {
|
|
2085
|
+
...entry,
|
|
2086
|
+
index: idx
|
|
2087
|
+
} : {
|
|
1466
2088
|
r: 0,
|
|
1467
2089
|
g: 0,
|
|
1468
2090
|
b: 0
|
|
1469
|
-
}
|
|
2091
|
+
},
|
|
1470
2092
|
nextIndex: startIndex + 3
|
|
1471
2093
|
};
|
|
1472
2094
|
} else if (type === 2 && startIndex + 4 < params.length) return {
|
|
@@ -1485,11 +2107,15 @@ function createScreen(options = {}) {
|
|
|
1485
2107
|
const type = subs[1];
|
|
1486
2108
|
if (type === 5 && subs.length >= 3) {
|
|
1487
2109
|
const idx = subs[2];
|
|
1488
|
-
|
|
2110
|
+
const entry = palette256[idx];
|
|
2111
|
+
return entry ? {
|
|
2112
|
+
...entry,
|
|
2113
|
+
index: idx
|
|
2114
|
+
} : {
|
|
1489
2115
|
r: 0,
|
|
1490
2116
|
g: 0,
|
|
1491
2117
|
b: 0
|
|
1492
|
-
}
|
|
2118
|
+
};
|
|
1493
2119
|
} else if (type === 2) {
|
|
1494
2120
|
if (subs.length >= 5) {
|
|
1495
2121
|
if (subs.length >= 6) return {
|
|
@@ -1582,7 +2208,10 @@ function createScreen(options = {}) {
|
|
|
1582
2208
|
if (isNaN(idx) || idx < 0 || idx > 4) continue;
|
|
1583
2209
|
if (spec === "?") {
|
|
1584
2210
|
const c = specialColors.get(idx);
|
|
1585
|
-
if (onResponse)
|
|
2211
|
+
if (onResponse) {
|
|
2212
|
+
const payload = c ? formatColorResponse(c) : "rgb:0000/0000/0000";
|
|
2213
|
+
onResponse(`\x1b]5;${idx};${payload}\x1b\\`);
|
|
2214
|
+
}
|
|
1586
2215
|
} else {
|
|
1587
2216
|
const c = parseColorSpec(spec);
|
|
1588
2217
|
if (c) specialColors.set(idx, c);
|
|
@@ -1626,6 +2255,30 @@ function createScreen(options = {}) {
|
|
|
1626
2255
|
if (c) cursorColor = c;
|
|
1627
2256
|
}
|
|
1628
2257
|
break;
|
|
2258
|
+
case 13:
|
|
2259
|
+
if (value === "?") {
|
|
2260
|
+
if (onResponse) onResponse(`\x1b]13;${formatColorResponse(pointerFgColor ?? defaultFgColor ?? {
|
|
2261
|
+
r: 255,
|
|
2262
|
+
g: 255,
|
|
2263
|
+
b: 255
|
|
2264
|
+
})}\x1b\\`);
|
|
2265
|
+
} else {
|
|
2266
|
+
const c = parseColorSpec(value);
|
|
2267
|
+
if (c) pointerFgColor = c;
|
|
2268
|
+
}
|
|
2269
|
+
break;
|
|
2270
|
+
case 14:
|
|
2271
|
+
if (value === "?") {
|
|
2272
|
+
if (onResponse) onResponse(`\x1b]14;${formatColorResponse(pointerBgColor ?? defaultBgColor ?? {
|
|
2273
|
+
r: 0,
|
|
2274
|
+
g: 0,
|
|
2275
|
+
b: 0
|
|
2276
|
+
})}\x1b\\`);
|
|
2277
|
+
} else {
|
|
2278
|
+
const c = parseColorSpec(value);
|
|
2279
|
+
if (c) pointerBgColor = c;
|
|
2280
|
+
}
|
|
2281
|
+
break;
|
|
1629
2282
|
case 17:
|
|
1630
2283
|
if (value === "?") {
|
|
1631
2284
|
if (onResponse) onResponse(`\x1b]17;${formatColorResponse(highlightBgColor ?? defaultFgColor ?? {
|
|
@@ -1755,6 +2408,12 @@ function createScreen(options = {}) {
|
|
|
1755
2408
|
case 112:
|
|
1756
2409
|
cursorColor = null;
|
|
1757
2410
|
break;
|
|
2411
|
+
case 113:
|
|
2412
|
+
pointerFgColor = null;
|
|
2413
|
+
break;
|
|
2414
|
+
case 114:
|
|
2415
|
+
pointerBgColor = null;
|
|
2416
|
+
break;
|
|
1758
2417
|
case 117:
|
|
1759
2418
|
highlightBgColor = null;
|
|
1760
2419
|
break;
|
|
@@ -1766,7 +2425,10 @@ function createScreen(options = {}) {
|
|
|
1766
2425
|
if (clipSemi !== -1) {
|
|
1767
2426
|
const data = value.substring(clipSemi + 1);
|
|
1768
2427
|
if (data === "?") {
|
|
1769
|
-
if (onResponse)
|
|
2428
|
+
if (onResponse) {
|
|
2429
|
+
const encoded = btoa(clipboard);
|
|
2430
|
+
onResponse(`\x1b]52;c;${encoded}\x1b\\`);
|
|
2431
|
+
}
|
|
1770
2432
|
} else {
|
|
1771
2433
|
try {
|
|
1772
2434
|
clipboard = atob(data);
|
|
@@ -1820,9 +2482,58 @@ function createScreen(options = {}) {
|
|
|
1820
2482
|
}
|
|
1821
2483
|
}
|
|
1822
2484
|
break;
|
|
2485
|
+
case 701:
|
|
2486
|
+
if (value === "?") {
|
|
2487
|
+
if (onResponse) onResponse(`\x1b]701;${locale}\x1b\\`);
|
|
2488
|
+
} else if (value !== "") locale = value;
|
|
2489
|
+
break;
|
|
2490
|
+
case 702:
|
|
2491
|
+
if (onResponse) onResponse("\x1B]702;vterm.js;vterm;0;2\x1B\\");
|
|
2492
|
+
break;
|
|
2493
|
+
case 720: {
|
|
2494
|
+
const n = parseInt(value, 10);
|
|
2495
|
+
scrollViewport(!isNaN(n) && n > 0 ? n : 1);
|
|
2496
|
+
break;
|
|
2497
|
+
}
|
|
2498
|
+
case 721: {
|
|
2499
|
+
const n = parseInt(value, 10);
|
|
2500
|
+
scrollViewport(-(!isNaN(n) && n > 0 ? n : 1));
|
|
2501
|
+
break;
|
|
2502
|
+
}
|
|
2503
|
+
case 776:
|
|
2504
|
+
if (onResponse) onResponse(`\x1b]776;${CELL_W_PX};${CELL_H_PX};${FONT_ASCENT_PX}\x1b\\`);
|
|
2505
|
+
break;
|
|
2506
|
+
case 7770:
|
|
2507
|
+
if (value === "?") {
|
|
2508
|
+
if (onResponse) onResponse(`\x1b]7770;${fontSize}\x1b\\`);
|
|
2509
|
+
} else if (value === "") fontSize = 12;
|
|
2510
|
+
else {
|
|
2511
|
+
const n = parseInt(value, 10);
|
|
2512
|
+
if (!isNaN(n) && n > 0) fontSize = n;
|
|
2513
|
+
}
|
|
2514
|
+
break;
|
|
2515
|
+
case 7777:
|
|
2516
|
+
if (value === "?") {
|
|
2517
|
+
if (onResponse) onResponse(`\x1b]7777;${fontWindowSize}\x1b\\`);
|
|
2518
|
+
} else if (value === "") fontWindowSize = 12;
|
|
2519
|
+
else {
|
|
2520
|
+
const n = parseInt(value, 10);
|
|
2521
|
+
if (!isNaN(n) && n > 0) fontWindowSize = n;
|
|
2522
|
+
}
|
|
2523
|
+
break;
|
|
2524
|
+
case 30001:
|
|
2525
|
+
colorStack.push(snapshotColorState());
|
|
2526
|
+
break;
|
|
2527
|
+
case 30101: {
|
|
2528
|
+
const snapshot = colorStack.pop();
|
|
2529
|
+
if (snapshot) restoreColorState(snapshot);
|
|
2530
|
+
break;
|
|
2531
|
+
}
|
|
1823
2532
|
case 5522:
|
|
1824
|
-
if (value === "?" && onResponse)
|
|
1825
|
-
|
|
2533
|
+
if (value === "?" && onResponse) {
|
|
2534
|
+
const encoded = btoa(advancedClipboard);
|
|
2535
|
+
onResponse(`\x1b]5522;${encoded}\x1b\\`);
|
|
2536
|
+
} else try {
|
|
1826
2537
|
advancedClipboard = atob(value);
|
|
1827
2538
|
} catch {
|
|
1828
2539
|
advancedClipboard = "";
|
|
@@ -1851,8 +2562,10 @@ function createScreen(options = {}) {
|
|
|
1851
2562
|
return;
|
|
1852
2563
|
}
|
|
1853
2564
|
if (data.startsWith("+q") && onResponse) {
|
|
1854
|
-
if (data.substring(2) === "544e")
|
|
1855
|
-
|
|
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\\");
|
|
1856
2569
|
return;
|
|
1857
2570
|
}
|
|
1858
2571
|
}
|
|
@@ -1898,6 +2611,7 @@ function createScreen(options = {}) {
|
|
|
1898
2611
|
altGrid = makeGrid(cols, rows);
|
|
1899
2612
|
grid = mainGrid;
|
|
1900
2613
|
scrollback = [];
|
|
2614
|
+
scrollbackSoftWrapped = [];
|
|
1901
2615
|
curX = 0;
|
|
1902
2616
|
curY = 0;
|
|
1903
2617
|
curVisible = true;
|
|
@@ -1943,6 +2657,9 @@ function createScreen(options = {}) {
|
|
|
1943
2657
|
altScrollMode = false;
|
|
1944
2658
|
utf8MouseMode = false;
|
|
1945
2659
|
textScale = 1;
|
|
2660
|
+
fontSize = 12;
|
|
2661
|
+
fontWindowSize = 12;
|
|
2662
|
+
locale = "en_US.UTF-8";
|
|
1946
2663
|
advancedClipboard = "";
|
|
1947
2664
|
viewportOffset = 0;
|
|
1948
2665
|
charsetG0 = false;
|
|
@@ -1954,8 +2671,11 @@ function createScreen(options = {}) {
|
|
|
1954
2671
|
defaultBgColor = null;
|
|
1955
2672
|
cursorColor = null;
|
|
1956
2673
|
specialColors.clear();
|
|
2674
|
+
pointerFgColor = null;
|
|
2675
|
+
pointerBgColor = null;
|
|
1957
2676
|
highlightBgColor = null;
|
|
1958
2677
|
highlightFgColor = null;
|
|
2678
|
+
colorStack.length = 0;
|
|
1959
2679
|
tabStops = defaultTabStops(cols);
|
|
1960
2680
|
lastChar = "";
|
|
1961
2681
|
pendingRegionalIndicator = null;
|
|
@@ -1963,14 +2683,87 @@ function createScreen(options = {}) {
|
|
|
1963
2683
|
parserState = "ground";
|
|
1964
2684
|
escBuf = "";
|
|
1965
2685
|
oscBuf = "";
|
|
2686
|
+
dcsBuf = "";
|
|
2687
|
+
dcsStartRow = 0;
|
|
2688
|
+
dcsStartCol = 0;
|
|
1966
2689
|
apcBuf = "";
|
|
2690
|
+
utf8PendingBytes = [];
|
|
1967
2691
|
semanticZones = [];
|
|
1968
2692
|
mainSoftWrapped = new Array(rows).fill(false);
|
|
1969
2693
|
altSoftWrapped = new Array(rows).fill(false);
|
|
1970
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);
|
|
1971
2764
|
}
|
|
1972
2765
|
function process(data) {
|
|
1973
|
-
const text =
|
|
2766
|
+
const text = decodeInput(data);
|
|
1974
2767
|
for (let i = 0; i < text.length; i++) {
|
|
1975
2768
|
const ch = text[i];
|
|
1976
2769
|
const code = text.charCodeAt(i);
|
|
@@ -1979,18 +2772,25 @@ function createScreen(options = {}) {
|
|
|
1979
2772
|
if (code === 27) {
|
|
1980
2773
|
parserState = "escape";
|
|
1981
2774
|
escBuf = "";
|
|
1982
|
-
} else if (code === 7)
|
|
2775
|
+
} else if (code === 7) emitExecute(7);
|
|
2776
|
+
else if (code === 8) {
|
|
1983
2777
|
if (curX > 0) curX--;
|
|
1984
|
-
|
|
1985
|
-
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) {
|
|
1986
2783
|
softWrapped[curY] = false;
|
|
1987
2784
|
curY++;
|
|
1988
2785
|
if (curY > scrollBottom) {
|
|
1989
2786
|
curY = scrollBottom;
|
|
1990
2787
|
scrollUp(scrollTop, scrollBottom);
|
|
1991
2788
|
}
|
|
1992
|
-
|
|
1993
|
-
else if (code
|
|
2789
|
+
emitExecute(code);
|
|
2790
|
+
} else if (code === 13) {
|
|
2791
|
+
curX = 0;
|
|
2792
|
+
emitExecute(13);
|
|
2793
|
+
} else if (code >= 32) {
|
|
1994
2794
|
let char = ch;
|
|
1995
2795
|
if (code >= 55296 && code <= 56319 && i + 1 < text.length) {
|
|
1996
2796
|
const nextCode = text.charCodeAt(i + 1);
|
|
@@ -2000,6 +2800,7 @@ function createScreen(options = {}) {
|
|
|
2000
2800
|
}
|
|
2001
2801
|
}
|
|
2002
2802
|
writeChar(char);
|
|
2803
|
+
if (parserListeners.size > 0) printRunParts.push(char);
|
|
2003
2804
|
}
|
|
2004
2805
|
break;
|
|
2005
2806
|
case "escape":
|
|
@@ -2018,6 +2819,7 @@ function createScreen(options = {}) {
|
|
|
2018
2819
|
} else if (ch === "c") {
|
|
2019
2820
|
fullReset();
|
|
2020
2821
|
parserState = "ground";
|
|
2822
|
+
emitEsc("c", "");
|
|
2021
2823
|
} else if (ch === "D") {
|
|
2022
2824
|
curY++;
|
|
2023
2825
|
if (curY > scrollBottom) {
|
|
@@ -2025,6 +2827,7 @@ function createScreen(options = {}) {
|
|
|
2025
2827
|
scrollUp(scrollTop, scrollBottom);
|
|
2026
2828
|
}
|
|
2027
2829
|
parserState = "ground";
|
|
2830
|
+
emitEsc("D", "");
|
|
2028
2831
|
} else if (ch === "M") {
|
|
2029
2832
|
curY--;
|
|
2030
2833
|
if (curY < scrollTop) {
|
|
@@ -2032,6 +2835,7 @@ function createScreen(options = {}) {
|
|
|
2032
2835
|
scrollDown(scrollTop, scrollBottom);
|
|
2033
2836
|
}
|
|
2034
2837
|
parserState = "ground";
|
|
2838
|
+
emitEsc("M", "");
|
|
2035
2839
|
} else if (ch === "7") {
|
|
2036
2840
|
savedState = {
|
|
2037
2841
|
curX,
|
|
@@ -2047,6 +2851,7 @@ function createScreen(options = {}) {
|
|
|
2047
2851
|
charsetG0
|
|
2048
2852
|
};
|
|
2049
2853
|
parserState = "ground";
|
|
2854
|
+
emitEsc("7", "");
|
|
2050
2855
|
} else if (ch === "8") {
|
|
2051
2856
|
curX = savedState.curX;
|
|
2052
2857
|
curY = savedState.curY;
|
|
@@ -2061,6 +2866,7 @@ function createScreen(options = {}) {
|
|
|
2061
2866
|
charsetG0 = savedState.charsetG0;
|
|
2062
2867
|
clampCursor();
|
|
2063
2868
|
parserState = "ground";
|
|
2869
|
+
emitEsc("8", "");
|
|
2064
2870
|
} else if (ch === "E") {
|
|
2065
2871
|
curX = 0;
|
|
2066
2872
|
curY++;
|
|
@@ -2069,18 +2875,28 @@ function createScreen(options = {}) {
|
|
|
2069
2875
|
scrollUp(scrollTop, scrollBottom);
|
|
2070
2876
|
}
|
|
2071
2877
|
parserState = "ground";
|
|
2878
|
+
emitEsc("E", "");
|
|
2072
2879
|
} else if (ch === "H") {
|
|
2073
2880
|
tabStops.add(curX);
|
|
2074
2881
|
parserState = "ground";
|
|
2075
|
-
|
|
2076
|
-
else if (ch === "
|
|
2077
|
-
|
|
2078
|
-
|
|
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 === "=") {
|
|
2079
2893
|
applicationKeypad = true;
|
|
2080
2894
|
parserState = "ground";
|
|
2895
|
+
emitEsc("=", "");
|
|
2081
2896
|
} else if (ch === ">") {
|
|
2082
2897
|
applicationKeypad = false;
|
|
2083
2898
|
parserState = "ground";
|
|
2899
|
+
emitEsc(">", "");
|
|
2084
2900
|
} else if (ch === "_") {
|
|
2085
2901
|
parserState = "apc";
|
|
2086
2902
|
apcBuf = "";
|
|
@@ -2090,22 +2906,22 @@ function createScreen(options = {}) {
|
|
|
2090
2906
|
if (ch === "0") charsetG0 = true;
|
|
2091
2907
|
else charsetG0 = false;
|
|
2092
2908
|
parserState = "ground";
|
|
2909
|
+
emitEsc(ch, escIntermediate);
|
|
2093
2910
|
break;
|
|
2094
2911
|
case "escape_hash":
|
|
2095
2912
|
if (ch === "8") {
|
|
2913
|
+
const alignCell = emptyCell();
|
|
2914
|
+
alignCell.char = "E";
|
|
2096
2915
|
for (let r = 0; r < rows; r++) {
|
|
2097
2916
|
const row = grid[r];
|
|
2098
|
-
for (let c = 0; c < cols; c++)
|
|
2099
|
-
const cell = emptyCell();
|
|
2100
|
-
cell.char = "E";
|
|
2101
|
-
row[c] = cell;
|
|
2102
|
-
}
|
|
2917
|
+
for (let c = 0; c < cols; c++) row.setCellRaw(c, alignCell);
|
|
2103
2918
|
softWrapped[r] = false;
|
|
2104
2919
|
}
|
|
2105
2920
|
curX = 0;
|
|
2106
2921
|
curY = 0;
|
|
2107
2922
|
}
|
|
2108
2923
|
parserState = "ground";
|
|
2924
|
+
emitEsc(ch, escIntermediate);
|
|
2109
2925
|
break;
|
|
2110
2926
|
case "csi":
|
|
2111
2927
|
if (code >= 64 && code <= 126) {
|
|
@@ -2127,6 +2943,7 @@ function createScreen(options = {}) {
|
|
|
2127
2943
|
else if (paramPart.startsWith("=")) handleCSIEq(paramPart.substring(1), intermediatePart, ch);
|
|
2128
2944
|
else handleCSI(paramPart, intermediatePart, ch);
|
|
2129
2945
|
parserState = "ground";
|
|
2946
|
+
emitCsiEvent(paramPart, intermediatePart, ch);
|
|
2130
2947
|
} else if (escBuf.length >= 256) parserState = "ground";
|
|
2131
2948
|
else escBuf += ch;
|
|
2132
2949
|
break;
|
|
@@ -2134,14 +2951,18 @@ function createScreen(options = {}) {
|
|
|
2134
2951
|
if (code === 7) {
|
|
2135
2952
|
handleOSC(oscBuf);
|
|
2136
2953
|
parserState = "ground";
|
|
2954
|
+
emitOscEvent(oscBuf);
|
|
2137
2955
|
} else if (code === 27) parserState = "osc_st";
|
|
2138
2956
|
else if (oscBuf.length >= 4096) parserState = "ground";
|
|
2139
2957
|
else oscBuf += ch;
|
|
2140
2958
|
break;
|
|
2141
|
-
case "osc_st":
|
|
2142
|
-
|
|
2959
|
+
case "osc_st": {
|
|
2960
|
+
const oscComplete = ch === "\\";
|
|
2961
|
+
if (oscComplete) handleOSC(oscBuf);
|
|
2143
2962
|
parserState = "ground";
|
|
2963
|
+
if (oscComplete) emitOscEvent(oscBuf);
|
|
2144
2964
|
break;
|
|
2965
|
+
}
|
|
2145
2966
|
case "dcs":
|
|
2146
2967
|
if (code === 27) parserState = "dcs_st";
|
|
2147
2968
|
else if (code === 7) {
|
|
@@ -2169,6 +2990,12 @@ function createScreen(options = {}) {
|
|
|
2169
2990
|
break;
|
|
2170
2991
|
}
|
|
2171
2992
|
}
|
|
2993
|
+
flushPrintRun();
|
|
2994
|
+
if (opListeners.size > 0) emitOp({
|
|
2995
|
+
type: "output",
|
|
2996
|
+
data
|
|
2997
|
+
});
|
|
2998
|
+
flushSignals();
|
|
2172
2999
|
}
|
|
2173
3000
|
/**
|
|
2174
3001
|
* Reconstruct logical lines from a grid, joining rows that were soft-wrapped.
|
|
@@ -2180,7 +3007,7 @@ function createScreen(options = {}) {
|
|
|
2180
3007
|
for (let r = 0; r < srcRows; r++) {
|
|
2181
3008
|
const row = srcGrid[r];
|
|
2182
3009
|
if (!row) continue;
|
|
2183
|
-
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));
|
|
2184
3011
|
if (srcSoftWrapped[r]) continue;
|
|
2185
3012
|
logical.push(currentLine);
|
|
2186
3013
|
currentLine = [];
|
|
@@ -2190,20 +3017,34 @@ function createScreen(options = {}) {
|
|
|
2190
3017
|
}
|
|
2191
3018
|
/**
|
|
2192
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.
|
|
2193
3028
|
*/
|
|
2194
|
-
function rewrapLines(logicalLines, newCols) {
|
|
3029
|
+
function rewrapLines(logicalLines, newCols, track) {
|
|
2195
3030
|
const outRows = [];
|
|
2196
3031
|
const outWrapped = [];
|
|
2197
|
-
|
|
3032
|
+
let tracked = null;
|
|
3033
|
+
for (let li = 0; li < logicalLines.length; li++) {
|
|
3034
|
+
const line = logicalLines[li];
|
|
2198
3035
|
let lineLen = line.length;
|
|
2199
3036
|
while (lineLen > 0) {
|
|
2200
3037
|
const cell = line[lineLen - 1];
|
|
2201
|
-
if (cell
|
|
3038
|
+
if (cell.char === "" && !cell.wide) lineLen--;
|
|
2202
3039
|
else break;
|
|
2203
3040
|
}
|
|
2204
3041
|
if (lineLen === 0) {
|
|
2205
3042
|
outRows.push(makeRow(newCols));
|
|
2206
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
|
+
};
|
|
2207
3048
|
continue;
|
|
2208
3049
|
}
|
|
2209
3050
|
let pos = 0;
|
|
@@ -2213,34 +3054,65 @@ function createScreen(options = {}) {
|
|
|
2213
3054
|
while (col < newCols && pos < lineLen) {
|
|
2214
3055
|
const cell = line[pos];
|
|
2215
3056
|
if (cell.wide && col + 2 > newCols) break;
|
|
2216
|
-
|
|
3057
|
+
if (track !== void 0 && track.line === li && pos === track.offset) tracked = {
|
|
3058
|
+
row: outRows.length,
|
|
3059
|
+
col
|
|
3060
|
+
};
|
|
3061
|
+
row.setCellRaw(col, cell);
|
|
2217
3062
|
col++;
|
|
2218
3063
|
pos++;
|
|
2219
3064
|
}
|
|
2220
3065
|
const moreContent = pos < lineLen;
|
|
2221
3066
|
outRows.push(row);
|
|
2222
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
|
+
}
|
|
2223
3075
|
}
|
|
2224
3076
|
}
|
|
2225
3077
|
return {
|
|
2226
3078
|
rows: outRows,
|
|
2227
|
-
wrapped: outWrapped
|
|
3079
|
+
wrapped: outWrapped,
|
|
3080
|
+
tracked
|
|
2228
3081
|
};
|
|
2229
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
|
+
}
|
|
2230
3088
|
/**
|
|
2231
3089
|
* Trim trailing empty rows from reflowed result, so they don't push content off the top
|
|
2232
3090
|
* when we take the last newRows rows.
|
|
2233
3091
|
*/
|
|
2234
3092
|
function trimTrailingEmptyRows(result) {
|
|
2235
|
-
while (result.rows.length > 1)
|
|
2236
|
-
result.rows.
|
|
2237
|
-
result.wrapped.
|
|
2238
|
-
|
|
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
|
+
}
|
|
2239
3100
|
}
|
|
2240
3101
|
function resize(newCols, newRows) {
|
|
2241
|
-
|
|
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);
|
|
2242
3114
|
trimTrailingEmptyRows(mainResult);
|
|
2243
|
-
const altResult = rewrapLines(getLogicalLines(altGrid, altSoftWrapped, rows), newCols);
|
|
3115
|
+
const altResult = rewrapLines(getLogicalLines(altGrid, altSoftWrapped, rows), newCols, useAltScreen ? cursorTrack : void 0);
|
|
2244
3116
|
trimTrailingEmptyRows(altResult);
|
|
2245
3117
|
const newMain = makeGrid(newCols, newRows);
|
|
2246
3118
|
const newMainWrapped = new Array(newRows).fill(false);
|
|
@@ -2256,6 +3128,12 @@ function createScreen(options = {}) {
|
|
|
2256
3128
|
newAlt[r] = altResult.rows[altStartRow + r];
|
|
2257
3129
|
newAltWrapped[r] = altResult.wrapped[altStartRow + r];
|
|
2258
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
|
+
}
|
|
2259
3137
|
mainGrid = newMain;
|
|
2260
3138
|
altGrid = newAlt;
|
|
2261
3139
|
mainSoftWrapped = newMainWrapped;
|
|
@@ -2271,31 +3149,265 @@ function createScreen(options = {}) {
|
|
|
2271
3149
|
rightMargin = cols - 1;
|
|
2272
3150
|
if (newCols > oldCols) for (let i = Math.max(8, Math.ceil(oldCols / 8) * 8); i < newCols; i += 8) tabStops.add(i);
|
|
2273
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();
|
|
2274
3345
|
}
|
|
2275
3346
|
function getCell(row, col) {
|
|
2276
3347
|
const r = grid[row];
|
|
2277
3348
|
if (!r || col >= cols) return emptyCell();
|
|
2278
|
-
return
|
|
3349
|
+
return stripCellColorIndex(r.getCellRaw(col));
|
|
2279
3350
|
}
|
|
2280
3351
|
function getLine(row) {
|
|
2281
3352
|
const r = grid[row];
|
|
2282
|
-
if (!r) return makeRow(cols);
|
|
2283
|
-
return r.
|
|
3353
|
+
if (!r) return makeRow(cols).toCells();
|
|
3354
|
+
return r.toCells().map(stripCellColorIndex);
|
|
2284
3355
|
}
|
|
2285
3356
|
function getText() {
|
|
2286
3357
|
const lines = [];
|
|
2287
3358
|
for (let r = 0; r < rows; r++) lines.push(rowToString(grid[r]));
|
|
2288
3359
|
return lines.join("\n");
|
|
2289
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
|
+
}
|
|
2290
3402
|
function rowToString(row) {
|
|
2291
3403
|
let line = "";
|
|
2292
3404
|
for (let i = 0; i < row.length; i++) {
|
|
2293
|
-
const
|
|
2294
|
-
if (
|
|
2295
|
-
else if (
|
|
2296
|
-
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;
|
|
2297
3409
|
line += " ";
|
|
2298
|
-
} else line +=
|
|
3410
|
+
} else line += ch;
|
|
2299
3411
|
}
|
|
2300
3412
|
return line.replace(/\s+$/, "");
|
|
2301
3413
|
}
|
|
@@ -2308,10 +3420,10 @@ function createScreen(options = {}) {
|
|
|
2308
3420
|
const colEnd = row === endRow ? endCol : cols;
|
|
2309
3421
|
let line = "";
|
|
2310
3422
|
for (let col = colStart; col < colEnd; col++) {
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
if (
|
|
2314
|
-
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 || " ";
|
|
2315
3427
|
}
|
|
2316
3428
|
parts.push(line.replace(/\s+$/, ""));
|
|
2317
3429
|
}
|
|
@@ -2341,6 +3453,176 @@ function createScreen(options = {}) {
|
|
|
2341
3453
|
default: return false;
|
|
2342
3454
|
}
|
|
2343
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
|
+
}
|
|
2344
3626
|
return {
|
|
2345
3627
|
get cols() {
|
|
2346
3628
|
return cols;
|
|
@@ -2348,13 +3630,33 @@ function createScreen(options = {}) {
|
|
|
2348
3630
|
get rows() {
|
|
2349
3631
|
return rows;
|
|
2350
3632
|
},
|
|
3633
|
+
get signals() {
|
|
3634
|
+
return getSignals();
|
|
3635
|
+
},
|
|
2351
3636
|
process,
|
|
2352
3637
|
resize,
|
|
3638
|
+
apply,
|
|
3639
|
+
tapOps,
|
|
3640
|
+
tapParser,
|
|
2353
3641
|
reset: fullReset,
|
|
3642
|
+
snapshot,
|
|
3643
|
+
restore,
|
|
2354
3644
|
getCell,
|
|
3645
|
+
getRow: getLine,
|
|
2355
3646
|
getLine,
|
|
2356
3647
|
getText,
|
|
3648
|
+
getScrollbackText,
|
|
2357
3649
|
getTextRange,
|
|
3650
|
+
totalRows,
|
|
3651
|
+
screenRows,
|
|
3652
|
+
viewportTop,
|
|
3653
|
+
getRowAbsolute,
|
|
3654
|
+
firstRetainedRow,
|
|
3655
|
+
takeDirty,
|
|
3656
|
+
getCursor: () => ({
|
|
3657
|
+
col: curX,
|
|
3658
|
+
row: curY
|
|
3659
|
+
}),
|
|
2358
3660
|
getCursorPosition: () => ({
|
|
2359
3661
|
x: curX,
|
|
2360
3662
|
y: curY
|
|
@@ -2369,12 +3671,890 @@ function createScreen(options = {}) {
|
|
|
2369
3671
|
getNotifications: () => [...notifications],
|
|
2370
3672
|
getScrollbackLength: () => scrollback.length,
|
|
2371
3673
|
getViewportOffset: () => viewportOffset,
|
|
2372
|
-
scrollViewport
|
|
2373
|
-
viewportOffset = Math.max(0, Math.min(scrollback.length, viewportOffset + delta));
|
|
2374
|
-
},
|
|
3674
|
+
scrollViewport,
|
|
2375
3675
|
getSemanticZones: () => semanticZones.map((z) => ({ ...z })),
|
|
2376
|
-
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
|
|
2377
4557
|
};
|
|
2378
4558
|
}
|
|
2379
4559
|
//#endregion
|
|
2380
|
-
export { createScreen as createVtermScreen };
|
|
4560
|
+
export { createScreen as createVtermScreen, decodeScreenSnapshotBinary, encodeScreenSnapshotBinary, serializeSnapshot };
|