vterm.js 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  //#region src/screen.ts
2
2
  /** Default retained payload bound for one APC/DCS sequence (16 MiB of ASCII/base64). */
3
- const DEFAULT_MAX_STRING_SEQUENCE_LENGTH = 16 * 1024 * 1024;
3
+ const DEFAULT_MAX_STRING_SEQUENCE_LENGTH = 16777216;
4
4
  /** Frozen sentinel for unwritten cells — never mutate, copy-on-write in writeChar(). */
5
5
  const EMPTY_CELL = Object.freeze({
6
6
  char: "",
@@ -58,7 +58,7 @@ const F_HIDDEN$1 = 64;
58
58
  const F_BLINK$1 = 128;
59
59
  const F_WIDE$1 = 256;
60
60
  const UL_SHIFT$1 = 9;
61
- const UL_MASK$1 = 7 << UL_SHIFT$1;
61
+ const UL_MASK$1 = 3584;
62
62
  const F_HAS_FG = 4096;
63
63
  const F_HAS_BG = 8192;
64
64
  const F_HAS_UL = 16384;
@@ -79,6 +79,17 @@ function underlineToBits(u) {
79
79
  function bitsToUnderline(meta) {
80
80
  return UL_STYLES[(meta & UL_MASK$1) >> UL_SHIFT$1] ?? "none";
81
81
  }
82
+ /** Require an invariant-owned value and fail where the impossible state is first observed. */
83
+ function required(value, subject) {
84
+ if (value === null || value === void 0) throw new Error(`Missing ${subject}`);
85
+ return value;
86
+ }
87
+ /** Read a dense indexed value and fail at the violated invariant instead of propagating `undefined`. */
88
+ function itemAt(items, index, subject) {
89
+ const value = items[index];
90
+ if (value === void 0) throw new RangeError(`${subject}[${index}] is outside its ${items.length}-item range`);
91
+ return value;
92
+ }
82
93
  /** Pack a color's 8-bit channels into a 24-bit `0xRRGGBB` word. */
83
94
  function packRgb(c) {
84
95
  return ((c.r & 255) << 16 | (c.g & 255) << 8 | c.b & 255) >>> 0;
@@ -110,7 +121,7 @@ function makePackedRow(width) {
110
121
  let ulIdx = null;
111
122
  let urlMap = null;
112
123
  function setFg(col, c) {
113
- if (fgRgb === null) {
124
+ if (fgRgb === null || fgIdx === null) {
114
125
  fgRgb = new Uint32Array(width);
115
126
  fgIdx = new Int16Array(width);
116
127
  }
@@ -118,7 +129,7 @@ function makePackedRow(width) {
118
129
  fgIdx[col] = c.index ?? -1;
119
130
  }
120
131
  function setBg(col, c) {
121
- if (bgRgb === null) {
132
+ if (bgRgb === null || bgIdx === null) {
122
133
  bgRgb = new Uint32Array(width);
123
134
  bgIdx = new Int16Array(width);
124
135
  }
@@ -126,7 +137,7 @@ function makePackedRow(width) {
126
137
  bgIdx[col] = c.index ?? -1;
127
138
  }
128
139
  function setUl(col, c) {
129
- if (ulRgb === null) {
140
+ if (ulRgb === null || ulIdx === null) {
130
141
  ulRgb = new Uint32Array(width);
131
142
  ulIdx = new Int16Array(width);
132
143
  }
@@ -211,27 +222,27 @@ function makePackedRow(width) {
211
222
  } else meta[col] = 0;
212
223
  },
213
224
  widen(col) {
214
- meta[col] = (meta[col] | F_WIDE$1) >>> 0;
225
+ meta[col] = (itemAt(meta, col, "packed row metadata") | F_WIDE$1) >>> 0;
215
226
  if (col + 1 < width) row.setEmpty(col + 1);
216
227
  },
217
228
  appendChar(col, ch) {
218
- chars[col] = chars[col] + ch;
229
+ chars[col] = itemAt(chars, col, "packed row characters") + ch;
219
230
  },
220
231
  copyCellFrom(col, src, srcCol) {
221
232
  row.setCellRaw(col, src.getCellRaw(srcCol));
222
233
  },
223
234
  getCellRaw(col) {
224
- const m = meta[col];
235
+ const m = itemAt(meta, col, "packed row metadata");
225
236
  if (m === 0 && chars[col] === "") return emptyCell();
226
237
  return {
227
- char: chars[col],
228
- fg: m & F_HAS_FG ? unpackColor(fgRgb[col], fgIdx[col]) : null,
229
- bg: m & F_HAS_BG ? unpackColor(bgRgb[col], bgIdx[col]) : null,
238
+ char: itemAt(chars, col, "packed row characters"),
239
+ fg: m & F_HAS_FG ? unpackColor(itemAt(required(fgRgb, "foreground color plane"), col, "foreground color plane"), itemAt(required(fgIdx, "foreground color index plane"), col, "foreground color index plane")) : null,
240
+ bg: m & F_HAS_BG ? unpackColor(itemAt(required(bgRgb, "background color plane"), col, "background color plane"), itemAt(required(bgIdx, "background color index plane"), col, "background color index plane")) : null,
230
241
  bold: (m & F_BOLD$1) !== 0,
231
242
  faint: (m & F_FAINT$1) !== 0,
232
243
  italic: (m & F_ITALIC$1) !== 0,
233
244
  underline: bitsToUnderline(m),
234
- underlineColor: m & F_HAS_UL ? unpackColor(ulRgb[col], ulIdx[col]) : null,
245
+ underlineColor: m & F_HAS_UL ? unpackColor(itemAt(required(ulRgb, "underline color plane"), col, "underline color plane"), itemAt(required(ulIdx, "underline color index plane"), col, "underline color index plane")) : null,
235
246
  overline: (m & F_OVERLINE$1) !== 0,
236
247
  strikethrough: (m & F_STRIKE$1) !== 0,
237
248
  inverse: (m & F_INVERSE$1) !== 0,
@@ -239,7 +250,7 @@ function makePackedRow(width) {
239
250
  blink: (m & F_BLINK$1) !== 0,
240
251
  protected: (m & F_PROTECTED$1) !== 0,
241
252
  wide: (m & F_WIDE$1) !== 0,
242
- url: m & F_HAS_URL ? urlMap.get(col) ?? null : null
253
+ url: m & F_HAS_URL ? required(urlMap, "packed row URL map").get(col) ?? null : null
243
254
  };
244
255
  },
245
256
  toCells() {
@@ -254,10 +265,10 @@ function makePackedRow(width) {
254
265
  return meta[col] === 0 && chars[col] === "";
255
266
  },
256
267
  getChar(col) {
257
- return chars[col];
268
+ return itemAt(chars, col, "packed row characters");
258
269
  },
259
270
  isWide(col) {
260
- return (meta[col] & F_WIDE$1) !== 0;
271
+ return (itemAt(meta, col, "packed row metadata") & F_WIDE$1) !== 0;
261
272
  }
262
273
  };
263
274
  return row;
@@ -361,9 +372,9 @@ function buildPalette256() {
361
372
  255
362
373
  ];
363
374
  for (let r = 0; r < 6; r++) for (let g = 0; g < 6; g++) for (let b = 0; b < 6; b++) palette.push({
364
- r: levels[r],
365
- g: levels[g],
366
- b: levels[b]
375
+ r: itemAt(levels, r, "ANSI cube red levels"),
376
+ g: itemAt(levels, g, "ANSI cube green levels"),
377
+ b: itemAt(levels, b, "ANSI cube blue levels")
367
378
  });
368
379
  for (let i = 0; i < 24; i++) {
369
380
  const v = 8 + i * 10;
@@ -389,14 +400,14 @@ function parseColorSpec(spec) {
389
400
  return Math.round(v * 255 / max);
390
401
  };
391
402
  return {
392
- r: scale(rgbMatch[1]),
393
- g: scale(rgbMatch[2]),
394
- b: scale(rgbMatch[3])
403
+ r: scale(itemAt(rgbMatch, 1, "X11 red capture")),
404
+ g: scale(itemAt(rgbMatch, 2, "X11 green capture")),
405
+ b: scale(itemAt(rgbMatch, 3, "X11 blue capture"))
395
406
  };
396
407
  }
397
408
  const hex6 = /^#([0-9a-fA-F]{6})$/.exec(s);
398
409
  if (hex6) {
399
- const v = hex6[1];
410
+ const v = itemAt(hex6, 1, "six-digit hex capture");
400
411
  return {
401
412
  r: parseInt(v.substring(0, 2), 16),
402
413
  g: parseInt(v.substring(2, 4), 16),
@@ -405,12 +416,12 @@ function parseColorSpec(spec) {
405
416
  }
406
417
  const hex3 = /^#([0-9a-fA-F]{3})$/.exec(s);
407
418
  if (hex3) {
408
- const v = hex3[1];
419
+ const v = itemAt(hex3, 1, "three-digit hex capture");
409
420
  const expand = (ch) => parseInt(ch + ch, 16);
410
421
  return {
411
- r: expand(v[0]),
412
- g: expand(v[1]),
413
- b: expand(v[2])
422
+ r: expand(itemAt(v, 0, "short hex red channel")),
423
+ g: expand(itemAt(v, 1, "short hex green channel")),
424
+ b: expand(itemAt(v, 2, "short hex blue channel"))
414
425
  };
415
426
  }
416
427
  return null;
@@ -467,6 +478,7 @@ function createScreen(options = {}) {
467
478
  let rows = options.rows ?? 24;
468
479
  let scrollbackLimit = options.scrollbackLimit ?? 1e3;
469
480
  const onResponse = options.onResponse;
481
+ const kittyGraphicsImages = options.kittyGraphicsImages ?? false;
470
482
  const maxStringSequenceLength = options.maxStringSequenceLength ?? 16777216;
471
483
  if (!Number.isSafeInteger(maxStringSequenceLength) || maxStringSequenceLength < 0) throw new RangeError("maxStringSequenceLength must be a non-negative safe integer");
472
484
  let mainGrid = makeGrid(cols, rows);
@@ -484,12 +496,15 @@ function createScreen(options = {}) {
484
496
  attrs: resetAttrs(),
485
497
  originMode: false,
486
498
  autoWrap: true,
487
- charsetG0: false
499
+ charsetG0: false,
500
+ charsetG1: false,
501
+ activeG1: false
488
502
  };
489
503
  let savedCurX = 0;
490
504
  let savedCurY = 0;
491
505
  let attrs = resetAttrs();
492
506
  let title = "";
507
+ let iconLabel = "";
493
508
  let useAltScreen = false;
494
509
  let bracketedPaste = false;
495
510
  let applicationCursor = false;
@@ -506,7 +521,6 @@ function createScreen(options = {}) {
506
521
  let syncOutput = false;
507
522
  let kittyKeyboardFlags = 0;
508
523
  let kittyKeyboardStack = [];
509
- let hasKittyGraphics = false;
510
524
  let scrollTop = 0;
511
525
  let scrollBottom = rows - 1;
512
526
  let leftRightMarginMode = false;
@@ -663,6 +677,8 @@ function createScreen(options = {}) {
663
677
  assertBoolean("savedState.originMode", saved.originMode);
664
678
  assertBoolean("savedState.autoWrap", saved.autoWrap);
665
679
  assertBoolean("savedState.charsetG0", saved.charsetG0);
680
+ if (Object.hasOwn(saved, "charsetG1")) assertBoolean("savedState.charsetG1", saved.charsetG1);
681
+ if (Object.hasOwn(saved, "activeG1")) assertBoolean("savedState.activeG1", saved.activeG1);
666
682
  assertRecord("attrs", value.attrs);
667
683
  const modes = value.modes;
668
684
  assertRecord("modes", modes);
@@ -701,6 +717,7 @@ function createScreen(options = {}) {
701
717
  if (!Array.isArray(value.tabStops)) throw new TypeError("Invalid vterm snapshot tabStops");
702
718
  for (const stop of value.tabStops) assertInteger("tabStops", stop);
703
719
  assertString("title", value.title);
720
+ if (Object.hasOwn(value, "iconLabel")) assertString("iconLabel", value.iconLabel);
704
721
  assertString("clipboard", value.clipboard);
705
722
  assertString("cwd", value.cwd);
706
723
  if (!Array.isArray(value.notifications) || value.notifications.some((entry) => typeof entry !== "string")) throw new TypeError("Invalid vterm snapshot notifications");
@@ -745,6 +762,8 @@ function createScreen(options = {}) {
745
762
  const unicode = value.unicode;
746
763
  assertRecord("unicode", unicode);
747
764
  assertBoolean("unicode.charsetG0", unicode.charsetG0);
765
+ if (Object.hasOwn(unicode, "charsetG1")) assertBoolean("unicode.charsetG1", unicode.charsetG1);
766
+ if (Object.hasOwn(unicode, "activeG1")) assertBoolean("unicode.activeG1", unicode.activeG1);
748
767
  assertString("unicode.lastChar", unicode.lastChar);
749
768
  if (unicode.pendingRegionalIndicator !== null) assertString("unicode.pendingRegionalIndicator", unicode.pendingRegionalIndicator);
750
769
  assertBoolean("unicode.afterZWJ", unicode.afterZWJ);
@@ -765,15 +784,14 @@ function createScreen(options = {}) {
765
784
  return 0;
766
785
  }
767
786
  let textScale = 1;
768
- const CELL_W_PX = 8;
769
- const CELL_H_PX = 17;
770
- const FONT_ASCENT_PX = 14;
771
787
  let fontSize = 12;
772
788
  let fontWindowSize = 12;
773
789
  let locale = "en_US.UTF-8";
774
790
  let advancedClipboard = "";
775
791
  let viewportOffset = 0;
776
792
  let charsetG0 = false;
793
+ let charsetG1 = false;
794
+ let activeG1 = false;
777
795
  let clipboard = "";
778
796
  let cwd = "";
779
797
  let notifications = [];
@@ -939,13 +957,12 @@ function createScreen(options = {}) {
939
957
  if (bytes.length === 0) return 0;
940
958
  let continuationCount = 0;
941
959
  let leadIndex = bytes.length - 1;
942
- while (leadIndex >= 0 && (bytes[leadIndex] & 192) === 128 && continuationCount < 3) {
960
+ while (leadIndex >= 0 && (itemAt(bytes, leadIndex, "UTF-8 pending bytes") & 192) === 128 && continuationCount < 3) {
943
961
  continuationCount++;
944
962
  leadIndex--;
945
963
  }
946
964
  if (leadIndex < 0) return bytes.length;
947
- const lead = bytes[leadIndex];
948
- const expectedLength = utf8SequenceLength(lead);
965
+ const expectedLength = utf8SequenceLength(itemAt(bytes, leadIndex, "UTF-8 lead byte"));
949
966
  if (expectedLength === 0) return bytes.length;
950
967
  return expectedLength > bytes.length - leadIndex ? leadIndex : bytes.length;
951
968
  }
@@ -1051,11 +1068,11 @@ function createScreen(options = {}) {
1051
1068
  const lm = leftMargin;
1052
1069
  const rm = rightMargin;
1053
1070
  for (let r = top; r < bottom; r++) {
1054
- const srcRow = grid[r + 1];
1055
- const dstRow = grid[r];
1071
+ const srcRow = itemAt(grid, r + 1, "scroll source rows");
1072
+ const dstRow = itemAt(grid, r, "scroll destination rows");
1056
1073
  for (let c = lm; c <= rm && c < cols; c++) dstRow.copyCellFrom(c, srcRow, c);
1057
1074
  }
1058
- const bottomRow = grid[bottom];
1075
+ const bottomRow = itemAt(grid, bottom, "scroll bottom row");
1059
1076
  for (let c = lm; c <= rm && c < cols; c++) bottomRow.setEmpty(c);
1060
1077
  markScreenRowsDirty(top, bottom);
1061
1078
  } else {
@@ -1098,16 +1115,16 @@ function createScreen(options = {}) {
1098
1115
  const lm = leftMargin;
1099
1116
  const rm = rightMargin;
1100
1117
  for (let r = bottom; r > top; r--) {
1101
- const srcRow = grid[r - 1];
1102
- const dstRow = grid[r];
1118
+ const srcRow = itemAt(grid, r - 1, "reverse-scroll source rows");
1119
+ const dstRow = itemAt(grid, r, "reverse-scroll destination rows");
1103
1120
  for (let c = lm; c <= rm && c < cols; c++) dstRow.copyCellFrom(c, srcRow, c);
1104
1121
  }
1105
- const topRow = grid[top];
1122
+ const topRow = itemAt(grid, top, "reverse-scroll top row");
1106
1123
  for (let c = lm; c <= rm && c < cols; c++) topRow.setEmpty(c);
1107
1124
  } else {
1108
1125
  for (let i = bottom; i > top; i--) {
1109
- grid[i] = grid[i - 1];
1110
- softWrapped[i] = softWrapped[i - 1];
1126
+ grid[i] = itemAt(grid, i - 1, "reverse-scroll source rows");
1127
+ softWrapped[i] = itemAt(softWrapped, i - 1, "reverse-scroll source wrap flags");
1111
1128
  }
1112
1129
  grid[top] = makeRow(cols);
1113
1130
  softWrapped[top] = false;
@@ -1132,7 +1149,7 @@ function createScreen(options = {}) {
1132
1149
  if (prevRow < 0) return null;
1133
1150
  prevCol = cols - 1;
1134
1151
  }
1135
- const row = grid[prevRow];
1152
+ const row = itemAt(grid, prevRow, "previous-cell rows");
1136
1153
  if (prevCol > 0 && row.getChar(prevCol) === "" && row.isWide(prevCol - 1)) prevCol--;
1137
1154
  if (row.isEmpty(prevCol)) return null;
1138
1155
  return {
@@ -1146,7 +1163,7 @@ function createScreen(options = {}) {
1146
1163
  row.widen(col);
1147
1164
  }
1148
1165
  function writeChar(ch) {
1149
- if (charsetG0 && ch.length === 1) {
1166
+ if ((activeG1 ? charsetG1 : charsetG0) && ch.length === 1) {
1150
1167
  const mapped = DEC_SPECIAL_GRAPHICS[ch];
1151
1168
  if (mapped) ch = mapped;
1152
1169
  }
@@ -1196,14 +1213,16 @@ function createScreen(options = {}) {
1196
1213
  return;
1197
1214
  }
1198
1215
  }
1199
- if (isRegionalIndicator(codePoint)) if (pendingRegionalIndicator !== null) {
1200
- const flag = pendingRegionalIndicator + ch;
1201
- pendingRegionalIndicator = null;
1202
- writeCharCore(flag, true);
1203
- return;
1204
- } else {
1205
- pendingRegionalIndicator = ch;
1206
- return;
1216
+ if (isRegionalIndicator(codePoint)) {
1217
+ if (pendingRegionalIndicator !== null) {
1218
+ const flag = pendingRegionalIndicator + ch;
1219
+ pendingRegionalIndicator = null;
1220
+ writeCharCore(flag, true);
1221
+ return;
1222
+ } else {
1223
+ pendingRegionalIndicator = ch;
1224
+ return;
1225
+ }
1207
1226
  }
1208
1227
  if (pendingRegionalIndicator !== null) {
1209
1228
  const ri = pendingRegionalIndicator;
@@ -1218,18 +1237,20 @@ function createScreen(options = {}) {
1218
1237
  const charWidth = wide ? 2 : 1;
1219
1238
  const wrapBoundary = leftRightMarginMode ? rightMargin + 1 : rowCols(curY);
1220
1239
  const wrapReturn = leftRightMarginMode ? leftMargin : 0;
1221
- if (curX + charWidth > wrapBoundary) if (autoWrap) {
1222
- softWrapped[curY] = true;
1223
- curX = wrapReturn;
1224
- curY++;
1225
- if (curY > scrollBottom) {
1226
- curY = scrollBottom;
1227
- scrollUp(scrollTop, scrollBottom);
1228
- }
1229
- } else curX = wrapBoundary - charWidth;
1240
+ if (curX + charWidth > wrapBoundary) {
1241
+ if (autoWrap) {
1242
+ softWrapped[curY] = true;
1243
+ curX = wrapReturn;
1244
+ curY++;
1245
+ if (curY > scrollBottom) {
1246
+ curY = scrollBottom;
1247
+ scrollUp(scrollTop, scrollBottom);
1248
+ }
1249
+ } else curX = wrapBoundary - charWidth;
1250
+ }
1230
1251
  if (insertMode) {
1231
1252
  const insertEnd = leftRightMarginMode ? rightMargin + 1 : cols;
1232
- mutateRowAsCells(grid[curY], (cells) => {
1253
+ mutateRowAsCells(itemAt(grid, curY, "cursor row"), (cells) => {
1233
1254
  for (let i = 0; i < charWidth; i++) {
1234
1255
  cells.splice(insertEnd - 1, 1);
1235
1256
  cells.splice(curX, 0, emptyCell());
@@ -1272,7 +1293,6 @@ function createScreen(options = {}) {
1272
1293
  case 6:
1273
1294
  cursorShape = "bar";
1274
1295
  cursorBlinking = false;
1275
- break;
1276
1296
  }
1277
1297
  else if (finalByte === "@") handleShiftLeft(Math.max(parts[0] ?? 1, 1));
1278
1298
  else if (finalByte === "A") handleShiftRight(Math.max(parts[0] ?? 1, 1));
@@ -1301,7 +1321,7 @@ function createScreen(options = {}) {
1301
1321
  const { t, l, b, r } = normalizeRect(parts[1] ?? 1, parts[2] ?? 1, parts[3] ?? rows, parts[4] ?? cols);
1302
1322
  const fillChar = String.fromCharCode(charCode);
1303
1323
  for (let row = t; row <= b && row < rows; row++) {
1304
- const gr = grid[row];
1324
+ const gr = itemAt(grid, row, "rectangular erase rows");
1305
1325
  for (let col = l; col <= r && col < cols; col++) {
1306
1326
  const cell = emptyCell();
1307
1327
  cell.char = fillChar;
@@ -1312,7 +1332,7 @@ function createScreen(options = {}) {
1312
1332
  } else if (finalByte === "z" || finalByte === "{") {
1313
1333
  const { t, l, b, r } = normalizeRect(parts[0] ?? 1, parts[1] ?? 1, parts[2] ?? rows, parts[3] ?? cols);
1314
1334
  for (let row = t; row <= b && row < rows; row++) {
1315
- const gr = grid[row];
1335
+ const gr = itemAt(grid, row, "rectangular fill rows");
1316
1336
  for (let col = l; col <= r && col < cols; col++) gr.setEmpty(col);
1317
1337
  }
1318
1338
  markScreenRowsDirty(t, Math.min(b, rows - 1));
@@ -1332,11 +1352,11 @@ function createScreen(options = {}) {
1332
1352
  for (let row = 0; row < h; row++) {
1333
1353
  const dr = dstTop + row;
1334
1354
  if (dr < 0 || dr >= rows) continue;
1335
- const dstRow = grid[dr];
1355
+ const dstRow = itemAt(grid, dr, "rectangular copy destination rows");
1336
1356
  for (let col = 0; col < w; col++) {
1337
1357
  const dc = dstLeft + col;
1338
1358
  if (dc < 0 || dc >= cols) continue;
1339
- dstRow.setCellRaw(dc, snapshot[row][col]);
1359
+ dstRow.setCellRaw(dc, itemAt(itemAt(snapshot, row, "rectangular copy rows"), col, "rectangular copy cells"));
1340
1360
  }
1341
1361
  }
1342
1362
  markScreenRowsDirty(Math.max(0, dstTop), Math.min(dstTop + h - 1, rows - 1));
@@ -1345,7 +1365,7 @@ function createScreen(options = {}) {
1345
1365
  const sgrParts = parts.slice(4);
1346
1366
  const reverse = finalByte === "t";
1347
1367
  for (let row = t; row <= b && row < rows; row++) {
1348
- const gr = grid[row];
1368
+ const gr = itemAt(grid, row, "rectangular attribute rows");
1349
1369
  for (let col = l; col <= r && col < cols; col++) {
1350
1370
  const cell = gr.getCellRaw(col);
1351
1371
  applyRectAttrs(cell, sgrParts, reverse);
@@ -1444,24 +1464,20 @@ function createScreen(options = {}) {
1444
1464
  case "t": {
1445
1465
  const op = parts[0] ?? 0;
1446
1466
  if (!onResponse) break;
1447
- const CELL_H = 16;
1448
- const CELL_W = 8;
1449
1467
  switch (op) {
1450
1468
  case 14:
1451
- onResponse(`\x1b[4;${rows * CELL_H};${cols * CELL_W}t`);
1469
+ onResponse("\x1B[4;0;0t");
1452
1470
  break;
1453
1471
  case 16:
1454
- onResponse(`\x1b[6;${CELL_H};${CELL_W}t`);
1472
+ onResponse("\x1B[6;0;0t");
1455
1473
  break;
1456
1474
  case 18:
1457
1475
  onResponse(`\x1b[8;${rows};${cols}t`);
1458
1476
  break;
1459
1477
  case 20:
1460
- onResponse(`\x1b]L${title}\x1b\\`);
1461
- break;
1462
- case 21:
1463
- onResponse(`\x1b]l${title}\x1b\\`);
1478
+ onResponse(`\x1b]L${iconLabel}\x1b\\`);
1464
1479
  break;
1480
+ case 21: onResponse(`\x1b]l${title}\x1b\\`);
1465
1481
  }
1466
1482
  break;
1467
1483
  }
@@ -1580,8 +1596,6 @@ function createScreen(options = {}) {
1580
1596
  curX = savedCurX;
1581
1597
  curY = savedCurY;
1582
1598
  clampCursor();
1583
- break;
1584
- default: break;
1585
1599
  }
1586
1600
  }
1587
1601
  function handleCSIGt(params, _intermediates, finalByte) {
@@ -1688,9 +1702,7 @@ function createScreen(options = {}) {
1688
1702
  case 1007:
1689
1703
  value = altScrollMode ? 1 : 2;
1690
1704
  break;
1691
- case 1048:
1692
- value = 2;
1693
- break;
1705
+ case 1048: value = 2;
1694
1706
  }
1695
1707
  onResponse(`\x1b[?${mode};${value}$y`);
1696
1708
  }
@@ -1819,16 +1831,14 @@ function createScreen(options = {}) {
1819
1831
  case 1007:
1820
1832
  altScrollMode = set;
1821
1833
  break;
1822
- case 1048:
1823
- if (set) {
1824
- savedCurX = curX;
1825
- savedCurY = curY;
1826
- } else {
1827
- curX = savedCurX;
1828
- curY = savedCurY;
1829
- clampCursor();
1830
- }
1831
- break;
1834
+ case 1048: if (set) {
1835
+ savedCurX = curX;
1836
+ savedCurY = curY;
1837
+ } else {
1838
+ curX = savedCurX;
1839
+ curY = savedCurY;
1840
+ clampCursor();
1841
+ }
1832
1842
  }
1833
1843
  }
1834
1844
  function handleEraseDisplay(mode, selective = false) {
@@ -1846,7 +1856,6 @@ function createScreen(options = {}) {
1846
1856
  for (let row = 0; row < rows; row++) eraseCells(row, 0, row, cols - 1, selective);
1847
1857
  if (mode === 3) clearScrollback();
1848
1858
  markAllDirty();
1849
- break;
1850
1859
  }
1851
1860
  }
1852
1861
  function handleEraseLine(mode, selective = false) {
@@ -1859,9 +1868,7 @@ function createScreen(options = {}) {
1859
1868
  case 1:
1860
1869
  eraseCells(curY, eraseLeft, curY, curX, selective);
1861
1870
  break;
1862
- case 2:
1863
- eraseCells(curY, eraseLeft, curY, eraseRight, selective);
1864
- break;
1871
+ case 2: eraseCells(curY, eraseLeft, curY, eraseRight, selective);
1865
1872
  }
1866
1873
  }
1867
1874
  function eraseCells(row, startCol, _endRow, endCol, selective = false) {
@@ -2014,9 +2021,7 @@ function createScreen(options = {}) {
2014
2021
  case 25:
2015
2022
  cell.blink = false;
2016
2023
  break;
2017
- case 27:
2018
- cell.inverse = false;
2019
- break;
2024
+ case 27: cell.inverse = false;
2020
2025
  }
2021
2026
  }
2022
2027
  function handleSGR(rawParams) {
@@ -2026,7 +2031,7 @@ function createScreen(options = {}) {
2026
2031
  for (const seg of segments) if (seg.includes(":")) {
2027
2032
  const subs = seg.split(":").map((s) => s === "" ? 0 : parseInt(s, 10));
2028
2033
  subParams.set(params.length, subs);
2029
- params.push(subs[0]);
2034
+ params.push(itemAt(subs, 0, "SGR sub-parameters"));
2030
2035
  } else params.push(seg === "" ? 0 : parseInt(seg, 10));
2031
2036
  if (params.length === 0 || params.length === 1 && params[0] === 0) {
2032
2037
  attrs = resetSgrAttrs();
@@ -2034,7 +2039,7 @@ function createScreen(options = {}) {
2034
2039
  }
2035
2040
  let i = 0;
2036
2041
  while (i < params.length) {
2037
- const code = params[i];
2042
+ const code = itemAt(params, i, "SGR parameters");
2038
2043
  switch (code) {
2039
2044
  case 0:
2040
2045
  attrs = resetSgrAttrs();
@@ -2050,7 +2055,7 @@ function createScreen(options = {}) {
2050
2055
  break;
2051
2056
  case 4: {
2052
2057
  const subs = subParams.get(i);
2053
- if (subs && subs.length > 1) switch (subs[1]) {
2058
+ if (subs && subs.length > 1) switch (itemAt(subs, 1, "underline style sub-parameters")) {
2054
2059
  case 0:
2055
2060
  attrs.underline = "none";
2056
2061
  break;
@@ -2069,9 +2074,7 @@ function createScreen(options = {}) {
2069
2074
  case 5:
2070
2075
  attrs.underline = "dashed";
2071
2076
  break;
2072
- default:
2073
- attrs.underline = "single";
2074
- break;
2077
+ default: attrs.underline = "single";
2075
2078
  }
2076
2079
  else attrs.underline = "single";
2077
2080
  break;
@@ -2125,7 +2128,7 @@ function createScreen(options = {}) {
2125
2128
  case 36:
2126
2129
  case 37:
2127
2130
  attrs.fg = {
2128
- ...palette256[code - 30],
2131
+ ...itemAt(palette256, code - 30, "basic foreground palette"),
2129
2132
  index: code - 30
2130
2133
  };
2131
2134
  break;
@@ -2155,7 +2158,7 @@ function createScreen(options = {}) {
2155
2158
  case 46:
2156
2159
  case 47:
2157
2160
  attrs.bg = {
2158
- ...palette256[code - 40],
2161
+ ...itemAt(palette256, code - 40, "basic background palette"),
2159
2162
  index: code - 40
2160
2163
  };
2161
2164
  break;
@@ -2208,7 +2211,7 @@ function createScreen(options = {}) {
2208
2211
  case 96:
2209
2212
  case 97:
2210
2213
  attrs.fg = {
2211
- ...palette256[code - 90 + 8],
2214
+ ...itemAt(palette256, code - 90 + 8, "bright foreground palette"),
2212
2215
  index: code - 90 + 8
2213
2216
  };
2214
2217
  break;
@@ -2219,12 +2222,10 @@ function createScreen(options = {}) {
2219
2222
  case 104:
2220
2223
  case 105:
2221
2224
  case 106:
2222
- case 107:
2223
- attrs.bg = {
2224
- ...palette256[code - 100 + 8],
2225
- index: code - 100 + 8
2226
- };
2227
- break;
2225
+ case 107: attrs.bg = {
2226
+ ...itemAt(palette256, code - 100 + 8, "bright background palette"),
2227
+ index: code - 100 + 8
2228
+ };
2228
2229
  }
2229
2230
  i++;
2230
2231
  }
@@ -2233,7 +2234,7 @@ function createScreen(options = {}) {
2233
2234
  if (startIndex + 1 >= params.length) return null;
2234
2235
  const type = params[startIndex + 1];
2235
2236
  if (type === 5 && startIndex + 2 < params.length) {
2236
- const idx = params[startIndex + 2];
2237
+ const idx = itemAt(params, startIndex + 2, "indexed color parameters");
2237
2238
  const entry = palette256[idx];
2238
2239
  return {
2239
2240
  color: entry ? {
@@ -2248,9 +2249,9 @@ function createScreen(options = {}) {
2248
2249
  };
2249
2250
  } else if (type === 2 && startIndex + 4 < params.length) return {
2250
2251
  color: {
2251
- r: params[startIndex + 2],
2252
- g: params[startIndex + 3],
2253
- b: params[startIndex + 4]
2252
+ r: itemAt(params, startIndex + 2, "truecolor red parameter"),
2253
+ g: itemAt(params, startIndex + 3, "truecolor green parameter"),
2254
+ b: itemAt(params, startIndex + 4, "truecolor blue parameter")
2254
2255
  },
2255
2256
  nextIndex: startIndex + 5
2256
2257
  };
@@ -2261,7 +2262,7 @@ function createScreen(options = {}) {
2261
2262
  if (subs.length < 3) return null;
2262
2263
  const type = subs[1];
2263
2264
  if (type === 5 && subs.length >= 3) {
2264
- const idx = subs[2];
2265
+ const idx = itemAt(subs, 2, "indexed color sub-parameters");
2265
2266
  const entry = palette256[idx];
2266
2267
  return entry ? {
2267
2268
  ...entry,
@@ -2274,18 +2275,18 @@ function createScreen(options = {}) {
2274
2275
  } else if (type === 2) {
2275
2276
  if (subs.length >= 5) {
2276
2277
  if (subs.length >= 6) return {
2277
- r: subs[3],
2278
- g: subs[4],
2279
- b: subs[5]
2278
+ r: itemAt(subs, 3, "colorspace red sub-parameter"),
2279
+ g: itemAt(subs, 4, "colorspace green sub-parameter"),
2280
+ b: itemAt(subs, 5, "colorspace blue sub-parameter")
2280
2281
  };
2281
2282
  return {
2282
- r: subs[2],
2283
- g: subs[3],
2284
- b: subs[4]
2283
+ r: itemAt(subs, 2, "truecolor red sub-parameter"),
2284
+ g: itemAt(subs, 3, "truecolor green sub-parameter"),
2285
+ b: itemAt(subs, 4, "truecolor blue sub-parameter")
2285
2286
  };
2286
2287
  } else if (subs.length >= 4) return {
2287
- r: subs[2],
2288
- g: subs[3],
2288
+ r: itemAt(subs, 2, "partial truecolor red sub-parameter"),
2289
+ g: itemAt(subs, 3, "partial truecolor green sub-parameter"),
2289
2290
  b: 0
2290
2291
  };
2291
2292
  }
@@ -2298,6 +2299,9 @@ function createScreen(options = {}) {
2298
2299
  const value = semicolonIdx === -1 ? "" : oscString.substring(semicolonIdx + 1);
2299
2300
  switch (code) {
2300
2301
  case 0:
2302
+ iconLabel = value;
2303
+ title = value;
2304
+ break;
2301
2305
  case 2:
2302
2306
  title = value;
2303
2307
  break;
@@ -2317,17 +2321,16 @@ function createScreen(options = {}) {
2317
2321
  startCol: curX
2318
2322
  });
2319
2323
  break;
2320
- case "C":
2321
- semanticZones.push({
2322
- type: "output",
2323
- startRow: curY,
2324
- startCol: curX
2325
- });
2326
- break;
2327
- case "D": break;
2324
+ case "C": semanticZones.push({
2325
+ type: "output",
2326
+ startRow: curY,
2327
+ startCol: curX
2328
+ });
2328
2329
  }
2329
2330
  break;
2330
- case 1: break;
2331
+ case 1:
2332
+ iconLabel = value;
2333
+ break;
2331
2334
  case 7:
2332
2335
  cwd = value;
2333
2336
  break;
@@ -2342,8 +2345,8 @@ function createScreen(options = {}) {
2342
2345
  case 4: {
2343
2346
  const fields = value.split(";");
2344
2347
  for (let fi = 0; fi + 1 < fields.length; fi += 2) {
2345
- const idx = parseInt(fields[fi], 10);
2346
- const spec = fields[fi + 1];
2348
+ const idx = parseInt(itemAt(fields, fi, "OSC 4 palette fields"), 10);
2349
+ const spec = itemAt(fields, fi + 1, "OSC 4 palette fields");
2347
2350
  if (isNaN(idx) || idx < 0 || idx > 255) continue;
2348
2351
  if (spec === "?") {
2349
2352
  const c = palette256[idx];
@@ -2358,8 +2361,8 @@ function createScreen(options = {}) {
2358
2361
  case 5: {
2359
2362
  const fields = value.split(";");
2360
2363
  for (let fi = 0; fi + 1 < fields.length; fi += 2) {
2361
- const idx = parseInt(fields[fi], 10);
2362
- const spec = fields[fi + 1];
2364
+ const idx = parseInt(itemAt(fields, fi, "OSC 5 special-color fields"), 10);
2365
+ const spec = itemAt(fields, fi + 1, "OSC 5 special-color fields");
2363
2366
  if (isNaN(idx) || idx < 0 || idx > 4) continue;
2364
2367
  if (spec === "?") {
2365
2368
  const c = specialColors.get(idx);
@@ -2469,70 +2472,76 @@ function createScreen(options = {}) {
2469
2472
  const isQuery = val === "?";
2470
2473
  const paletteMatch = /^color(\d+)$/.exec(key);
2471
2474
  if (paletteMatch) {
2472
- const idx = parseInt(paletteMatch[1], 10);
2475
+ const idx = parseInt(itemAt(paletteMatch, 1, "OSC 104 palette capture"), 10);
2473
2476
  if (idx < 0 || idx > 255) continue;
2474
2477
  if (isQuery) {
2475
2478
  const c = palette256[idx];
2476
2479
  if (c) response.push(`${key}=${formatColorResponse(c)}`);
2477
2480
  } else if (val === "") {
2478
2481
  const fresh = buildPalette256();
2479
- palette256[idx] = fresh[idx];
2482
+ palette256[idx] = itemAt(fresh, idx, "fresh ANSI palette");
2480
2483
  } else {
2481
2484
  const c = parseColorSpec(val);
2482
2485
  if (c) palette256[idx] = c;
2483
2486
  }
2484
- } else if (key === "foreground") if (isQuery) {
2485
- const c = defaultFgColor ?? {
2486
- r: 255,
2487
- g: 255,
2488
- b: 255
2489
- };
2490
- response.push(`${key}=${formatColorResponse(c)}`);
2491
- } else if (val === "") defaultFgColor = null;
2492
- else {
2493
- const c = parseColorSpec(val);
2494
- if (c) defaultFgColor = c;
2495
- }
2496
- else if (key === "background") if (isQuery) {
2497
- const c = defaultBgColor ?? {
2498
- r: 0,
2499
- g: 0,
2500
- b: 0
2501
- };
2502
- response.push(`${key}=${formatColorResponse(c)}`);
2503
- } else if (val === "") defaultBgColor = null;
2504
- else {
2505
- const c = parseColorSpec(val);
2506
- if (c) defaultBgColor = c;
2507
- }
2508
- else if (key === "cursor") if (isQuery) {
2509
- const c = cursorColor ?? defaultFgColor ?? {
2510
- r: 255,
2511
- g: 255,
2512
- b: 255
2513
- };
2514
- response.push(`${key}=${formatColorResponse(c)}`);
2515
- } else if (val === "") cursorColor = null;
2516
- else {
2517
- const c = parseColorSpec(val);
2518
- if (c) cursorColor = c;
2487
+ } else if (key === "foreground") {
2488
+ if (isQuery) {
2489
+ const c = defaultFgColor ?? {
2490
+ r: 255,
2491
+ g: 255,
2492
+ b: 255
2493
+ };
2494
+ response.push(`${key}=${formatColorResponse(c)}`);
2495
+ } else if (val === "") defaultFgColor = null;
2496
+ else {
2497
+ const c = parseColorSpec(val);
2498
+ if (c) defaultFgColor = c;
2499
+ }
2500
+ } else if (key === "background") {
2501
+ if (isQuery) {
2502
+ const c = defaultBgColor ?? {
2503
+ r: 0,
2504
+ g: 0,
2505
+ b: 0
2506
+ };
2507
+ response.push(`${key}=${formatColorResponse(c)}`);
2508
+ } else if (val === "") defaultBgColor = null;
2509
+ else {
2510
+ const c = parseColorSpec(val);
2511
+ if (c) defaultBgColor = c;
2512
+ }
2513
+ } else if (key === "cursor") {
2514
+ if (isQuery) {
2515
+ const c = cursorColor ?? defaultFgColor ?? {
2516
+ r: 255,
2517
+ g: 255,
2518
+ b: 255
2519
+ };
2520
+ response.push(`${key}=${formatColorResponse(c)}`);
2521
+ } else if (val === "") cursorColor = null;
2522
+ else {
2523
+ const c = parseColorSpec(val);
2524
+ if (c) cursorColor = c;
2525
+ }
2526
+ } else if (key === "selection_background") {
2527
+ if (isQuery) {
2528
+ const c = highlightBgColor ?? defaultFgColor ?? {
2529
+ r: 255,
2530
+ g: 255,
2531
+ b: 255
2532
+ };
2533
+ response.push(`${key}=${formatColorResponse(c)}`);
2534
+ } else highlightBgColor = val === "" ? null : parseColorSpec(val);
2535
+ } else if (key === "selection_foreground") {
2536
+ if (isQuery) {
2537
+ const c = highlightFgColor ?? defaultBgColor ?? {
2538
+ r: 0,
2539
+ g: 0,
2540
+ b: 0
2541
+ };
2542
+ response.push(`${key}=${formatColorResponse(c)}`);
2543
+ } else highlightFgColor = val === "" ? null : parseColorSpec(val);
2519
2544
  }
2520
- else if (key === "selection_background") if (isQuery) {
2521
- const c = highlightBgColor ?? defaultFgColor ?? {
2522
- r: 255,
2523
- g: 255,
2524
- b: 255
2525
- };
2526
- response.push(`${key}=${formatColorResponse(c)}`);
2527
- } else highlightBgColor = val === "" ? null : parseColorSpec(val);
2528
- else if (key === "selection_foreground") if (isQuery) {
2529
- const c = highlightFgColor ?? defaultBgColor ?? {
2530
- r: 0,
2531
- g: 0,
2532
- b: 0
2533
- };
2534
- response.push(`${key}=${formatColorResponse(c)}`);
2535
- } else highlightFgColor = val === "" ? null : parseColorSpec(val);
2536
2545
  }
2537
2546
  if (response.length > 0 && onResponse) onResponse(`\x1b]21;${response.join(";")}\x1b\\`);
2538
2547
  break;
@@ -2543,7 +2552,7 @@ function createScreen(options = {}) {
2543
2552
  const fresh = buildPalette256();
2544
2553
  for (const tok of value.split(";")) {
2545
2554
  const idx = parseInt(tok, 10);
2546
- if (!isNaN(idx) && idx >= 0 && idx < 256) palette256[idx] = fresh[idx];
2555
+ if (!isNaN(idx) && idx >= 0 && idx < 256) palette256[idx] = itemAt(fresh, idx, "fresh ANSI palette");
2547
2556
  }
2548
2557
  }
2549
2558
  break;
@@ -2614,14 +2623,11 @@ function createScreen(options = {}) {
2614
2623
  startCol: curX
2615
2624
  });
2616
2625
  break;
2617
- case "C":
2618
- semanticZones.push({
2619
- type: "output",
2620
- startRow: curY,
2621
- startCol: curX
2622
- });
2623
- break;
2624
- case "D": break;
2626
+ case "C": semanticZones.push({
2627
+ type: "output",
2628
+ startRow: curY,
2629
+ startCol: curX
2630
+ });
2625
2631
  }
2626
2632
  break;
2627
2633
  case 66:
@@ -2656,7 +2662,7 @@ function createScreen(options = {}) {
2656
2662
  break;
2657
2663
  }
2658
2664
  case 776:
2659
- if (onResponse) onResponse(`\x1b]776;${CELL_W_PX};${CELL_H_PX};${FONT_ASCENT_PX}\x1b\\`);
2665
+ if (onResponse) onResponse("\x1B]776;0;0;0\x1B\\");
2660
2666
  break;
2661
2667
  case 7770:
2662
2668
  if (value === "?") {
@@ -2694,10 +2700,8 @@ function createScreen(options = {}) {
2694
2700
  advancedClipboard = "";
2695
2701
  }
2696
2702
  break;
2697
- case 1337:
2698
- if (value === "ReportCellSize" && onResponse) onResponse("\x1B]1337;ReportCellSize=16;8\x1B\\");
2699
- else if (value === "RequestCapabilities" && onResponse) onResponse("\x1B]1337;Capabilities=\x1B\\");
2700
- break;
2703
+ case 1337: if (value === "ReportCellSize" && onResponse) onResponse("\x1B]1337;ReportCellSize=0;0\x1B\\");
2704
+ else if (value === "RequestCapabilities" && onResponse) onResponse("\x1B]1337;Capabilities=\x1B\\");
2701
2705
  }
2702
2706
  }
2703
2707
  function handleDCS(data) {
@@ -2705,7 +2709,7 @@ function createScreen(options = {}) {
2705
2709
  if (match) {
2706
2710
  hasSixel = true;
2707
2711
  sixelImages.push({
2708
- data: match[2],
2712
+ data: itemAt(match, 2, "sixel payload capture"),
2709
2713
  row: dcsStartRow,
2710
2714
  col: dcsStartCol
2711
2715
  });
@@ -2726,7 +2730,6 @@ function createScreen(options = {}) {
2726
2730
  }
2727
2731
  function handleAPC(data) {
2728
2732
  if (!data.startsWith("G")) return;
2729
- hasKittyGraphics = true;
2730
2733
  const semicolonIdx = data.indexOf(";");
2731
2734
  const kvPart = semicolonIdx >= 0 ? data.substring(1, semicolonIdx) : data.substring(1);
2732
2735
  const params = {};
@@ -2734,7 +2737,11 @@ function createScreen(options = {}) {
2734
2737
  const eqIdx = pair.indexOf("=");
2735
2738
  if (eqIdx >= 0) params[pair.substring(0, eqIdx)] = pair.substring(eqIdx + 1);
2736
2739
  }
2737
- if (params.a === "q" && onResponse) onResponse(`\x1b_Gi=${params.i ?? "0"};OK\x1b\\`);
2740
+ if (params.a === "q" && onResponse) {
2741
+ if (kittyGraphicsImages) {
2742
+ if (params.q !== "1" && params.q !== "2") onResponse(`\x1b_Gi=${params.i ?? "0"};OK\x1b\\`);
2743
+ } else if (params.q !== "2") onResponse(`\x1b_Gi=${params.i ?? "0"};ENOTSUP\x1b\\`);
2744
+ }
2738
2745
  }
2739
2746
  function softReset() {
2740
2747
  insertMode = false;
@@ -2759,6 +2766,8 @@ function createScreen(options = {}) {
2759
2766
  rightMargin = cols - 1;
2760
2767
  attrs = resetAttrs();
2761
2768
  charsetG0 = false;
2769
+ charsetG1 = false;
2770
+ activeG1 = false;
2762
2771
  curX = 0;
2763
2772
  curY = 0;
2764
2773
  }
@@ -2781,10 +2790,13 @@ function createScreen(options = {}) {
2781
2790
  attrs: resetAttrs(),
2782
2791
  originMode: false,
2783
2792
  autoWrap: true,
2784
- charsetG0: false
2793
+ charsetG0: false,
2794
+ charsetG1: false,
2795
+ activeG1: false
2785
2796
  };
2786
2797
  attrs = resetAttrs();
2787
2798
  title = "";
2799
+ iconLabel = "";
2788
2800
  useAltScreen = false;
2789
2801
  bracketedPaste = false;
2790
2802
  applicationCursor = false;
@@ -2801,7 +2813,6 @@ function createScreen(options = {}) {
2801
2813
  syncOutput = false;
2802
2814
  kittyKeyboardFlags = 0;
2803
2815
  kittyKeyboardStack = [];
2804
- hasKittyGraphics = false;
2805
2816
  hasSixel = false;
2806
2817
  sixelImages = [];
2807
2818
  scrollTop = 0;
@@ -2820,6 +2831,8 @@ function createScreen(options = {}) {
2820
2831
  advancedClipboard = "";
2821
2832
  viewportOffset = 0;
2822
2833
  charsetG0 = false;
2834
+ charsetG1 = false;
2835
+ activeG1 = false;
2823
2836
  clipboard = "";
2824
2837
  cwd = "";
2825
2838
  notifications = [];
@@ -3015,6 +3028,12 @@ function createScreen(options = {}) {
3015
3028
  } else if (code === 13) {
3016
3029
  curX = 0;
3017
3030
  emitExecute(13);
3031
+ } else if (code === 14) {
3032
+ activeG1 = true;
3033
+ emitExecute(14);
3034
+ } else if (code === 15) {
3035
+ activeG1 = false;
3036
+ emitExecute(15);
3018
3037
  } else if (code >= 32) {
3019
3038
  let char = ch;
3020
3039
  if (code >= 55296 && code <= 56319 && i + 1 < text.length) {
@@ -3067,7 +3086,9 @@ function createScreen(options = {}) {
3067
3086
  },
3068
3087
  originMode,
3069
3088
  autoWrap,
3070
- charsetG0
3089
+ charsetG0,
3090
+ charsetG1,
3091
+ activeG1
3071
3092
  };
3072
3093
  parserState = "ground";
3073
3094
  emitEsc("7", "");
@@ -3083,6 +3104,8 @@ function createScreen(options = {}) {
3083
3104
  originMode = savedState.originMode;
3084
3105
  autoWrap = savedState.autoWrap;
3085
3106
  charsetG0 = savedState.charsetG0;
3107
+ charsetG1 = savedState.charsetG1;
3108
+ activeG1 = savedState.activeG1;
3086
3109
  clampCursor();
3087
3110
  parserState = "ground";
3088
3111
  emitEsc("8", "");
@@ -3104,9 +3127,11 @@ function createScreen(options = {}) {
3104
3127
  parserState = "escape_hash";
3105
3128
  } else if (ch === "(") {
3106
3129
  escIntermediate = "(";
3130
+ escBuf = "(";
3107
3131
  parserState = "escape_charset";
3108
3132
  } else if (ch === ")") {
3109
3133
  escIntermediate = ")";
3134
+ escBuf = ")";
3110
3135
  parserState = "escape_charset";
3111
3136
  } else if (ch === "=") {
3112
3137
  applicationKeypad = true;
@@ -3126,17 +3151,19 @@ function createScreen(options = {}) {
3126
3151
  } else parserState = "ground";
3127
3152
  break;
3128
3153
  case "escape_charset":
3129
- if (ch === "0") charsetG0 = true;
3130
- else charsetG0 = false;
3154
+ const designator = escBuf === ")" ? ")" : "(";
3155
+ if (designator === ")") charsetG1 = ch === "0";
3156
+ else charsetG0 = ch === "0";
3157
+ escBuf = "";
3131
3158
  parserState = "ground";
3132
- emitEsc(ch, escIntermediate);
3159
+ emitEsc(ch, designator);
3133
3160
  break;
3134
3161
  case "escape_hash":
3135
3162
  if (ch === "8") {
3136
3163
  const alignCell = emptyCell();
3137
3164
  alignCell.char = "E";
3138
3165
  for (let r = 0; r < rows; r++) {
3139
- const row = grid[r];
3166
+ const row = itemAt(grid, r, "selective erase rows");
3140
3167
  for (let c = 0; c < cols; c++) row.setCellRaw(c, alignCell);
3141
3168
  softWrapped[r] = false;
3142
3169
  }
@@ -3223,7 +3250,6 @@ function createScreen(options = {}) {
3223
3250
  apcOverflow = false;
3224
3251
  }
3225
3252
  parserState = "ground";
3226
- break;
3227
3253
  }
3228
3254
  }
3229
3255
  flushPrintRun();
@@ -3283,10 +3309,10 @@ function createScreen(options = {}) {
3283
3309
  const outLine = [];
3284
3310
  let tracked = null;
3285
3311
  for (let li = 0; li < logicalLines.length; li++) {
3286
- const line = logicalLines[li];
3312
+ const line = itemAt(logicalLines, li, "reflow logical lines");
3287
3313
  let lineLen = line.length;
3288
3314
  while (lineLen > 0) {
3289
- const cell = line[lineLen - 1];
3315
+ const cell = itemAt(line, lineLen - 1, "reflow logical-line cells");
3290
3316
  if (cell.char === "" && !cell.wide) lineLen--;
3291
3317
  else break;
3292
3318
  }
@@ -3305,7 +3331,7 @@ function createScreen(options = {}) {
3305
3331
  const row = makeRow(newCols);
3306
3332
  let col = 0;
3307
3333
  while (col < newCols && pos < lineLen) {
3308
- const cell = line[pos];
3334
+ const cell = itemAt(line, pos, "reflow logical-line cells");
3309
3335
  if (cell.wide && col + 2 > newCols) break;
3310
3336
  if (track?.line === li && pos === track.offset) tracked = {
3311
3337
  row: outRows.length,
@@ -3351,14 +3377,11 @@ function createScreen(options = {}) {
3351
3377
  */
3352
3378
  function trimTrailingEmptyRows(result) {
3353
3379
  const floor = result.tracked == null ? 1 : Math.max(1, result.tracked.row + 1);
3354
- while (result.rows.length > floor) {
3355
- const lastRow = result.rows[result.rows.length - 1];
3356
- if (packedRowIsBlank(lastRow) && !result.wrapped[result.rows.length - 2]) {
3357
- result.rows.pop();
3358
- result.wrapped.pop();
3359
- result.lineIndex?.pop();
3360
- } else break;
3361
- }
3380
+ while (result.rows.length > floor) if (packedRowIsBlank(itemAt(result.rows, result.rows.length - 1, "reflow result rows")) && !result.wrapped[result.rows.length - 2]) {
3381
+ result.rows.pop();
3382
+ result.wrapped.pop();
3383
+ result.lineIndex?.pop();
3384
+ } else break;
3362
3385
  }
3363
3386
  function resize(newCols, newRows) {
3364
3387
  if (newCols === cols && newRows === rows) return;
@@ -3388,7 +3411,7 @@ function createScreen(options = {}) {
3388
3411
  if (restoredRows.length > 0) {
3389
3412
  let completed = 0;
3390
3413
  for (const wrapped of restoredWrapped) if (!wrapped) completed++;
3391
- if (cursorTrack.line === 0 && restoredWrapped[restoredWrapped.length - 1] === true) for (let i = restoredWrapped.length - 1; i >= 0 && restoredWrapped[i]; i--) cursorTrack.offset += restoredRows[i].length;
3414
+ if (cursorTrack.line === 0 && restoredWrapped[restoredWrapped.length - 1] === true) for (let i = restoredWrapped.length - 1; i >= 0 && restoredWrapped[i]; i--) cursorTrack.offset += itemAt(restoredRows, i, "restored rows").length;
3392
3415
  cursorTrack.line += completed;
3393
3416
  }
3394
3417
  const mainSrc = restoredRows.length > 0 ? [...restoredRows, ...mainGrid] : mainGrid;
@@ -3410,8 +3433,8 @@ function createScreen(options = {}) {
3410
3433
  if (overflow !== void 0) bankScrollbackRow(overflow, mainResult.wrapped[r] ?? false, mainLogicalAttrs[mainResult.lineIndex[r] ?? 0] ?? 0);
3411
3434
  }
3412
3435
  for (let r = 0; r < newRows && mainStartRow + r < mainResult.rows.length; r++) {
3413
- newMain[r] = mainResult.rows[mainStartRow + r];
3414
- newMainWrapped[r] = mainResult.wrapped[mainStartRow + r];
3436
+ newMain[r] = itemAt(mainResult.rows, mainStartRow + r, "resized main rows");
3437
+ newMainWrapped[r] = itemAt(mainResult.wrapped, mainStartRow + r, "resized main wrap flags");
3415
3438
  newMainAttrs[r] = mainLogicalAttrs[mainResult.lineIndex[mainStartRow + r] ?? 0] ?? 0;
3416
3439
  }
3417
3440
  const newAlt = makeGrid(newCols, newRows);
@@ -3419,8 +3442,8 @@ function createScreen(options = {}) {
3419
3442
  const newAltAttrs = new Array(newRows).fill(0);
3420
3443
  const altStartRow = Math.max(0, altResult.rows.length - newRows);
3421
3444
  for (let r = 0; r < newRows && altStartRow + r < altResult.rows.length; r++) {
3422
- newAlt[r] = altResult.rows[altStartRow + r];
3423
- newAltWrapped[r] = altResult.wrapped[altStartRow + r];
3445
+ newAlt[r] = itemAt(altResult.rows, altStartRow + r, "resized alternate rows");
3446
+ newAltWrapped[r] = itemAt(altResult.wrapped, altStartRow + r, "resized alternate wrap flags");
3424
3447
  newAltAttrs[r] = altLogicalAttrs[altResult.lineIndex[altStartRow + r] ?? 0] ?? 0;
3425
3448
  }
3426
3449
  const activeResult = useAltScreen ? altResult : mainResult;
@@ -3492,7 +3515,9 @@ function createScreen(options = {}) {
3492
3515
  attrs: cloneAttrsSnapshot(savedState.attrs),
3493
3516
  originMode: savedState.originMode,
3494
3517
  autoWrap: savedState.autoWrap,
3495
- charsetG0: savedState.charsetG0
3518
+ charsetG0: savedState.charsetG0,
3519
+ charsetG1: savedState.charsetG1,
3520
+ activeG1: savedState.activeG1
3496
3521
  },
3497
3522
  attrs: cloneAttrsSnapshot(attrs),
3498
3523
  modes: {
@@ -3511,7 +3536,7 @@ function createScreen(options = {}) {
3511
3536
  syncOutput,
3512
3537
  kittyKeyboardFlags,
3513
3538
  kittyKeyboardStack: [...kittyKeyboardStack],
3514
- kittyGraphics: hasKittyGraphics,
3539
+ kittyGraphics: kittyGraphicsImages,
3515
3540
  colorSchemeReporting,
3516
3541
  decColumn: decColumnMode,
3517
3542
  altScroll: altScrollMode,
@@ -3530,6 +3555,7 @@ function createScreen(options = {}) {
3530
3555
  },
3531
3556
  tabStops: [...tabStops].sort((a, b) => a - b),
3532
3557
  title,
3558
+ iconLabel,
3533
3559
  clipboard,
3534
3560
  cwd,
3535
3561
  notifications: [...notifications],
@@ -3556,6 +3582,8 @@ function createScreen(options = {}) {
3556
3582
  },
3557
3583
  unicode: {
3558
3584
  charsetG0,
3585
+ charsetG1,
3586
+ activeG1,
3559
3587
  lastChar,
3560
3588
  pendingRegionalIndicator,
3561
3589
  afterZWJ
@@ -3593,7 +3621,9 @@ function createScreen(options = {}) {
3593
3621
  attrs: cloneAttrsSnapshot(snapshotValue.savedState.attrs),
3594
3622
  originMode: snapshotValue.savedState.originMode,
3595
3623
  autoWrap: snapshotValue.savedState.autoWrap,
3596
- charsetG0: snapshotValue.savedState.charsetG0
3624
+ charsetG0: snapshotValue.savedState.charsetG0,
3625
+ charsetG1: snapshotValue.savedState.charsetG1 ?? false,
3626
+ activeG1: snapshotValue.savedState.activeG1 ?? false
3597
3627
  };
3598
3628
  attrs = cloneAttrsSnapshot(snapshotValue.attrs);
3599
3629
  bracketedPaste = snapshotValue.modes.bracketedPaste;
@@ -3611,7 +3641,6 @@ function createScreen(options = {}) {
3611
3641
  syncOutput = snapshotValue.modes.syncOutput;
3612
3642
  kittyKeyboardFlags = snapshotValue.modes.kittyKeyboardFlags;
3613
3643
  kittyKeyboardStack = [...snapshotValue.modes.kittyKeyboardStack];
3614
- hasKittyGraphics = snapshotValue.modes.kittyGraphics;
3615
3644
  colorSchemeReporting = snapshotValue.modes.colorSchemeReporting;
3616
3645
  decColumnMode = snapshotValue.modes.decColumn;
3617
3646
  altScrollMode = snapshotValue.modes.altScroll;
@@ -3626,6 +3655,7 @@ function createScreen(options = {}) {
3626
3655
  colorStack.push(...snapshotValue.colors.stack.map(cloneColorStateSnapshot));
3627
3656
  tabStops = new Set(snapshotValue.tabStops);
3628
3657
  title = snapshotValue.title;
3658
+ iconLabel = Object.hasOwn(snapshotValue, "iconLabel") ? snapshotValue.iconLabel : snapshotValue.title;
3629
3659
  clipboard = snapshotValue.clipboard;
3630
3660
  cwd = snapshotValue.cwd;
3631
3661
  notifications = [...snapshotValue.notifications];
@@ -3645,6 +3675,8 @@ function createScreen(options = {}) {
3645
3675
  apcOverflow = snapshotValue.parser.apcOverflow ?? false;
3646
3676
  utf8PendingBytes = [...snapshotValue.parser.utf8PendingBytes];
3647
3677
  charsetG0 = snapshotValue.unicode.charsetG0;
3678
+ charsetG1 = snapshotValue.unicode.charsetG1 ?? false;
3679
+ activeG1 = snapshotValue.unicode.activeG1 ?? false;
3648
3680
  lastChar = snapshotValue.unicode.lastChar;
3649
3681
  pendingRegionalIndicator = snapshotValue.unicode.pendingRegionalIndicator;
3650
3682
  afterZWJ = snapshotValue.unicode.afterZWJ;
@@ -3678,7 +3710,7 @@ function createScreen(options = {}) {
3678
3710
  }
3679
3711
  function getText() {
3680
3712
  const lines = [];
3681
- for (let r = 0; r < rows; r++) lines.push(rowToString(grid[r]));
3713
+ for (let r = 0; r < rows; r++) lines.push(rowToString(itemAt(grid, r, "screen text rows")));
3682
3714
  return lines.join("\n");
3683
3715
  }
3684
3716
  function getScrollbackText() {
@@ -3697,7 +3729,7 @@ function createScreen(options = {}) {
3697
3729
  }
3698
3730
  function getRowAbsolute(row) {
3699
3731
  if (row < 0 || row >= scrollback.length + rows) return makeRow(cols).toCells();
3700
- return (row < scrollback.length ? scrollback[row] : grid[row - scrollback.length]).toCells().map(stripCellColorIndex);
3732
+ return (row < scrollback.length ? itemAt(scrollback, row, "scrollback rows") : itemAt(grid, row - scrollback.length, "screen rows")).toCells().map(stripCellColorIndex);
3701
3733
  }
3702
3734
  function firstRetainedRow() {
3703
3735
  return trimmedRowCount;
@@ -3772,7 +3804,7 @@ function createScreen(options = {}) {
3772
3804
  case "leftRightMargin": return leftRightMarginMode;
3773
3805
  case "colorSchemeReporting": return colorSchemeReporting;
3774
3806
  case "kittyKeyboard": return kittyKeyboardFlags > 0;
3775
- case "kittyGraphics": return hasKittyGraphics;
3807
+ case "kittyGraphics": return kittyGraphicsImages;
3776
3808
  case "sixel": return hasSixel;
3777
3809
  default: return false;
3778
3810
  }
@@ -3850,7 +3882,7 @@ function createScreen(options = {}) {
3850
3882
  leftRightMargin: leftRightMarginMode,
3851
3883
  colorSchemeReporting,
3852
3884
  kittyKeyboard: kittyKeyboardFlags > 0,
3853
- kittyGraphics: hasKittyGraphics,
3885
+ kittyGraphics: kittyGraphicsImages,
3854
3886
  sixel: hasSixel
3855
3887
  };
3856
3888
  }
@@ -4267,7 +4299,7 @@ function serializeSnapshot(snapshot, options = {}) {
4267
4299
  const wraps = snapshot.scrollbackSoftWrapped;
4268
4300
  const last = snapshot.scrollback.length - 1;
4269
4301
  for (let i = 0; i < snapshot.scrollback.length; i++) {
4270
- const row = snapshot.scrollback[i];
4302
+ const row = itemAt(snapshot.scrollback, i, "snapshot scrollback rows");
4271
4303
  if (wraps?.[i] === true && i < last) out.push(serializeEncodeRow(row, hyperlinks, snapshot.cols), "\x1B[0m");
4272
4304
  else out.push(serializeEncodeRow(row, hyperlinks), "\x1B[0m\r\n");
4273
4305
  }
@@ -4295,7 +4327,7 @@ function serializeSnapshot(snapshot, options = {}) {
4295
4327
  const def = SERIALIZE_DEFAULT_PALETTE[i];
4296
4328
  if (cur !== void 0 && def !== void 0 && !serializeColorEq(cur, def)) out.push(`\x1b]4;${String(i)};${serializeColorSpec(cur)}\x1b\\`);
4297
4329
  }
4298
- if (options.includeTitle === true && snapshot.title.length > 0) out.push(`\x1b]0;${snapshot.title}\x1b\\`);
4330
+ if (options.includeTitle === true && snapshot.title.length > 0) out.push(`\x1b]2;${snapshot.title}\x1b\\`);
4299
4331
  out.push("\x1B[?7l", "\x1B[H\x1B[2J");
4300
4332
  const grid = snapshot.activeBuffer === "alt" ? snapshot.alt.grid : snapshot.main.grid;
4301
4333
  for (let r = 0; r < rows; r++) {
@@ -4306,6 +4338,8 @@ function serializeSnapshot(snapshot, options = {}) {
4306
4338
  out.push(m.autoWrap ? "\x1B[?7h" : "\x1B[?7l");
4307
4339
  if (snapshot.cursor.shape !== "block" || !snapshot.cursor.blinking) out.push(`\x1b[${String(serializeCursorStyleCode(snapshot.cursor.shape, snapshot.cursor.blinking))} q`);
4308
4340
  if (snapshot.unicode.charsetG0) out.push("\x1B(0");
4341
+ if (snapshot.unicode.charsetG1) out.push("\x1B)0");
4342
+ if (snapshot.unicode.activeG1) out.push("");
4309
4343
  if (m.insert && !excluded.has("insert")) out.push("\x1B[4h");
4310
4344
  if (!serializeTabStopsAreDefault(snapshot.tabStops, snapshot.cols)) {
4311
4345
  out.push("\x1B[3g");
@@ -4791,6 +4825,7 @@ function readGridSection(r, softWrapNullable, expected, strings, colors, styles)
4791
4825
  * deterministic for identical input and decodes back to a deep-equal snapshot.
4792
4826
  */
4793
4827
  function encodeScreenSnapshotBinary(snapshot) {
4828
+ if (Object.hasOwn(snapshot, "iconLabel") && typeof snapshot.iconLabel !== "string") throw new TypeError("vterm snapshot codec: invalid iconLabel");
4794
4829
  const w = createWriter();
4795
4830
  w.u8(VERSION);
4796
4831
  const header = {
@@ -4807,6 +4842,7 @@ function encodeScreenSnapshotBinary(snapshot) {
4807
4842
  colors: snapshot.colors,
4808
4843
  tabStops: snapshot.tabStops,
4809
4844
  title: snapshot.title,
4845
+ ...Object.hasOwn(snapshot, "iconLabel") ? { iconLabel: snapshot.iconLabel } : {},
4810
4846
  clipboard: snapshot.clipboard,
4811
4847
  cwd: snapshot.cwd,
4812
4848
  notifications: snapshot.notifications,
@@ -4840,6 +4876,7 @@ function decodeScreenSnapshotBinary(bytes) {
4840
4876
  if (version !== VERSION) throw new Error(`vterm snapshot codec: unsupported version byte ${version} (expected ${VERSION})`);
4841
4877
  const headerLen = r.varint();
4842
4878
  const header = JSON.parse(decoder.decode(r.take(headerLen)));
4879
+ if (Object.hasOwn(header, "iconLabel") && typeof header.iconLabel !== "string") throw new TypeError("vterm snapshot codec: invalid iconLabel");
4843
4880
  const strings = readStringTable(r);
4844
4881
  const colors = readColorTable(r);
4845
4882
  const styles = readStyleTable(r);
@@ -4875,6 +4912,7 @@ function decodeScreenSnapshotBinary(bytes) {
4875
4912
  colors: header.colors,
4876
4913
  tabStops: header.tabStops,
4877
4914
  title: header.title,
4915
+ ...Object.hasOwn(header, "iconLabel") ? { iconLabel: header.iconLabel } : {},
4878
4916
  clipboard: header.clipboard,
4879
4917
  cwd: header.cwd,
4880
4918
  notifications: header.notifications,