@simon_he/vue-tui 0.0.5 → 0.0.6

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.
Files changed (4) hide show
  1. package/dist/index.cjs +1478 -125
  2. package/dist/index.d.ts +119 -102
  3. package/dist/index.js +1295 -112
  4. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -2662,12 +2662,53 @@ function createGridBuffer(cols, rows) {
2662
2662
  cursorStyle: DEFAULT_STYLE,
2663
2663
  scrollback: [],
2664
2664
  scrollbackLimit: 1e3,
2665
- rowPool: []
2665
+ rowPool: [],
2666
+ soaFingerprints: null,
2667
+ fingerprintFn: null
2666
2668
  };
2667
2669
  }
2668
2670
  function clamp(n, min, max) {
2669
2671
  return Math.max(min, Math.min(max, n));
2670
2672
  }
2673
+ function setFingerprintFn(buffer, fn) {
2674
+ buffer.fingerprintFn = fn;
2675
+ const len = buffer.rows * buffer.cols;
2676
+ if (!len) {
2677
+ buffer.soaFingerprints = null;
2678
+ return;
2679
+ }
2680
+ buffer.soaFingerprints = new Uint32Array(len);
2681
+ for (let y = 0; y < buffer.rows; y++) {
2682
+ const row = getBufferRow(buffer, y);
2683
+ const physY = physicalRowIndex(buffer, y);
2684
+ const base = physY * buffer.cols;
2685
+ for (let x = 0; x < buffer.cols; x++) {
2686
+ const cell = row[x];
2687
+ buffer.soaFingerprints[base + x] = fn(cell.ch, cell.style);
2688
+ }
2689
+ }
2690
+ }
2691
+ function getRowFingerprints(buffer, y) {
2692
+ if (!buffer.soaFingerprints)
2693
+ return null;
2694
+ const physY = physicalRowIndex(buffer, y);
2695
+ const base = physY * buffer.cols;
2696
+ return buffer.soaFingerprints.subarray(base, base + buffer.cols);
2697
+ }
2698
+ function updateCellFingerprint(buffer, physY, x, ch, style) {
2699
+ if (!buffer.soaFingerprints || !buffer.fingerprintFn)
2700
+ return;
2701
+ buffer.soaFingerprints[physY * buffer.cols + x] = buffer.fingerprintFn(ch, style);
2702
+ }
2703
+ function updateRangeFingerprint(buffer, physY, x0, x1, ch, style) {
2704
+ if (!buffer.soaFingerprints || !buffer.fingerprintFn)
2705
+ return;
2706
+ const fp = buffer.fingerprintFn(ch, style);
2707
+ const base = physY * buffer.cols;
2708
+ for (let x = x0; x < x1; x++) {
2709
+ buffer.soaFingerprints[base + x] = fp;
2710
+ }
2711
+ }
2671
2712
  function physicalRowIndex(buffer, y) {
2672
2713
  return (buffer.gridStart + y) % buffer.rows;
2673
2714
  }
@@ -2740,9 +2781,17 @@ function putCell(buffer, x, y, ch, style) {
2740
2781
  markDirty(buffer, y);
2741
2782
  return;
2742
2783
  }
2743
- row[x] = createCell(ch, style);
2784
+ const cell = createCell(ch, style);
2785
+ row[x] = cell;
2744
2786
  if (width === 2 && x + 1 < buffer.cols)
2745
2787
  row[x + 1] = createContinuationCell(style);
2788
+ if (buffer.soaFingerprints) {
2789
+ const physY = physicalRowIndex(buffer, y);
2790
+ updateCellFingerprint(buffer, physY, x, cell.ch, cell.style);
2791
+ if (width === 2 && x + 1 < buffer.cols) {
2792
+ updateCellFingerprint(buffer, physY, x + 1, "", cell.style);
2793
+ }
2794
+ }
2746
2795
  markDirty(buffer, y);
2747
2796
  }
2748
2797
  function fillRect(buffer, x, y, w, h18, ch = " ", style) {
@@ -2766,6 +2815,10 @@ function fillRect(buffer, x, y, w, h18, ch = " ", style) {
2766
2815
  row.fill(fillCell, x0, x1);
2767
2816
  if (x1 < buffer.cols && ((_b2 = row[x1]) == null ? void 0 : _b2.continuation))
2768
2817
  row[x1] = blank;
2818
+ if (buffer.soaFingerprints) {
2819
+ const physY = physicalRowIndex(buffer, yy);
2820
+ updateRangeFingerprint(buffer, physY, x0, x1, fillCell.ch, fillCell.style);
2821
+ }
2769
2822
  markDirty(buffer, yy);
2770
2823
  }
2771
2824
  return;
@@ -2834,6 +2887,19 @@ function blankRow(buffer) {
2834
2887
  pooled[x] = blank;
2835
2888
  return pooled;
2836
2889
  }
2890
+ function recomputeFingerprintsForRows(buffer, startY, endY) {
2891
+ if (!buffer.soaFingerprints || !buffer.fingerprintFn)
2892
+ return;
2893
+ for (let y = startY; y < endY; y++) {
2894
+ const row = getBufferRow(buffer, y);
2895
+ const physY = physicalRowIndex(buffer, y);
2896
+ const base = physY * buffer.cols;
2897
+ for (let x = 0; x < buffer.cols; x++) {
2898
+ const cell = row[x];
2899
+ buffer.soaFingerprints[base + x] = buffer.fingerprintFn(cell.ch, cell.style);
2900
+ }
2901
+ }
2902
+ }
2837
2903
  function scrollBuffer(buffer, lines) {
2838
2904
  const n = Math.trunc(lines);
2839
2905
  if (n === 0 || buffer.rows === 0)
@@ -2868,6 +2934,70 @@ function scrollBuffer(buffer, lines) {
2868
2934
  markAllDirty(buffer);
2869
2935
  buffer.cursorY = clamp(buffer.cursorY - n, 0, Math.max(0, buffer.rows - 1));
2870
2936
  }
2937
+ function scrollBufferRegion(buffer, startY, endY, lines) {
2938
+ const n = Math.trunc(lines);
2939
+ if (n === 0 || buffer.rows === 0)
2940
+ return [];
2941
+ const start = clamp(Math.floor(startY), 0, buffer.rows);
2942
+ const end = clamp(Math.floor(endY), 0, buffer.rows);
2943
+ const height = end - start;
2944
+ if (height <= 0)
2945
+ return [];
2946
+ const absDelta = Math.abs(n);
2947
+ const insertedRows = [];
2948
+ const replaceRegionWithBlankRows = () => {
2949
+ for (let y = start; y < end; y++) {
2950
+ const physY = physicalRowIndex(buffer, y);
2951
+ buffer.rowPool.push(buffer.grid[physY]);
2952
+ buffer.grid[physY] = blankRow(buffer);
2953
+ markDirty(buffer, y);
2954
+ insertedRows.push(y);
2955
+ }
2956
+ };
2957
+ if (absDelta >= height) {
2958
+ replaceRegionWithBlankRows();
2959
+ recomputeFingerprintsForRows(buffer, start, end);
2960
+ if (buffer.cursorY >= start && buffer.cursorY < end)
2961
+ buffer.cursorY = clamp(buffer.cursorY - n, start, Math.max(start, end - 1));
2962
+ return insertedRows;
2963
+ }
2964
+ const regionRows = [];
2965
+ for (let y = start; y < end; y++)
2966
+ regionRows.push(getBufferRow(buffer, y));
2967
+ const nextRows = Array.from({ length: height }, () => []);
2968
+ if (n > 0) {
2969
+ const movedCount = height - n;
2970
+ for (let i = 0; i < movedCount; i++)
2971
+ nextRows[i] = regionRows[i + n];
2972
+ for (let i = 0; i < n; i++) {
2973
+ buffer.rowPool.push(regionRows[i]);
2974
+ const y = end - n + i;
2975
+ nextRows[movedCount + i] = blankRow(buffer);
2976
+ markDirty(buffer, y);
2977
+ insertedRows.push(y);
2978
+ }
2979
+ } else {
2980
+ const insertedCount = -n;
2981
+ const movedCount = height - insertedCount;
2982
+ for (let i = 0; i < insertedCount; i++) {
2983
+ buffer.rowPool.push(regionRows[height - insertedCount + i]);
2984
+ const y = start + i;
2985
+ nextRows[i] = blankRow(buffer);
2986
+ markDirty(buffer, y);
2987
+ insertedRows.push(y);
2988
+ }
2989
+ for (let i = 0; i < movedCount; i++)
2990
+ nextRows[insertedCount + i] = regionRows[i];
2991
+ }
2992
+ for (let i = 0; i < height; i++) {
2993
+ const physY = physicalRowIndex(buffer, start + i);
2994
+ buffer.grid[physY] = nextRows[i];
2995
+ }
2996
+ recomputeFingerprintsForRows(buffer, start, end);
2997
+ if (buffer.cursorY >= start && buffer.cursorY < end)
2998
+ buffer.cursorY = clamp(buffer.cursorY - n, start, Math.max(start, end - 1));
2999
+ return insertedRows;
3000
+ }
2871
3001
  function resizeBuffer(buffer, cols, rows) {
2872
3002
  var _a2;
2873
3003
  const nextCols = Math.max(0, Math.floor(cols));
@@ -2904,6 +3034,22 @@ function resizeBuffer(buffer, cols, rows) {
2904
3034
  buffer.dirtyAll = nextRows > 0;
2905
3035
  buffer.cursorX = clamp(buffer.cursorX, 0, Math.max(0, nextCols));
2906
3036
  buffer.cursorY = clamp(buffer.cursorY, 0, Math.max(0, nextRows - 1));
3037
+ if (buffer.fingerprintFn) {
3038
+ const len = nextRows * nextCols;
3039
+ if (len > 0) {
3040
+ buffer.soaFingerprints = new Uint32Array(len);
3041
+ for (let y = 0; y < nextRows; y++) {
3042
+ const row = nextGrid[y];
3043
+ const base = y * nextCols;
3044
+ for (let x = 0; x < nextCols; x++) {
3045
+ const cell = row[x];
3046
+ buffer.soaFingerprints[base + x] = buffer.fingerprintFn(cell.ch, cell.style);
3047
+ }
3048
+ }
3049
+ } else {
3050
+ buffer.soaFingerprints = null;
3051
+ }
3052
+ }
2907
3053
  }
2908
3054
  function snapshotText(buffer) {
2909
3055
  const out = [];
@@ -3092,6 +3238,40 @@ function scrollPlaneCoverage(state, lines) {
3092
3238
  for (let i = 0; i < -n; i++)
3093
3239
  state.coverage.unshift(new Uint8Array(cols));
3094
3240
  }
3241
+ function scrollPlaneCoverageRegion(state, startY, endY, lines) {
3242
+ const n = Math.trunc(lines);
3243
+ if (n === 0 || state.coverage.length === 0)
3244
+ return;
3245
+ const start = Math.max(0, Math.min(state.coverage.length, Math.floor(startY)));
3246
+ const end = Math.max(0, Math.min(state.coverage.length, Math.floor(endY)));
3247
+ const height = end - start;
3248
+ if (height <= 0)
3249
+ return;
3250
+ const cols = state.buffer.cols;
3251
+ if (Math.abs(n) >= height) {
3252
+ for (let y = start; y < end; y++)
3253
+ state.coverage[y] = new Uint8Array(cols);
3254
+ return;
3255
+ }
3256
+ const region = state.coverage.slice(start, end);
3257
+ const next = Array.from({ length: height }, () => new Uint8Array(0));
3258
+ if (n > 0) {
3259
+ const movedCount = height - n;
3260
+ for (let i = 0; i < movedCount; i++)
3261
+ next[i] = region[i + n];
3262
+ for (let i = 0; i < n; i++)
3263
+ next[movedCount + i] = new Uint8Array(cols);
3264
+ } else {
3265
+ const insertedCount = -n;
3266
+ const movedCount = height - insertedCount;
3267
+ for (let i = 0; i < insertedCount; i++)
3268
+ next[i] = new Uint8Array(cols);
3269
+ for (let i = 0; i < movedCount; i++)
3270
+ next[insertedCount + i] = region[i];
3271
+ }
3272
+ for (let i = 0; i < height; i++)
3273
+ state.coverage[start + i] = next[i];
3274
+ }
3095
3275
  function resizePlaneState(state, cols, rows) {
3096
3276
  resizeBuffer(state.buffer, cols, rows);
3097
3277
  state.coverage = resizeCoverage(state.coverage, state.buffer.cols, state.buffer.rows);
@@ -3127,6 +3307,14 @@ function resetPlaneRowsForRender(terminal, plane, dirtyRows) {
3127
3307
  const internals = terminal[TERMINAL_PLANE_INTERNALS];
3128
3308
  internals == null ? void 0 : internals.resetRowsForRender(plane, dirtyRows);
3129
3309
  }
3310
+ function getPlaneRowCoverageKind(terminal, plane, y) {
3311
+ const internals = terminal[TERMINAL_PLANE_INTERNALS];
3312
+ return (internals == null ? void 0 : internals.getRowCoverageKind(plane, y)) ?? 0;
3313
+ }
3314
+ function scrollPlaneRows(terminal, plane, startY, endY, lines) {
3315
+ const internals = terminal[TERMINAL_PLANE_INTERNALS];
3316
+ internals == null ? void 0 : internals.scrollRows(plane, startY, endY, lines);
3317
+ }
3130
3318
  function createTerminal(opts) {
3131
3319
  const emitter = new Emitter();
3132
3320
  const compositeBuffer = createGridBuffer(opts.cols, opts.rows);
@@ -3136,6 +3324,7 @@ function createTerminal(opts) {
3136
3324
  let pendingCommit = false;
3137
3325
  let pendingCommitAllPlanes = false;
3138
3326
  let pendingCommitPlanes = null;
3327
+ const pendingPlaneScrollOps = /* @__PURE__ */ new Map();
3139
3328
  const planeTerminals = /* @__PURE__ */ new Map();
3140
3329
  let base;
3141
3330
  compositeBuffer.cursorVisible = false;
@@ -3274,8 +3463,171 @@ function createTerminal(opts) {
3274
3463
  scrollBuffer(state.buffer, lines);
3275
3464
  scrollPlaneCoverage(state, lines);
3276
3465
  }
3466
+ function normalizeScrollOperation(startY, endY, delta, totalRows) {
3467
+ const start = Math.max(0, Math.min(totalRows, Math.floor(startY)));
3468
+ const end = Math.max(0, Math.min(totalRows, Math.floor(endY)));
3469
+ const height = end - start;
3470
+ const lines = Math.trunc(delta);
3471
+ if (height <= 0 || lines === 0 || Math.abs(lines) >= height)
3472
+ return null;
3473
+ return { startY: start, endY: end, delta: lines };
3474
+ }
3475
+ function recordPendingScrollOp(plane, op) {
3476
+ const prev = pendingPlaneScrollOps.get(plane);
3477
+ if (prev && prev.startY === op.startY && prev.endY === op.endY) {
3478
+ const nextDelta = prev.delta + op.delta;
3479
+ if (nextDelta === 0) {
3480
+ pendingPlaneScrollOps.delete(plane);
3481
+ return;
3482
+ }
3483
+ const next = normalizeScrollOperation(
3484
+ op.startY,
3485
+ op.endY,
3486
+ nextDelta,
3487
+ compositeBuffer.rows
3488
+ );
3489
+ if (!next) {
3490
+ pendingPlaneScrollOps.delete(plane);
3491
+ return;
3492
+ }
3493
+ pendingPlaneScrollOps.set(plane, next);
3494
+ return;
3495
+ }
3496
+ pendingPlaneScrollOps.set(plane, op);
3497
+ }
3498
+ function takePendingScrollOps(planes) {
3499
+ const planesToTake = (planes == null ? void 0 : planes.length) ? planes : TERMINAL_RENDER_PLANES;
3500
+ const out = [];
3501
+ for (const plane of planesToTake) {
3502
+ const op = pendingPlaneScrollOps.get(plane);
3503
+ if (!op)
3504
+ continue;
3505
+ pendingPlaneScrollOps.delete(plane);
3506
+ out.push({ plane, ...op });
3507
+ }
3508
+ return out.length ? out : null;
3509
+ }
3510
+ function higherPlanesFor(plane) {
3511
+ const planeIndex = TERMINAL_RENDER_PLANES.indexOf(plane);
3512
+ if (planeIndex < 0)
3513
+ return [];
3514
+ return TERMINAL_RENDER_PLANES.slice(planeIndex + 1);
3515
+ }
3516
+ function higherPlaneCoverageKind(plane, y) {
3517
+ var _a2;
3518
+ const higherPlanes = higherPlanesFor(plane);
3519
+ if (!higherPlanes.length || y < 0 || y >= compositeBuffer.rows)
3520
+ return 0;
3521
+ let covered = 0;
3522
+ for (let x = 0; x < compositeBuffer.cols; x++) {
3523
+ let cellCovered = false;
3524
+ for (const higherPlane of higherPlanes) {
3525
+ const state = planeStates.get(higherPlane);
3526
+ if ((_a2 = state == null ? void 0 : state.coverage[y]) == null ? void 0 : _a2[x]) {
3527
+ cellCovered = true;
3528
+ break;
3529
+ }
3530
+ }
3531
+ if (cellCovered)
3532
+ covered++;
3533
+ }
3534
+ if (covered === 0)
3535
+ return 0;
3536
+ if (covered >= compositeBuffer.cols)
3537
+ return 2;
3538
+ return 1;
3539
+ }
3540
+ function prepareCompositeScrollOps(pendingOps) {
3541
+ if (!(pendingOps == null ? void 0 : pendingOps.length)) {
3542
+ return {
3543
+ scrollOperations: null,
3544
+ extraDirtyRows: []
3545
+ };
3546
+ }
3547
+ const scrollOperations = [];
3548
+ const extraDirtyRows = /* @__PURE__ */ new Set();
3549
+ for (const pending of pendingOps) {
3550
+ const blockedRows = /* @__PURE__ */ new Set();
3551
+ const partiallyBlockedRows = /* @__PURE__ */ new Set();
3552
+ for (let y = pending.startY; y < pending.endY; y++) {
3553
+ const coverageKind = higherPlaneCoverageKind(pending.plane, y);
3554
+ if (coverageKind === 0)
3555
+ continue;
3556
+ blockedRows.add(y);
3557
+ if (coverageKind === 1)
3558
+ partiallyBlockedRows.add(y);
3559
+ }
3560
+ if (!blockedRows.size) {
3561
+ scrollOperations.push({
3562
+ startY: pending.startY,
3563
+ endY: pending.endY,
3564
+ delta: pending.delta
3565
+ });
3566
+ continue;
3567
+ }
3568
+ const absDelta = Math.abs(pending.delta);
3569
+ for (let y = pending.startY; y < pending.endY; y++) {
3570
+ if (partiallyBlockedRows.has(y))
3571
+ extraDirtyRows.add(y);
3572
+ }
3573
+ let bandStart = -1;
3574
+ const flushBand = (bandEnd) => {
3575
+ if (bandStart < 0 || bandEnd <= bandStart)
3576
+ return;
3577
+ const bandHeight = bandEnd - bandStart;
3578
+ if (absDelta >= bandHeight) {
3579
+ for (let y = bandStart; y < bandEnd; y++)
3580
+ extraDirtyRows.add(y);
3581
+ bandStart = -1;
3582
+ return;
3583
+ }
3584
+ scrollOperations.push({
3585
+ startY: bandStart,
3586
+ endY: bandEnd,
3587
+ delta: pending.delta
3588
+ });
3589
+ if (pending.delta > 0) {
3590
+ for (let y = bandEnd - absDelta; y < bandEnd; y++)
3591
+ extraDirtyRows.add(y);
3592
+ } else {
3593
+ for (let y = bandStart; y < bandStart + absDelta; y++)
3594
+ extraDirtyRows.add(y);
3595
+ }
3596
+ bandStart = -1;
3597
+ };
3598
+ for (let y = pending.startY; y < pending.endY; y++) {
3599
+ if (blockedRows.has(y)) {
3600
+ flushBand(y);
3601
+ continue;
3602
+ }
3603
+ if (bandStart < 0)
3604
+ bandStart = y;
3605
+ }
3606
+ flushBand(pending.endY);
3607
+ }
3608
+ return {
3609
+ scrollOperations: scrollOperations.length ? scrollOperations : null,
3610
+ extraDirtyRows: Array.from(extraDirtyRows).sort((a, b) => a - b)
3611
+ };
3612
+ }
3613
+ function scrollRowsForPlane(plane, startY, endY, lines) {
3614
+ const state = getPlaneState(plane);
3615
+ const op = normalizeScrollOperation(
3616
+ startY,
3617
+ endY,
3618
+ lines,
3619
+ state.buffer.rows
3620
+ );
3621
+ if (!op)
3622
+ return;
3623
+ scrollBufferRegion(state.buffer, op.startY, op.endY, op.delta);
3624
+ scrollPlaneCoverageRegion(state, op.startY, op.endY, op.delta);
3625
+ recordPendingScrollOp(plane, op);
3626
+ }
3277
3627
  function composeRows(rows) {
3278
3628
  const blank = createBlankCell();
3629
+ const fpFn = compositeBuffer.fingerprintFn;
3630
+ const fpArr = compositeBuffer.soaFingerprints;
3279
3631
  const rowList = rows ?? Array.from({ length: compositeBuffer.rows }, (_, index) => index);
3280
3632
  for (const y of rowList) {
3281
3633
  if (y < 0 || y >= compositeBuffer.rows)
@@ -3310,6 +3662,14 @@ function createTerminal(opts) {
3310
3662
  }
3311
3663
  }
3312
3664
  }
3665
+ if (fpFn && fpArr) {
3666
+ const physY = (compositeBuffer.gridStart + y) % compositeBuffer.rows;
3667
+ const base2 = physY * cols;
3668
+ for (let x = 0; x < cols; x++) {
3669
+ const cell = dst[x];
3670
+ fpArr[base2 + x] = fpFn(cell.ch, cell.style);
3671
+ }
3672
+ }
3313
3673
  markDirty(compositeBuffer, y);
3314
3674
  }
3315
3675
  }
@@ -3506,6 +3866,14 @@ function createTerminal(opts) {
3506
3866
  return all.slice(
3507
3867
  Math.max(0, all.length - Math.max(0, Math.floor(count)))
3508
3868
  );
3869
+ },
3870
+ setFingerprintFn(fn) {
3871
+ assertNotDisposed();
3872
+ base.setFingerprintFn(fn);
3873
+ },
3874
+ getRowFingerprints(y) {
3875
+ assertNotDisposed();
3876
+ return base.getRowFingerprints(y);
3509
3877
  }
3510
3878
  };
3511
3879
  planeTerminals.set(plane, api);
@@ -3580,6 +3948,9 @@ function createTerminal(opts) {
3580
3948
  return pendingDirtyRowsForPlanes((meta == null ? void 0 : meta.planes) ?? null);
3581
3949
  }
3582
3950
  const planesToCompose = ((_b2 = meta == null ? void 0 : meta.planes) == null ? void 0 : _b2.length) ? meta.planes : TERMINAL_RENDER_PLANES;
3951
+ const pendingScrollOps = takePendingScrollOps(planesToCompose);
3952
+ const preparedScroll = prepareCompositeScrollOps(pendingScrollOps);
3953
+ const scrollOperations = preparedScroll.scrollOperations;
3583
3954
  let composeAllRows = false;
3584
3955
  const dirtyRows = /* @__PURE__ */ new Set();
3585
3956
  for (const plane of planesToCompose) {
@@ -3594,12 +3965,23 @@ function createTerminal(opts) {
3594
3965
  for (const y of rows)
3595
3966
  dirtyRows.add(y);
3596
3967
  }
3968
+ for (const y of preparedScroll.extraDirtyRows)
3969
+ dirtyRows.add(y);
3597
3970
  const normalizedDirtyRows = composeAllRows ? null : Array.from(dirtyRows).sort((a, b) => a - b);
3971
+ if (scrollOperations) {
3972
+ for (const op of scrollOperations)
3973
+ scrollBufferRegion(compositeBuffer, op.startY, op.endY, op.delta);
3974
+ }
3598
3975
  if (normalizedDirtyRows !== null && normalizedDirtyRows.length === 0) {
3599
3976
  const pendingCompositeRows = collectAndClearDirtyRows(compositeBuffer);
3600
3977
  if (pendingCompositeRows !== null && pendingCompositeRows.length === 0)
3601
3978
  return pendingCompositeRows;
3602
- emitter.emit("commit", { dirtyRows: pendingCompositeRows, planes: (meta == null ? void 0 : meta.planes) ?? null });
3979
+ emitter.emit("commit", {
3980
+ dirtyRows: pendingCompositeRows,
3981
+ planes: (meta == null ? void 0 : meta.planes) ?? null,
3982
+ sync: meta == null ? void 0 : meta.sync,
3983
+ scrollOperations
3984
+ });
3603
3985
  return pendingCompositeRows;
3604
3986
  }
3605
3987
  composeRows(normalizedDirtyRows);
@@ -3609,7 +3991,12 @@ function createTerminal(opts) {
3609
3991
  const committedRows = collectAndClearDirtyRows(compositeBuffer);
3610
3992
  if (committedRows !== null && committedRows.length === 0)
3611
3993
  return committedRows;
3612
- emitter.emit("commit", { dirtyRows: committedRows, planes: (meta == null ? void 0 : meta.planes) ?? null });
3994
+ emitter.emit("commit", {
3995
+ dirtyRows: committedRows,
3996
+ planes: (meta == null ? void 0 : meta.planes) ?? null,
3997
+ sync: meta == null ? void 0 : meta.sync,
3998
+ scrollOperations
3999
+ });
3613
4000
  return committedRows;
3614
4001
  },
3615
4002
  on(event, cb) {
@@ -3675,6 +4062,14 @@ function createTerminal(opts) {
3675
4062
  return all.slice(
3676
4063
  Math.max(0, all.length - Math.max(0, Math.floor(count)))
3677
4064
  );
4065
+ },
4066
+ setFingerprintFn(fn) {
4067
+ assertNotDisposed();
4068
+ setFingerprintFn(compositeBuffer, fn);
4069
+ },
4070
+ getRowFingerprints(y) {
4071
+ assertNotDisposed();
4072
+ return getRowFingerprints(compositeBuffer, y);
3678
4073
  }
3679
4074
  };
3680
4075
  base[TERMINAL_PLANE_INTERNALS] = {
@@ -3689,13 +4084,34 @@ function createTerminal(opts) {
3689
4084
  }
3690
4085
  for (const y of rows)
3691
4086
  clearPlaneCoverageRow(state, y);
4087
+ },
4088
+ getRowCoverageKind(plane, y) {
4089
+ const state = getPlaneState(plane);
4090
+ if (y < 0 || y >= state.coverage.length)
4091
+ return 0;
4092
+ const row = state.coverage[y];
4093
+ if (!(row == null ? void 0 : row.length))
4094
+ return 0;
4095
+ let covered = 0;
4096
+ for (let x = 0; x < row.length; x++) {
4097
+ if (row[x])
4098
+ covered++;
4099
+ }
4100
+ if (covered === 0)
4101
+ return 0;
4102
+ if (covered >= row.length)
4103
+ return 2;
4104
+ return 1;
4105
+ },
4106
+ scrollRows(plane, startY, endY, lines) {
4107
+ scrollRowsForPlane(plane, startY, endY, lines);
3692
4108
  }
3693
4109
  };
3694
4110
  return base;
3695
4111
  }
3696
4112
 
3697
4113
  // src/create-terminal-app.ts
3698
- var import_node_process8 = __toESM(require("process"), 1);
4114
+ var import_node_process7 = __toESM(require("process"), 1);
3699
4115
  var import_vue4 = require("vue");
3700
4116
 
3701
4117
  // src/cli/headless-renderer.ts
@@ -6028,9 +6444,6 @@ var RenderStackKey = Symbol(
6028
6444
  "RenderStack"
6029
6445
  );
6030
6446
 
6031
- // src/vue/render/render-manager.ts
6032
- var import_node_process7 = __toESM(require("process"), 1);
6033
-
6034
6447
  // src/core/debug-logger.ts
6035
6448
  var import_node_fs4 = require("fs");
6036
6449
  var import_node_process6 = __toESM(require("process"), 1);
@@ -6615,6 +7028,30 @@ function createRenderManager(terminal) {
6615
7028
  }
6616
7029
  }
6617
7030
  }
7031
+ function markRows(plane, rows) {
7032
+ if (!rows.length)
7033
+ return;
7034
+ const state = getDirtyState(plane);
7035
+ for (let i = 0; i < rows.length; i++) {
7036
+ const y = Math.floor(rows[i] ?? -1);
7037
+ if (y < 0 || y >= terminalRows)
7038
+ continue;
7039
+ if (state.dirtyRowBits[y] === 0) {
7040
+ state.dirtyRowBits[y] = 1;
7041
+ state.dirtyRowCount++;
7042
+ if (y < state.dirtyMinY)
7043
+ state.dirtyMinY = y;
7044
+ if (y > state.dirtyMaxY)
7045
+ state.dirtyMaxY = y;
7046
+ }
7047
+ }
7048
+ }
7049
+ function scrollPlane(plane, startY, endY, delta) {
7050
+ scrollPlaneRows(terminal, plane, startY, endY, delta);
7051
+ }
7052
+ function invalidatePlane(plane) {
7053
+ getDirtyState(plane).allRowsDirty = true;
7054
+ }
6618
7055
  function register(node) {
6619
7056
  const id = `r${nextNodeId++}`;
6620
7057
  const rect = node.rect ?? null;
@@ -6645,8 +7082,14 @@ function createRenderManager(terminal) {
6645
7082
  const nextPlane = next.plane ?? prev.plane;
6646
7083
  const nextRect = next.rect ?? prev.rect;
6647
7084
  const { y0, y1 } = rectToYBounds(nextRect);
6648
- markRect(prev.plane, prev.rect);
6649
- markRect(nextPlane, nextRect);
7085
+ const dirtyRowsHint = next.dirtyRowsHint;
7086
+ const canUseDirtyRowsHint = nextPlane === prev.plane && dirtyRowsHint != null && dirtyRowsHint.length > 0;
7087
+ if (canUseDirtyRowsHint) {
7088
+ markRows(nextPlane, dirtyRowsHint);
7089
+ } else {
7090
+ markRect(prev.plane, prev.rect);
7091
+ markRect(nextPlane, nextRect);
7092
+ }
6650
7093
  const full = Object.freeze({
6651
7094
  ...prev,
6652
7095
  stack: next.stack ?? prev.stack,
@@ -6713,11 +7156,10 @@ function createRenderManager(terminal) {
6713
7156
  return false;
6714
7157
  }
6715
7158
  function render(options) {
6716
- var _a2;
6717
7159
  const renderStart = profiler == null ? void 0 : profiler.now();
6718
7160
  const activePlanes = (options == null ? void 0 : options.activePlanes) ?? null;
6719
7161
  const requestedPlanes = activePlanes ?? TERMINAL_RENDER_PLANES;
6720
- const env = (_a2 = import_node_process7.default) == null ? void 0 : _a2.env;
7162
+ const env = process == null ? void 0 : process.env;
6721
7163
  if ((env == null ? void 0 : env.DIMCODE_DEBUG) === "1")
6722
7164
  renderMgrDebugLog.render("[RENDER-MANAGER] render() called");
6723
7165
  if (!hasPendingDirtyWork(requestedPlanes)) {
@@ -6847,6 +7289,8 @@ function createRenderManager(terminal) {
6847
7289
  return {
6848
7290
  rootStack,
6849
7291
  createStack,
7292
+ invalidatePlane,
7293
+ scrollPlane,
6850
7294
  register,
6851
7295
  update,
6852
7296
  unregister,
@@ -6935,9 +7379,10 @@ function createTerminalApp(options) {
6935
7379
  let lastFlushAtMs = 0;
6936
7380
  let scheduledAtMs = 0;
6937
7381
  let timer = null;
7382
+ let pendingInvalidateAfterFlush = false;
6938
7383
  let pendingInvalidateAllPlanes = false;
6939
7384
  const pendingInvalidatePlanes = /* @__PURE__ */ new Set();
6940
- const env = ((_a2 = import_node_process8.default) == null ? void 0 : _a2.env) ?? {};
7385
+ const env = ((_a2 = import_node_process7.default) == null ? void 0 : _a2.env) ?? {};
6941
7386
  const isGhostty = "GHOSTTY_RESOURCES_DIR" in env;
6942
7387
  const throttleMs = (() => {
6943
7388
  const raw = env.DIMCODE_TUI_THROTTLE_MS;
@@ -6948,7 +7393,7 @@ function createTerminalApp(options) {
6948
7393
  })();
6949
7394
  const frameThrottleMs = (() => {
6950
7395
  var _a3, _b2;
6951
- if (!((_b2 = (_a3 = import_node_process8.default) == null ? void 0 : _a3.stdout) == null ? void 0 : _b2.isTTY))
7396
+ if (!((_b2 = (_a3 = import_node_process7.default) == null ? void 0 : _a3.stdout) == null ? void 0 : _b2.isTTY))
6952
7397
  return 0;
6953
7398
  return isGhostty ? 24 : 16;
6954
7399
  })();
@@ -6974,17 +7419,21 @@ function createTerminalApp(options) {
6974
7419
  pendingInvalidatePlanes.clear();
6975
7420
  return activePlanes;
6976
7421
  }
6977
- function flush() {
7422
+ function flush(sync) {
6978
7423
  if (disposed)
6979
7424
  return;
6980
7425
  scheduled = false;
6981
7426
  flushing = true;
6982
7427
  const activePlanes = takeActivePlanes();
6983
7428
  render.render({ activePlanes });
6984
- terminal.commit({ planes: activePlanes });
7429
+ terminal.commit({ planes: activePlanes, sync });
6985
7430
  flushing = false;
6986
7431
  lastFlushAtMs = Date.now();
6987
7432
  scheduledAtMs = 0;
7433
+ if (pendingInvalidateAfterFlush) {
7434
+ pendingInvalidateAfterFlush = false;
7435
+ flush(true);
7436
+ }
6988
7437
  }
6989
7438
  function clearScheduledTimer() {
6990
7439
  if (!timer)
@@ -7006,9 +7455,9 @@ function createTerminalApp(options) {
7006
7455
  }
7007
7456
  clearScheduledTimer();
7008
7457
  scheduledAtMs = nowMs;
7009
- if (typeof import_node_process8.default.nextTick === "function") {
7458
+ if (typeof import_node_process7.default.nextTick === "function") {
7010
7459
  ;
7011
- import_node_process8.default.nextTick(() => {
7460
+ import_node_process7.default.nextTick(() => {
7012
7461
  flush();
7013
7462
  });
7014
7463
  } else if (typeof globalThis.setImmediate === "function") {
@@ -7029,16 +7478,23 @@ function createTerminalApp(options) {
7029
7478
  return;
7030
7479
  clearScheduledTimer();
7031
7480
  scheduled = false;
7032
- flush();
7481
+ flush(true);
7033
7482
  }
7034
7483
  function invalidate(options2) {
7035
7484
  if (disposed)
7036
7485
  return;
7037
- if (flushing)
7486
+ if (flushing) {
7487
+ queueInvalidatePlane(options2 == null ? void 0 : options2.plane);
7488
+ pendingInvalidateAfterFlush = true;
7038
7489
  return;
7490
+ }
7039
7491
  const priority = (options2 == null ? void 0 : options2.priority) ?? "normal";
7040
7492
  queueInvalidatePlane(options2 == null ? void 0 : options2.plane);
7041
7493
  profiler == null ? void 0 : profiler.recordInvalidate({ plane: (options2 == null ? void 0 : options2.plane) ?? null });
7494
+ if (priority === "high") {
7495
+ flushNow();
7496
+ return;
7497
+ }
7042
7498
  const nowMs = Date.now();
7043
7499
  const throttleDelayMs = throttleMs > 0 ? Math.max(0, lastFlushAtMs + throttleMs - nowMs) : 0;
7044
7500
  const laneDelayMs = priority === "low" ? Math.max(throttleDelayMs, frameThrottleMs || 16) : priority === "normal" ? Math.max(throttleDelayMs, frameThrottleMs) : 0;
@@ -7181,6 +7637,7 @@ function createTerminalApp(options) {
7181
7637
  if (disposed)
7182
7638
  return;
7183
7639
  disposed = true;
7640
+ clearScheduledTimer();
7184
7641
  if (mounted)
7185
7642
  app.unmount();
7186
7643
  offResize == null ? void 0 : offResize();
@@ -7238,7 +7695,7 @@ function recordStdoutFrame(info) {
7238
7695
 
7239
7696
  // src/renderer/cli/stdout-renderer.ts
7240
7697
  var import_node_buffer2 = require("buffer");
7241
- var import_node_process9 = __toESM(require("process"), 1);
7698
+ var import_node_process8 = __toESM(require("process"), 1);
7242
7699
  var debugLog = null;
7243
7700
  function getDebugLog() {
7244
7701
  if (!debugLog) {
@@ -7276,7 +7733,7 @@ function parseWriteMode(raw) {
7276
7733
  }
7277
7734
  function createStdoutRenderer(terminal, options) {
7278
7735
  var _a2, _b2, _c, _d;
7279
- const output = (options == null ? void 0 : options.output) ?? import_node_process9.default.stdout;
7736
+ const output = (options == null ? void 0 : options.output) ?? import_node_process8.default.stdout;
7280
7737
  if (!output)
7281
7738
  throw new Error("createStdoutRenderer requires a Node stdout-like output");
7282
7739
  const out = output;
@@ -7292,7 +7749,7 @@ function createStdoutRenderer(terminal, options) {
7292
7749
  var _a3;
7293
7750
  if ((options == null ? void 0 : options.useSyncOutput) != null)
7294
7751
  return options.useSyncOutput;
7295
- const env2 = ((_a3 = import_node_process9.default) == null ? void 0 : _a3.env) ?? {};
7752
+ const env2 = ((_a3 = import_node_process8.default) == null ? void 0 : _a3.env) ?? {};
7296
7753
  const envValue = env2.DIMCODE_SYNC_OUTPUT;
7297
7754
  if (envValue != null) {
7298
7755
  const v = String(envValue).trim().toLowerCase();
@@ -7325,8 +7782,8 @@ function createStdoutRenderer(terminal, options) {
7325
7782
  return 0;
7326
7783
  }
7327
7784
  const useSyncOutput = resolveUseSyncOutput();
7328
- const isGhostty = "GHOSTTY_RESOURCES_DIR" in (((_a2 = import_node_process9.default) == null ? void 0 : _a2.env) ?? {});
7329
- const env = ((_b2 = import_node_process9.default) == null ? void 0 : _b2.env) ?? {};
7785
+ const isGhostty = "GHOSTTY_RESOURCES_DIR" in (((_a2 = import_node_process8.default) == null ? void 0 : _a2.env) ?? {});
7786
+ const env = ((_b2 = import_node_process8.default) == null ? void 0 : _b2.env) ?? {};
7330
7787
  const forcedWriteMode = parseWriteMode(
7331
7788
  env.DIMCODE_TUI_WRITE_MODE ?? env.VUE_TUI_WRITE_MODE
7332
7789
  );
@@ -7353,14 +7810,14 @@ function createStdoutRenderer(terminal, options) {
7353
7810
  return Math.min(1, Math.max(0.05, n));
7354
7811
  return 0.6;
7355
7812
  })();
7356
- const disableCursorPos = isGhostty && ((_d = (_c = import_node_process9.default) == null ? void 0 : _c.env) == null ? void 0 : _d.DIMCODE_DISABLE_CURSOR_POS) === "1";
7357
- const overlayTerminal = getPlaneTerminal(terminal, "overlay");
7813
+ const disableCursorPos = isGhostty && ((_d = (_c = import_node_process8.default) == null ? void 0 : _c.env) == null ? void 0 : _d.DIMCODE_DISABLE_CURSOR_POS) === "1";
7814
+ const isVscodeTerminal = String(env.TERM_PROGRAM ?? "").trim().toLowerCase() === "vscode" || "VSCODE_PID" in env || "VSCODE_IPC_HOOK_CLI" in env;
7358
7815
  const enableOsc8Links = (() => {
7359
7816
  if (!out.isTTY)
7360
7817
  return false;
7361
7818
  const raw = String(env.DIMCODE_TUI_LINKS ?? "").trim().toLowerCase();
7362
7819
  if (!raw) {
7363
- return !(String(env.TERM_PROGRAM ?? "").trim().toLowerCase() === "vscode" || "VSCODE_PID" in env || "VSCODE_IPC_HOOK_CLI" in env);
7820
+ return !isVscodeTerminal;
7364
7821
  }
7365
7822
  return !(raw === "0" || raw === "false" || raw === "no");
7366
7823
  })();
@@ -7369,11 +7826,15 @@ function createStdoutRenderer(terminal, options) {
7369
7826
  let pendingRender = false;
7370
7827
  let renderTimer = null;
7371
7828
  let writeEmaMs = 0;
7829
+ let lastCursorX = null;
7830
+ let lastCursorY = null;
7372
7831
  let accumulatedAllRows = false;
7373
7832
  let accumulatedDirtyBits = null;
7374
7833
  let accumulatedDirtyCount = 0;
7375
7834
  let accumulatedDirtyMin = Number.POSITIVE_INFINITY;
7376
7835
  let accumulatedDirtyMax = -1;
7836
+ let accumulatedScrollOperations = null;
7837
+ let accumulatedSkipUnchangedDirtyRows = true;
7377
7838
  const MIN_FRAME_MS = !out.isTTY ? 0 : isGhostty ? 24 : 16;
7378
7839
  function clampCellToViewport(cell, size) {
7379
7840
  const maxX = Math.max(0, Math.floor(size.cols) - 1);
@@ -7389,26 +7850,44 @@ function createStdoutRenderer(terminal, options) {
7389
7850
  const opt = (options == null ? void 0 : options.colorMode) ?? "auto";
7390
7851
  if (opt !== "auto")
7391
7852
  return opt;
7392
- const env2 = ((_a3 = import_node_process9.default) == null ? void 0 : _a3.env) ?? {};
7853
+ const env2 = ((_a3 = import_node_process8.default) == null ? void 0 : _a3.env) ?? {};
7393
7854
  return detectTerminalColorCapability({
7394
7855
  env: env2,
7395
7856
  isTTY: Boolean(out.isTTY),
7396
- platform: String(((_b3 = import_node_process9.default) == null ? void 0 : _b3.platform) ?? "")
7857
+ platform: String(((_b3 = import_node_process8.default) == null ? void 0 : _b3.platform) ?? "")
7397
7858
  }).mode;
7398
7859
  }
7399
7860
  const colorMode = resolveColorMode();
7400
7861
  const enableDim = colorMode === "truecolor";
7862
+ const COLOR_INDEX = {
7863
+ black: 1,
7864
+ red: 2,
7865
+ green: 3,
7866
+ yellow: 4,
7867
+ blue: 5,
7868
+ magenta: 6,
7869
+ cyan: 7,
7870
+ white: 8,
7871
+ blackBright: 9,
7872
+ redBright: 10,
7873
+ greenBright: 11,
7874
+ yellowBright: 12,
7875
+ blueBright: 13,
7876
+ magentaBright: 14,
7877
+ cyanBright: 15,
7878
+ whiteBright: 16
7879
+ };
7401
7880
  let styleKeyCache2 = /* @__PURE__ */ new WeakMap();
7402
7881
  function styleKey2(style) {
7403
7882
  const cached = styleKeyCache2.get(style);
7404
- if (cached)
7883
+ if (cached !== void 0)
7405
7884
  return cached;
7406
- const key = `${style.fg ?? ""}|${style.bg ?? defaultBg}|${style.bold ? 1 : 0}|${enableDim && style.dim ? 1 : 0}|${style.italic ? 1 : 0}|${style.underline ? 1 : 0}|${style.inverse ? 1 : 0}|${style.href ?? ""}`;
7885
+ const key = (COLOR_INDEX[style.fg] ?? 0) | (COLOR_INDEX[style.bg ?? defaultBg] ?? 0) << 5 | (style.bold ? 1 << 10 : 0) | (enableDim && style.dim ? 1 << 11 : 0) | (style.italic ? 1 << 12 : 0) | (style.underline ? 1 << 13 : 0) | (style.inverse ? 1 << 14 : 0) | (style.href ? 1 << 15 : 0);
7407
7886
  styleKeyCache2.set(style, key);
7408
7887
  return key;
7409
7888
  }
7410
7889
  function styleKeyFromParts(style) {
7411
- return `${style.fg ?? ""}|${style.bg ?? defaultBg}|${style.bold ? 1 : 0}|${enableDim && style.dim ? 1 : 0}|${style.italic ? 1 : 0}|${style.underline ? 1 : 0}|${style.inverse ? 1 : 0}|${style.href ?? ""}`;
7890
+ return (COLOR_INDEX[style.fg] ?? 0) | (COLOR_INDEX[style.bg ?? defaultBg] ?? 0) << 5 | (style.bold ? 1 << 10 : 0) | (enableDim && style.dim ? 1 << 11 : 0) | (style.italic ? 1 << 12 : 0) | (style.underline ? 1 << 13 : 0) | (style.inverse ? 1 << 14 : 0) | (style.href ? 1 << 15 : 0);
7412
7891
  }
7413
7892
  function openColor(fg) {
7414
7893
  if (!fg)
@@ -7455,7 +7934,47 @@ function createStdoutRenderer(terminal, options) {
7455
7934
  return result;
7456
7935
  }
7457
7936
  let lastRenderedRows = 0;
7458
- let renderedRowCache = [];
7937
+ let fpCols = 0;
7938
+ let fpRows = 0;
7939
+ let currentFP = new Uint32Array(0);
7940
+ let prevFP = new Uint32Array(0);
7941
+ let fpPrevValid = false;
7942
+ let prevOverlayBlockedRows = [];
7943
+ let prevOverlayPartialRows = [];
7944
+ function ensureFingerprints(cols, rows) {
7945
+ if (cols === fpCols && rows === fpRows)
7946
+ return;
7947
+ fpCols = cols;
7948
+ fpRows = rows;
7949
+ const len = cols * rows;
7950
+ currentFP = new Uint32Array(len);
7951
+ prevFP = new Uint32Array(len);
7952
+ fpPrevValid = false;
7953
+ prevOverlayBlockedRows = [];
7954
+ prevOverlayPartialRows = [];
7955
+ }
7956
+ function charHash16(ch) {
7957
+ if (ch.length <= 1)
7958
+ return ch.charCodeAt(0) || 0;
7959
+ let h18 = 33052;
7960
+ for (let i = 0; i < ch.length; i++) {
7961
+ h18 ^= ch.charCodeAt(i);
7962
+ h18 = h18 * 257 & 65535;
7963
+ }
7964
+ return h18;
7965
+ }
7966
+ function fingerprintRow(row, y, cols) {
7967
+ const rowFP = terminal.getRowFingerprints(y);
7968
+ if (rowFP && rowFP.length >= cols) {
7969
+ currentFP.set(rowFP.subarray(0, cols), y * fpCols);
7970
+ return;
7971
+ }
7972
+ const base = y * fpCols;
7973
+ for (let x = 0; x < cols; x++) {
7974
+ const cell = row[x];
7975
+ currentFP[base + x] = styleKey2(cell.style) << 16 | charHash16(cell.ch);
7976
+ }
7977
+ }
7459
7978
  const rowCursorToCol1 = [];
7460
7979
  const rowClearToEol = [];
7461
7980
  const rowTextPartsScratch = [];
@@ -7474,14 +7993,6 @@ function createStdoutRenderer(terminal, options) {
7474
7993
  rowClearToEol[y] = `\x1B[${y + 1};1H\x1B[K`;
7475
7994
  }
7476
7995
  };
7477
- const ensureRenderedRowCache = (rows) => {
7478
- if (renderedRowCache.length > rows) {
7479
- renderedRowCache.length = rows;
7480
- return;
7481
- }
7482
- while (renderedRowCache.length < rows)
7483
- renderedRowCache.push([]);
7484
- };
7485
7996
  const expandSpanStart = (row, startX) => {
7486
7997
  var _a3;
7487
7998
  let x = Math.max(0, Math.min(row.length, startX));
@@ -7501,40 +8012,296 @@ function createStdoutRenderer(terminal, options) {
7501
8012
  }
7502
8013
  return x;
7503
8014
  };
7504
- const resolveChangedSpan = (row, prevRow) => {
7505
- if (!prevRow || prevRow.length !== row.length) {
8015
+ const resolveChangedSpan = (row, y, cols) => {
8016
+ if (!fpCols || y >= fpRows || !fpPrevValid)
7506
8017
  return row.length ? { startX: 0, endXExclusive: row.length } : null;
8018
+ const base = y * fpCols;
8019
+ let startX = -1;
8020
+ let endXExclusive = -1;
8021
+ for (let x = 0; x < cols; x++) {
8022
+ if (currentFP[base + x] === prevFP[base + x])
8023
+ continue;
8024
+ if (startX === -1)
8025
+ startX = x;
8026
+ endXExclusive = x + 1;
7507
8027
  }
8028
+ if (startX === -1)
8029
+ return null;
8030
+ const expandedStart = expandSpanStart(row, startX);
8031
+ const expandedEnd = expandSpanEnd(row, endXExclusive);
8032
+ return expandedEnd > expandedStart ? { startX: expandedStart, endXExclusive: expandedEnd } : null;
8033
+ };
8034
+ const resolveChangedSpanAgainstFill = (row, y, cols, fillFingerprint) => {
8035
+ if (!fpCols || y >= fpRows)
8036
+ return row.length ? { startX: 0, endXExclusive: row.length } : null;
8037
+ const base = y * fpCols;
7508
8038
  let startX = -1;
7509
8039
  let endXExclusive = -1;
7510
- for (let x = 0; x < row.length; x++) {
7511
- if (row[x] === prevRow[x])
8040
+ for (let x = 0; x < cols; x++) {
8041
+ if (currentFP[base + x] === fillFingerprint)
7512
8042
  continue;
7513
8043
  if (startX === -1)
7514
8044
  startX = x;
7515
8045
  endXExclusive = x + 1;
7516
8046
  }
7517
- if (startX === -1 || endXExclusive === -1)
8047
+ if (startX === -1)
7518
8048
  return null;
7519
- const expandedStart = Math.min(
7520
- expandSpanStart(row, startX),
7521
- expandSpanStart(prevRow, startX)
7522
- );
7523
- const expandedEnd = Math.max(
7524
- expandSpanEnd(row, endXExclusive),
7525
- expandSpanEnd(prevRow, endXExclusive)
7526
- );
8049
+ const expandedStart = expandSpanStart(row, startX);
8050
+ const expandedEnd = expandSpanEnd(row, endXExclusive);
7527
8051
  return expandedEnd > expandedStart ? { startX: expandedStart, endXExclusive: expandedEnd } : null;
7528
8052
  };
7529
- function overlayPlaneHasVisibleContent() {
7530
- const lines = overlayTerminal.snapshot().lines;
7531
- for (const line of lines) {
7532
- for (let i = 0; i < line.length; i++) {
7533
- if (line.charCodeAt(i) !== 32)
7534
- return true;
7535
- }
8053
+ const resolveChangedSpanAgainstReference = (row, y, cols) => {
8054
+ if (!fpCols || y >= fpRows)
8055
+ return row.length ? { startX: 0, endXExclusive: row.length } : null;
8056
+ const rowFP = terminal.getRowFingerprints(y);
8057
+ const base = y * fpCols;
8058
+ let startX = -1;
8059
+ let endXExclusive = -1;
8060
+ for (let x = 0; x < cols; x++) {
8061
+ const fingerprint = rowFP && rowFP.length >= cols ? rowFP[x] : styleKey2(row[x].style) << 16 | charHash16(row[x].ch);
8062
+ if (fingerprint === currentFP[base + x])
8063
+ continue;
8064
+ if (startX === -1)
8065
+ startX = x;
8066
+ endXExclusive = x + 1;
8067
+ }
8068
+ if (startX === -1)
8069
+ return null;
8070
+ const expandedStart = expandSpanStart(row, startX);
8071
+ const expandedEnd = expandSpanEnd(row, endXExclusive);
8072
+ return expandedEnd > expandedStart ? { startX: expandedStart, endXExclusive: expandedEnd } : null;
8073
+ };
8074
+ const rowMatchesPreviousFrame = (y, cols) => {
8075
+ if (!fpCols || y < 0 || y >= fpRows || !fpPrevValid)
8076
+ return false;
8077
+ const rowFP = terminal.getRowFingerprints(y);
8078
+ const row = rowFP && rowFP.length >= cols ? null : terminal.getRow(y);
8079
+ const base = y * fpCols;
8080
+ for (let x = 0; x < cols; x++) {
8081
+ const fingerprint = rowFP && rowFP.length >= cols ? rowFP[x] : styleKey2(row[x].style) << 16 | charHash16(row[x].ch);
8082
+ if (fingerprint !== prevFP[base + x])
8083
+ return false;
8084
+ }
8085
+ return true;
8086
+ };
8087
+ const rowHasNonDefaultBlankCell = (source, y, cols) => {
8088
+ if (!fpCols || y < 0 || y >= fpRows)
8089
+ return false;
8090
+ const blankFP = styleKeyFromParts({ bg: defaultBg }) << 16 | charHash16(" ");
8091
+ const base = y * fpCols;
8092
+ const limit = Math.min(cols, fpCols);
8093
+ for (let x = 0; x < limit; x++) {
8094
+ if (source[base + x] !== blankFP)
8095
+ return true;
7536
8096
  }
7537
8097
  return false;
8098
+ };
8099
+ function overlayPlaneCoverageRows(totalRows) {
8100
+ const blockedRows = [];
8101
+ const partialRows = [];
8102
+ for (let y = 0; y < totalRows; y++) {
8103
+ const kind = getPlaneRowCoverageKind(terminal, "overlay", y);
8104
+ if (!kind)
8105
+ continue;
8106
+ blockedRows.push(y);
8107
+ if (kind === 1)
8108
+ partialRows.push(y);
8109
+ }
8110
+ return { blockedRows, partialRows };
8111
+ }
8112
+ const enableScrollRegions = (() => {
8113
+ const raw = String(env.DIMCODE_TUI_SCROLL_REGIONS ?? "").trim();
8114
+ if (raw === "0" || raw === "false")
8115
+ return false;
8116
+ if (isGhostty || isVscodeTerminal)
8117
+ return false;
8118
+ return out.isTTY !== false;
8119
+ })();
8120
+ function prepareExplicitScrollOperations(operations, blockedRows) {
8121
+ if (!operations.length || !(blockedRows == null ? void 0 : blockedRows.size)) {
8122
+ return {
8123
+ operations,
8124
+ hiddenRows: /* @__PURE__ */ new Set(),
8125
+ trimmed: false,
8126
+ blockedInterior: false
8127
+ };
8128
+ }
8129
+ const hiddenRows = /* @__PURE__ */ new Set();
8130
+ const prepared = [];
8131
+ let trimmed = false;
8132
+ for (const op of operations) {
8133
+ let startY = op.startY;
8134
+ let endY = op.endY;
8135
+ while (startY < endY && blockedRows.has(startY)) {
8136
+ hiddenRows.add(startY);
8137
+ startY++;
8138
+ trimmed = true;
8139
+ }
8140
+ while (endY > startY && blockedRows.has(endY - 1)) {
8141
+ hiddenRows.add(endY - 1);
8142
+ endY--;
8143
+ trimmed = true;
8144
+ }
8145
+ for (let y = startY; y < endY; y++) {
8146
+ if (!blockedRows.has(y))
8147
+ continue;
8148
+ return {
8149
+ operations: null,
8150
+ hiddenRows,
8151
+ trimmed,
8152
+ blockedInterior: true
8153
+ };
8154
+ }
8155
+ if (endY <= startY || Math.abs(op.delta) >= endY - startY) {
8156
+ return {
8157
+ operations: null,
8158
+ hiddenRows,
8159
+ trimmed,
8160
+ blockedInterior: false
8161
+ };
8162
+ }
8163
+ prepared.push(
8164
+ startY === op.startY && endY === op.endY ? op : { startY, endY, delta: op.delta }
8165
+ );
8166
+ }
8167
+ return {
8168
+ operations: prepared,
8169
+ hiddenRows,
8170
+ trimmed,
8171
+ blockedInterior: false
8172
+ };
8173
+ }
8174
+ function largestDirtyBand(rows, blockedRows) {
8175
+ if (rows.length === 0)
8176
+ return null;
8177
+ const blocked = (blockedRows == null ? void 0 : blockedRows.length) ? new Set(blockedRows) : null;
8178
+ let bestStart = -1;
8179
+ let bestEnd = -1;
8180
+ let bandStart = -1;
8181
+ let prev = -1;
8182
+ for (let i = 0; i < rows.length; i++) {
8183
+ const y = rows[i];
8184
+ if (blocked == null ? void 0 : blocked.has(y)) {
8185
+ if (bandStart !== -1 && prev + 1 - bandStart > bestEnd - bestStart) {
8186
+ bestStart = bandStart;
8187
+ bestEnd = prev + 1;
8188
+ }
8189
+ bandStart = -1;
8190
+ prev = -1;
8191
+ continue;
8192
+ }
8193
+ if (bandStart === -1) {
8194
+ bandStart = y;
8195
+ prev = y;
8196
+ if (bestStart === -1) {
8197
+ bestStart = y;
8198
+ bestEnd = y + 1;
8199
+ }
8200
+ continue;
8201
+ }
8202
+ if (y === prev + 1) {
8203
+ prev = y;
8204
+ continue;
8205
+ }
8206
+ if (prev + 1 - bandStart > bestEnd - bestStart) {
8207
+ bestStart = bandStart;
8208
+ bestEnd = prev + 1;
8209
+ }
8210
+ bandStart = y;
8211
+ prev = y;
8212
+ }
8213
+ if (bandStart !== -1 && prev + 1 - bandStart > bestEnd - bestStart) {
8214
+ bestStart = bandStart;
8215
+ bestEnd = prev + 1;
8216
+ }
8217
+ if (bestStart === -1)
8218
+ return null;
8219
+ const outsideRows = [];
8220
+ for (const y of rows) {
8221
+ if ((blocked == null ? void 0 : blocked.has(y)) || y < bestStart || y >= bestEnd) {
8222
+ outsideRows.push(y);
8223
+ }
8224
+ }
8225
+ return { start: bestStart, end: bestEnd, outsideRows };
8226
+ }
8227
+ function detectScrollShift(cols, dirtyRows, blockedRows) {
8228
+ if (!fpPrevValid || !fpCols || cols !== fpCols)
8229
+ return null;
8230
+ const band = largestDirtyBand(dirtyRows, blockedRows);
8231
+ if (!band || band.end - band.start < 3)
8232
+ return null;
8233
+ const maxDelta = Math.min(
8234
+ 12,
8235
+ Math.max(5, band.end - band.start - 1)
8236
+ );
8237
+ for (let delta = -maxDelta; delta <= maxDelta; delta++) {
8238
+ if (delta === 0)
8239
+ continue;
8240
+ let matches = 0;
8241
+ let checked = 0;
8242
+ let mismatches = 0;
8243
+ for (let y = band.start; y < band.end; y++) {
8244
+ const srcY = y + delta;
8245
+ if (srcY < band.start || srcY >= band.end)
8246
+ continue;
8247
+ const informative = rowHasNonDefaultBlankCell(currentFP, y, cols) || rowHasNonDefaultBlankCell(prevFP, srcY, cols);
8248
+ if (!informative)
8249
+ continue;
8250
+ checked++;
8251
+ const curBase = y * fpCols;
8252
+ const prevBase = srcY * fpCols;
8253
+ let rowMatch = true;
8254
+ for (let x = 0; x < cols; x++) {
8255
+ if (currentFP[curBase + x] !== prevFP[prevBase + x]) {
8256
+ rowMatch = false;
8257
+ break;
8258
+ }
8259
+ }
8260
+ if (rowMatch) {
8261
+ matches++;
8262
+ } else {
8263
+ mismatches++;
8264
+ }
8265
+ }
8266
+ if (checked > 0 && matches >= 3 && mismatches <= Math.ceil(checked * 0.2)) {
8267
+ const absDelta = Math.abs(delta);
8268
+ let newRowStart;
8269
+ let newRowEnd;
8270
+ if (delta > 0) {
8271
+ newRowStart = Math.max(band.start, band.end - absDelta);
8272
+ newRowEnd = band.end;
8273
+ } else {
8274
+ newRowStart = band.start;
8275
+ newRowEnd = Math.min(band.end, band.start + absDelta);
8276
+ }
8277
+ const extraDirty = [...band.outsideRows];
8278
+ for (let y = band.start; y < band.end; y++) {
8279
+ const srcY = y + delta;
8280
+ if (srcY < band.start || srcY >= band.end)
8281
+ continue;
8282
+ const curBase = y * fpCols;
8283
+ const prevBase = srcY * fpCols;
8284
+ let rowMatch = true;
8285
+ for (let x = 0; x < cols; x++) {
8286
+ if (currentFP[curBase + x] !== prevFP[prevBase + x]) {
8287
+ rowMatch = false;
8288
+ break;
8289
+ }
8290
+ }
8291
+ if (!rowMatch)
8292
+ extraDirty.push(y);
8293
+ }
8294
+ return {
8295
+ regionStart: band.start,
8296
+ regionEnd: band.end,
8297
+ delta,
8298
+ newRowStart,
8299
+ newRowEnd,
8300
+ extraDirtyRows: extraDirty
8301
+ };
8302
+ }
8303
+ }
8304
+ return null;
7538
8305
  }
7539
8306
  function writeChunked(data) {
7540
8307
  if (!isGhostty) {
@@ -7569,7 +8336,7 @@ function createStdoutRenderer(terminal, options) {
7569
8336
  getDebugLog().error(`writeChunked: write error`, e);
7570
8337
  }
7571
8338
  }
7572
- function doRender(dirtyRows) {
8339
+ function doRender(dirtyRows, scrollOperations, skipUnchangedDirtyRows = false) {
7573
8340
  if (isDebugEnabled()) {
7574
8341
  getDebugLog().render(
7575
8342
  `doRender() START: dirtyRows=${(dirtyRows == null ? void 0 : dirtyRows.length) ?? "null"}`
@@ -7583,15 +8350,56 @@ function createStdoutRenderer(terminal, options) {
7583
8350
  accumulatedDirtyCount = 0;
7584
8351
  accumulatedDirtyMin = Number.POSITIVE_INFINITY;
7585
8352
  accumulatedDirtyMax = -1;
8353
+ accumulatedScrollOperations = null;
8354
+ accumulatedSkipUnchangedDirtyRows = true;
7586
8355
  lastFrameTime = Date.now();
7587
8356
  const renderStart = performance.now();
7588
8357
  const size = terminal.size();
7589
8358
  ensureRowEscapes(Math.max(size.rows, lastRenderedRows));
7590
- ensureRenderedRowCache(size.rows);
8359
+ ensureFingerprints(size.cols, size.rows);
7591
8360
  const bgSeq = openBg(defaultBg);
7592
8361
  const bgOnlyStyle = { bg: defaultBg };
7593
8362
  const bgKey = styleKeyFromParts({ bg: defaultBg });
8363
+ const blankFP = bgKey << 16 | charHash16(" ");
8364
+ const cellFingerprint = (cell) => styleKey2(cell.style) << 16 | charHash16(cell.ch);
8365
+ const lastOccupiedColumnInRow = (row, cols) => {
8366
+ const limit = Math.min(row.length, cols);
8367
+ for (let x = limit - 1; x >= 0; x--) {
8368
+ if (cellFingerprint(row[x]) !== blankFP)
8369
+ return x;
8370
+ }
8371
+ return -1;
8372
+ };
8373
+ const lastOccupiedColumnInReference = (source, y, cols) => {
8374
+ if (!fpCols || y < 0 || y >= fpRows)
8375
+ return -1;
8376
+ const limit = Math.min(fpCols, cols);
8377
+ const base = y * fpCols;
8378
+ for (let x = limit - 1; x >= 0; x--) {
8379
+ if (source[base + x] !== blankFP)
8380
+ return x;
8381
+ }
8382
+ return -1;
8383
+ };
8384
+ const shouldRewriteRowTail = (row, y, cols, reference) => lastOccupiedColumnInRow(row, cols) < lastOccupiedColumnInReference(reference, y, cols);
7594
8385
  let dirtySorted = true;
8386
+ const normalizedScrollOperations = (() => {
8387
+ if (!(scrollOperations == null ? void 0 : scrollOperations.length))
8388
+ return null;
8389
+ const outOps = [];
8390
+ for (const op of scrollOperations) {
8391
+ const startY = Math.max(0, Math.min(size.rows, Math.floor(op.startY)));
8392
+ const endY = Math.max(0, Math.min(size.rows, Math.floor(op.endY)));
8393
+ const delta = Math.trunc(op.delta);
8394
+ if (endY <= startY || delta === 0 || Math.abs(delta) >= endY - startY)
8395
+ continue;
8396
+ outOps.push({ startY, endY, delta });
8397
+ }
8398
+ if (!outOps.length)
8399
+ return null;
8400
+ outOps.sort((a, b) => a.startY - b.startY);
8401
+ return outOps;
8402
+ })();
7595
8403
  let rowsToRender = (() => {
7596
8404
  if (!dirtyRows || dirtyRows.length === 0)
7597
8405
  return null;
@@ -7617,13 +8425,13 @@ function createStdoutRenderer(terminal, options) {
7617
8425
  outRows.sort((a, b) => a - b);
7618
8426
  return outRows;
7619
8427
  })();
7620
- if (rowsToRender && rowsToRender.length >= size.rows * dirtyFullThreshold) {
7621
- rowsToRender = null;
7622
- }
7623
8428
  const frameParts = [];
7624
8429
  frameParts.push(!isGhostty && useSyncOutput ? SYNC_START : "");
7625
8430
  frameParts.push("\x1B[?7l");
7626
8431
  frameParts.push(SGR_RESET, bgSeq);
8432
+ let lastRenderedY = -1;
8433
+ let lastRenderWasFullRow = false;
8434
+ let hasFrameOutput = false;
7627
8435
  let activeStyleKey = null;
7628
8436
  let activeStyle = {
7629
8437
  fg: null,
@@ -7688,13 +8496,17 @@ function createStdoutRenderer(terminal, options) {
7688
8496
  Math.min(row.length, Math.floor(endXExclusive))
7689
8497
  );
7690
8498
  if (spanStart >= spanEnd && !clearToEol) {
7691
- renderedRowCache[y] = row.slice();
7692
8499
  return;
7693
8500
  }
8501
+ hasFrameOutput = true;
7694
8502
  if (!disableCursorPos) {
7695
- frameParts.push(
7696
- spanStart === 0 ? rowCursorToCol1[y] : `\x1B[${y + 1};${spanStart + 1}H`
7697
- );
8503
+ if (spanStart === 0 && lastRenderWasFullRow && y === lastRenderedY + 1) {
8504
+ frameParts.push("\r\n");
8505
+ } else {
8506
+ frameParts.push(
8507
+ spanStart === 0 ? rowCursorToCol1[y] : `\x1B[${y + 1};${spanStart + 1}H`
8508
+ );
8509
+ }
7698
8510
  }
7699
8511
  let currentKey = null;
7700
8512
  let currentStyle = null;
@@ -7718,7 +8530,7 @@ function createStdoutRenderer(terminal, options) {
7718
8530
  }
7719
8531
  continue;
7720
8532
  }
7721
- if (key === currentKey) {
8533
+ if (key === currentKey && !(key & 32768 && nextStyle.href !== (currentStyle == null ? void 0 : currentStyle.href))) {
7722
8534
  currentTextParts.push(ch);
7723
8535
  if (needsWideCursorFix(cell, ch)) {
7724
8536
  currentTextParts.push(
@@ -7750,39 +8562,334 @@ function createStdoutRenderer(terminal, options) {
7750
8562
  }
7751
8563
  frameParts.push("\x1B[K");
7752
8564
  }
7753
- renderedRowCache[y] = row.slice();
8565
+ lastRenderedY = y;
8566
+ lastRenderWasFullRow = spanStart === 0 && clearToEol;
7754
8567
  };
7755
- if (!rowsToRender) {
8568
+ const overlayCoverage = overlayPlaneCoverageRows(size.rows);
8569
+ const overlayRows = Array.from(
8570
+ /* @__PURE__ */ new Set([
8571
+ ...overlayCoverage.blockedRows,
8572
+ ...prevOverlayBlockedRows
8573
+ ])
8574
+ ).sort((a, b) => a - b);
8575
+ const overlayRowSet = overlayRows.length ? new Set(overlayRows) : null;
8576
+ const overlayPartialRows = Array.from(
8577
+ /* @__PURE__ */ new Set([
8578
+ ...overlayCoverage.partialRows,
8579
+ ...prevOverlayPartialRows
8580
+ ])
8581
+ ).sort((a, b) => a - b);
8582
+ const overlayPartialRowSet = overlayPartialRows.length ? new Set(overlayPartialRows) : null;
8583
+ let explicitScrollOperations = normalizedScrollOperations;
8584
+ let hiddenExplicitDirtyRows = null;
8585
+ let allowInferredScrollRegions = true;
8586
+ if (explicitScrollOperations && overlayRows.length) {
8587
+ const preparedExplicit = prepareExplicitScrollOperations(
8588
+ explicitScrollOperations,
8589
+ overlayRowSet
8590
+ );
8591
+ hiddenExplicitDirtyRows = preparedExplicit.hiddenRows.size ? preparedExplicit.hiddenRows : null;
8592
+ if (preparedExplicit.operations) {
8593
+ explicitScrollOperations = preparedExplicit.operations;
8594
+ if (preparedExplicit.trimmed && isDebugEnabled()) {
8595
+ getDebugLog().render(
8596
+ ` 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(", ")}`
8597
+ );
8598
+ }
8599
+ } else {
8600
+ allowInferredScrollRegions = false;
8601
+ const expandedRows = new Set(rowsToRender ?? []);
8602
+ for (const op of explicitScrollOperations) {
8603
+ for (let y = op.startY; y < op.endY; y++)
8604
+ expandedRows.add(y);
8605
+ }
8606
+ rowsToRender = Array.from(expandedRows).sort((a, b) => a - b);
8607
+ dirtySorted = true;
8608
+ explicitScrollOperations = null;
8609
+ hiddenExplicitDirtyRows = null;
8610
+ if (isDebugEnabled()) {
8611
+ getDebugLog().render(
8612
+ preparedExplicit.blockedInterior ? " Explicit scroll ops split by interior overlay rows; falling back to region repaint" : " Explicit scroll ops could not be clipped safely; falling back to region repaint"
8613
+ );
8614
+ }
8615
+ }
8616
+ }
8617
+ const scrollRowsCandidate = rowsToRender;
8618
+ if (explicitScrollOperations && rowsToRender && (!enableScrollRegions || !fpPrevValid)) {
8619
+ const expandedRows = new Set(rowsToRender);
8620
+ for (const op of explicitScrollOperations) {
8621
+ for (let y = op.startY; y < op.endY; y++) {
8622
+ if (hiddenExplicitDirtyRows == null ? void 0 : hiddenExplicitDirtyRows.has(y))
8623
+ continue;
8624
+ expandedRows.add(y);
8625
+ }
8626
+ }
8627
+ rowsToRender = Array.from(expandedRows).sort((a, b) => a - b);
8628
+ dirtySorted = true;
8629
+ explicitScrollOperations = null;
8630
+ hiddenExplicitDirtyRows = null;
8631
+ if (isDebugEnabled()) {
8632
+ getDebugLog().render(
8633
+ 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"
8634
+ );
8635
+ }
8636
+ }
8637
+ const denseDirtyRows = Boolean(
8638
+ rowsToRender && rowsToRender.length >= size.rows * dirtyFullThreshold
8639
+ );
8640
+ if (denseDirtyRows) {
8641
+ rowsToRender = null;
8642
+ }
8643
+ let profiledRowCount = rowsToRender ? rowsToRender.length : size.rows;
8644
+ const useScrollRegions = enableScrollRegions && fpPrevValid && scrollRowsCandidate && scrollRowsCandidate.length >= 3 && allowInferredScrollRegions;
8645
+ let scrollHandled = false;
8646
+ if (enableScrollRegions && explicitScrollOperations && scrollRowsCandidate && fpPrevValid) {
8647
+ if (currentFP.length === prevFP.length)
8648
+ currentFP.set(prevFP);
8649
+ const insertedRows = /* @__PURE__ */ new Set();
8650
+ const explicitRowsToRender = /* @__PURE__ */ new Set();
8651
+ for (const y of scrollRowsCandidate) {
8652
+ if (hiddenExplicitDirtyRows == null ? void 0 : hiddenExplicitDirtyRows.has(y))
8653
+ continue;
8654
+ explicitRowsToRender.add(y);
8655
+ }
8656
+ if (isDebugEnabled()) {
8657
+ getDebugLog().render(
8658
+ ` Explicit scroll ops: ${explicitScrollOperations.map((op) => `${op.startY}-${op.endY - 1}:${op.delta}`).join(", ")}`
8659
+ );
8660
+ }
8661
+ for (const op of explicitScrollOperations) {
8662
+ hasFrameOutput = true;
8663
+ frameParts.push(`\x1B[${op.startY + 1};${op.endY}r`);
8664
+ if (op.delta > 0) {
8665
+ frameParts.push(`\x1B[${op.endY};1H`);
8666
+ frameParts.push(`\x1B[${op.delta}S`);
8667
+ for (let y = op.startY; y < op.endY - op.delta; y++) {
8668
+ const dstBase = y * fpCols;
8669
+ const srcBase = (y + op.delta) * fpCols;
8670
+ for (let x = 0; x < size.cols; x++)
8671
+ currentFP[dstBase + x] = prevFP[srcBase + x];
8672
+ }
8673
+ for (let y = op.endY - op.delta; y < op.endY; y++) {
8674
+ const base = y * fpCols;
8675
+ insertedRows.add(y);
8676
+ explicitRowsToRender.add(y);
8677
+ for (let x = 0; x < size.cols; x++)
8678
+ currentFP[base + x] = blankFP;
8679
+ }
8680
+ } else {
8681
+ const absDelta = -op.delta;
8682
+ frameParts.push(`\x1B[${op.startY + 1};1H`);
8683
+ frameParts.push(`\x1B[${absDelta}T`);
8684
+ for (let y = op.endY - 1; y >= op.startY + absDelta; y--) {
8685
+ const dstBase = y * fpCols;
8686
+ const srcBase = (y - absDelta) * fpCols;
8687
+ for (let x = 0; x < size.cols; x++)
8688
+ currentFP[dstBase + x] = prevFP[srcBase + x];
8689
+ }
8690
+ for (let y = op.startY; y < op.startY + absDelta; y++) {
8691
+ const base = y * fpCols;
8692
+ insertedRows.add(y);
8693
+ explicitRowsToRender.add(y);
8694
+ for (let x = 0; x < size.cols; x++)
8695
+ currentFP[base + x] = blankFP;
8696
+ }
8697
+ }
8698
+ frameParts.push("\x1B[r");
8699
+ }
8700
+ const explicitDirtyRows = Array.from(explicitRowsToRender).sort(
8701
+ (a, b) => a - b
8702
+ );
8703
+ const overlayDirtyRows = overlayRowSet ? explicitDirtyRows.filter((y) => overlayRowSet.has(y)) : [];
8704
+ const paintedExplicitRows = [];
8705
+ for (const y of explicitDirtyRows) {
8706
+ const row = terminal.getRow(y);
8707
+ const overlayRow = overlayRowSet == null ? void 0 : overlayRowSet.has(y);
8708
+ if (overlayRow) {
8709
+ fingerprintRow(row, y, size.cols);
8710
+ renderRow2(y, row);
8711
+ paintedExplicitRows.push(y);
8712
+ continue;
8713
+ }
8714
+ const span = (() => {
8715
+ if (insertedRows.has(y)) {
8716
+ fingerprintRow(row, y, size.cols);
8717
+ return resolveChangedSpanAgainstFill(row, y, size.cols, blankFP);
8718
+ }
8719
+ return resolveChangedSpanAgainstReference(row, y, size.cols);
8720
+ })();
8721
+ if (!span) {
8722
+ if (!skipUnchangedDirtyRows)
8723
+ fingerprintRow(row, y, size.cols);
8724
+ if (!skipUnchangedDirtyRows) {
8725
+ renderRow2(y, row);
8726
+ paintedExplicitRows.push(y);
8727
+ }
8728
+ continue;
8729
+ }
8730
+ const rewriteTail = !insertedRows.has(y) && shouldRewriteRowTail(row, y, size.cols, currentFP);
8731
+ fingerprintRow(row, y, size.cols);
8732
+ renderRow2(
8733
+ y,
8734
+ row,
8735
+ span.startX,
8736
+ rewriteTail ? row.length : span.endXExclusive,
8737
+ rewriteTail || span.endXExclusive >= row.length
8738
+ );
8739
+ paintedExplicitRows.push(y);
8740
+ }
8741
+ if (isDebugEnabled() && overlayDirtyRows.length) {
8742
+ getDebugLog().render(
8743
+ ` Explicit scroll ops overlay rows: [${overlayDirtyRows.join(", ")}]`
8744
+ );
8745
+ }
8746
+ profiledRowCount = explicitDirtyRows.length;
8747
+ scrollHandled = true;
8748
+ } else if (useScrollRegions && scrollRowsCandidate) {
8749
+ for (let y = 0; y < size.rows; y++) {
8750
+ fingerprintRow(terminal.getRow(y), y, size.cols);
8751
+ }
8752
+ const shift = detectScrollShift(
8753
+ size.cols,
8754
+ scrollRowsCandidate,
8755
+ overlayRows
8756
+ );
8757
+ if (shift) {
8758
+ if (currentFP.length === prevFP.length)
8759
+ currentFP.set(prevFP);
8760
+ hasFrameOutput = true;
8761
+ if (isDebugEnabled()) {
8762
+ getDebugLog().render(
8763
+ ` Scroll region: delta=${shift.delta}, region=${shift.regionStart}-${shift.regionEnd - 1}, new rows=${shift.newRowStart}-${shift.newRowEnd - 1}`
8764
+ );
8765
+ }
8766
+ frameParts.push(`\x1B[${shift.regionStart + 1};${shift.regionEnd}r`);
8767
+ if (shift.delta > 0) {
8768
+ frameParts.push(`\x1B[${shift.regionEnd};1H`);
8769
+ frameParts.push(`\x1B[${shift.delta}S`);
8770
+ } else {
8771
+ frameParts.push(`\x1B[${shift.regionStart + 1};1H`);
8772
+ frameParts.push(`\x1B[${-shift.delta}T`);
8773
+ }
8774
+ frameParts.push("\x1B[r");
8775
+ if (shift.delta > 0) {
8776
+ for (let y = shift.regionStart; y < shift.regionEnd - shift.delta; y++) {
8777
+ const dstBase = y * fpCols;
8778
+ const srcBase = (y + shift.delta) * fpCols;
8779
+ for (let x = 0; x < size.cols; x++)
8780
+ currentFP[dstBase + x] = prevFP[srcBase + x];
8781
+ }
8782
+ for (let y = shift.regionEnd - shift.delta; y < shift.regionEnd; y++) {
8783
+ const base = y * fpCols;
8784
+ for (let x = 0; x < size.cols; x++)
8785
+ currentFP[base + x] = blankFP;
8786
+ }
8787
+ } else {
8788
+ const absDelta = -shift.delta;
8789
+ for (let y = shift.regionEnd - 1; y >= shift.regionStart + absDelta; y--) {
8790
+ const dstBase = y * fpCols;
8791
+ const srcBase = (y - absDelta) * fpCols;
8792
+ for (let x = 0; x < size.cols; x++)
8793
+ currentFP[dstBase + x] = prevFP[srcBase + x];
8794
+ }
8795
+ for (let y = shift.regionStart; y < shift.regionStart + absDelta; y++) {
8796
+ const base = y * fpCols;
8797
+ for (let x = 0; x < size.cols; x++)
8798
+ currentFP[base + x] = blankFP;
8799
+ }
8800
+ }
8801
+ for (let y = shift.newRowStart; y < shift.newRowEnd; y++) {
8802
+ const row = terminal.getRow(y);
8803
+ fingerprintRow(row, y, size.cols);
8804
+ renderRow2(y, row);
8805
+ }
8806
+ const extraDirtyRows = overlayRowSet ? shift.extraDirtyRows.filter(
8807
+ (y) => !overlayRowSet.has(y) || Boolean(overlayPartialRowSet == null ? void 0 : overlayPartialRowSet.has(y))
8808
+ ) : shift.extraDirtyRows;
8809
+ for (const y of extraDirtyRows) {
8810
+ const row = terminal.getRow(y);
8811
+ const overlayRow = overlayRowSet == null ? void 0 : overlayRowSet.has(y);
8812
+ if (overlayRow) {
8813
+ renderRow2(y, row);
8814
+ fingerprintRow(row, y, size.cols);
8815
+ continue;
8816
+ }
8817
+ const span = resolveChangedSpanAgainstReference(row, y, size.cols);
8818
+ if (!span) {
8819
+ if (!skipUnchangedDirtyRows) {
8820
+ fingerprintRow(row, y, size.cols);
8821
+ renderRow2(y, row);
8822
+ }
8823
+ continue;
8824
+ }
8825
+ const rewriteTail = shouldRewriteRowTail(row, y, size.cols, currentFP);
8826
+ renderRow2(
8827
+ y,
8828
+ row,
8829
+ span.startX,
8830
+ rewriteTail ? row.length : span.endXExclusive,
8831
+ rewriteTail || span.endXExclusive >= row.length
8832
+ );
8833
+ fingerprintRow(row, y, size.cols);
8834
+ }
8835
+ for (let y = shift.newRowStart; y < shift.newRowEnd; y++) {
8836
+ fingerprintRow(terminal.getRow(y), y, size.cols);
8837
+ }
8838
+ const renderedRows = /* @__PURE__ */ new Set();
8839
+ for (let y = shift.newRowStart; y < shift.newRowEnd; y++)
8840
+ renderedRows.add(y);
8841
+ for (const y of extraDirtyRows)
8842
+ renderedRows.add(y);
8843
+ profiledRowCount = renderedRows.size;
8844
+ scrollHandled = true;
8845
+ }
8846
+ }
8847
+ if (!scrollHandled && denseDirtyRows && scrollRowsCandidate && useScrollRegions) {
8848
+ rowsToRender = scrollRowsCandidate;
8849
+ profiledRowCount = rowsToRender.length;
8850
+ if (isDebugEnabled()) {
8851
+ getDebugLog().render(
8852
+ ` Dense dirty rows fell back to partial render: ${rowsToRender.length} rows`
8853
+ );
8854
+ }
8855
+ }
8856
+ if (scrollHandled) {
8857
+ } else if (!rowsToRender) {
7756
8858
  if (isDebugEnabled())
7757
8859
  getDebugLog().render(` Full render: ${size.rows} rows`);
7758
8860
  for (let y = 0; y < size.rows; y++) {
7759
8861
  const row = terminal.getRow(y);
8862
+ fingerprintRow(row, y, size.cols);
7760
8863
  renderRow2(y, row);
7761
8864
  }
7762
8865
  } else {
7763
- const repaintFullDirtyRows = overlayPlaneHasVisibleContent();
8866
+ const overlayDirtyRows = overlayRowSet ? rowsToRender.filter((y) => overlayRowSet.has(y)) : [];
7764
8867
  if (isDebugEnabled()) {
7765
8868
  getDebugLog().render(
7766
- ` Partial render: ${rowsToRender.length} dirty rows: [${rowsToRender.join(", ")}]${repaintFullDirtyRows ? " (overlay-full-row)" : ""}`
8869
+ ` Partial render: ${rowsToRender.length} dirty rows: [${rowsToRender.join(", ")}]${overlayDirtyRows.length ? ` (overlay-full-row: [${overlayDirtyRows.join(", ")}])` : ""}`
7767
8870
  );
7768
8871
  }
7769
8872
  for (const y of rowsToRender) {
7770
8873
  const row = terminal.getRow(y);
7771
- if (repaintFullDirtyRows) {
8874
+ fingerprintRow(row, y, size.cols);
8875
+ const overlayRow = overlayRowSet == null ? void 0 : overlayRowSet.has(y);
8876
+ if (overlayRow) {
7772
8877
  renderRow2(y, row);
7773
8878
  continue;
7774
8879
  }
7775
- const span = resolveChangedSpan(row, renderedRowCache[y]);
8880
+ const span = resolveChangedSpan(row, y, size.cols);
7776
8881
  if (!span) {
7777
- renderRow2(y, row);
8882
+ if (!skipUnchangedDirtyRows)
8883
+ renderRow2(y, row);
7778
8884
  continue;
7779
8885
  }
8886
+ const rewriteTail = shouldRewriteRowTail(row, y, size.cols, prevFP);
7780
8887
  renderRow2(
7781
8888
  y,
7782
8889
  row,
7783
8890
  span.startX,
7784
- span.endXExclusive,
7785
- span.endXExclusive >= row.length
8891
+ rewriteTail ? row.length : span.endXExclusive,
8892
+ rewriteTail || span.endXExclusive >= row.length
7786
8893
  );
7787
8894
  }
7788
8895
  }
@@ -7793,20 +8900,35 @@ function createStdoutRenderer(terminal, options) {
7793
8900
  activeStyleKey = bgKey;
7794
8901
  }
7795
8902
  for (let i = 0; i < extraRows; i++) {
8903
+ hasFrameOutput = true;
7796
8904
  frameParts.push(rowClearToEol[size.rows + i]);
7797
8905
  }
7798
8906
  }
7799
8907
  if (!rowsToRender)
7800
8908
  lastRenderedRows = size.rows;
7801
- if (!rowsToRender || renderedRowCache.length !== size.rows)
7802
- renderedRowCache.length = size.rows;
8909
+ const tmpFP = prevFP;
8910
+ prevFP = currentFP;
8911
+ currentFP = tmpFP;
8912
+ fpPrevValid = true;
8913
+ prevOverlayBlockedRows = overlayCoverage.blockedRows;
8914
+ prevOverlayPartialRows = overlayCoverage.partialRows;
8915
+ let emittedCursorPos = null;
7803
8916
  if (getImeAnchor) {
7804
8917
  const anchor = getImeAnchor();
7805
8918
  if (anchor) {
7806
8919
  const { x, y } = clampCellToViewport(anchor, size);
7807
- frameParts.push(`\x1B[${y + 1};${x + 1}H`);
8920
+ if (hasFrameOutput || x !== lastCursorX || y !== lastCursorY) {
8921
+ hasFrameOutput = true;
8922
+ emittedCursorPos = { x, y };
8923
+ frameParts.push(`\x1B[${y + 1};${x + 1}H`);
8924
+ }
7808
8925
  }
7809
8926
  }
8927
+ if (!hasFrameOutput) {
8928
+ if (isDebugEnabled())
8929
+ getDebugLog().render(" No-op frame skipped");
8930
+ return;
8931
+ }
7810
8932
  if (enableOsc8Links && activeStyle.href)
7811
8933
  frameParts.push(OSC8_CLOSE);
7812
8934
  frameParts.push(SGR_RESET);
@@ -7816,9 +8938,9 @@ function createStdoutRenderer(terminal, options) {
7816
8938
  if (profiler) {
7817
8939
  profiler.recordRender({
7818
8940
  durationMs: profiler.now() - renderStart,
7819
- rows: rowsToRender ? rowsToRender.length : size.rows,
8941
+ rows: profiledRowCount,
7820
8942
  nodes: 0,
7821
- fullRepaint: !rowsToRender,
8943
+ fullRepaint: !rowsToRender && !scrollHandled,
7822
8944
  sorted: dirtySorted
7823
8945
  });
7824
8946
  }
@@ -7944,6 +9066,13 @@ function createStdoutRenderer(terminal, options) {
7944
9066
  {
7945
9067
  const writeMs = performance.now() - writeStart;
7946
9068
  writeEmaMs = writeEmaMs === 0 ? writeMs : writeEmaMs * 0.85 + writeMs * 0.15;
9069
+ if (emittedCursorPos) {
9070
+ lastCursorX = emittedCursorPos.x;
9071
+ lastCursorY = emittedCursorPos.y;
9072
+ } else {
9073
+ lastCursorX = null;
9074
+ lastCursorY = null;
9075
+ }
7947
9076
  recordStdoutFrame({
7948
9077
  at: Date.now(),
7949
9078
  bytes: frame.length,
@@ -7965,7 +9094,7 @@ function createStdoutRenderer(terminal, options) {
7965
9094
  getDebugLog().render(`doRender() END`);
7966
9095
  }
7967
9096
  }
7968
- function render(dirtyRows) {
9097
+ function render(dirtyRows, sync, scrollOperations, skipUnchangedDirtyRows = false) {
7969
9098
  if (disposed)
7970
9099
  return;
7971
9100
  if (isDebugEnabled()) {
@@ -7973,6 +9102,24 @@ function createStdoutRenderer(terminal, options) {
7973
9102
  `render() called: dirtyRows=${(dirtyRows == null ? void 0 : dirtyRows.length) ?? "null"}, pending=${pendingRender}, elapsed=${Date.now() - lastFrameTime}ms`
7974
9103
  );
7975
9104
  }
9105
+ const isSmallSyncPatch = Boolean(
9106
+ sync && (dirtyRows == null ? void 0 : dirtyRows.length) && dirtyRows.length <= 12 && !(scrollOperations == null ? void 0 : scrollOperations.length)
9107
+ );
9108
+ if (isSmallSyncPatch)
9109
+ skipUnchangedDirtyRows = false;
9110
+ if ((dirtyRows == null ? void 0 : dirtyRows.length) && skipUnchangedDirtyRows && fpPrevValid && !(scrollOperations == null ? void 0 : scrollOperations.length) && dirtyRows.length < terminal.size().rows) {
9111
+ const cols = terminal.size().cols;
9112
+ const nextDirtyRows = dirtyRows.filter((y) => !rowMatchesPreviousFrame(y, cols));
9113
+ if (nextDirtyRows.length === 0) {
9114
+ if (isDebugEnabled()) {
9115
+ getDebugLog().render(
9116
+ `Skipped unchanged dirty commit: ${dirtyRows.length} rows`
9117
+ );
9118
+ }
9119
+ return;
9120
+ }
9121
+ dirtyRows = nextDirtyRows;
9122
+ }
7976
9123
  const ensureDirtyBits = (rowCount) => {
7977
9124
  if (!accumulatedDirtyBits || accumulatedDirtyBits.length !== rowCount) {
7978
9125
  accumulatedDirtyBits = new Uint8Array(rowCount);
@@ -8003,12 +9150,43 @@ function createStdoutRenderer(terminal, options) {
8003
9150
  out2.length = outLen;
8004
9151
  return outLen ? out2 : null;
8005
9152
  };
9153
+ const buildAccumulatedScrollOperations = () => {
9154
+ return (accumulatedScrollOperations == null ? void 0 : accumulatedScrollOperations.length) ? accumulatedScrollOperations : null;
9155
+ };
9156
+ const mergeScrollOperations = (next) => {
9157
+ if (!(next == null ? void 0 : next.length) || accumulatedAllRows)
9158
+ return;
9159
+ const merged = accumulatedScrollOperations ? accumulatedScrollOperations.slice() : [];
9160
+ for (const op of next) {
9161
+ const startY = Math.floor(op.startY);
9162
+ const endY = Math.floor(op.endY);
9163
+ const delta = Math.trunc(op.delta);
9164
+ if (endY <= startY || delta === 0)
9165
+ continue;
9166
+ const existingIndex = merged.findIndex(
9167
+ (existing) => existing.startY === startY && existing.endY === endY
9168
+ );
9169
+ if (existingIndex >= 0) {
9170
+ const existing = merged[existingIndex];
9171
+ const nextDelta = existing.delta + delta;
9172
+ if (nextDelta === 0)
9173
+ merged.splice(existingIndex, 1);
9174
+ else
9175
+ merged[existingIndex] = { startY, endY, delta: nextDelta };
9176
+ } else {
9177
+ merged.push({ startY, endY, delta });
9178
+ }
9179
+ }
9180
+ accumulatedScrollOperations = merged.length ? merged : null;
9181
+ };
8006
9182
  if (!dirtyRows || dirtyRows.length === 0) {
8007
9183
  accumulatedAllRows = true;
8008
9184
  accumulatedDirtyBits = null;
8009
9185
  accumulatedDirtyCount = 0;
8010
9186
  accumulatedDirtyMin = Number.POSITIVE_INFINITY;
8011
9187
  accumulatedDirtyMax = -1;
9188
+ accumulatedScrollOperations = null;
9189
+ accumulatedSkipUnchangedDirtyRows = false;
8012
9190
  } else if (!accumulatedAllRows) {
8013
9191
  const rowCount = terminal.size().rows;
8014
9192
  const bits = ensureDirtyBits(rowCount);
@@ -8025,14 +9203,31 @@ function createStdoutRenderer(terminal, options) {
8025
9203
  accumulatedDirtyMax = y;
8026
9204
  }
8027
9205
  }
9206
+ mergeScrollOperations(scrollOperations);
9207
+ accumulatedSkipUnchangedDirtyRows && (accumulatedSkipUnchangedDirtyRows = skipUnchangedDirtyRows);
8028
9208
  }
8029
- if (pendingRender)
9209
+ if (pendingRender && !sync) {
8030
9210
  return;
9211
+ }
8031
9212
  const now3 = Date.now();
8032
9213
  const elapsed = now3 - lastFrameTime;
9214
+ if (sync) {
9215
+ if (pendingRender) {
9216
+ pendingRender = false;
9217
+ if (renderTimer) {
9218
+ clearTimeout(renderTimer);
9219
+ renderTimer = null;
9220
+ }
9221
+ }
9222
+ const rows = buildAccumulatedRows();
9223
+ const pendingScrolls = buildAccumulatedScrollOperations();
9224
+ doRender(rows, pendingScrolls, accumulatedSkipUnchangedDirtyRows);
9225
+ return;
9226
+ }
8033
9227
  if (elapsed >= MIN_FRAME_MS) {
8034
9228
  const rows = buildAccumulatedRows();
8035
- doRender(rows);
9229
+ const pendingScrolls = buildAccumulatedScrollOperations();
9230
+ doRender(rows, pendingScrolls, accumulatedSkipUnchangedDirtyRows);
8036
9231
  } else {
8037
9232
  pendingRender = true;
8038
9233
  if (renderTimer)
@@ -8041,11 +9236,15 @@ function createStdoutRenderer(terminal, options) {
8041
9236
  renderTimer = null;
8042
9237
  if (!disposed) {
8043
9238
  const rows = buildAccumulatedRows();
8044
- doRender(rows);
9239
+ const pendingScrolls = buildAccumulatedScrollOperations();
9240
+ doRender(rows, pendingScrolls, accumulatedSkipUnchangedDirtyRows);
8045
9241
  }
8046
9242
  }, MIN_FRAME_MS - elapsed);
8047
9243
  }
8048
9244
  }
9245
+ terminal.setFingerprintFn((ch, style) => {
9246
+ return styleKey2(style) << 16 | charHash16(ch);
9247
+ });
8049
9248
  if (altScreen && out.isTTY)
8050
9249
  out.write("\x1B[?1049h");
8051
9250
  if (hideCursor)
@@ -8053,15 +9252,15 @@ function createStdoutRenderer(terminal, options) {
8053
9252
  if (clear) {
8054
9253
  out.write(`${SGR_RESET}${openBg(defaultBg)}\x1B[2J\x1B[H${SGR_RESET}`);
8055
9254
  }
8056
- const off = terminal.on("commit", ({ dirtyRows }) => {
9255
+ const off = terminal.on("commit", ({ dirtyRows, scrollOperations, sync }) => {
8057
9256
  if (isDebugEnabled()) {
8058
9257
  getDebugLog().render(
8059
- `Commit event: dirtyRows=${(dirtyRows == null ? void 0 : dirtyRows.length) ?? "null"}, rows=${(dirtyRows == null ? void 0 : dirtyRows.join(",")) ?? "all"}`
9258
+ `Commit event: dirtyRows=${(dirtyRows == null ? void 0 : dirtyRows.length) ?? "null"}, rows=${(dirtyRows == null ? void 0 : dirtyRows.join(",")) ?? "all"}${(scrollOperations == null ? void 0 : scrollOperations.length) ? `, scrollOps=${scrollOperations.map((op) => `${op.startY}-${op.endY - 1}:${op.delta}`).join("|")}` : ""}${sync ? " (sync)" : ""}`
8060
9259
  );
8061
9260
  }
8062
- render(dirtyRows);
9261
+ render(dirtyRows, sync, scrollOperations, true);
8063
9262
  });
8064
- const resizeSource = (options == null ? void 0 : options.output) ?? import_node_process9.default.stdout;
9263
+ const resizeSource = (options == null ? void 0 : options.output) ?? import_node_process8.default.stdout;
8065
9264
  const canTrackResize = Boolean(
8066
9265
  trackResize && out.isTTY && typeof (resizeSource == null ? void 0 : resizeSource.on) === "function"
8067
9266
  );
@@ -8093,6 +9292,8 @@ function createStdoutRenderer(terminal, options) {
8093
9292
  { cols: size.cols, rows: size.rows }
8094
9293
  );
8095
9294
  out.write(`\x1B[${cy + 1};${cx + 1}H`);
9295
+ lastCursorX = cx;
9296
+ lastCursorY = cy;
8096
9297
  }
8097
9298
  function showCursor(visible) {
8098
9299
  if (disposed)
@@ -8137,7 +9338,10 @@ function createStdoutRenderer(terminal, options) {
8137
9338
  if ("palette" in next)
8138
9339
  palette = next.palette;
8139
9340
  styleKeyCache2 = /* @__PURE__ */ new WeakMap();
8140
- renderedRowCache = [];
9341
+ fpRows = 0;
9342
+ fpPrevValid = false;
9343
+ prevOverlayBlockedRows = [];
9344
+ prevOverlayPartialRows = [];
8141
9345
  render();
8142
9346
  };
8143
9347
  return { render, dispose, setCursor, showCursor, updateTheme };
@@ -8439,10 +9643,10 @@ function createDomRenderer(terminal, container) {
8439
9643
  }
8440
9644
 
8441
9645
  // src/runtime/index.ts
8442
- var import_node_process10 = __toESM(require("process"), 1);
9646
+ var import_node_process9 = __toESM(require("process"), 1);
8443
9647
  function isNodeLike2() {
8444
9648
  var _a2, _b2;
8445
- return typeof ((_b2 = (_a2 = import_node_process10.default) == null ? void 0 : _a2.versions) == null ? void 0 : _b2.node) === "string";
9649
+ return typeof ((_b2 = (_a2 = import_node_process9.default) == null ? void 0 : _a2.versions) == null ? void 0 : _b2.node) === "string";
8446
9650
  }
8447
9651
  function detectEnv() {
8448
9652
  const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
@@ -8602,23 +9806,39 @@ function useTerminal() {
8602
9806
 
8603
9807
  // src/vue/composables/use-render-node.ts
8604
9808
  var pendingInvalidateByScheduler = /* @__PURE__ */ new WeakMap();
8605
- function requestBatchedInvalidate(scheduler, plane) {
9809
+ function mergePriority(prev, next) {
9810
+ if (prev === "high" || next === "high")
9811
+ return "high";
9812
+ if (prev === "normal" || next === "normal")
9813
+ return "normal";
9814
+ return "low";
9815
+ }
9816
+ function requestBatchedInvalidate(scheduler, plane, priority) {
8606
9817
  let state = pendingInvalidateByScheduler.get(scheduler);
8607
9818
  if (!state) {
8608
- state = { queued: false, plane: null };
9819
+ state = { queued: false, plane: null, priority: "low" };
8609
9820
  pendingInvalidateByScheduler.set(scheduler, state);
8610
9821
  }
8611
9822
  if (state.queued) {
8612
9823
  state.plane = state.plane == null || state.plane === plane ? plane : null;
9824
+ state.priority = mergePriority(state.priority, priority);
8613
9825
  return;
8614
9826
  }
8615
9827
  state.plane = plane;
9828
+ state.priority = priority;
8616
9829
  state.queued = true;
8617
9830
  queueMicrotask(() => {
8618
9831
  state.queued = false;
8619
9832
  const queuedPlane = state.plane;
9833
+ const queuedPriority = state.priority;
8620
9834
  state.plane = null;
8621
- scheduler.invalidate(queuedPlane ? { plane: queuedPlane } : void 0);
9835
+ state.priority = "low";
9836
+ scheduler.invalidate(
9837
+ queuedPlane || queuedPriority !== "normal" ? {
9838
+ plane: queuedPlane ?? void 0,
9839
+ priority: queuedPriority
9840
+ } : void 0
9841
+ );
8622
9842
  });
8623
9843
  }
8624
9844
  function useRenderNode(getOptions) {
@@ -8626,11 +9846,13 @@ function useRenderNode(getOptions) {
8626
9846
  const parentStack = useRenderStack();
8627
9847
  const plane = (0, import_vue7.inject)(RenderPlaneContextKey, (0, import_vue7.ref)("default"));
8628
9848
  const id = (0, import_vue7.ref)(null);
9849
+ const lastPlane = (0, import_vue7.ref)(plane.value);
8629
9850
  const options = (0, import_vue7.computed)(() => getOptions());
8630
9851
  const stop = (0, import_vue7.watchEffect)(() => {
8631
9852
  const opt = options.value;
8632
9853
  void opt.deps;
8633
9854
  const stack = opt.stack ?? parentStack.value;
9855
+ lastPlane.value = plane.value;
8634
9856
  if (!stack)
8635
9857
  return;
8636
9858
  if (!id.value) {
@@ -8642,23 +9864,24 @@ function useRenderNode(getOptions) {
8642
9864
  paint: opt.paint
8643
9865
  });
8644
9866
  id.value = node.id;
8645
- requestBatchedInvalidate(scheduler, plane.value);
9867
+ requestBatchedInvalidate(scheduler, plane.value, opt.priority ?? "normal");
8646
9868
  return;
8647
9869
  }
8648
9870
  render.update(id.value, {
8649
9871
  stack,
8650
9872
  zIndex: opt.zIndex ?? 0,
8651
9873
  rect: opt.rect ?? null,
9874
+ dirtyRowsHint: opt.dirtyRowsHint,
8652
9875
  plane: plane.value,
8653
9876
  paint: opt.paint
8654
9877
  });
8655
- requestBatchedInvalidate(scheduler, plane.value);
9878
+ requestBatchedInvalidate(scheduler, plane.value, opt.priority ?? "normal");
8656
9879
  });
8657
9880
  (0, import_vue7.onBeforeUnmount)(() => {
8658
9881
  stop();
8659
9882
  if (id.value) {
8660
9883
  render.unregister(id.value);
8661
- requestBatchedInvalidate(scheduler, plane.value);
9884
+ requestBatchedInvalidate(scheduler, lastPlane.value, "normal");
8662
9885
  }
8663
9886
  });
8664
9887
  return { id };
@@ -9910,6 +11133,8 @@ function createPromptMentionPlugin(options = {}) {
9910
11133
  ctx.render.rootStack,
9911
11134
  1e4
9912
11135
  );
11136
+ let lastTopClampedRect = null;
11137
+ let lastPromptRect = null;
9913
11138
  const derivedStyleCache = /* @__PURE__ */ new WeakMap();
9914
11139
  const reactiveProps = (0, import_vue10.reactive)({
9915
11140
  promptSuggestions: getProps().promptSuggestions,
@@ -9957,6 +11182,25 @@ function createPromptMentionPlugin(options = {}) {
9957
11182
  multilineToken: "\uFFFC",
9958
11183
  mentionToken: ctx.mentionToken
9959
11184
  });
11185
+ (0, import_vue10.watch)(
11186
+ () => {
11187
+ if (!ctx.visible.value || !promptMention.promptVisible.value)
11188
+ return null;
11189
+ const rect = promptMention.promptRect.value;
11190
+ return [
11191
+ Math.floor(rect.x),
11192
+ Math.floor(rect.y),
11193
+ Math.floor(rect.w),
11194
+ Math.floor(rect.h)
11195
+ ].join(":");
11196
+ },
11197
+ (nextRectKey, prevRectKey) => {
11198
+ if (!prevRectKey || nextRectKey === prevRectKey)
11199
+ return;
11200
+ ctx.render.invalidatePlane("default");
11201
+ ctx.scheduler.invalidate({ priority: "high" });
11202
+ }
11203
+ );
9960
11204
  ctx.registerChipStyleProvider({
9961
11205
  getStyle: (baseStyle, chip) => {
9962
11206
  if (chip.kind !== "mention")
@@ -10176,6 +11420,28 @@ function createPromptMentionPlugin(options = {}) {
10176
11420
  useRenderNode(() => ({
10177
11421
  zIndex: getProps().zIndex + 5,
10178
11422
  stack: promptOverlayStack,
11423
+ dirtyRowsHint: (() => {
11424
+ const currentRect = ctx.visible.value && promptMention.promptVisible.value ? {
11425
+ y: Math.floor(promptMention.promptRect.value.y),
11426
+ h: Math.max(0, Math.floor(promptMention.promptRect.value.h))
11427
+ } : null;
11428
+ const rects = [lastPromptRect, currentRect].filter(Boolean);
11429
+ if (!rects.length)
11430
+ return void 0;
11431
+ const maxRows = ctx.terminal.size().rows;
11432
+ const start = Math.max(
11433
+ 0,
11434
+ Math.min(...rects.map((rect) => rect.y)) - 1
11435
+ );
11436
+ const end = Math.min(
11437
+ maxRows,
11438
+ Math.max(...rects.map((rect) => rect.y + rect.h)) + 1
11439
+ );
11440
+ if (end <= start)
11441
+ return void 0;
11442
+ return Array.from({ length: end - start }, (_, index) => start + index);
11443
+ })(),
11444
+ priority: "high",
10179
11445
  rect: ctx.visible.value && promptMention.promptVisible.value ? promptMention.promptRect.value : { x: 0, y: 0, w: 0, h: 0 },
10180
11446
  deps: [
10181
11447
  ctx.visible.value,
@@ -10199,6 +11465,10 @@ function createPromptMentionPlugin(options = {}) {
10199
11465
  const r = promptMention.promptRect.value;
10200
11466
  if (r.w < 3 || r.h < 3)
10201
11467
  return;
11468
+ lastPromptRect = {
11469
+ y: Math.floor(r.y),
11470
+ h: Math.max(0, Math.floor(r.h))
11471
+ };
10202
11472
  const base = getProps().style ?? ctx.defaultStyle.value;
10203
11473
  const selectedOverride = { bg: "blue" };
10204
11474
  const styleKey2 = base && typeof base === "object" ? base : null;
@@ -10231,6 +11501,18 @@ function createPromptMentionPlugin(options = {}) {
10231
11501
  const contentW = Math.max(0, innerW - sidePad * 2);
10232
11502
  const contentX = x0 + 1 + sidePad;
10233
11503
  const borderStyle = derived.borderStyle;
11504
+ if (lastTopClampedRect && y0 > 0) {
11505
+ ctx.terminal.write(spaces(lastTopClampedRect.w), {
11506
+ x: lastTopClampedRect.x,
11507
+ y: 0,
11508
+ style: getProps().style ?? ctx.defaultStyle.value
11509
+ });
11510
+ lastTopClampedRect = null;
11511
+ }
11512
+ if (y0 === 0)
11513
+ lastTopClampedRect = { x: x0, w };
11514
+ else
11515
+ lastTopClampedRect = null;
10234
11516
  ctx.terminal.write(`\u250C${repeatChar("\u2500", innerW)}\u2510`, {
10235
11517
  x: x0,
10236
11518
  y: y0,
@@ -12376,6 +13658,8 @@ var TerminalProvider = (0, import_vue19.defineComponent)({
12376
13658
  let rafToken = 0;
12377
13659
  let pendingInvalidate = false;
12378
13660
  let timer = null;
13661
+ let holdNormalInvalidates = false;
13662
+ let holdReleaseToken = 0;
12379
13663
  let pendingInvalidateAllPlanes = false;
12380
13664
  const pendingInvalidatePlanes = /* @__PURE__ */ new Set();
12381
13665
  let imeComposing = false;
@@ -12406,12 +13690,12 @@ var TerminalProvider = (0, import_vue19.defineComponent)({
12406
13690
  pendingInvalidatePlanes.clear();
12407
13691
  return activePlanes;
12408
13692
  }
12409
- function flush() {
13693
+ function flush(sync = false) {
12410
13694
  if (unmounting)
12411
13695
  return;
12412
13696
  const activePlanes = takeActivePlanes();
12413
13697
  render.render({ activePlanes });
12414
- terminal.commit({ planes: activePlanes });
13698
+ terminal.commit({ planes: activePlanes, sync });
12415
13699
  updateImePositionAfterFlush == null ? void 0 : updateImePositionAfterFlush();
12416
13700
  }
12417
13701
  function clearTimer() {
@@ -12420,6 +13704,18 @@ var TerminalProvider = (0, import_vue19.defineComponent)({
12420
13704
  clearTimeout(timer);
12421
13705
  timer = null;
12422
13706
  }
13707
+ function scheduleNormalInvalidateRelease() {
13708
+ const token = ++holdReleaseToken;
13709
+ void (0, import_vue19.nextTick)(() => {
13710
+ if (token !== holdReleaseToken)
13711
+ return;
13712
+ holdNormalInvalidates = false;
13713
+ if (unmounting || !pendingInvalidate)
13714
+ return;
13715
+ pendingInvalidate = false;
13716
+ invalidate({ priority: "normal" });
13717
+ });
13718
+ }
12423
13719
  function flushNow() {
12424
13720
  if (unmounting)
12425
13721
  return;
@@ -12431,7 +13727,9 @@ var TerminalProvider = (0, import_vue19.defineComponent)({
12431
13727
  raf = 0;
12432
13728
  }
12433
13729
  clearTimer();
12434
- flush();
13730
+ holdNormalInvalidates = true;
13731
+ scheduleNormalInvalidateRelease();
13732
+ flush(true);
12435
13733
  }
12436
13734
  function invalidate(options) {
12437
13735
  if (unmounting)
@@ -12443,6 +13741,10 @@ var TerminalProvider = (0, import_vue19.defineComponent)({
12443
13741
  flushNow();
12444
13742
  return;
12445
13743
  }
13744
+ if (holdNormalInvalidates) {
13745
+ pendingInvalidate = true;
13746
+ return;
13747
+ }
12446
13748
  if (priority === "low") {
12447
13749
  if (timer || raf)
12448
13750
  return;
@@ -13022,6 +14324,8 @@ var TerminalProvider = (0, import_vue19.defineComponent)({
13022
14324
  (0, import_vue19.onBeforeUnmount)(() => {
13023
14325
  unmounting = true;
13024
14326
  offCommit == null ? void 0 : offCommit();
14327
+ clearTimer();
14328
+ holdReleaseToken++;
13025
14329
  if (raf > 0)
13026
14330
  cancelAnimationFrame(raf);
13027
14331
  raf = 0;
@@ -14024,7 +15328,10 @@ var TInput = (0, import_vue21.defineComponent)({
14024
15328
  emit("input", nextValue);
14025
15329
  if (commit)
14026
15330
  emit("change", nextValue);
14027
- scheduler.invalidate();
15331
+ scheduler.flushNow();
15332
+ void (0, import_vue21.nextTick)(() => {
15333
+ scheduler.flushNow();
15334
+ });
14028
15335
  }
14029
15336
  function applyMove(nextCursor, extend) {
14030
15337
  const prev = cursor.value;
@@ -14058,7 +15365,7 @@ var TInput = (0, import_vue21.defineComponent)({
14058
15365
  }
14059
15366
  ensureCursorVisible();
14060
15367
  syncImeAnchorNow();
14061
- scheduler.invalidate();
15368
+ scheduler.flushNow();
14062
15369
  }
14063
15370
  (0, import_vue21.watch)(
14064
15371
  [() => absRect.value.w, () => absRect.value.h, () => wrapMode.value],
@@ -14192,7 +15499,11 @@ var TInput = (0, import_vue21.defineComponent)({
14192
15499
  terminal,
14193
15500
  scheduler,
14194
15501
  defaultStyle,
14195
- render,
15502
+ render: {
15503
+ rootStack: render.rootStack,
15504
+ createStack: render.createStack,
15505
+ invalidatePlane: render.invalidatePlane
15506
+ },
14196
15507
  visible,
14197
15508
  rawAbsRect,
14198
15509
  eventZ,
@@ -14306,7 +15617,7 @@ var TInput = (0, import_vue21.defineComponent)({
14306
15617
  emitMentions(nextMentions);
14307
15618
  ensureCursorVisible();
14308
15619
  syncImeAnchorNow();
14309
- scheduler.invalidate();
15620
+ scheduler.flushNow();
14310
15621
  }
14311
15622
  function removePendingPasteImage(placeholderPath) {
14312
15623
  const current = getCurrentMentions();
@@ -15124,10 +16435,12 @@ var TInput = (0, import_vue21.defineComponent)({
15124
16435
  clearAll();
15125
16436
  return;
15126
16437
  }
15127
- focused.value = false;
15128
- anchor.value = null;
15129
- emit("blur");
15130
- scheduler.invalidate();
16438
+ const manager = events.value;
16439
+ if (manager && manager.getFocused()) {
16440
+ manager.focus(null);
16441
+ return;
16442
+ }
16443
+ applyBlur();
15131
16444
  return;
15132
16445
  }
15133
16446
  if (isPrintableKey(e)) {
@@ -15390,6 +16703,7 @@ var TInput = (0, import_vue21.defineComponent)({
15390
16703
  var _a2;
15391
16704
  return {
15392
16705
  zIndex: props.zIndex,
16706
+ priority: pendingValue.value != null ? "high" : "normal",
15393
16707
  rect: visible.value ? absRect.value : { x: 0, y: 0, w: 0, h: 0 },
15394
16708
  deps: [
15395
16709
  visible.value,
@@ -15417,6 +16731,7 @@ var TInput = (0, import_vue21.defineComponent)({
15417
16731
  blinkOn.value,
15418
16732
  defaultStyle.value,
15419
16733
  ((_a2 = chipStyleProvider.value) == null ? void 0 : _a2.version.value) ?? 0,
16734
+ pendingValue.value,
15420
16735
  props.skillHighlightStyle,
15421
16736
  props.skillTrigger
15422
16737
  ],
@@ -15986,19 +17301,27 @@ var import_vue24 = require("vue");
15986
17301
 
15987
17302
  // src/vue/utils/wheel-scroll.ts
15988
17303
  var LINE_UNIT_THRESHOLD = 3;
15989
- var PIXELS_PER_LINE = 100;
17304
+ var PIXELS_PER_LINE = 0.75;
15990
17305
  var ACCEL_WINDOW_MS = 120;
15991
- var MAX_ACCEL = 2.5;
17306
+ var MAX_ACCEL = 26;
15992
17307
  var EDGE_BOUNCE_MS = 120;
15993
17308
  var MIN_TICK_INTERVAL_MS = 6;
15994
17309
  function clamp5(n, min, max) {
15995
17310
  return Math.max(min, Math.min(max, n));
15996
17311
  }
15997
- function isLineUnitDelta(deltaY) {
17312
+ function isLineUnitDelta(deltaY, mode = "auto") {
17313
+ if (mode === "line")
17314
+ return true;
17315
+ if (mode === "pixel")
17316
+ return false;
15998
17317
  const abs = Math.abs(deltaY);
15999
17318
  return abs > 0 && abs <= LINE_UNIT_THRESHOLD && Number.isInteger(deltaY);
16000
17319
  }
16001
- function normalizeDelta(deltaY) {
17320
+ function normalizeDelta(deltaY, mode = "auto") {
17321
+ if (mode === "line")
17322
+ return deltaY;
17323
+ if (mode === "pixel")
17324
+ return deltaY / PIXELS_PER_LINE;
16002
17325
  const abs = Math.abs(deltaY);
16003
17326
  if (abs <= LINE_UNIT_THRESHOLD && Number.isInteger(deltaY))
16004
17327
  return deltaY;
@@ -16019,7 +17342,7 @@ function createWheelScrollState() {
16019
17342
  lastEdgeAt: 0
16020
17343
  };
16021
17344
  }
16022
- function applyWheelScroll(state, deltaY, scrollTop, maxTop, now3 = Date.now()) {
17345
+ function applyWheelScroll(state, deltaY, scrollTop, maxTop, now3 = Date.now(), deltaMode = "auto") {
16023
17346
  if (!Number.isFinite(deltaY) || deltaY === 0 || maxTop <= 0) {
16024
17347
  if (maxTop <= 0) {
16025
17348
  state.accumulator = 0;
@@ -16029,12 +17352,12 @@ function applyWheelScroll(state, deltaY, scrollTop, maxTop, now3 = Date.now()) {
16029
17352
  return { nextTop: scrollTop, dir: 0, lines: 0 };
16030
17353
  }
16031
17354
  const dt = state.lastAt ? now3 - state.lastAt : Infinity;
16032
- const lineUnits = isLineUnitDelta(deltaY);
17355
+ const lineUnits = isLineUnitDelta(deltaY, deltaMode);
16033
17356
  if (lineUnits && dt !== Infinity && dt >= 0 && dt < MIN_TICK_INTERVAL_MS)
16034
17357
  return { nextTop: scrollTop, dir: 0, lines: 0 };
16035
17358
  const accel = lineUnits ? 1 : state.lastAt ? accelFactor(now3, state.lastAt) : 1;
16036
17359
  state.lastAt = now3;
16037
- state.accumulator += normalizeDelta(deltaY) * accel;
17360
+ state.accumulator += normalizeDelta(deltaY, deltaMode) * accel;
16038
17361
  const lines = Math.trunc(state.accumulator);
16039
17362
  if (lines === 0)
16040
17363
  return { nextTop: scrollTop, dir: 0, lines: 0 };
@@ -16071,6 +17394,18 @@ function applyWheelScroll(state, deltaY, scrollTop, maxTop, now3 = Date.now()) {
16071
17394
  function clamp6(n, min, max) {
16072
17395
  return Math.max(min, Math.min(max, n));
16073
17396
  }
17397
+ function getWheelScrollInput(e) {
17398
+ const deltaY = Number(e.deltaY ?? 0);
17399
+ const deltaMode = typeof e.deltaMode === "number" ? e.deltaMode : void 0;
17400
+ if (Number.isInteger(deltaY) && deltaY !== 0 && Math.abs(deltaY) >= 100 && Math.abs(deltaY) % 100 === 0 && (deltaMode == null || deltaMode === 0)) {
17401
+ return { deltaY: deltaY / 100, mode: "line" };
17402
+ }
17403
+ if (deltaMode === 1)
17404
+ return { deltaY, mode: "line" };
17405
+ if (deltaMode === 0)
17406
+ return { deltaY, mode: "pixel" };
17407
+ return { deltaY, mode: "auto" };
17408
+ }
16074
17409
  var TList = (0, import_vue24.defineComponent)({
16075
17410
  name: "TList",
16076
17411
  props: {
@@ -16249,7 +17584,8 @@ var TList = (0, import_vue24.defineComponent)({
16249
17584
  commit(idx);
16250
17585
  },
16251
17586
  wheel: (e) => {
16252
- const delta = e.deltaY ?? 0;
17587
+ const { deltaY, mode } = getWheelScrollInput(e);
17588
+ const delta = deltaY;
16253
17589
  if (!delta)
16254
17590
  return;
16255
17591
  const h18 = Math.max(0, props.h);
@@ -16258,7 +17594,9 @@ var TList = (0, import_vue24.defineComponent)({
16258
17594
  wheelState,
16259
17595
  delta,
16260
17596
  scrollTop.value,
16261
- maxTop
17597
+ maxTop,
17598
+ Date.now(),
17599
+ mode
16262
17600
  );
16263
17601
  if (!dir || nextTop === scrollTop.value)
16264
17602
  return;
@@ -16552,6 +17890,18 @@ init_path();
16552
17890
  function clamp7(n, min, max) {
16553
17891
  return Math.max(min, Math.min(max, n));
16554
17892
  }
17893
+ function getWheelScrollInput2(e) {
17894
+ const deltaY = Number(e.deltaY ?? 0);
17895
+ const deltaMode = typeof e.deltaMode === "number" ? e.deltaMode : void 0;
17896
+ if (Number.isInteger(deltaY) && deltaY !== 0 && Math.abs(deltaY) >= 100 && Math.abs(deltaY) % 100 === 0 && (deltaMode == null || deltaMode === 0)) {
17897
+ return { deltaY: deltaY / 100, mode: "line" };
17898
+ }
17899
+ if (deltaMode === 1)
17900
+ return { deltaY, mode: "line" };
17901
+ if (deltaMode === 0)
17902
+ return { deltaY, mode: "pixel" };
17903
+ return { deltaY, mode: "auto" };
17904
+ }
16555
17905
  function isCtrlLike(e) {
16556
17906
  return Boolean(e.ctrlKey || e.metaKey);
16557
17907
  }
@@ -16917,7 +18267,8 @@ var TPathPicker = (0, import_vue26.defineComponent)({
16917
18267
  await trySelect(s.absPath);
16918
18268
  },
16919
18269
  wheel: (e) => {
16920
- const delta = e.deltaY ?? 0;
18270
+ const { deltaY, mode } = getWheelScrollInput2(e);
18271
+ const delta = deltaY;
16921
18272
  if (!delta)
16922
18273
  return;
16923
18274
  const h18 = Math.max(0, listH.value);
@@ -16926,7 +18277,9 @@ var TPathPicker = (0, import_vue26.defineComponent)({
16926
18277
  wheelState,
16927
18278
  delta,
16928
18279
  scrollTop.value,
16929
- maxTop
18280
+ maxTop,
18281
+ Date.now(),
18282
+ mode
16930
18283
  );
16931
18284
  if (!dir || nextTop === scrollTop.value)
16932
18285
  return;