From ba884cd0a28890ee67f415080a5c0f16e9c8b6fa Mon Sep 17 00:00:00 2001 From: Gregor Lohaus Date: Sat, 30 May 2026 01:23:16 +0200 Subject: [PATCH] probably wrong fix --- .../gregor/jprototerm/TerminalCanvasView.java | 45 +++------------- .../com/gregor/jprototerm/TerminalPane.java | 54 +++---------------- 2 files changed, 12 insertions(+), 87 deletions(-) diff --git a/src/main/java/com/gregor/jprototerm/TerminalCanvasView.java b/src/main/java/com/gregor/jprototerm/TerminalCanvasView.java index 764968b..9865635 100644 --- a/src/main/java/com/gregor/jprototerm/TerminalCanvasView.java +++ b/src/main/java/com/gregor/jprototerm/TerminalCanvasView.java @@ -100,9 +100,6 @@ public final class TerminalCanvasView { } // GhosttyRenderStateDirty values (stable C ABI; see ghostty/vt/render.h). - private static final int DIRTY_PARTIAL = 1; - private static final int DIRTY_FULL = 2; - // Thin tab strip shown at the top when more than one tab is open. private static final double TAB_BAR_HEIGHT = 22.0; @@ -188,50 +185,20 @@ public final class TerminalCanvasView { gc.restore(); } - // Repaint one pane whose content changed, then restore the (opaque) panes stacked above - // it wherever they overlap the repainted region, so the z-order stays correct. + // Repaint one pane whose content changed (a full, reliable repaint of the pane), then + // restore the (opaque) panes stacked above it where they overlap, keeping z-order. private void repaintPaneContent(GraphicsContext gc, List panes, int index, Font font, FontMetrics metrics) { TerminalPane pane = panes.get(index); double px = Math.round(pane.x()); double py = Math.round(pane.y()); double pw = pane.width(); double ph = pane.height(); - boolean kitty = config.kittyGraphics() && paneHasKittyGraphics(pane); - - // A pane just resized (e.g. from a split) can't be trusted to report its dirty rows - // for the app's post-SIGWINCH redraw, so force a full snapshot once. - boolean forceFull = pane.consumeFullRender(); - - double regionY0; - double regionY1; gc.save(); clipRect(gc, px, py, pw, ph); - if (kitty || forceFull) { - drawPaneContent(gc, pane, font, metrics, pane.renderSnapshotFull(), px, py, pw, ph, kitty); - regionY0 = py; - regionY1 = py + ph; - } else { - RenderStateSnapshot snapshot = pane.renderSnapshot(); - int dirty = snapshot == null ? DIRTY_FULL : snapshot.dirty(); - if (dirty == DIRTY_FULL) { - drawPaneContent(gc, pane, font, metrics, snapshot, px, py, pw, ph, false); - regionY0 = py; - regionY1 = py + ph; - } else if (dirty == DIRTY_PARTIAL) { - double[] band = drawDirtyRows(gc, pane, font, metrics, snapshot, px, py, pw, ph); - gc.restore(); - if (band == null) { - return; - } - restoreStackedAbove(gc, panes, index, font, metrics, px, band[0], pw, band[1] - band[0]); - return; - } else { - gc.restore(); - return; // dirty == FALSE: nothing visible changed. - } - } + drawPaneContent(gc, pane, font, metrics, pane.renderSnapshot(), px, py, pw, ph, + config.kittyGraphics() && paneHasKittyGraphics(pane)); gc.restore(); - restoreStackedAbove(gc, panes, index, font, metrics, px, regionY0, pw, regionY1 - regionY0); + restoreStackedAbove(gc, panes, index, font, metrics, px, py, pw, ph); } // Redraw any panes above `index` in z-order that intersect the given screen rect, so a @@ -251,7 +218,7 @@ public final class TerminalCanvasView { } gc.save(); clipRect(gc, ox0, oy0, ox1 - ox0, oy1 - oy0); - drawPaneContent(gc, above, font, metrics, above.renderSnapshotFull(), ax, ay, above.width(), above.height(), + drawPaneContent(gc, above, font, metrics, above.renderSnapshot(), ax, ay, above.width(), above.height(), config.kittyGraphics() && paneHasKittyGraphics(above)); gc.restore(); } diff --git a/src/main/java/com/gregor/jprototerm/TerminalPane.java b/src/main/java/com/gregor/jprototerm/TerminalPane.java index 517dd83..6afd5bb 100644 --- a/src/main/java/com/gregor/jprototerm/TerminalPane.java +++ b/src/main/java/com/gregor/jprototerm/TerminalPane.java @@ -6,7 +6,6 @@ import dev.jlibghostty.MouseAction; import dev.jlibghostty.MouseEncoder; import dev.jlibghostty.MouseEncoderSize; import dev.jlibghostty.MouseInput; -import dev.jlibghostty.RenderState; import dev.jlibghostty.RenderStateSnapshot; import dev.jlibghostty.ScrollViewport; import dev.jlibghostty.Terminal; @@ -27,9 +26,6 @@ public final class TerminalPane implements AutoCloseable { private final Terminal terminal; private final MouseEncoder mouseEncoder = new MouseEncoder(); - // A persistent render state (reused across frames) is what makes ghostty's per-row - // dirty tracking meaningful: update() accumulates dirty since the last resetDirty(). - private RenderState renderState = new RenderState(); private RenderStateSnapshot cachedSnapshot; private ShellSession session; private boolean floating; @@ -47,7 +43,6 @@ public final class TerminalPane implements AutoCloseable { private volatile long renderVersion; private long snapshotVersion = -1; private volatile boolean closed; - private boolean needsFullRender; private TerminalPane(Terminal terminal, int columns, int rows) { this.terminal = terminal; @@ -135,34 +130,15 @@ public final class TerminalPane implements AutoCloseable { } /** - * Incremental snapshot: cells are marshalled only for rows that changed since the last - * frame (global dirty == PARTIAL), reused across calls for the same content version. - * Snapshotting is deferred here rather than done in refresh(), so a burst of writes - * between two frames collapses into a single snapshot. + * Full render snapshot of the current screen, memoised per content version (so a burst + * of writes between two frames yields one snapshot). Uses a throwaway render state per + * snapshot, which always returns the complete, correct screen — a persistent render + * state's per-row dirty tracking proved unreliable across resizes and screen clears. */ public RenderStateSnapshot renderSnapshot() { - return snapshot(false); - } - - /** - * Full snapshot with every row's cells populated. Used where the whole pane is redrawn - * regardless of dirty state (the kitty-graphics path). - */ - public RenderStateSnapshot renderSnapshotFull() { - return snapshot(true); - } - - private RenderStateSnapshot snapshot(boolean full) { synchronized (terminal) { - if (full) { - renderState.update(terminal); - cachedSnapshot = renderState.snapshot(); - renderState.resetDirty(); - snapshotVersion = renderVersion; - } else if (snapshotVersion != renderVersion) { - renderState.update(terminal); - cachedSnapshot = renderState.snapshotIncremental(); - renderState.resetDirty(); + if (snapshotVersion != renderVersion) { + cachedSnapshot = terminal.renderSnapshot(); snapshotVersion = renderVersion; } return cachedSnapshot; @@ -241,27 +217,10 @@ public final class TerminalPane implements AutoCloseable { this.rows = rows; this.pixelWidth = pixelWidth; this.pixelHeight = pixelHeight; - // A persistent render state gets corrupted by a terminal resize: its next snapshot - // comes back blank (a throwaway render state, as ghostty's API was originally used, - // never had this). Recreate it so the next snapshot is a clean full render of the - // resized grid, even for an idle pane that won't redraw on its own. - renderState.close(); - renderState = new RenderState(); - snapshotVersion = -1; - // The app (e.g. a TUI) also redraws a moment later via SIGWINCH; force the next - // content repaint to use a full snapshot so we don't rely on dirty across the resize. - needsFullRender = true; refresh(); } } - /** Returns and clears the "force a full repaint" flag set by {@link #resize}. */ - public boolean consumeFullRender() { - boolean pending = needsFullRender; - needsFullRender = false; - return pending; - } - private void refresh() { // Only mark the pane dirty; the snapshot itself is computed lazily in // renderSnapshot() so a burst of writes collapses into a single snapshot per frame. @@ -281,7 +240,6 @@ public final class TerminalPane implements AutoCloseable { session = null; } mouseEncoder.close(); - renderState.close(); synchronized (terminal) { terminal.close(); }