@simon_he/vue-tui 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -3278,14 +3278,14 @@ function clamp(n, min, max) {
3278
3278
  return Math.max(min, Math.min(max, n));
3279
3279
  }
3280
3280
  /**
3281
- * Register a fingerprint function on the buffer. This enables SoA fingerprint
3281
+ * Register or clear a fingerprint function on the buffer. This enables SoA fingerprint
3282
3282
  * pre-computation: cell writes will update fingerprints inline, and the renderer
3283
3283
  * can read them via getRowFingerprints() instead of per-cell hash computation.
3284
3284
  */
3285
3285
  function setFingerprintFn(buffer, fn) {
3286
3286
  buffer.fingerprintFn = fn;
3287
3287
  const len = buffer.rows * buffer.cols;
3288
- if (!len) {
3288
+ if (!fn || !len) {
3289
3289
  buffer.soaFingerprints = null;
3290
3290
  return;
3291
3291
  }
@@ -3306,7 +3306,10 @@ function setFingerprintFn(buffer, fn) {
3306
3306
  */
3307
3307
  function getRowFingerprints(buffer, y) {
3308
3308
  if (!buffer.soaFingerprints) return null;
3309
- const physY = physicalRowIndex(buffer, y);
3309
+ if (buffer.rows <= 0 || buffer.cols <= 0) return null;
3310
+ const yy = Math.floor(y);
3311
+ if (!Number.isFinite(yy) || yy < 0 || yy >= buffer.rows) return null;
3312
+ const physY = physicalRowIndex(buffer, yy);
3310
3313
  const base = physY * buffer.cols;
3311
3314
  return buffer.soaFingerprints.subarray(base, base + buffer.cols);
3312
3315
  }
@@ -4405,11 +4408,15 @@ function createTerminal(opts) {
4405
4408
  },
4406
4409
  setFingerprintFn(fn) {
4407
4410
  assertNotDisposed();
4408
- base.setFingerprintFn(fn);
4411
+ const state = getPlaneState(plane);
4412
+ setFingerprintFn(state.buffer, fn);
4409
4413
  },
4410
4414
  getRowFingerprints(y) {
4411
4415
  assertNotDisposed();
4412
- return base.getRowFingerprints(y);
4416
+ const yy = Math.floor(y);
4417
+ const state = getPlaneState(plane);
4418
+ if (!Number.isFinite(yy) || yy < 0 || yy >= state.buffer.rows) throw new RangeError("Row out of bounds");
4419
+ return getRowFingerprints(state.buffer, yy);
4413
4420
  }
4414
4421
  };
4415
4422
  planeTerminals.set(plane, api);
@@ -4585,7 +4592,9 @@ function createTerminal(opts) {
4585
4592
  },
4586
4593
  getRowFingerprints(y) {
4587
4594
  assertNotDisposed();
4588
- return getRowFingerprints(compositeBuffer, y);
4595
+ const yy = Math.floor(y);
4596
+ if (!Number.isFinite(yy) || yy < 0 || yy >= compositeBuffer.rows) throw new RangeError("Row out of bounds");
4597
+ return getRowFingerprints(compositeBuffer, yy);
4589
4598
  }
4590
4599
  };
4591
4600
  base[TERMINAL_PLANE_INTERNALS] = {
@@ -8422,15 +8431,24 @@ const SYNC_START = "\x1B[?2026h";
8422
8431
  const SYNC_END = "\x1B[?2026l";
8423
8432
  const OSC8_OPEN = (href) => `\u001B]8;;${href}\u0007`;
8424
8433
  const OSC8_CLOSE = "\x1B]8;;\x07";
8434
+ const stdoutFingerprintOwners = new WeakMap();
8425
8435
  function createStdoutRenderer(terminal, options) {
8426
8436
  const env = (process === null || process === void 0 ? void 0 : process.env) ?? {};
8427
8437
  if (shouldInstallFileWriters(env)) installNodeFileWriters();
8428
8438
  const output = (options === null || options === void 0 ? void 0 : options.output) ?? process.stdout;
8429
8439
  if (!output) throw new Error("createStdoutRenderer requires a Node stdout-like output");
8430
8440
  const out = output;
8441
+ const outputFd = Number(out.fd);
8442
+ const canUseSyncStdout = (options === null || options === void 0 ? void 0 : options.output) == null && Number.isInteger(outputFd) && outputFd === 1;
8443
+ const fingerprintOwner = Symbol("stdout-renderer");
8444
+ let ownsFingerprintFn = false;
8445
+ const fingerprintTerminal = terminal;
8446
+ const canInstallTerminalFingerprintFn = () => typeof fingerprintTerminal.setFingerprintFn === "function" && typeof fingerprintTerminal.getRowFingerprints === "function";
8447
+ const outputIsTTY = Boolean(out.isTTY);
8448
+ const outputAllowsOsc8Links = out.isTTY !== false;
8431
8449
  const clear = (options === null || options === void 0 ? void 0 : options.clear) ?? true;
8432
8450
  const hideCursor = (options === null || options === void 0 ? void 0 : options.hideCursor) ?? true;
8433
- const altScreen = (options === null || options === void 0 ? void 0 : options.altScreen) ?? Boolean(out.isTTY);
8451
+ const altScreen = (options === null || options === void 0 ? void 0 : options.altScreen) ?? outputIsTTY;
8434
8452
  let defaultBg = (options === null || options === void 0 ? void 0 : options.defaultBg) == null || (options === null || options === void 0 ? void 0 : options.defaultBg) === "transparent" ? void 0 : (options === null || options === void 0 ? void 0 : options.defaultBg) ?? "black";
8435
8453
  let palette = (options === null || options === void 0 ? void 0 : options.palette) ?? null;
8436
8454
  const trackResize = (options === null || options === void 0 ? void 0 : options.trackResize) ?? true;
@@ -8444,11 +8462,11 @@ function createStdoutRenderer(terminal, options) {
8444
8462
  });
8445
8463
  function resolveUseSyncOutput() {
8446
8464
  if ((options === null || options === void 0 ? void 0 : options.useSyncOutput) != null) return options.useSyncOutput;
8447
- const term = String(env.TERM ?? "").toLowerCase();
8465
+ const term$1 = String(env.TERM ?? "").toLowerCase();
8448
8466
  const termProgram$1 = String(env.TERM_PROGRAM ?? "").toLowerCase();
8449
8467
  const termProgramVersion = env.TERM_PROGRAM_VERSION;
8450
8468
  const isGhostty$1 = "GHOSTTY_RESOURCES_DIR" in env;
8451
- const isProblematicTerminal = isGhostty$1 || termProgram$1.includes("apple_terminal") || term.includes("screen") && !term.includes("tmux");
8469
+ const isProblematicTerminal = isGhostty$1 || termProgram$1.includes("apple_terminal") || term$1.includes("screen") && !term$1.includes("tmux");
8452
8470
  if (isProblematicTerminal) return false;
8453
8471
  const isGoodTerminal = termProgram$1.includes("iterm") && termProgramVersion && compareVersion(String(termProgramVersion), "3.5.0") >= 0;
8454
8472
  if (isGoodTerminal) return true;
@@ -8475,10 +8493,90 @@ function createStdoutRenderer(terminal, options) {
8475
8493
  const chunkThresholdBytes = 64 * 1024;
8476
8494
  const syncMaxBytes = 128 * 1024;
8477
8495
  const dirtyFullThreshold = .6;
8496
+ const DIRTY_SPAN_MERGE_GAP_CELLS = 2;
8497
+ const DIRTY_SPAN_MAX_PER_ROW = 12;
8498
+ const DIRTY_SPAN_FULL_ROW_THRESHOLD = .7;
8499
+ const EST_STYLE_SWITCH_BYTES = 8;
8500
+ const EST_CLEAR_TO_EOL_BYTES = 3;
8478
8501
  const disableCursorPos = false;
8479
8502
  const termProgram = String(env.TERM_PROGRAM ?? "").trim().toLowerCase();
8503
+ const term = String(env.TERM ?? "").trim().toLowerCase();
8504
+ const isKitty = "KITTY_WINDOW_ID" in env || termProgram.includes("kitty") || term.includes("kitty");
8505
+ const isAlacritty = "ALACRITTY_WINDOW_ID" in env || "ALACRITTY_LOG" in env || termProgram.includes("alacritty") || term.includes("alacritty");
8506
+ const isWezTerm = "WEZTERM_PANE" in env || "WEZTERM_EXECUTABLE" in env || termProgram.includes("wezterm");
8480
8507
  const isVscodeTerminal = termProgram === "vscode" || "VSCODE_PID" in env || "VSCODE_IPC_HOOK_CLI" in env;
8481
- const enableOsc8Links = out.isTTY !== false && !isVscodeTerminal;
8508
+ const useConservativeDirtyRows = Boolean(outputIsTTY && (isGhostty || isKitty || isAlacritty || isWezTerm));
8509
+ const normalizeDirtyRowPatchMode = (value) => {
8510
+ const normalized = typeof value === "string" ? value.trim().toLowerCase() : value;
8511
+ return normalized === "auto" || normalized === "row" || normalized === "span" ? normalized : null;
8512
+ };
8513
+ const normalizeColumnDiffMode = (value) => {
8514
+ if (value === true) return "span";
8515
+ if (value === false) return "row";
8516
+ if (typeof value === "string" && value.trim().toLowerCase() === "auto") return "auto";
8517
+ return null;
8518
+ };
8519
+ const firstValidDirtyRowPatchMode = (...values) => {
8520
+ for (const value of values) {
8521
+ const mode = normalizeDirtyRowPatchMode(value);
8522
+ if (mode) return mode;
8523
+ }
8524
+ return null;
8525
+ };
8526
+ function resolveDirtyRowPatchMode() {
8527
+ const explicitMode = options === null || options === void 0 ? void 0 : options.dirtyRowPatchMode;
8528
+ if (explicitMode != null) {
8529
+ const optionMode = normalizeDirtyRowPatchMode(explicitMode);
8530
+ if (!optionMode) throw new Error(`Invalid dirtyRowPatchMode=${JSON.stringify(explicitMode)}; expected "auto", "row", or "span".`);
8531
+ return optionMode;
8532
+ }
8533
+ const legacy = options === null || options === void 0 ? void 0 : options.columnDiff;
8534
+ if (legacy !== void 0) {
8535
+ const legacyMode = normalizeColumnDiffMode(legacy);
8536
+ if (!legacyMode) throw new Error(`Invalid columnDiff=${JSON.stringify(legacy)}; expected boolean or "auto".`);
8537
+ return legacyMode;
8538
+ }
8539
+ const envMode = firstValidDirtyRowPatchMode(env.VUE_TUI_DIRTY_ROW_PATCH_MODE, env.DIMCODE_TUI_DIRTY_ROW_RENDER_MODE, env.DIMCODE_TUI_DIRTY_ROW_PATCH_MODE);
8540
+ if (envMode) return envMode;
8541
+ return "auto";
8542
+ }
8543
+ const dirtyRowPatchMode = resolveDirtyRowPatchMode();
8544
+ const dirtySpanConservativeMaxCells = (() => {
8545
+ if ((options === null || options === void 0 ? void 0 : options.dirtySpanConservativeMaxCells) !== void 0) {
8546
+ const raw$1 = options.dirtySpanConservativeMaxCells;
8547
+ const value$1 = typeof raw$1 === "number" ? raw$1 : Number(raw$1);
8548
+ if (!Number.isFinite(value$1) || value$1 <= 0) throw new Error(`Invalid dirtySpanConservativeMaxCells=${JSON.stringify(raw$1)}; expected a positive finite number.`);
8549
+ return Math.floor(value$1);
8550
+ }
8551
+ const raw = firstNonEmptyEnv(env, "VUE_TUI_DIRTY_SPAN_MAX_CELLS", "DIMCODE_TUI_DIRTY_SPAN_MAX_CELLS");
8552
+ const value = Number(raw);
8553
+ return Number.isFinite(value) && value > 0 ? Math.floor(value) : 32;
8554
+ })();
8555
+ function resolveInternalColumnDiffMode() {
8556
+ const value = options === null || options === void 0 ? void 0 : options.__columnDiffMode;
8557
+ if (value == null) return "auto";
8558
+ if (value === "auto" || value === "single-span" || value === "multi-span" || value === "full-row") return value;
8559
+ throw new Error(`Invalid __columnDiffMode=${JSON.stringify(value)}; expected "auto", "single-span", "multi-span", or "full-row".`);
8560
+ }
8561
+ const columnDiffMode = resolveInternalColumnDiffMode();
8562
+ function resolveEffectiveDirtyRowPatchMode() {
8563
+ switch (columnDiffMode) {
8564
+ case "full-row": return "row";
8565
+ case "single-span": return "single-span";
8566
+ case "multi-span": return "multi-span";
8567
+ case "auto": break;
8568
+ }
8569
+ switch (dirtyRowPatchMode) {
8570
+ case "row": return "row";
8571
+ case "span":
8572
+ case "auto": return "multi-span";
8573
+ }
8574
+ }
8575
+ const effectiveDirtyRowPatchMode = resolveEffectiveDirtyRowPatchMode();
8576
+ const shouldUseDirtySpans = () => effectiveDirtyRowPatchMode !== "row";
8577
+ const shouldUseMultiDirtySpans = () => effectiveDirtyRowPatchMode === "multi-span";
8578
+ const enableOsc8Links = outputAllowsOsc8Links && !isVscodeTerminal;
8579
+ const renderedHref = (value) => enableOsc8Links ? normalizeHref(value) : null;
8482
8580
  let disposed = false;
8483
8581
  let lastFrameTime = 0;
8484
8582
  let pendingRender = false;
@@ -8491,8 +8589,9 @@ function createStdoutRenderer(terminal, options) {
8491
8589
  let accumulatedDirtyCount = 0;
8492
8590
  let accumulatedDirtyMin = Number.POSITIVE_INFINITY;
8493
8591
  let accumulatedDirtyMax = -1;
8592
+ let accumulatedDirtyRowsRequireRepaint = false;
8494
8593
  let accumulatedScrollOperations = null;
8495
- const MIN_FRAME_MS = !out.isTTY ? 0 : 16;
8594
+ const MIN_FRAME_MS = outputIsTTY ? 16 : 0;
8496
8595
  function clampCellToViewport(cell, size) {
8497
8596
  const maxX = Math.max(0, Math.floor(size.cols) - 1);
8498
8597
  const maxY = Math.max(0, Math.floor(size.rows) - 1);
@@ -8511,56 +8610,68 @@ function createStdoutRenderer(terminal, options) {
8511
8610
  const env$1 = (process === null || process === void 0 ? void 0 : process.env) ?? {};
8512
8611
  return detectTerminalColorCapability({
8513
8612
  env: env$1,
8514
- isTTY: Boolean(out.isTTY),
8613
+ isTTY: outputIsTTY,
8515
8614
  platform: String((process === null || process === void 0 ? void 0 : process.platform) ?? "")
8516
8615
  }).mode;
8517
8616
  }
8518
8617
  const colorMode = resolveColorMode();
8519
8618
  const enableDim = colorMode === "truecolor";
8520
- const COLOR_INDEX = {
8521
- black: 1,
8522
- red: 2,
8523
- green: 3,
8524
- yellow: 4,
8525
- blue: 5,
8526
- magenta: 6,
8527
- cyan: 7,
8528
- white: 8,
8529
- blackBright: 9,
8530
- redBright: 10,
8531
- greenBright: 11,
8532
- yellowBright: 12,
8533
- blueBright: 13,
8534
- magentaBright: 14,
8535
- cyanBright: 15,
8536
- whiteBright: 16
8537
- };
8538
- const BUILTIN_COLOR_INDEX = { ...COLOR_INDEX };
8539
- let nextColorIdx = 17;
8540
- const MAX_COLOR_INDEX = 255;
8541
- function colorIndex(color) {
8542
- if (!color) return 0;
8543
- let idx = COLOR_INDEX[color];
8544
- if (idx !== void 0) return idx;
8545
- if (nextColorIdx > MAX_COLOR_INDEX) return MAX_COLOR_INDEX;
8546
- idx = nextColorIdx++;
8547
- COLOR_INDEX[color] = idx;
8548
- return idx;
8549
- }
8550
8619
  let styleKeyCache = new WeakMap();
8620
+ const styleKeyIndex = new Map();
8621
+ let nextStyleKey = 1;
8622
+ const MAX_STYLE_KEYS = 131072;
8623
+ const MAX_UINT32_INTERN_ID = 4294967295;
8624
+ const STYLE_KEY_SEP = "";
8551
8625
  const normalizedHrefCache = new Map();
8552
8626
  const MAX_HREF_CACHE = 2048;
8553
- const HREF_STYLE_FLAG = 1 << 21;
8627
+ let fingerprintInternResetRequiresBaseline = false;
8628
+ let fingerprintBaselineGeneration = 0;
8629
+ let activeRenderBaselineGeneration = 0;
8630
+ let terminalFingerprintFnDirty = false;
8631
+ let fingerprintInstallBlockedByExternalProvider = false;
8632
+ function invalidateFingerprintBaseline() {
8633
+ fpPrevValid = false;
8634
+ fingerprintInternResetRequiresBaseline = true;
8635
+ fingerprintBaselineGeneration++;
8636
+ }
8637
+ const activeRenderBaselineChanged = () => activeRenderBaselineGeneration !== fingerprintBaselineGeneration;
8638
+ function resetStyleKeyIndex(resetIds = false) {
8639
+ styleKeyCache = new WeakMap();
8640
+ styleKeyIndex.clear();
8641
+ if (resetIds) nextStyleKey = 1;
8642
+ terminalFingerprintFnDirty = true;
8643
+ invalidateFingerprintBaseline();
8644
+ }
8645
+ function internStyleKey(signature) {
8646
+ const cached = styleKeyIndex.get(signature);
8647
+ if (cached !== void 0) return cached;
8648
+ if (styleKeyIndex.size >= MAX_STYLE_KEYS) resetStyleKeyIndex();
8649
+ if (nextStyleKey >= MAX_UINT32_INTERN_ID) resetStyleKeyIndex(true);
8650
+ const key = nextStyleKey++;
8651
+ styleKeyIndex.set(signature, key);
8652
+ return key;
8653
+ }
8654
+ function styleSignature(style) {
8655
+ return [
8656
+ style.fg ?? "",
8657
+ style.bg ?? defaultBg ?? "",
8658
+ style.bold ? "1" : "0",
8659
+ enableDim && style.dim ? "1" : "0",
8660
+ style.italic ? "1" : "0",
8661
+ style.underline ? "1" : "0",
8662
+ style.inverse ? "1" : "0",
8663
+ renderedHref(style.href) ? "1" : "0"
8664
+ ].join(STYLE_KEY_SEP);
8665
+ }
8554
8666
  function styleKey(style) {
8555
8667
  const cached = styleKeyCache.get(style);
8556
8668
  if (cached !== void 0) return cached;
8557
- const href = normalizeHref(style.href);
8558
- const key = colorIndex(style.fg) | colorIndex(style.bg ?? defaultBg) << 8 | (style.bold ? 65536 : 0) | (enableDim && style.dim ? 1 << 17 : 0) | (style.italic ? 1 << 18 : 0) | (style.underline ? 1 << 19 : 0) | (style.inverse ? 1 << 20 : 0) | (href ? HREF_STYLE_FLAG : 0);
8669
+ const key = internStyleKey(styleSignature(style));
8559
8670
  styleKeyCache.set(style, key);
8560
8671
  return key;
8561
8672
  }
8562
8673
  function styleKeyFromParts(style) {
8563
- return colorIndex(style.fg) | colorIndex(style.bg ?? defaultBg) << 8 | (style.bold ? 65536 : 0) | (enableDim && style.dim ? 1 << 17 : 0) | (style.italic ? 1 << 18 : 0) | (style.underline ? 1 << 19 : 0) | (style.inverse ? 1 << 20 : 0) | (normalizeHref(style.href) ? HREF_STYLE_FLAG : 0);
8674
+ return internStyleKey(styleSignature(style));
8564
8675
  }
8565
8676
  function normalizeHref(value) {
8566
8677
  if (typeof value !== "string") return null;
@@ -8633,17 +8744,25 @@ function createStdoutRenderer(terminal, options) {
8633
8744
  let prevFP = new Uint32Array(0);
8634
8745
  let currentHrefIds = new Uint32Array(0);
8635
8746
  let prevHrefIds = new Uint32Array(0);
8747
+ let currentTextIds = new Uint32Array(0);
8748
+ let prevTextIds = new Uint32Array(0);
8636
8749
  let fpPrevValid = false;
8750
+ let nextCellTextId = 1;
8751
+ let blankTextId = 0;
8752
+ let emptyTextId = 0;
8753
+ let continuationTextId = 0;
8754
+ const cellTextIdCache = new Map();
8755
+ const MAX_CELL_TEXT_IDS = 131072;
8637
8756
  let prevOverlayBlockedRows = [];
8638
8757
  let prevOverlayPartialRows = [];
8639
8758
  const hrefIndex = new Map();
8640
8759
  let nextHrefId = 1;
8641
8760
  const MAX_HREF_IDS = 8192;
8642
8761
  let hrefIndexResetRequiresBaseline = false;
8643
- function resetHrefIndex() {
8762
+ function resetHrefIndex(resetIds = false) {
8644
8763
  hrefIndex.clear();
8645
- nextHrefId = 1;
8646
- fpPrevValid = false;
8764
+ if (resetIds) nextHrefId = 1;
8765
+ invalidateFingerprintBaseline();
8647
8766
  hrefIndexResetRequiresBaseline = true;
8648
8767
  }
8649
8768
  function ensureFingerprints(cols, rows) {
@@ -8655,44 +8774,359 @@ function createStdoutRenderer(terminal, options) {
8655
8774
  prevFP = new Uint32Array(len);
8656
8775
  currentHrefIds = new Uint32Array(len);
8657
8776
  prevHrefIds = new Uint32Array(len);
8777
+ currentTextIds = new Uint32Array(len);
8778
+ prevTextIds = new Uint32Array(len);
8658
8779
  fpPrevValid = false;
8659
8780
  prevOverlayBlockedRows = [];
8660
8781
  prevOverlayPartialRows = [];
8661
8782
  }
8662
- function charHash10(ch) {
8663
- if (ch.length <= 1) return (ch.charCodeAt(0) || 0) & 1023;
8664
- let h$1 = 33052;
8665
- for (let i = 0; i < ch.length; i++) {
8666
- h$1 ^= ch.charCodeAt(i);
8667
- h$1 = h$1 * 257 & 65535;
8783
+ function cellFingerprintFromStyleKey(_ch, key) {
8784
+ return key >>> 0;
8785
+ }
8786
+ function cellFingerprint(_ch, style) {
8787
+ return styleKey(style) >>> 0;
8788
+ }
8789
+ function allocateCellTextId(text) {
8790
+ const id = nextCellTextId++;
8791
+ cellTextIdCache.set(text, id);
8792
+ return id;
8793
+ }
8794
+ function resetCellTextIdCache(resetIds = false) {
8795
+ cellTextIdCache.clear();
8796
+ if (resetIds) nextCellTextId = 1;
8797
+ blankTextId = allocateCellTextId(" ");
8798
+ emptyTextId = allocateCellTextId("");
8799
+ continuationTextId = nextCellTextId++;
8800
+ invalidateFingerprintBaseline();
8801
+ }
8802
+ function cellIdentityTextId(cell) {
8803
+ if (cell.continuation) return continuationTextId;
8804
+ if (cell.ch === "") return emptyTextId;
8805
+ return cellTextId(cell.ch);
8806
+ }
8807
+ function cellTextId(text) {
8808
+ let cached = cellTextIdCache.get(text);
8809
+ if (cached !== void 0) return cached;
8810
+ const mustResetTextIds = nextCellTextId >= MAX_UINT32_INTERN_ID - 2;
8811
+ if (cellTextIdCache.size >= MAX_CELL_TEXT_IDS || mustResetTextIds) {
8812
+ resetCellTextIdCache(mustResetTextIds);
8813
+ cached = cellTextIdCache.get(text);
8814
+ if (cached !== void 0) return cached;
8668
8815
  }
8669
- return h$1 & 1023;
8816
+ return allocateCellTextId(text);
8670
8817
  }
8818
+ resetCellTextIdCache(true);
8671
8819
  function hrefId(href) {
8672
8820
  if (!href) return 0;
8673
8821
  const cached = hrefIndex.get(href);
8674
8822
  if (cached != null) return cached;
8675
- if (hrefIndex.size >= MAX_HREF_IDS || nextHrefId >= 4294967295) resetHrefIndex();
8823
+ const mustResetHrefIds = nextHrefId >= MAX_UINT32_INTERN_ID;
8824
+ if (hrefIndex.size >= MAX_HREF_IDS || mustResetHrefIds) resetHrefIndex(mustResetHrefIds);
8676
8825
  const id = nextHrefId++;
8677
8826
  hrefIndex.set(href, id);
8678
8827
  return id;
8679
8828
  }
8680
- function cellFingerprint(ch, style) {
8681
- return styleKey(style) << 10 | charHash10(ch);
8829
+ const BLANK_STYLE = Object.freeze({});
8830
+ const BLANK_CELL = Object.freeze({
8831
+ ch: " ",
8832
+ width: 1,
8833
+ style: BLANK_STYLE
8834
+ });
8835
+ const cellAt = (row, x) => row[x] ?? BLANK_CELL;
8836
+ function rowFingerprintsForThisRenderer(y) {
8837
+ var _fingerprintTerminal$;
8838
+ if (terminalFingerprintFnDirty) return null;
8839
+ if (!ownsFingerprintFn || stdoutFingerprintOwners.get(terminal) !== fingerprintOwner) return null;
8840
+ return ((_fingerprintTerminal$ = fingerprintTerminal.getRowFingerprints) === null || _fingerprintTerminal$ === void 0 ? void 0 : _fingerprintTerminal$.call(fingerprintTerminal, y)) ?? null;
8841
+ }
8842
+ function hasActiveExternalFingerprintProvider() {
8843
+ if (!canInstallTerminalFingerprintFn()) return false;
8844
+ if (stdoutFingerprintOwners.has(terminal)) return false;
8845
+ try {
8846
+ const rows = terminal.size().rows;
8847
+ if (rows <= 0) return false;
8848
+ return fingerprintTerminal.getRowFingerprints(0) != null;
8849
+ } catch {
8850
+ return false;
8851
+ }
8852
+ }
8853
+ function shouldAttemptFingerprintInstall() {
8854
+ return !fingerprintInstallBlockedByExternalProvider || !hasActiveExternalFingerprintProvider();
8682
8855
  }
8683
8856
  function fingerprintRow(row, y, cols) {
8684
- const rowFP = terminal.getRowFingerprints(y);
8857
+ const rowFP = rowFingerprintsForThisRenderer(y);
8685
8858
  const base = y * fpCols;
8686
- if (rowFP && rowFP.length >= cols) currentFP.set(rowFP.subarray(0, cols), base);
8859
+ if (rowFP && rowFP.length >= cols && row.length >= cols) currentFP.set(rowFP.subarray(0, cols), base);
8687
8860
  else for (let x = 0; x < cols; x++) {
8688
- const cell = row[x];
8861
+ const cell = cellAt(row, x);
8689
8862
  currentFP[base + x] = cellFingerprint(cell.ch, cell.style);
8690
8863
  }
8691
8864
  for (let x = 0; x < cols; x++) {
8692
- const cell = row[x];
8693
- currentHrefIds[base + x] = hrefId(normalizeHref(cell.style.href));
8865
+ const cell = cellAt(row, x);
8866
+ currentHrefIds[base + x] = hrefId(renderedHref(cell.style.href));
8867
+ currentTextIds[base + x] = cellIdentityTextId(cell);
8868
+ }
8869
+ }
8870
+ function snapshotRowFingerprints(row, y, cols) {
8871
+ const fp = new Uint32Array(cols);
8872
+ const hrefIds = new Uint32Array(cols);
8873
+ const textIds = new Uint32Array(cols);
8874
+ const rowFP = rowFingerprintsForThisRenderer(y);
8875
+ if (rowFP && rowFP.length >= cols && row.length >= cols) fp.set(rowFP.subarray(0, cols));
8876
+ else for (let x = 0; x < cols; x++) {
8877
+ const cell = cellAt(row, x);
8878
+ fp[x] = cellFingerprint(cell.ch, cell.style);
8879
+ }
8880
+ for (let x = 0; x < cols; x++) {
8881
+ const cell = cellAt(row, x);
8882
+ hrefIds[x] = hrefId(renderedHref(cell.style.href));
8883
+ textIds[x] = cellIdentityTextId(cell);
8694
8884
  }
8885
+ return {
8886
+ fp,
8887
+ hrefIds,
8888
+ textIds
8889
+ };
8695
8890
  }
8891
+ const currentCellMatchesPrevious = (base, x) => {
8892
+ return currentFP[base + x] === prevFP[base + x] && currentTextIds[base + x] === prevTextIds[base + x] && currentHrefIds[base + x] === prevHrefIds[base + x];
8893
+ };
8894
+ const cellMatchesPreviousAt = (currentBase, previousBase, x) => {
8895
+ return currentFP[currentBase + x] === prevFP[previousBase + x] && currentTextIds[currentBase + x] === prevTextIds[previousBase + x] && currentHrefIds[currentBase + x] === prevHrefIds[previousBase + x];
8896
+ };
8897
+ const referenceCellMatchesCurrent = (row, rowFP, base, x, cols, referenceFP, referenceHrefIds, referenceTextIds) => {
8898
+ const cell = cellAt(row, x);
8899
+ const fingerprint = rowFP && rowFP.length >= cols && row.length >= cols ? rowFP[x] : cellFingerprint(cell.ch, cell.style);
8900
+ return fingerprint === referenceFP[base + x] && cellIdentityTextId(cell) === referenceTextIds[base + x] && hrefId(renderedHref(cell.style.href)) === referenceHrefIds[base + x];
8901
+ };
8902
+ const fullRowDirtySpan = (cols) => {
8903
+ return cols > 0 ? [{
8904
+ startX: 0,
8905
+ endXExclusive: cols
8906
+ }] : [];
8907
+ };
8908
+ const dirtySpanWidth = (span) => Math.max(0, span.endXExclusive - span.startX);
8909
+ const dirtySpanCoverage = (spans) => {
8910
+ let total = 0;
8911
+ for (const span of spans) total += dirtySpanWidth(span);
8912
+ return total;
8913
+ };
8914
+ const estimatedCursorMoveBytes = (y, x) => {
8915
+ return 3 + String(y + 1).length + 1 + String(x + 1).length + 1;
8916
+ };
8917
+ const estimatedRenderableTextBytes = (row, startX, endXExclusive, cols) => {
8918
+ const start = Math.max(0, Math.min(cols, Math.floor(startX)));
8919
+ const end = Math.max(start, Math.min(cols, Math.floor(endXExclusive)));
8920
+ let bytes = 0;
8921
+ for (let x = start; x < end; x++) {
8922
+ const cell = cellAt(row, x);
8923
+ if (cell.continuation) continue;
8924
+ bytes += Buffer.byteLength(cell.ch || " ", "utf8");
8925
+ }
8926
+ return bytes;
8927
+ };
8928
+ const estimatedSpanPatchBytes = (row, spans, y, cols, clearToEol) => {
8929
+ let bytes = 0;
8930
+ for (const span of spans) {
8931
+ bytes += estimatedCursorMoveBytes(y, span.startX);
8932
+ bytes += estimatedRenderableTextBytes(row, span.startX, span.endXExclusive, cols);
8933
+ bytes += EST_STYLE_SWITCH_BYTES;
8934
+ }
8935
+ if (clearToEol) bytes += EST_CLEAR_TO_EOL_BYTES;
8936
+ return bytes;
8937
+ };
8938
+ const estimatedContiguousPatchBytes = (row, spans, y, cols, clearToEol) => {
8939
+ if (!spans.length) return 0;
8940
+ const first = spans[0];
8941
+ const last = spans[spans.length - 1];
8942
+ return estimatedCursorMoveBytes(y, first.startX) + estimatedRenderableTextBytes(row, first.startX, last.endXExclusive, cols) + EST_STYLE_SWITCH_BYTES + (clearToEol ? EST_CLEAR_TO_EOL_BYTES : 0);
8943
+ };
8944
+ const estimatedFullRowPatchBytes = (row, y, cols, clearToEol) => {
8945
+ return estimatedCursorMoveBytes(y, 0) + EST_STYLE_SWITCH_BYTES + estimatedRenderableTextBytes(row, 0, cols, cols) + (clearToEol ? EST_CLEAR_TO_EOL_BYTES : 0);
8946
+ };
8947
+ const resolveDirtySpanRenderMode = (row, spans, y, cols, clearToEol) => {
8948
+ if (spans.length <= 0) return "spans";
8949
+ const forceSpanPatch = dirtyRowPatchMode === "span" || columnDiffMode === "single-span" || columnDiffMode === "multi-span";
8950
+ const coverage = dirtySpanCoverage(spans);
8951
+ const fullRowCoverageLimit = Math.max(1, Math.floor(cols * DIRTY_SPAN_FULL_ROW_THRESHOLD));
8952
+ if (!forceSpanPatch && dirtyRowPatchMode === "auto" && useConservativeDirtyRows && coverage > dirtySpanConservativeMaxCells) return "row";
8953
+ if (!forceSpanPatch && coverage >= fullRowCoverageLimit) return "row";
8954
+ const fullRowCost = estimatedFullRowPatchBytes(row, y, cols, clearToEol);
8955
+ if (spans.length === 1) {
8956
+ const spanCost = estimatedSpanPatchBytes(row, spans, y, cols, clearToEol);
8957
+ return forceSpanPatch || spanCost < fullRowCost ? "spans" : "row";
8958
+ }
8959
+ const multiCost = estimatedSpanPatchBytes(row, spans, y, cols, clearToEol);
8960
+ const first = spans[0];
8961
+ const last = spans[spans.length - 1];
8962
+ const contiguousWidth = Math.max(0, last.endXExclusive - first.startX);
8963
+ const canUseContiguous = forceSpanPatch || contiguousWidth < fullRowCoverageLimit;
8964
+ const contiguousCost = canUseContiguous ? estimatedContiguousPatchBytes(row, spans, y, cols, clearToEol) : Number.POSITIVE_INFINITY;
8965
+ const tooManySpans = !forceSpanPatch && spans.length > DIRTY_SPAN_MAX_PER_ROW;
8966
+ const effectiveMultiCost = tooManySpans ? Number.POSITIVE_INFINITY : multiCost;
8967
+ if (forceSpanPatch) return contiguousCost < multiCost ? "contiguous" : "spans";
8968
+ if (contiguousCost < effectiveMultiCost && contiguousCost < fullRowCost) return "contiguous";
8969
+ if (!tooManySpans && multiCost < fullRowCost) return "spans";
8970
+ return "row";
8971
+ };
8972
+ const expandSpanStart = (row, startX, cols) => {
8973
+ var _row$x;
8974
+ let x = Math.max(0, Math.min(cols, Math.floor(startX)));
8975
+ while (x > 0 && ((_row$x = row[x]) === null || _row$x === void 0 ? void 0 : _row$x.continuation)) x--;
8976
+ const prev = row[x - 1];
8977
+ if (prev && prev.width === 2 && !prev.continuation) x--;
8978
+ return Math.max(0, x);
8979
+ };
8980
+ const expandSpanEnd = (row, endXExclusive, cols) => {
8981
+ var _row$x2;
8982
+ let x = Math.max(0, Math.min(cols, Math.floor(endXExclusive)));
8983
+ const last = row[x - 1];
8984
+ if (last && last.width === 2 && !last.continuation) x++;
8985
+ while (x < cols && ((_row$x2 = row[x]) === null || _row$x2 === void 0 ? void 0 : _row$x2.continuation)) x++;
8986
+ return Math.min(cols, x);
8987
+ };
8988
+ const previousCellLooksLikeWideContinuation = (referenceTextIds, base, x) => {
8989
+ if (x <= 0 || x >= fpCols) return false;
8990
+ return referenceTextIds[base + x] === continuationTextId;
8991
+ };
8992
+ const expandSpanForReferenceWideGlyphs = (span, referenceTextIds, base, cols) => {
8993
+ let startX = span.startX;
8994
+ let endXExclusive = span.endXExclusive;
8995
+ const limit = Math.max(0, Math.min(cols, fpCols));
8996
+ if (startX > 0) {
8997
+ if (previousCellLooksLikeWideContinuation(referenceTextIds, base, startX)) startX--;
8998
+ else if (previousCellLooksLikeWideContinuation(referenceTextIds, base, startX - 1)) startX = Math.max(0, startX - 2);
8999
+ }
9000
+ while (endXExclusive < limit) {
9001
+ if (!previousCellLooksLikeWideContinuation(referenceTextIds, base, endXExclusive)) break;
9002
+ endXExclusive++;
9003
+ }
9004
+ return {
9005
+ startX,
9006
+ endXExclusive
9007
+ };
9008
+ };
9009
+ const widenSpanToCurrentGlyphBoundaries = (row, span, cols) => {
9010
+ return {
9011
+ startX: expandSpanStart(row, span.startX, cols),
9012
+ endXExclusive: expandSpanEnd(row, span.endXExclusive, cols)
9013
+ };
9014
+ };
9015
+ const expandSpansForReferenceWideGlyphs = (spans, row, y, cols, referenceTextIds) => {
9016
+ if (!spans.length || !fpCols || y < 0 || y >= fpRows) return spans;
9017
+ const base = y * fpCols;
9018
+ const expanded = [];
9019
+ for (const span of spans) {
9020
+ const next = widenSpanToCurrentGlyphBoundaries(row, expandSpanForReferenceWideGlyphs(span, referenceTextIds, base, cols), cols);
9021
+ const prev = expanded[expanded.length - 1];
9022
+ if (prev && next.startX <= prev.endXExclusive + DIRTY_SPAN_MERGE_GAP_CELLS) {
9023
+ expanded[expanded.length - 1] = {
9024
+ startX: prev.startX,
9025
+ endXExclusive: Math.max(prev.endXExclusive, next.endXExclusive)
9026
+ };
9027
+ continue;
9028
+ }
9029
+ expanded.push(next);
9030
+ }
9031
+ return expanded;
9032
+ };
9033
+ const pushDirtySpan = (spans, row, startX, endXExclusive, cols) => {
9034
+ let expandedStart = Math.max(0, Math.min(cols, startX));
9035
+ let expandedEnd = Math.max(expandedStart, Math.min(cols, endXExclusive));
9036
+ if (expandedEnd <= expandedStart) return;
9037
+ expandedStart = expandSpanStart(row, expandedStart, cols);
9038
+ expandedEnd = expandSpanEnd(row, expandedEnd, cols);
9039
+ expandedStart = Math.max(0, Math.min(cols, expandedStart));
9040
+ expandedEnd = Math.max(expandedStart, Math.min(cols, expandedEnd));
9041
+ if (expandedEnd <= expandedStart) return;
9042
+ const prev = spans[spans.length - 1];
9043
+ if (prev && expandedStart <= prev.endXExclusive + DIRTY_SPAN_MERGE_GAP_CELLS) {
9044
+ spans[spans.length - 1] = {
9045
+ startX: prev.startX,
9046
+ endXExclusive: Math.max(prev.endXExclusive, expandedEnd)
9047
+ };
9048
+ return;
9049
+ }
9050
+ spans.push({
9051
+ startX: expandedStart,
9052
+ endXExclusive: expandedEnd
9053
+ });
9054
+ };
9055
+ const collectChangedSpans = (row, cols, changedAt) => {
9056
+ const limit = Math.max(0, cols);
9057
+ if (limit <= 0) return [];
9058
+ const spans = [];
9059
+ let startX = -1;
9060
+ for (let x = 0; x < limit; x++) {
9061
+ if (changedAt(x)) {
9062
+ if (startX < 0) startX = x;
9063
+ continue;
9064
+ }
9065
+ if (startX >= 0) {
9066
+ pushDirtySpan(spans, row, startX, x, cols);
9067
+ startX = -1;
9068
+ }
9069
+ }
9070
+ if (startX >= 0) pushDirtySpan(spans, row, startX, limit, cols);
9071
+ return spans;
9072
+ };
9073
+ const resolveChangedSpans = (row, y, cols, nextSnapshot) => {
9074
+ if (!fpCols || cols !== fpCols || y >= fpRows || !fpPrevValid) return fullRowDirtySpan(cols);
9075
+ const base = y * fpCols;
9076
+ let spans;
9077
+ if (!nextSnapshot) spans = expandSpansForReferenceWideGlyphs(collectChangedSpans(row, cols, (x) => !currentCellMatchesPrevious(base, x)), row, y, cols, prevTextIds);
9078
+ else spans = expandSpansForReferenceWideGlyphs(collectChangedSpans(row, cols, (x) => {
9079
+ return nextSnapshot.fp[x] !== prevFP[base + x] || nextSnapshot.textIds[x] !== prevTextIds[base + x] || nextSnapshot.hrefIds[x] !== prevHrefIds[base + x];
9080
+ }), row, y, cols, prevTextIds);
9081
+ return activeRenderBaselineChanged() ? fullRowDirtySpan(cols) : spans;
9082
+ };
9083
+ const resolveChangedSpansAgainstFill = (row, y, cols, fillFingerprint, fillTextId = blankTextId, fillHrefId = 0) => {
9084
+ if (!fpCols || cols !== fpCols || y >= fpRows) return fullRowDirtySpan(cols);
9085
+ const base = y * fpCols;
9086
+ const spans = collectChangedSpans(row, cols, (x) => {
9087
+ return currentFP[base + x] !== fillFingerprint || currentTextIds[base + x] !== fillTextId || currentHrefIds[base + x] !== fillHrefId;
9088
+ });
9089
+ return activeRenderBaselineChanged() ? fullRowDirtySpan(cols) : spans;
9090
+ };
9091
+ const resolveChangedSpansAgainstReference = (row, y, cols, referenceFP, referenceHrefIds, referenceTextIds) => {
9092
+ if (!fpCols || cols !== fpCols || y >= fpRows) return fullRowDirtySpan(cols);
9093
+ const rowFP = rowFingerprintsForThisRenderer(y);
9094
+ const base = y * fpCols;
9095
+ const spans = expandSpansForReferenceWideGlyphs(collectChangedSpans(row, cols, (x) => {
9096
+ return !referenceCellMatchesCurrent(row, rowFP, base, x, cols, referenceFP, referenceHrefIds, referenceTextIds);
9097
+ }), row, y, cols, referenceTextIds);
9098
+ return activeRenderBaselineChanged() ? fullRowDirtySpan(cols) : spans;
9099
+ };
9100
+ const isEolClearEquivalentBlankCell = (cell, defaultBlankStyleKey) => {
9101
+ if (!cell) return true;
9102
+ if (cell.continuation) return false;
9103
+ const ch = cell.ch || " ";
9104
+ if (ch !== " ") return false;
9105
+ if (cell.width !== 1) return false;
9106
+ if (renderedHref(cell.style.href)) return false;
9107
+ return styleKey(cell.style) === defaultBlankStyleKey;
9108
+ };
9109
+ const lastExplicitPaintColumnInRow = (row, cols, defaultBlankStyleKey) => {
9110
+ const limit = Math.max(0, cols);
9111
+ for (let x = limit - 1; x >= 0; x--) {
9112
+ const cell = cellAt(row, x);
9113
+ if (!isEolClearEquivalentBlankCell(cell, defaultBlankStyleKey)) {
9114
+ if (cell && !cell.continuation && cell.width === 2 && x + 1 < limit) return x + 1;
9115
+ return x;
9116
+ }
9117
+ }
9118
+ return -1;
9119
+ };
9120
+ const shouldRewriteRowTail = (row, y, cols, referenceFP, referenceHrefIds, referenceTextIds, defaultBlankStyleKey = styleKeyFromParts({ bg: defaultBg })) => {
9121
+ if (!fpCols || y >= fpRows) return true;
9122
+ const currentTailStart = Math.max(0, Math.min(cols, lastExplicitPaintColumnInRow(row, cols, defaultBlankStyleKey) + 1));
9123
+ if (currentTailStart >= cols) return false;
9124
+ const blankFP = cellFingerprintFromStyleKey(" ", defaultBlankStyleKey);
9125
+ const base = y * fpCols;
9126
+ const limit = Math.min(cols, fpCols);
9127
+ for (let x = currentTailStart; x < limit; x++) if (referenceFP[base + x] !== blankFP || referenceTextIds[base + x] !== blankTextId || referenceHrefIds[base + x] !== 0) return true;
9128
+ return false;
9129
+ };
8696
9130
  const rowCursorToCol1 = [];
8697
9131
  const rowClearToEol = [];
8698
9132
  const rowTextPartsScratch = [];
@@ -8755,12 +9189,12 @@ function createStdoutRenderer(terminal, options) {
8755
9189
  rowClearToEol[y] = `\u001B[${y + 1};1H\u001B[K`;
8756
9190
  }
8757
9191
  };
8758
- const rowHasNonDefaultBlankCell = (source, y, cols) => {
9192
+ const rowHasNonDefaultBlankCell = (sourceFP, sourceTextIds, sourceHrefIds, y, cols) => {
8759
9193
  if (!fpCols || y < 0 || y >= fpRows) return false;
8760
- const blankFP = styleKeyFromParts({ bg: defaultBg }) << 10 | charHash10(" ");
9194
+ const blankFP = cellFingerprintFromStyleKey(" ", styleKeyFromParts({ bg: defaultBg }));
8761
9195
  const base = y * fpCols;
8762
9196
  const limit = Math.min(cols, fpCols);
8763
- for (let x = 0; x < limit; x++) if (source[base + x] !== blankFP) return true;
9197
+ for (let x = 0; x < limit; x++) if (sourceFP[base + x] !== blankFP || sourceTextIds[base + x] !== blankTextId || sourceHrefIds[base + x] !== 0) return true;
8764
9198
  return false;
8765
9199
  };
8766
9200
  function overlayPlaneCoverageRows(totalRows) {
@@ -8781,7 +9215,7 @@ function createStdoutRenderer(terminal, options) {
8781
9215
  const raw = String(firstNonEmptyEnv(env, "VUE_TUI_SCROLL_REGIONS", "DIMCODE_TUI_SCROLL_REGIONS") ?? "").trim();
8782
9216
  if (raw === "0" || raw === "false") return false;
8783
9217
  if (isGhostty || isVscodeTerminal) return false;
8784
- return out.isTTY !== false;
9218
+ return outputIsTTY;
8785
9219
  })();
8786
9220
  function prepareExplicitScrollOperations(operations, blockedRows) {
8787
9221
  if (!operations.length || !(blockedRows === null || blockedRows === void 0 ? void 0 : blockedRows.size)) return {
@@ -8904,13 +9338,13 @@ function createStdoutRenderer(terminal, options) {
8904
9338
  for (let y = band.start; y < band.end; y++) {
8905
9339
  const srcY = y + delta;
8906
9340
  if (srcY < band.start || srcY >= band.end) continue;
8907
- const informative = rowHasNonDefaultBlankCell(currentFP, y, cols) || rowHasNonDefaultBlankCell(prevFP, srcY, cols);
9341
+ const informative = rowHasNonDefaultBlankCell(currentFP, currentTextIds, currentHrefIds, y, cols) || rowHasNonDefaultBlankCell(prevFP, prevTextIds, prevHrefIds, srcY, cols);
8908
9342
  if (!informative) continue;
8909
9343
  checked++;
8910
9344
  const curBase = y * fpCols;
8911
9345
  const prevBase = srcY * fpCols;
8912
9346
  let rowMatch = true;
8913
- for (let x = 0; x < cols; x++) if (currentFP[curBase + x] !== prevFP[prevBase + x] || currentHrefIds[curBase + x] !== prevHrefIds[prevBase + x]) {
9347
+ for (let x = 0; x < cols; x++) if (!cellMatchesPreviousAt(curBase, prevBase, x)) {
8914
9348
  rowMatch = false;
8915
9349
  break;
8916
9350
  }
@@ -8935,7 +9369,7 @@ function createStdoutRenderer(terminal, options) {
8935
9369
  const curBase = y * fpCols;
8936
9370
  const prevBase = srcY * fpCols;
8937
9371
  let rowMatch = true;
8938
- for (let x = 0; x < cols; x++) if (currentFP[curBase + x] !== prevFP[prevBase + x] || currentHrefIds[curBase + x] !== prevHrefIds[prevBase + x]) {
9372
+ for (let x = 0; x < cols; x++) if (!cellMatchesPreviousAt(curBase, prevBase, x)) {
8939
9373
  rowMatch = false;
8940
9374
  break;
8941
9375
  }
@@ -8953,45 +9387,83 @@ function createStdoutRenderer(terminal, options) {
8953
9387
  }
8954
9388
  return null;
8955
9389
  }
9390
+ const isHighSurrogate = (code) => code >= 55296 && code <= 56319;
9391
+ const isLowSurrogate = (code) => code >= 56320 && code <= 57343;
9392
+ const codePointEnd = (data, index) => {
9393
+ const code = data.charCodeAt(index);
9394
+ if (isHighSurrogate(code) && index + 1 < data.length) {
9395
+ const next = data.charCodeAt(index + 1);
9396
+ if (isLowSurrogate(next)) return index + 2;
9397
+ }
9398
+ return index + 1;
9399
+ };
9400
+ const codePointBefore = (data, index) => {
9401
+ if (index <= 0) return null;
9402
+ const prev = data.charCodeAt(index - 1);
9403
+ if (isLowSurrogate(prev) && index - 2 >= 0) {
9404
+ const high = data.charCodeAt(index - 2);
9405
+ if (isHighSurrogate(high)) return data.codePointAt(index - 2) ?? null;
9406
+ }
9407
+ return data.codePointAt(index - 1) ?? null;
9408
+ };
9409
+ const isGraphemeContinuationCodePoint = (codePoint) => codePoint >= 768 && codePoint <= 879 || codePoint >= 6832 && codePoint <= 6911 || codePoint >= 7616 && codePoint <= 7679 || codePoint >= 8400 && codePoint <= 8447 || codePoint >= 65056 && codePoint <= 65071 || codePoint >= 65024 && codePoint <= 65039 || codePoint >= 917760 && codePoint <= 917999 || codePoint >= 127995 && codePoint <= 127999 || codePoint >= 917536 && codePoint <= 917631;
9410
+ const shouldAttachToPreviousGrapheme = (data, index) => {
9411
+ const codePoint = data.codePointAt(index);
9412
+ if (codePoint == null) return false;
9413
+ if (codePoint === 8205) return true;
9414
+ if (codePointBefore(data, index) === 8205) return true;
9415
+ return isGraphemeContinuationCodePoint(codePoint);
9416
+ };
9417
+ const safeChunkEnd = (data, start, maxBytes) => {
9418
+ let end = start;
9419
+ let bytes = 0;
9420
+ const limit = Math.max(1, Math.floor(maxBytes));
9421
+ while (end < data.length) {
9422
+ const next = codePointEnd(data, end);
9423
+ const partBytes = Buffer.byteLength(data.slice(end, next), "utf8");
9424
+ if (end > start && bytes + partBytes > limit && !shouldAttachToPreviousGrapheme(data, end)) break;
9425
+ bytes += partBytes;
9426
+ end = next;
9427
+ while (end < data.length && shouldAttachToPreviousGrapheme(data, end)) {
9428
+ const attachedEnd = codePointEnd(data, end);
9429
+ bytes += Buffer.byteLength(data.slice(end, attachedEnd), "utf8");
9430
+ end = attachedEnd;
9431
+ }
9432
+ if (bytes >= limit && (end >= data.length || !shouldAttachToPreviousGrapheme(data, end))) break;
9433
+ }
9434
+ return end > start ? end : codePointEnd(data, start);
9435
+ };
8956
9436
  /**
8957
9437
  * Write data in chunks to avoid overwhelming terminal buffers.
8958
9438
  * This is especially important for ghostty which can hang on large writes.
8959
- *
8960
- * CRITICAL: In ghostty, setTimeout callbacks don't execute, so we must use
8961
- * synchronous chunked writes. We break large writes into smaller chunks to
8962
- * reduce the chance of terminal hangs.
8963
9439
  */
8964
9440
  function writeChunked(data) {
8965
- if (!isGhostty) {
8966
- if (data.length <= chunkSize) {
8967
- out.write(data);
8968
- return;
8969
- }
8970
- for (let i = 0; i < data.length; i += chunkSize) {
8971
- const chunk = data.slice(i, i + chunkSize);
8972
- out.write(chunk);
8973
- }
9441
+ if (Buffer.byteLength(data, "utf8") <= chunkSize) {
9442
+ out.write(data);
8974
9443
  return;
8975
9444
  }
8976
- if (isDebugEnabled()) getDebugLog().render(`writeChunked: sync chunked write of ${data.length} bytes`);
9445
+ if (isDebugEnabled()) getDebugLog().render(`writeChunked: sync chunked write of ${Buffer.byteLength(data, "utf8")} bytes`);
8977
9446
  try {
8978
- if (data.length <= chunkSize) out.write(data);
8979
- else for (let i = 0; i < data.length; i += chunkSize) {
8980
- const chunk = data.slice(i, i + chunkSize);
8981
- out.write(chunk);
9447
+ for (let start = 0; start < data.length;) {
9448
+ const end = safeChunkEnd(data, start, chunkSize);
9449
+ out.write(data.slice(start, end));
9450
+ start = end;
8982
9451
  }
8983
9452
  if (isDebugEnabled()) getDebugLog().render(`writeChunked: chunked write completed`);
8984
9453
  } catch (e) {
8985
9454
  if (isDebugEnabled()) getDebugLog().error(`writeChunked: write error`, e);
9455
+ throw e;
8986
9456
  }
8987
9457
  }
8988
9458
  /**
8989
9459
  * Internal function that actually renders the frame to stdout.
8990
9460
  * When dirtyRows is provided, only those rows are repainted.
8991
9461
  */
8992
- function doRender(dirtyRows, scrollOperations) {
9462
+ function doRender(dirtyRows, scrollOperations, forceDirtyRowsRepaint = false) {
8993
9463
  if (isDebugEnabled()) getDebugLog().render(`doRender() START: dirtyRows=${(dirtyRows === null || dirtyRows === void 0 ? void 0 : dirtyRows.length) ?? "null"}`);
8994
9464
  if (disposed) return;
9465
+ const currentFingerprintOwner = stdoutFingerprintOwners.get(terminal);
9466
+ if ((!ownsFingerprintFn || terminalFingerprintFnDirty) && (!currentFingerprintOwner || currentFingerprintOwner === fingerprintOwner) && canInstallTerminalFingerprintFn() && shouldAttemptFingerprintInstall()) installFingerprintFn(terminalFingerprintFnDirty);
8995
9467
  cliLatency === null || cliLatency === void 0 || cliLatency.recordStdoutRenderStart();
8996
9468
  pendingRender = false;
8997
9469
  accumulatedAllRows = false;
@@ -8999,6 +9471,7 @@ function createStdoutRenderer(terminal, options) {
8999
9471
  accumulatedDirtyCount = 0;
9000
9472
  accumulatedDirtyMin = Number.POSITIVE_INFINITY;
9001
9473
  accumulatedDirtyMax = -1;
9474
+ accumulatedDirtyRowsRequireRepaint = false;
9002
9475
  accumulatedScrollOperations = null;
9003
9476
  lastFrameTime = Date.now();
9004
9477
  const renderStart = performance.now();
@@ -9008,10 +9481,15 @@ function createStdoutRenderer(terminal, options) {
9008
9481
  const bgSeq = openBg(defaultBg);
9009
9482
  const bgOnlyStyle = { bg: defaultBg };
9010
9483
  const bgKey = styleKeyFromParts({ bg: defaultBg });
9011
- const blankFP = bgKey << 10 | charHash10(" ");
9484
+ const blankFP = cellFingerprintFromStyleKey(" ", bgKey);
9012
9485
  let dirtySorted = true;
9013
- const forceFullRender = hrefIndexResetRequiresBaseline;
9014
- if (forceFullRender) hrefIndexResetRequiresBaseline = false;
9486
+ const forceFullRender = hrefIndexResetRequiresBaseline || fingerprintInternResetRequiresBaseline;
9487
+ if (forceFullRender) {
9488
+ hrefIndexResetRequiresBaseline = false;
9489
+ fingerprintInternResetRequiresBaseline = false;
9490
+ fpPrevValid = false;
9491
+ }
9492
+ activeRenderBaselineGeneration = fingerprintBaselineGeneration;
9015
9493
  const normalizedScrollOperations = (() => {
9016
9494
  if (!(scrollOperations === null || scrollOperations === void 0 ? void 0 : scrollOperations.length)) return null;
9017
9495
  const outOps = [];
@@ -9030,8 +9508,9 @@ function createStdoutRenderer(terminal, options) {
9030
9508
  return outOps;
9031
9509
  })();
9032
9510
  let rowsToRender = (() => {
9033
- if (forceFullRender) return null;
9034
- if (!dirtyRows || dirtyRows.length === 0) return null;
9511
+ if (forceFullRender || !fpPrevValid) return null;
9512
+ if (!dirtyRows) return null;
9513
+ if (dirtyRows.length === 0) return [];
9035
9514
  const outRows = [];
9036
9515
  outRows.length = dirtyRows.length;
9037
9516
  let outLen = 0;
@@ -9039,20 +9518,35 @@ function createStdoutRenderer(terminal, options) {
9039
9518
  let prev = -1;
9040
9519
  for (let i = 0; i < dirtyRows.length; i++) {
9041
9520
  const y = Math.floor(dirtyRows[i] ?? -1);
9042
- if (y < 0 || y >= size.rows) continue;
9521
+ if (!Number.isFinite(y) || y < 0 || y >= size.rows) continue;
9043
9522
  if (y <= prev) sorted = false;
9044
9523
  prev = y;
9045
9524
  outRows[outLen++] = y;
9046
9525
  }
9047
9526
  outRows.length = outLen;
9048
- if (!outRows.length) return null;
9527
+ if (!outRows.length) return [];
9049
9528
  dirtySorted = sorted;
9050
9529
  if (!sorted) outRows.sort((a, b) => a - b);
9051
9530
  return outRows;
9052
9531
  })();
9532
+ if ((rowsToRender === null || rowsToRender === void 0 ? void 0 : rowsToRender.length) === 0 && !normalizedScrollOperations && !getImeAnchor) {
9533
+ if (isDebugEnabled()) getDebugLog().render(" No valid dirty rows; frame skipped");
9534
+ cliLatency === null || cliLatency === void 0 || cliLatency.recordStdoutNoOutput();
9535
+ return;
9536
+ }
9537
+ const dirtyRowSnapshots = fpPrevValid && rowsToRender ? new Map() : null;
9538
+ const snapshotDirtyRowFingerprints = () => {
9539
+ if (!dirtyRowSnapshots || !rowsToRender) return;
9540
+ for (const y of rowsToRender) {
9541
+ const row = terminal.getRow(y);
9542
+ dirtyRowSnapshots.set(y, snapshotRowFingerprints(row, y, size.cols));
9543
+ }
9544
+ };
9545
+ snapshotDirtyRowFingerprints();
9053
9546
  if (fpPrevValid && rowsToRender) {
9054
9547
  currentFP.set(prevFP);
9055
9548
  currentHrefIds.set(prevHrefIds);
9549
+ currentTextIds.set(prevTextIds);
9056
9550
  }
9057
9551
  const frameParts = [];
9058
9552
  frameParts.push(!isGhostty && useSyncOutput ? SYNC_START : "");
@@ -9072,6 +9566,14 @@ function createStdoutRenderer(terminal, options) {
9072
9566
  inverse: false,
9073
9567
  href: null
9074
9568
  };
9569
+ const closeActiveHrefBeforeCursorMove = () => {
9570
+ if (!enableOsc8Links || !activeStyle.href) return;
9571
+ frameParts.push(OSC8_CLOSE);
9572
+ activeStyle = {
9573
+ ...activeStyle,
9574
+ href: null
9575
+ };
9576
+ };
9075
9577
  const normalizeStyle$1 = (style) => {
9076
9578
  return {
9077
9579
  fg: style.fg ?? null,
@@ -9081,7 +9583,7 @@ function createStdoutRenderer(terminal, options) {
9081
9583
  italic: Boolean(style.italic),
9082
9584
  underline: Boolean(style.underline),
9083
9585
  inverse: Boolean(style.inverse),
9084
- href: normalizeHref(style.href)
9586
+ href: renderedHref(style.href)
9085
9587
  };
9086
9588
  };
9087
9589
  const emitStyle = (nextStyle, nextKey) => {
@@ -9108,20 +9610,29 @@ function createStdoutRenderer(terminal, options) {
9108
9610
  activeStyleKey = nextKey;
9109
9611
  activeStyle = next;
9110
9612
  };
9111
- const shouldEmitStyle = (style, key) => activeStyleKey !== key || activeStyle.href !== normalizeHref(style.href);
9112
- const renderRow = (y, row, startX = 0, endXExclusive = row.length, clearToEol = true) => {
9113
- const spanStart = Math.max(0, Math.min(row.length, Math.floor(startX)));
9114
- const spanEnd = Math.max(spanStart, Math.min(row.length, Math.floor(endXExclusive)));
9115
- if (spanStart >= spanEnd && !clearToEol) return;
9613
+ const shouldEmitStyle = (style, key) => activeStyleKey !== key || activeStyle.href !== renderedHref(style.href);
9614
+ const renderRow = (y, row, startX = 0, endXExclusive = size.cols, clearToEol = true) => {
9615
+ const spanStart = Math.max(0, Math.min(size.cols, Math.floor(startX)));
9616
+ const spanEnd = Math.max(spanStart, Math.min(size.cols, Math.floor(endXExclusive)));
9617
+ const shouldClearToEol = clearToEol && spanEnd < size.cols;
9618
+ if (spanStart >= spanEnd && !shouldClearToEol) return;
9116
9619
  hasFrameOutput = true;
9117
- if (!disableCursorPos) if (spanStart === 0 && lastRenderWasFullRow && y === lastRenderedY + 1) frameParts.push("\r\n");
9118
- else frameParts.push(spanStart === 0 ? rowCursorToCol1[y] : `\u001B[${y + 1};${spanStart + 1}H`);
9620
+ const pushCursorFixAfterCell = (parts, cellX, cell) => {
9621
+ const afterX = cellX + (cell.width ?? 1);
9622
+ if (afterX >= size.cols) return;
9623
+ parts.push(`\u001B[${y + 1};${afterX + 1}H`);
9624
+ };
9625
+ if (!disableCursorPos) {
9626
+ closeActiveHrefBeforeCursorMove();
9627
+ if (spanStart === 0 && lastRenderWasFullRow && y === lastRenderedY + 1) frameParts.push("\r\n");
9628
+ else frameParts.push(spanStart === 0 ? rowCursorToCol1[y] : `\u001B[${y + 1};${spanStart + 1}H`);
9629
+ }
9119
9630
  let currentKey = null;
9120
9631
  let currentStyle = null;
9121
9632
  const currentTextParts = rowTextPartsScratch;
9122
9633
  currentTextParts.length = 0;
9123
9634
  for (let x = spanStart; x < spanEnd; x++) {
9124
- const cell = row[x];
9635
+ const cell = cellAt(row, x);
9125
9636
  if (cell.continuation) continue;
9126
9637
  const ch = cell.ch || " ";
9127
9638
  const nextStyle = cell.style;
@@ -9130,17 +9641,12 @@ function createStdoutRenderer(terminal, options) {
9130
9641
  currentKey = key;
9131
9642
  currentStyle = nextStyle;
9132
9643
  currentTextParts.push(ch);
9133
- if (needsCursorFix(cell, ch)) currentTextParts.push(`\u001B[${y + 1};${x + 1 + (cell.width ?? 1)}H`);
9644
+ if (needsCursorFix(cell, ch)) pushCursorFixAfterCell(currentTextParts, x, cell);
9134
9645
  continue;
9135
9646
  }
9136
- if (key === currentKey && normalizeHref(nextStyle.href) === normalizeHref(
9137
- // 8-bit limit
9138
- // saturate to avoid overflow
9139
- // where this row's content came from in prev frame
9140
- currentStyle === null || currentStyle === void 0 ? void 0 : currentStyle.href
9141
- )) {
9647
+ if (key === currentKey && renderedHref(nextStyle.href) === renderedHref(currentStyle === null || currentStyle === void 0 ? void 0 : currentStyle.href)) {
9142
9648
  currentTextParts.push(ch);
9143
- if (needsCursorFix(cell, ch)) currentTextParts.push(`\u001B[${y + 1};${x + 1 + (cell.width ?? 1)}H`);
9649
+ if (needsCursorFix(cell, ch)) pushCursorFixAfterCell(currentTextParts, x, cell);
9144
9650
  continue;
9145
9651
  }
9146
9652
  if (shouldEmitStyle(currentStyle, currentKey)) emitStyle(currentStyle, currentKey);
@@ -9149,18 +9655,82 @@ function createStdoutRenderer(terminal, options) {
9149
9655
  currentStyle = nextStyle;
9150
9656
  currentTextParts.length = 0;
9151
9657
  currentTextParts.push(ch);
9152
- if (needsCursorFix(cell, ch)) currentTextParts.push(`\u001B[${y + 1};${x + 1 + (cell.width ?? 1)}H`);
9658
+ if (needsCursorFix(cell, ch)) pushCursorFixAfterCell(currentTextParts, x, cell);
9153
9659
  }
9154
9660
  if (currentKey != null) {
9155
9661
  if (shouldEmitStyle(currentStyle, currentKey)) emitStyle(currentStyle, currentKey);
9156
9662
  frameParts.push(currentTextParts.join(""));
9157
9663
  }
9158
- if (clearToEol) {
9664
+ if (shouldClearToEol) {
9159
9665
  if (activeStyleKey !== bgKey) emitStyle(bgOnlyStyle, bgKey);
9160
9666
  frameParts.push("\x1B[K");
9161
9667
  }
9162
9668
  lastRenderedY = y;
9163
- lastRenderWasFullRow = spanStart === 0 && clearToEol;
9669
+ lastRenderWasFullRow = spanStart === 0 && (spanEnd >= size.cols || shouldClearToEol);
9670
+ };
9671
+ const lastRenderableColumnInCurrentRow = (rowY, cols, blankFingerprint) => {
9672
+ if (!fpCols || rowY < 0 || rowY >= fpRows) return -1;
9673
+ const base = rowY * fpCols;
9674
+ const limit = Math.min(cols, fpCols);
9675
+ for (let x = limit - 1; x >= 0; x--) if (currentFP[base + x] !== blankFingerprint || currentTextIds[base + x] !== blankTextId || currentHrefIds[base + x] !== 0) return x;
9676
+ return -1;
9677
+ };
9678
+ const resolveTailClearStartX = (rowY, cols, shouldRewriteTail, blankFingerprint) => {
9679
+ if (!shouldRewriteTail) return null;
9680
+ const lastRenderableX = lastRenderableColumnInCurrentRow(rowY, cols, blankFingerprint);
9681
+ const clearStartX = Math.max(0, Math.min(cols, lastRenderableX + 1));
9682
+ return clearStartX < cols ? clearStartX : null;
9683
+ };
9684
+ const renderDirtySpans = (rowY, row, rawSpans, rewriteTail) => {
9685
+ if (!rawSpans.length && !rewriteTail) return;
9686
+ const rowCols = size.cols;
9687
+ const clearStartX = resolveTailClearStartX(rowY, rowCols, rewriteTail, blankFP);
9688
+ const paintTailInsteadOfEscK = clearStartX != null && dirtyRowPatchMode === "auto" && useConservativeDirtyRows;
9689
+ const spansToPaint = [];
9690
+ for (const span of rawSpans) {
9691
+ const startX = Math.max(0, Math.min(rowCols, span.startX));
9692
+ const endXExclusive = Math.max(startX, Math.min(rowCols, span.endXExclusive));
9693
+ if (endXExclusive <= startX) continue;
9694
+ if (clearStartX != null && !paintTailInsteadOfEscK) {
9695
+ if (startX >= clearStartX) continue;
9696
+ const clippedEnd = Math.min(endXExclusive, clearStartX);
9697
+ if (clippedEnd > startX) spansToPaint.push({
9698
+ startX,
9699
+ endXExclusive: clippedEnd
9700
+ });
9701
+ continue;
9702
+ }
9703
+ spansToPaint.push({
9704
+ startX,
9705
+ endXExclusive
9706
+ });
9707
+ }
9708
+ if (paintTailInsteadOfEscK && clearStartX != null && clearStartX < rowCols) {
9709
+ const prev = spansToPaint[spansToPaint.length - 1];
9710
+ if (prev && clearStartX <= prev.endXExclusive + DIRTY_SPAN_MERGE_GAP_CELLS) spansToPaint[spansToPaint.length - 1] = {
9711
+ startX: prev.startX,
9712
+ endXExclusive: rowCols
9713
+ };
9714
+ else spansToPaint.push({
9715
+ startX: clearStartX,
9716
+ endXExclusive: rowCols
9717
+ });
9718
+ }
9719
+ const clearToEolForCost = clearStartX != null && clearStartX < rowCols && !paintTailInsteadOfEscK;
9720
+ const renderMode = !shouldUseMultiDirtySpans() && spansToPaint.length > 0 ? "contiguous" : resolveDirtySpanRenderMode(row, spansToPaint, rowY, rowCols, clearToEolForCost);
9721
+ if (renderMode === "row") {
9722
+ if (clearStartX != null && !paintTailInsteadOfEscK) renderRow(rowY, row, 0, clearStartX, true);
9723
+ else renderRow(rowY, row, 0, rowCols, false);
9724
+ return;
9725
+ } else if (renderMode === "contiguous") {
9726
+ const first = spansToPaint[0];
9727
+ const last = spansToPaint[spansToPaint.length - 1];
9728
+ if (first && last) {
9729
+ const rewriteEnd = clearStartX == null || paintTailInsteadOfEscK ? last.endXExclusive : Math.min(last.endXExclusive, clearStartX);
9730
+ if (rewriteEnd > first.startX) renderRow(rowY, row, first.startX, rewriteEnd, false);
9731
+ }
9732
+ } else for (const span of spansToPaint) renderRow(rowY, row, span.startX, span.endXExclusive, false);
9733
+ if (clearStartX != null && !paintTailInsteadOfEscK) renderRow(rowY, row, clearStartX, clearStartX, true);
9164
9734
  };
9165
9735
  const overlayCoverage = overlayPlaneCoverageRows(size.rows);
9166
9736
  const overlayRows = Array.from(new Set([...overlayCoverage.blockedRows, ...prevOverlayBlockedRows])).sort((a, b) => a - b);
@@ -9182,9 +9752,9 @@ function createStdoutRenderer(terminal, options) {
9182
9752
  if (preparedExplicit.trimmed && isDebugEnabled()) getDebugLog().render(` Explicit scroll ops clipped around overlay rows: ${normalizedScrollOperations.map((op, index) => `${op.startY}-${op.endY - 1}:${op.delta}->${explicitScrollOperations[index].startY}-${explicitScrollOperations[index].endY - 1}:${explicitScrollOperations[index].delta}`).join(", ")}`);
9183
9753
  } else {
9184
9754
  allowInferredScrollRegions = false;
9185
- const expandedRows = new Set(rowsToRender ?? []);
9186
- for (const op of explicitScrollOperations) for (let y = op.startY; y < op.endY; y++) expandedRows.add(y);
9187
- rowsToRender = Array.from(expandedRows).sort((a, b) => a - b);
9755
+ const expandedRows$1 = new Set(rowsToRender ?? []);
9756
+ for (const op of explicitScrollOperations) for (let y = op.startY; y < op.endY; y++) expandedRows$1.add(y);
9757
+ rowsToRender = Array.from(expandedRows$1).sort((a, b) => a - b);
9188
9758
  dirtySorted = true;
9189
9759
  explicitScrollOperations = null;
9190
9760
  hiddenExplicitDirtyRows = null;
@@ -9192,27 +9762,32 @@ function createStdoutRenderer(terminal, options) {
9192
9762
  }
9193
9763
  }
9194
9764
  const scrollRowsCandidate = rowsToRender;
9195
- if (explicitScrollOperations && rowsToRender && (!enableScrollRegions || !fpPrevValid)) {
9196
- const expandedRows = new Set(rowsToRender);
9197
- for (const op of explicitScrollOperations) for (let y = op.startY; y < op.endY; y++) {
9765
+ const expandedRows = new Set(rowsToRender ?? []);
9766
+ const includeRowsForPendingScrollOperations = (operations) => {
9767
+ if (!(operations === null || operations === void 0 ? void 0 : operations.length)) return;
9768
+ for (const op of operations) for (let y = op.startY; y < op.endY; y++) {
9198
9769
  if (hiddenExplicitDirtyRows === null || hiddenExplicitDirtyRows === void 0 ? void 0 : hiddenExplicitDirtyRows.has(y)) continue;
9199
9770
  expandedRows.add(y);
9200
9771
  }
9772
+ };
9773
+ if (explicitScrollOperations && rowsToRender && (!enableScrollRegions || !fpPrevValid)) {
9774
+ includeRowsForPendingScrollOperations(explicitScrollOperations);
9201
9775
  rowsToRender = Array.from(expandedRows).sort((a, b) => a - b);
9202
9776
  dirtySorted = true;
9203
9777
  explicitScrollOperations = null;
9204
9778
  hiddenExplicitDirtyRows = null;
9205
9779
  if (isDebugEnabled()) getDebugLog().render(enableScrollRegions ? " Explicit scroll ops fell back to region repaint because previous fingerprints are unavailable" : " Explicit scroll ops fell back to region repaint because scroll regions are disabled");
9206
9780
  }
9207
- const denseDirtyRows = Boolean(rowsToRender && rowsToRender.length >= size.rows * dirtyFullThreshold);
9781
+ const denseDirtyRows = Boolean(rowsToRender && size.rows > 1 && rowsToRender.length >= size.rows * dirtyFullThreshold);
9208
9782
  if (denseDirtyRows) rowsToRender = null;
9209
9783
  let profiledRowCount = rowsToRender ? rowsToRender.length : size.rows;
9210
- const useScrollRegions = enableScrollRegions && fpPrevValid && scrollRowsCandidate && scrollRowsCandidate.length >= 3 && allowInferredScrollRegions;
9784
+ const useScrollRegions = enableScrollRegions && fpPrevValid && scrollRowsCandidate && scrollRowsCandidate.length >= 3 && !forceDirtyRowsRepaint && allowInferredScrollRegions;
9211
9785
  let scrollHandled = false;
9212
9786
  if (enableScrollRegions && explicitScrollOperations && scrollRowsCandidate && fpPrevValid) {
9213
9787
  if (currentFP.length === prevFP.length) {
9214
9788
  currentFP.set(prevFP);
9215
9789
  currentHrefIds.set(prevHrefIds);
9790
+ currentTextIds.set(prevTextIds);
9216
9791
  }
9217
9792
  const explicitRowsToRender = new Set();
9218
9793
  for (const y of scrollRowsCandidate) {
@@ -9232,6 +9807,7 @@ function createStdoutRenderer(terminal, options) {
9232
9807
  for (let x = 0; x < size.cols; x++) {
9233
9808
  currentFP[dstBase + x] = prevFP[srcBase + x];
9234
9809
  currentHrefIds[dstBase + x] = prevHrefIds[srcBase + x];
9810
+ currentTextIds[dstBase + x] = prevTextIds[srcBase + x];
9235
9811
  }
9236
9812
  }
9237
9813
  for (let y = op.endY - op.delta; y < op.endY; y++) {
@@ -9240,6 +9816,7 @@ function createStdoutRenderer(terminal, options) {
9240
9816
  for (let x = 0; x < size.cols; x++) {
9241
9817
  currentFP[base + x] = blankFP;
9242
9818
  currentHrefIds[base + x] = 0;
9819
+ currentTextIds[base + x] = blankTextId;
9243
9820
  }
9244
9821
  }
9245
9822
  } else {
@@ -9252,6 +9829,7 @@ function createStdoutRenderer(terminal, options) {
9252
9829
  for (let x = 0; x < size.cols; x++) {
9253
9830
  currentFP[dstBase + x] = prevFP[srcBase + x];
9254
9831
  currentHrefIds[dstBase + x] = prevHrefIds[srcBase + x];
9832
+ currentTextIds[dstBase + x] = prevTextIds[srcBase + x];
9255
9833
  }
9256
9834
  }
9257
9835
  for (let y = op.startY; y < op.startY + absDelta; y++) {
@@ -9260,6 +9838,7 @@ function createStdoutRenderer(terminal, options) {
9260
9838
  for (let x = 0; x < size.cols; x++) {
9261
9839
  currentFP[base + x] = blankFP;
9262
9840
  currentHrefIds[base + x] = 0;
9841
+ currentTextIds[base + x] = blankTextId;
9263
9842
  }
9264
9843
  }
9265
9844
  }
@@ -9268,10 +9847,28 @@ function createStdoutRenderer(terminal, options) {
9268
9847
  const explicitDirtyRows = Array.from(explicitRowsToRender).sort((a, b) => a - b);
9269
9848
  const overlayDirtyRows = overlayTouchedRowSet ? explicitDirtyRows.filter((y) => overlayTouchedRowSet.has(y)) : [];
9270
9849
  const paintedExplicitRows = [];
9850
+ const insertedRows = new Set();
9851
+ for (const op of explicitScrollOperations) {
9852
+ const absDelta = Math.abs(op.delta);
9853
+ if (op.delta > 0) for (let y = op.endY - absDelta; y < op.endY; y++) insertedRows.add(y);
9854
+ else for (let y = op.startY; y < op.startY + absDelta; y++) insertedRows.add(y);
9855
+ }
9271
9856
  for (const y of explicitDirtyRows) {
9272
9857
  const row = terminal.getRow(y);
9273
- fingerprintRow(row, y, size.cols);
9274
- renderRow(y, row);
9858
+ const overlayRow = overlayTouchedRowSet === null || overlayTouchedRowSet === void 0 ? void 0 : overlayTouchedRowSet.has(y);
9859
+ if (forceDirtyRowsRepaint || !shouldUseDirtySpans() || overlayRow) {
9860
+ fingerprintRow(row, y, size.cols);
9861
+ renderRow(y, row);
9862
+ paintedExplicitRows.push(y);
9863
+ continue;
9864
+ }
9865
+ const insertedRow = insertedRows.has(y);
9866
+ if (insertedRow) fingerprintRow(row, y, size.cols);
9867
+ const spans = insertedRow ? resolveChangedSpansAgainstFill(row, y, size.cols, blankFP, blankTextId, 0) : resolveChangedSpansAgainstReference(row, y, size.cols, currentFP, currentHrefIds, currentTextIds);
9868
+ if (!spans.length) continue;
9869
+ const rewriteTail = !insertedRow && shouldRewriteRowTail(row, y, size.cols, currentFP, currentHrefIds, currentTextIds, bgKey);
9870
+ if (!insertedRow) fingerprintRow(row, y, size.cols);
9871
+ renderDirtySpans(y, row, spans, rewriteTail);
9275
9872
  paintedExplicitRows.push(y);
9276
9873
  }
9277
9874
  if (isDebugEnabled() && overlayDirtyRows.length) getDebugLog().render(` Explicit scroll ops overlay rows: [${overlayDirtyRows.join(", ")}]`);
@@ -9284,6 +9881,7 @@ function createStdoutRenderer(terminal, options) {
9284
9881
  if (currentFP.length === prevFP.length) {
9285
9882
  currentFP.set(prevFP);
9286
9883
  currentHrefIds.set(prevHrefIds);
9884
+ currentTextIds.set(prevTextIds);
9287
9885
  }
9288
9886
  hasFrameOutput = true;
9289
9887
  if (isDebugEnabled()) getDebugLog().render(` Scroll region: delta=${shift.delta}, region=${shift.regionStart}-${shift.regionEnd - 1}, new rows=${shift.newRowStart}-${shift.newRowEnd - 1}`);
@@ -9303,6 +9901,7 @@ function createStdoutRenderer(terminal, options) {
9303
9901
  for (let x = 0; x < size.cols; x++) {
9304
9902
  currentFP[dstBase + x] = prevFP[srcBase + x];
9305
9903
  currentHrefIds[dstBase + x] = prevHrefIds[srcBase + x];
9904
+ currentTextIds[dstBase + x] = prevTextIds[srcBase + x];
9306
9905
  }
9307
9906
  }
9308
9907
  for (let y = shift.regionEnd - shift.delta; y < shift.regionEnd; y++) {
@@ -9310,6 +9909,7 @@ function createStdoutRenderer(terminal, options) {
9310
9909
  for (let x = 0; x < size.cols; x++) {
9311
9910
  currentFP[base + x] = blankFP;
9312
9911
  currentHrefIds[base + x] = 0;
9912
+ currentTextIds[base + x] = blankTextId;
9313
9913
  }
9314
9914
  }
9315
9915
  } else {
@@ -9320,6 +9920,7 @@ function createStdoutRenderer(terminal, options) {
9320
9920
  for (let x = 0; x < size.cols; x++) {
9321
9921
  currentFP[dstBase + x] = prevFP[srcBase + x];
9322
9922
  currentHrefIds[dstBase + x] = prevHrefIds[srcBase + x];
9923
+ currentTextIds[dstBase + x] = prevTextIds[srcBase + x];
9323
9924
  }
9324
9925
  }
9325
9926
  for (let y = shift.regionStart; y < shift.regionStart + absDelta; y++) {
@@ -9327,6 +9928,7 @@ function createStdoutRenderer(terminal, options) {
9327
9928
  for (let x = 0; x < size.cols; x++) {
9328
9929
  currentFP[base + x] = blankFP;
9329
9930
  currentHrefIds[base + x] = 0;
9931
+ currentTextIds[base + x] = blankTextId;
9330
9932
  }
9331
9933
  }
9332
9934
  }
@@ -9338,6 +9940,17 @@ function createStdoutRenderer(terminal, options) {
9338
9940
  const extraDirtyRows = overlayRowSet ? shift.extraDirtyRows.filter((y) => !overlayRowSet.has(y) || Boolean(overlayPartialRowSet === null || overlayPartialRowSet === void 0 ? void 0 : overlayPartialRowSet.has(y))) : shift.extraDirtyRows;
9339
9941
  for (const y of extraDirtyRows) {
9340
9942
  const row = terminal.getRow(y);
9943
+ if (shouldUseDirtySpans()) {
9944
+ const spans = resolveChangedSpansAgainstReference(row, y, size.cols, currentFP, currentHrefIds, currentTextIds);
9945
+ if (!spans.length) {
9946
+ fingerprintRow(row, y, size.cols);
9947
+ continue;
9948
+ }
9949
+ const rewriteTail = shouldRewriteRowTail(row, y, size.cols, currentFP, currentHrefIds, currentTextIds, bgKey);
9950
+ fingerprintRow(row, y, size.cols);
9951
+ renderDirtySpans(y, row, spans, rewriteTail);
9952
+ continue;
9953
+ }
9341
9954
  fingerprintRow(row, y, size.cols);
9342
9955
  renderRow(y, row);
9343
9956
  }
@@ -9363,11 +9976,18 @@ function createStdoutRenderer(terminal, options) {
9363
9976
  }
9364
9977
  } else {
9365
9978
  const overlayDirtyRows = overlayTouchedRowSet ? rowsToRender.filter((y) => overlayTouchedRowSet.has(y)) : [];
9366
- if (isDebugEnabled()) getDebugLog().render(` Partial render: ${rowsToRender.length} dirty rows: [${rowsToRender.join(", ")}]${overlayDirtyRows.length ? ` (overlay-full-row: [${overlayDirtyRows.join(", ")}])` : ""}`);
9979
+ if (isDebugEnabled()) getDebugLog().render(` Partial render: ${rowsToRender.length} dirty rows: [${rowsToRender.join(",")}]${overlayDirtyRows.length ? ` (overlay-full-row: [${overlayDirtyRows.join(", ")}])` : ""}`);
9367
9980
  for (const y of rowsToRender) {
9368
9981
  const row = terminal.getRow(y);
9369
9982
  fingerprintRow(row, y, size.cols);
9370
- renderRow(y, row);
9983
+ if (forceDirtyRowsRepaint || !shouldUseDirtySpans()) {
9984
+ renderRow(y, row);
9985
+ continue;
9986
+ }
9987
+ const spans = resolveChangedSpans(row, y, size.cols, (dirtyRowSnapshots === null || dirtyRowSnapshots === void 0 ? void 0 : dirtyRowSnapshots.get(y)) ?? null);
9988
+ if (!spans.length) continue;
9989
+ const rewriteTail = shouldRewriteRowTail(row, y, size.cols, prevFP, prevHrefIds, prevTextIds, bgKey);
9990
+ renderDirtySpans(y, row, spans, rewriteTail);
9371
9991
  }
9372
9992
  }
9373
9993
  if (!rowsToRender && lastRenderedRows > size.rows) {
@@ -9381,16 +10001,22 @@ function createStdoutRenderer(terminal, options) {
9381
10001
  frameParts.push(rowClearToEol[size.rows + i]);
9382
10002
  }
9383
10003
  }
9384
- if (!rowsToRender) lastRenderedRows = size.rows;
9385
- const tmpFP = prevFP;
9386
- prevFP = currentFP;
9387
- currentFP = tmpFP;
9388
- const tmpHrefIds = prevHrefIds;
9389
- prevHrefIds = currentHrefIds;
9390
- currentHrefIds = tmpHrefIds;
9391
- fpPrevValid = !hrefIndexResetRequiresBaseline;
9392
- prevOverlayBlockedRows = overlayCoverage.blockedRows;
9393
- prevOverlayPartialRows = overlayCoverage.partialRows;
10004
+ const nextLastRenderedRows = !rowsToRender ? size.rows : lastRenderedRows;
10005
+ const commitRenderBaseline = () => {
10006
+ lastRenderedRows = nextLastRenderedRows;
10007
+ const tmpFP = prevFP;
10008
+ prevFP = currentFP;
10009
+ currentFP = tmpFP;
10010
+ const tmpHrefIds = prevHrefIds;
10011
+ prevHrefIds = currentHrefIds;
10012
+ currentHrefIds = tmpHrefIds;
10013
+ const tmpTextIds = prevTextIds;
10014
+ prevTextIds = currentTextIds;
10015
+ currentTextIds = tmpTextIds;
10016
+ fpPrevValid = !hrefIndexResetRequiresBaseline && !fingerprintInternResetRequiresBaseline;
10017
+ prevOverlayBlockedRows = overlayCoverage.blockedRows;
10018
+ prevOverlayPartialRows = overlayCoverage.partialRows;
10019
+ };
9394
10020
  let emittedCursorPos = null;
9395
10021
  if (getImeAnchor) {
9396
10022
  const anchor = getImeAnchor();
@@ -9402,11 +10028,13 @@ function createStdoutRenderer(terminal, options) {
9402
10028
  x,
9403
10029
  y
9404
10030
  };
10031
+ closeActiveHrefBeforeCursorMove();
9405
10032
  frameParts.push(`\u001B[${y + 1};${x + 1}H`);
9406
10033
  }
9407
10034
  }
9408
10035
  }
9409
10036
  if (!hasFrameOutput) {
10037
+ commitRenderBaseline();
9410
10038
  if (isDebugEnabled()) getDebugLog().render(" No-op frame skipped");
9411
10039
  cliLatency === null || cliLatency === void 0 || cliLatency.recordStdoutNoOutput();
9412
10040
  return;
@@ -9416,6 +10044,7 @@ function createStdoutRenderer(terminal, options) {
9416
10044
  frameParts.push("\x1B[?7h");
9417
10045
  frameParts.push(!isGhostty && useSyncOutput ? SYNC_END : "");
9418
10046
  const frame = frameParts.join("");
10047
+ const frameBytes = Buffer.byteLength(frame, "utf8");
9419
10048
  if (profiler) profiler.recordRender({
9420
10049
  durationMs: profiler.now() - renderStart,
9421
10050
  rows: profiledRowCount,
@@ -9469,7 +10098,7 @@ function createStdoutRenderer(terminal, options) {
9469
10098
  }
9470
10099
  return count;
9471
10100
  };
9472
- const frameSize = frame.length;
10101
+ const frameSize = frameBytes;
9473
10102
  const cursorSeqCount = countCursorMoves(frame);
9474
10103
  const resetCount = countResets(frame);
9475
10104
  const buildTime = (performance.now() - renderStart).toFixed(2);
@@ -9479,22 +10108,21 @@ function createStdoutRenderer(terminal, options) {
9479
10108
  const writeStart = performance.now();
9480
10109
  const resolveWriteMode = (frameSizeBytes) => {
9481
10110
  if (isGhostty) return "chunked";
9482
- const canWriteSync = out.fd === 1;
9483
10111
  const preferChunked = frameSizeBytes >= chunkThresholdBytes || writeEmaMs >= 24;
9484
10112
  if (preferChunked) return "chunked";
9485
- if (canWriteSync && frameSizeBytes <= syncMaxBytes) return "sync";
10113
+ if (canUseSyncStdout && frameSizeBytes <= syncMaxBytes) return "sync";
9486
10114
  return "stream";
9487
10115
  };
9488
- const writeMode = resolveWriteMode(frame.length);
10116
+ const writeMode = resolveWriteMode(frameBytes);
9489
10117
  if (isDebugEnabled()) getDebugLog().render(`Before write()`);
9490
10118
  try {
9491
10119
  if (writeMode === "chunked") {
9492
10120
  if (isDebugEnabled()) getDebugLog().render(` Using chunked write (chunkSize=${chunkSize}, threshold=${chunkThresholdBytes}, emaMs=${writeEmaMs.toFixed(2)})`);
9493
10121
  writeChunked(frame);
9494
- } else if (writeMode === "sync" && out.fd === 1) {
10122
+ } else if (writeMode === "sync" && canUseSyncStdout) {
9495
10123
  if (isDebugEnabled()) getDebugLog().render(` Using writeSync`);
9496
10124
  try {
9497
- writeFdSync(1, frame);
10125
+ writeFdSync(outputFd, frame);
9498
10126
  } catch {
9499
10127
  if (isDebugEnabled()) getDebugLog().render(` writeSync failed, falling back to stream write`);
9500
10128
  out.write(frame);
@@ -9509,11 +10137,12 @@ function createStdoutRenderer(terminal, options) {
9509
10137
  }
9510
10138
  } catch (writeError) {
9511
10139
  if (isDebugEnabled()) getDebugLog().error(`Write ERROR:`, writeError);
9512
- if (!isGhostty && useSyncOutput) try {
9513
- out.write(`\u001B[?7h${SYNC_END}`);
10140
+ try {
10141
+ out.write(`\u001B[?7h${!isGhostty && useSyncOutput ? SYNC_END : ""}`);
9514
10142
  } catch {}
9515
10143
  throw writeError;
9516
10144
  }
10145
+ commitRenderBaseline();
9517
10146
  {
9518
10147
  const writeMs = performance.now() - writeStart;
9519
10148
  writeEmaMs = writeEmaMs === 0 ? writeMs : writeEmaMs * .85 + writeMs * .15;
@@ -9526,19 +10155,19 @@ function createStdoutRenderer(terminal, options) {
9526
10155
  }
9527
10156
  recordStdoutFrame({
9528
10157
  at: Date.now(),
9529
- bytes: frame.length,
10158
+ bytes: frameBytes,
9530
10159
  writeMs,
9531
10160
  writeEmaMs,
9532
10161
  writeMode: writeMode === "sync" ? "sync" : writeMode === "chunked" ? "chunked" : "stream"
9533
10162
  });
9534
10163
  if (profiler) profiler.recordWrite({
9535
10164
  durationMs: writeMs,
9536
- bytes: frame.length,
10165
+ bytes: frameBytes,
9537
10166
  mode: writeMode === "sync" ? "sync" : writeMode === "chunked" ? "chunked" : "stream"
9538
10167
  });
9539
10168
  cliLatency === null || cliLatency === void 0 || cliLatency.recordStdoutWrite({
9540
10169
  durationMs: writeMs,
9541
- bytes: frame.length,
10170
+ bytes: frameBytes,
9542
10171
  mode: writeMode === "sync" ? "sync" : writeMode === "chunked" ? "chunked" : "stream"
9543
10172
  });
9544
10173
  }
@@ -9644,6 +10273,7 @@ function createStdoutRenderer(terminal, options) {
9644
10273
  }
9645
10274
  function markScrollOperationDirty(op, rowCount) {
9646
10275
  markAccumulatedDirtyRange(op.startY, op.endY, rowCount);
10276
+ accumulatedDirtyRowsRequireRepaint = true;
9647
10277
  }
9648
10278
  function mergeScrollOperations(next) {
9649
10279
  if (!(next === null || next === void 0 ? void 0 : next.length) || accumulatedAllRows) return;
@@ -9686,20 +10316,27 @@ function createStdoutRenderer(terminal, options) {
9686
10316
  function render(dirtyRows, sync, scrollOperations) {
9687
10317
  if (disposed) return;
9688
10318
  if (isDebugEnabled()) getDebugLog().render(`render() called: dirtyRows=${(dirtyRows === null || dirtyRows === void 0 ? void 0 : dirtyRows.length) ?? "null"}, pending=${pendingRender}, elapsed=${Date.now() - lastFrameTime}ms`);
9689
- if (!dirtyRows || dirtyRows.length === 0) {
10319
+ if (dirtyRows == null) {
9690
10320
  accumulatedAllRows = true;
9691
10321
  accumulatedDirtyBits = null;
9692
10322
  accumulatedDirtyCount = 0;
9693
10323
  accumulatedDirtyMin = Number.POSITIVE_INFINITY;
9694
10324
  accumulatedDirtyMax = -1;
10325
+ accumulatedDirtyRowsRequireRepaint = false;
9695
10326
  accumulatedScrollOperations = null;
10327
+ } else if (dirtyRows.length === 0) {
10328
+ if (scrollOperations === null || scrollOperations === void 0 ? void 0 : scrollOperations.length) mergeScrollOperations(scrollOperations);
10329
+ else if (!accumulatedAllRows && accumulatedDirtyCount === 0 && !getImeAnchor) {
10330
+ cliLatency === null || cliLatency === void 0 || cliLatency.recordStdoutQueued(0);
10331
+ return;
10332
+ }
9696
10333
  } else if (!accumulatedAllRows) {
9697
10334
  const rowCount = terminal.size().rows;
9698
10335
  mergeScrollOperations(scrollOperations);
9699
10336
  const bits = ensureAccumulatedDirtyBits(rowCount);
9700
10337
  for (let i = 0; i < dirtyRows.length; i++) {
9701
10338
  const y = Math.floor(dirtyRows[i] ?? -1);
9702
- if (y < 0 || y >= rowCount) continue;
10339
+ if (!Number.isFinite(y) || y < 0 || y >= rowCount) continue;
9703
10340
  if (!bits[y]) {
9704
10341
  bits[y] = 1;
9705
10342
  accumulatedDirtyCount++;
@@ -9725,15 +10362,17 @@ function createStdoutRenderer(terminal, options) {
9725
10362
  }
9726
10363
  const rows = buildAccumulatedRows(dirtyRows);
9727
10364
  const pendingScrolls = buildAccumulatedScrollOperations();
10365
+ const forceDirtyRowsRepaint = accumulatedDirtyRowsRequireRepaint;
9728
10366
  cliLatency === null || cliLatency === void 0 || cliLatency.recordStdoutQueued(0);
9729
- doRender(rows, pendingScrolls);
10367
+ doRender(rows, pendingScrolls, forceDirtyRowsRepaint);
9730
10368
  return;
9731
10369
  }
9732
10370
  if (elapsed >= MIN_FRAME_MS) {
9733
10371
  const rows = buildAccumulatedRows(dirtyRows);
9734
10372
  const pendingScrolls = buildAccumulatedScrollOperations();
10373
+ const forceDirtyRowsRepaint = accumulatedDirtyRowsRequireRepaint;
9735
10374
  cliLatency === null || cliLatency === void 0 || cliLatency.recordStdoutQueued(0);
9736
- doRender(rows, pendingScrolls);
10375
+ doRender(rows, pendingScrolls, forceDirtyRowsRepaint);
9737
10376
  } else {
9738
10377
  pendingRender = true;
9739
10378
  if (renderTimer) clearTimeout(renderTimer);
@@ -9742,19 +10381,62 @@ function createStdoutRenderer(terminal, options) {
9742
10381
  if (!disposed) {
9743
10382
  const rows = buildAccumulatedRows(dirtyRows);
9744
10383
  const pendingScrolls = buildAccumulatedScrollOperations();
9745
- doRender(rows, pendingScrolls);
10384
+ const forceDirtyRowsRepaint = accumulatedDirtyRowsRequireRepaint;
10385
+ doRender(rows, pendingScrolls, forceDirtyRowsRepaint);
9746
10386
  }
9747
10387
  }, queuedDelayMs);
9748
10388
  cliLatency === null || cliLatency === void 0 || cliLatency.recordStdoutQueued(queuedDelayMs);
9749
10389
  }
9750
10390
  }
9751
- function installFingerprintFn() {
9752
- terminal.setFingerprintFn((ch, style) => {
10391
+ function installFingerprintFn(force = false) {
10392
+ if (!canInstallTerminalFingerprintFn()) {
10393
+ if (ownsFingerprintFn && stdoutFingerprintOwners.get(terminal) === fingerprintOwner) {
10394
+ stdoutFingerprintOwners.delete(terminal);
10395
+ invalidateFingerprintBaseline();
10396
+ }
10397
+ ownsFingerprintFn = false;
10398
+ terminalFingerprintFnDirty = true;
10399
+ fingerprintInstallBlockedByExternalProvider = false;
10400
+ return;
10401
+ }
10402
+ const currentOwner = stdoutFingerprintOwners.get(terminal);
10403
+ if (!currentOwner && !ownsFingerprintFn && hasActiveExternalFingerprintProvider()) {
10404
+ fingerprintInstallBlockedByExternalProvider = true;
10405
+ ownsFingerprintFn = false;
10406
+ terminalFingerprintFnDirty = false;
10407
+ invalidateFingerprintBaseline();
10408
+ return;
10409
+ }
10410
+ fingerprintInstallBlockedByExternalProvider = false;
10411
+ if (currentOwner && currentOwner !== fingerprintOwner) {
10412
+ if (ownsFingerprintFn) invalidateFingerprintBaseline();
10413
+ ownsFingerprintFn = false;
10414
+ return;
10415
+ }
10416
+ if (ownsFingerprintFn && currentOwner === fingerprintOwner && !force && !terminalFingerprintFnDirty) return;
10417
+ stdoutFingerprintOwners.set(terminal, fingerprintOwner);
10418
+ ownsFingerprintFn = true;
10419
+ fingerprintTerminal.setFingerprintFn((ch, style) => {
9753
10420
  return cellFingerprint(ch, style);
9754
10421
  });
10422
+ terminalFingerprintFnDirty = false;
10423
+ invalidateFingerprintBaseline();
10424
+ }
10425
+ function releaseFingerprintFn() {
10426
+ if (!ownsFingerprintFn) return;
10427
+ if (stdoutFingerprintOwners.get(terminal) !== fingerprintOwner) {
10428
+ ownsFingerprintFn = false;
10429
+ return;
10430
+ }
10431
+ stdoutFingerprintOwners.delete(terminal);
10432
+ ownsFingerprintFn = false;
10433
+ try {
10434
+ var _fingerprintTerminal$2;
10435
+ (_fingerprintTerminal$2 = fingerprintTerminal.setFingerprintFn) === null || _fingerprintTerminal$2 === void 0 || _fingerprintTerminal$2.call(fingerprintTerminal, null);
10436
+ } catch {}
9755
10437
  }
9756
10438
  installFingerprintFn();
9757
- if (altScreen && out.isTTY) out.write("\x1B[?1049h");
10439
+ if (altScreen && outputIsTTY) out.write("\x1B[?1049h");
9758
10440
  if (hideCursor) out.write("\x1B[?25l");
9759
10441
  if (clear) out.write(`${SGR_RESET}${openBg(defaultBg)}\u001B[2J\u001B[H${SGR_RESET}`);
9760
10442
  const off = terminal.on("commit", ({ dirtyRows, scrollOperations, sync }) => {
@@ -9762,7 +10444,7 @@ function createStdoutRenderer(terminal, options) {
9762
10444
  render(dirtyRows, sync, scrollOperations);
9763
10445
  });
9764
10446
  const resizeSource = (options === null || options === void 0 ? void 0 : options.output) ?? process.stdout;
9765
- const canTrackResize = Boolean(trackResize && out.isTTY && typeof (resizeSource === null || resizeSource === void 0 ? void 0 : resizeSource.on) === "function");
10447
+ const canTrackResize = Boolean(trackResize && outputIsTTY && typeof (resizeSource === null || resizeSource === void 0 ? void 0 : resizeSource.on) === "function");
9766
10448
  const onResize = () => {
9767
10449
  const cols = Number(resizeSource === null || resizeSource === void 0 ? void 0 : resizeSource.columns);
9768
10450
  const rows = Number(resizeSource === null || resizeSource === void 0 ? void 0 : resizeSource.rows);
@@ -9809,7 +10491,9 @@ function createStdoutRenderer(terminal, options) {
9809
10491
  accumulatedDirtyCount = 0;
9810
10492
  accumulatedDirtyMin = Number.POSITIVE_INFINITY;
9811
10493
  accumulatedDirtyMax = -1;
10494
+ accumulatedDirtyRowsRequireRepaint = false;
9812
10495
  off();
10496
+ releaseFingerprintFn();
9813
10497
  if (canTrackResize && typeof (resizeSource === null || resizeSource === void 0 ? void 0 : resizeSource.off) === "function") try {
9814
10498
  resizeSource.off("resize", onResize);
9815
10499
  } catch {}
@@ -9817,7 +10501,7 @@ function createStdoutRenderer(terminal, options) {
9817
10501
  resizeSource.removeListener("resize", onResize);
9818
10502
  } catch {}
9819
10503
  if (hideCursor) out.write("\x1B[?25h");
9820
- if (altScreen && out.isTTY) out.write("\x1B[?1049l");
10504
+ if (altScreen && outputIsTTY) out.write("\x1B[?1049l");
9821
10505
  profiler === null || profiler === void 0 || profiler.dispose();
9822
10506
  }
9823
10507
  const updateTheme = (next) => {
@@ -9827,10 +10511,8 @@ function createStdoutRenderer(terminal, options) {
9827
10511
  if (nextBg !== defaultBg) defaultBg = nextBg;
9828
10512
  }
9829
10513
  if ("palette" in next) palette = next.palette ?? null;
9830
- styleKeyCache = new WeakMap();
9831
- for (const key of Object.keys(COLOR_INDEX)) if (!(key in BUILTIN_COLOR_INDEX)) delete COLOR_INDEX[key];
9832
- nextColorIdx = 17;
9833
- installFingerprintFn();
10514
+ resetStyleKeyIndex();
10515
+ installFingerprintFn(true);
9834
10516
  fpRows = 0;
9835
10517
  fpPrevValid = false;
9836
10518
  prevOverlayBlockedRows = [];