non fix of clearing issue
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
package com.gregor.jprototerm;
|
||||
|
||||
import javafx.application.Platform;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -94,23 +92,25 @@ public final class ShellSession implements AutoCloseable {
|
||||
}
|
||||
|
||||
private void readOutput(TerminalPane pane) {
|
||||
byte[] buffer = new byte[8192];
|
||||
byte[] buffer = new byte[65536];
|
||||
try {
|
||||
int read;
|
||||
while ((read = pty.read(buffer)) != -1) {
|
||||
if (!closed) {
|
||||
if (closed) {
|
||||
break;
|
||||
}
|
||||
byte[] bytes = new byte[read];
|
||||
System.arraycopy(buffer, 0, bytes, 0, read);
|
||||
Platform.runLater(() -> {
|
||||
if (!closed) {
|
||||
// Feed the terminal model straight from the reader thread. terminal access is
|
||||
// guarded by the per-terminal lock, and the render loop picks the change up on
|
||||
// the next pulse. Avoiding a Platform.runLater hop per chunk removes a frame of
|
||||
// latency and stops write tasks from contending with rendering on the FX thread
|
||||
// when a TUI repaints heavily (the input-lag culprit).
|
||||
pane.write(bytes);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException ex) {
|
||||
if (!closed) {
|
||||
Platform.runLater(() -> pane.write("\r\nshell output stopped: " + ex.getMessage() + "\r\n"));
|
||||
pane.write("\r\nshell output stopped: " + ex.getMessage() + "\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,16 +35,27 @@ final class Tab implements AutoCloseable {
|
||||
return List.of();
|
||||
}
|
||||
List<TerminalPane> visible = panes.stream().filter(TerminalPane::visible).toList();
|
||||
TerminalPane active = activePane();
|
||||
if (!active.visible() || !active.floating()) {
|
||||
return visible;
|
||||
if (visible.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
// Draw order = z-order: all tiled panes first (they never overlap), then floating
|
||||
// panes on top, with the active floating pane last (topmost). This holds regardless
|
||||
// of creation order, so a tiled pane created after a floating one still sits behind.
|
||||
TerminalPane active = activePane();
|
||||
List<TerminalPane> ordered = new ArrayList<>(visible.size());
|
||||
visible.stream()
|
||||
.filter(pane -> pane != active)
|
||||
.forEach(ordered::add);
|
||||
for (TerminalPane pane : visible) {
|
||||
if (!pane.floating()) {
|
||||
ordered.add(pane);
|
||||
}
|
||||
}
|
||||
for (TerminalPane pane : visible) {
|
||||
if (pane.floating() && pane != active) {
|
||||
ordered.add(pane);
|
||||
}
|
||||
}
|
||||
if (active.visible() && active.floating()) {
|
||||
ordered.add(active);
|
||||
}
|
||||
return List.copyOf(ordered);
|
||||
}
|
||||
|
||||
@@ -155,7 +166,12 @@ final class Tab implements AutoCloseable {
|
||||
void closeActivePane() {
|
||||
TerminalPane active = activePane();
|
||||
int removed = activeIndex;
|
||||
int previous = previousVisibleIndex(removed);
|
||||
// When closing a floating pane, focus the next visible floating pane if there is one
|
||||
// (don't jump to a tiled pane); otherwise fall back to the nearest visible pane.
|
||||
int target = active.floating() ? nearestVisibleFloatingIndex(removed) : -1;
|
||||
if (target < 0) {
|
||||
target = previousVisibleIndex(removed);
|
||||
}
|
||||
panes.remove(removed);
|
||||
if (active == lastFocusedFloating) {
|
||||
lastFocusedFloating = null;
|
||||
@@ -165,7 +181,7 @@ final class Tab implements AutoCloseable {
|
||||
activeIndex = 0;
|
||||
return;
|
||||
}
|
||||
activeIndex = adjustIndexAfterRemoval(previous, removed);
|
||||
activeIndex = adjustIndexAfterRemoval(target, removed);
|
||||
hiddenFloatingFocusIndex = adjustHiddenFocusAfterRemoval(hiddenFloatingFocusIndex, removed);
|
||||
|
||||
// If the last tiled (main) pane was closed, promote a floating pane to be the new
|
||||
@@ -273,6 +289,20 @@ final class Tab implements AutoCloseable {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int nearestVisibleFloatingIndex(int index) {
|
||||
for (int i = index + 1; i < panes.size(); i++) {
|
||||
if (panes.get(i).visible() && panes.get(i).floating()) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
for (int i = index - 1; i >= 0; i--) {
|
||||
if (panes.get(i).visible() && panes.get(i).floating()) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int previousVisibleIndex(int index) {
|
||||
for (int i = index - 1; i >= 0; i--) {
|
||||
if (panes.get(i).visible()) {
|
||||
|
||||
@@ -54,6 +54,9 @@ public final class TerminalCanvasView {
|
||||
// Last content version drawn to the canvas per pane, so a content frame repaints only
|
||||
// the panes that actually changed.
|
||||
private final Map<TerminalPane, Long> paneContentVersion = new HashMap<>();
|
||||
// Pane list from the last layout pass; reused on content-only frames so typing doesn't
|
||||
// re-run layout()/panes()/resize each frame.
|
||||
private List<TerminalPane> cachedPanes = List.of();
|
||||
private String fontFamily;
|
||||
private double fontSize;
|
||||
private Font cachedFont;
|
||||
@@ -126,24 +129,21 @@ public final class TerminalCanvasView {
|
||||
lastWorkspaceVersion = workspaceVersion;
|
||||
lastRenderTick = renderTick;
|
||||
|
||||
double topInset = workspace.tabCount() > 1 ? TAB_BAR_HEIGHT : 0.0;
|
||||
workspace.layout(width, height, topInset);
|
||||
Font font = currentFont();
|
||||
FontMetrics metrics = currentFontMetrics();
|
||||
List<TerminalPane> panes = workspace.panes();
|
||||
|
||||
// Apply terminal resizes up front so snapshots reflect current geometry (a no-op
|
||||
// when the grid is unchanged).
|
||||
for (TerminalPane pane : panes) {
|
||||
applyResize(pane, metrics);
|
||||
}
|
||||
|
||||
GraphicsContext gc = canvas.getGraphicsContext2D();
|
||||
gc.setFontSmoothingType(FontSmoothingType.LCD);
|
||||
|
||||
if (layoutChanged) {
|
||||
// Recomposite everything onto the retained canvas: clear, then paint panes
|
||||
// bottom-to-top (workspace.panes() puts the active floating pane last == on top).
|
||||
// Geometry/pane-set changed: relayout, resize terminals, and recomposite the
|
||||
// whole canvas, painting panes bottom-to-top (active floating pane last == top).
|
||||
double topInset = workspace.tabCount() > 1 ? TAB_BAR_HEIGHT : 0.0;
|
||||
workspace.layout(width, height, topInset);
|
||||
List<TerminalPane> panes = workspace.panes();
|
||||
for (TerminalPane pane : panes) {
|
||||
applyResize(pane, metrics);
|
||||
}
|
||||
cachedPanes = panes;
|
||||
paneContentVersion.keySet().retainAll(panes);
|
||||
gc.setFill(GAP_BACKGROUND);
|
||||
gc.fillRect(0, 0, width, height);
|
||||
@@ -157,8 +157,9 @@ public final class TerminalCanvasView {
|
||||
return;
|
||||
}
|
||||
|
||||
// Content-only frame: repaint just the panes whose content changed, directly on the
|
||||
// retained canvas, then restore any panes stacked above where they overlap.
|
||||
// Content-only frame: geometry is unchanged, so skip layout/panes/resize entirely and
|
||||
// reuse the cached pane list — repaint just the panes whose content changed.
|
||||
List<TerminalPane> panes = cachedPanes;
|
||||
for (int i = 0; i < panes.size(); i++) {
|
||||
TerminalPane pane = panes.get(i);
|
||||
Long drawn = paneContentVersion.get(pane);
|
||||
@@ -197,12 +198,16 @@ public final class TerminalCanvasView {
|
||||
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) {
|
||||
drawPaneContent(gc, pane, font, metrics, pane.renderSnapshotFull(), px, py, pw, ph, true);
|
||||
if (kitty || forceFull) {
|
||||
drawPaneContent(gc, pane, font, metrics, pane.renderSnapshotFull(), px, py, pw, ph, kitty);
|
||||
regionY0 = py;
|
||||
regionY1 = py + ph;
|
||||
} else {
|
||||
|
||||
@@ -29,7 +29,7 @@ public final class TerminalPane implements AutoCloseable {
|
||||
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 final RenderState renderState = new RenderState();
|
||||
private RenderState renderState = new RenderState();
|
||||
private RenderStateSnapshot cachedSnapshot;
|
||||
private ShellSession session;
|
||||
private boolean floating;
|
||||
@@ -42,8 +42,12 @@ public final class TerminalPane implements AutoCloseable {
|
||||
private int rows;
|
||||
private int pixelWidth;
|
||||
private int pixelHeight;
|
||||
private long renderVersion;
|
||||
// Bumped on the reader thread (terminal writes) and read on the FX thread (render loop),
|
||||
// so it must be volatile.
|
||||
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;
|
||||
@@ -61,6 +65,9 @@ public final class TerminalPane implements AutoCloseable {
|
||||
|
||||
public void write(String text) {
|
||||
synchronized (terminal) {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
terminal.write(text);
|
||||
refresh();
|
||||
}
|
||||
@@ -68,6 +75,9 @@ public final class TerminalPane implements AutoCloseable {
|
||||
|
||||
public void write(byte[] bytes) {
|
||||
synchronized (terminal) {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
terminal.write(bytes);
|
||||
refresh();
|
||||
}
|
||||
@@ -231,10 +241,27 @@ 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.
|
||||
@@ -244,12 +271,19 @@ public final class TerminalPane implements AutoCloseable {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// Stop accepting reader-thread writes first, then shut the session (which unblocks
|
||||
// and ends the reader), so terminal.close() can't race a write from that thread.
|
||||
synchronized (terminal) {
|
||||
closed = true;
|
||||
}
|
||||
if (session != null) {
|
||||
session.close();
|
||||
session = null;
|
||||
}
|
||||
mouseEncoder.close();
|
||||
renderState.close();
|
||||
synchronized (terminal) {
|
||||
terminal.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user