From 8bd81702706209e11623394d537a579cded186cc Mon Sep 17 00:00:00 2001 From: Gregor Lohaus Date: Fri, 19 Jun 2026 15:15:44 +0200 Subject: [PATCH 1/4] pane sync --- README.md | 9 ++ config.example.toml | 3 + .../java/com/gregor/jprototerm/AppConfig.java | 6 + .../com/gregor/jprototerm/Compositor.java | 120 ++++++++++++++++++ .../com/gregor/jprototerm/TerminalWindow.java | 41 +++++- 5 files changed, 178 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c877120..b4e4cd9 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,10 @@ next_tab = "ALT+SHIFT+L" open_font_selector = "ALT+T" open_scrollback = "ALT+S" create_worktree = "ALT+W" +pane_sync_select = "ALT+Y" +pane_sync_commit = "ALT+SHIFT+Y" +pane_sync_end = "ALT+U" +paste = "CTRL+SHIFT+V" ``` ## Defaults @@ -187,6 +191,11 @@ create_worktree = "ALT+W" - `Alt+s`: open the active pane scrollback in `$EDITOR` - `Alt+w`: edit a worktree name, then run `git worktree add /` from the previously focused pane's working directory +- `Alt+y`: enter pane-sync selection mode and toggle the focused pane in the sync set +- `Alt+Shift+y`: commit the current pane-sync selection; input typed or pasted into any synced + pane is mirrored to the other synced panes +- `Alt+u`: end pane sync +- `Ctrl+Shift+v`: paste - Font default: `JetBrainsMono Nerd Font` - Kitty graphics protocol parsing is enabled by default diff --git a/config.example.toml b/config.example.toml index e81eb8b..dc90429 100644 --- a/config.example.toml +++ b/config.example.toml @@ -41,4 +41,7 @@ next_tab = "ALT+SHIFT+L" open_font_selector = "ALT+T" open_scrollback = "ALT+S" create_worktree = "ALT+W" +pane_sync_select = "ALT+Y" +pane_sync_commit = "ALT+SHIFT+Y" +pane_sync_end = "ALT+U" paste = "CTRL+SHIFT+V" diff --git a/src/main/java/com/gregor/jprototerm/AppConfig.java b/src/main/java/com/gregor/jprototerm/AppConfig.java index cbe7380..b390c95 100644 --- a/src/main/java/com/gregor/jprototerm/AppConfig.java +++ b/src/main/java/com/gregor/jprototerm/AppConfig.java @@ -50,6 +50,9 @@ public record AppConfig( "open_font_selector", "open_scrollback", "create_worktree", + "pane_sync_select", + "pane_sync_commit", + "pane_sync_end", "paste" ); @@ -116,6 +119,9 @@ public record AppConfig( Map.entry("open_font_selector", KeyBinding.parse("ALT+T")), Map.entry("open_scrollback", KeyBinding.parse("ALT+S")), Map.entry("create_worktree", KeyBinding.parse("ALT+W")), + Map.entry("pane_sync_select", KeyBinding.parse("ALT+Y")), + Map.entry("pane_sync_commit", KeyBinding.parse("ALT+SHIFT+Y")), + Map.entry("pane_sync_end", KeyBinding.parse("ALT+U")), Map.entry("paste", KeyBinding.parse("CTRL+SHIFT+V")) ) ); diff --git a/src/main/java/com/gregor/jprototerm/Compositor.java b/src/main/java/com/gregor/jprototerm/Compositor.java index b8bae3f..a14cf5a 100644 --- a/src/main/java/com/gregor/jprototerm/Compositor.java +++ b/src/main/java/com/gregor/jprototerm/Compositor.java @@ -20,6 +20,7 @@ import javafx.scene.text.TextAlignment; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -37,6 +38,8 @@ public final class Compositor { // Canvas background shown wherever no pane covers (gaps). Painted on a full recomposite. private static final Color GAP_BACKGROUND = Color.rgb(16, 16, 18); private static final Color TAB_TEXT = Color.rgb(225, 229, 235); + private static final Color PANE_SYNC_SELECT_BORDER = Color.rgb(255, 183, 77); + private static final Color PANE_SYNC_COMMITTED_BORDER = Color.rgb(105, 214, 128); // Thin tab strip shown at the top when more than one tab is open. private static final double TAB_BAR_HEIGHT = 22.0; @@ -62,6 +65,9 @@ public final class Compositor { private final Map hiddenSince = new HashMap<>(); // Panes whose backbuffer is currently released, so we don't release again every frame. private final Set released = new HashSet<>(); + private final Set paneSyncSelection = new LinkedHashSet<>(); + private final Set paneSyncPanes = new LinkedHashSet<>(); + private boolean paneSyncSelectMode; // layoutVersion at the last sweep: lets an idle, all-released steady state skip the scan. private long lastSweepLayoutVersion = Long.MIN_VALUE; // Cheap per-frame dirty signal: skip the whole render when none of these changed. @@ -134,6 +140,57 @@ public final class Compositor { } } + public boolean isPaneSyncSelecting() { + return paneSyncSelectMode; + } + + public void togglePaneSyncSelection() { + TerminalPane active = activePane(); + if (active == null) { + return; + } + if (!paneSyncSelectMode) { + paneSyncSelectMode = true; + paneSyncSelection.clear(); + } + if (!paneSyncSelection.add(active)) { + paneSyncSelection.remove(active); + } + layoutVersion++; + } + + public void commitPaneSyncSelection() { + if (!paneSyncSelectMode) { + return; + } + paneSyncPanes.clear(); + paneSyncPanes.addAll(paneSyncSelection); + paneSyncSelection.clear(); + paneSyncSelectMode = false; + prunePaneSyncState(); + layoutVersion++; + } + + public void endPaneSync() { + if (!paneSyncSelectMode && paneSyncSelection.isEmpty() && paneSyncPanes.isEmpty()) { + return; + } + paneSyncSelectMode = false; + paneSyncSelection.clear(); + paneSyncPanes.clear(); + layoutVersion++; + } + + public List paneSyncPeers(TerminalPane source) { + prunePaneSyncState(); + if (source == null || !paneSyncPanes.contains(source)) { + return List.of(); + } + return paneSyncPanes.stream() + .filter(pane -> pane != source) + .toList(); + } + public void toggleFloating() { mutateCurrentTab(() -> currentTab().toggleFloating()); } @@ -190,6 +247,7 @@ public final class Compositor { for (int i = 0; i < tabs.size(); i++) { Tab tab = tabs.get(i); if (tab.closePane(pane)) { + removePaneFromSyncState(pane); if (tab.isEmpty()) { // Closing a tab's last pane closes the tab. Keep currentTabIndex pointing at the // same tab (or clamp it when the current/last tab went away). @@ -238,6 +296,9 @@ public final class Compositor { tab.close(); } tabs.clear(); + paneSyncSelectMode = false; + paneSyncSelection.clear(); + paneSyncPanes.clear(); } /** @@ -377,6 +438,7 @@ public final class Compositor { for (TerminalPane pane : panes) { paneContentVersion.put(pane, pane.paintFull(gc, isActive(pane))); } + drawPaneSyncOverlay(gc, panes); imageOverlay.sync(panes); } @@ -396,6 +458,7 @@ public final class Compositor { paneContentVersion.put(pane, pane.paintIncremental(gc, isActive(pane))); imageOverlay.updatePane(pane); } + drawPaneSyncOverlay(gc, panes); } private GraphicsContext beginFrame() { @@ -431,6 +494,63 @@ public final class Compositor { gc.setFontSmoothingType(FontSmoothingType.LCD); } + private void drawPaneSyncOverlay(GraphicsContext gc, List panes) { + Set highlighted = paneSyncSelectMode ? paneSyncSelection : paneSyncPanes; + if (highlighted.isEmpty()) { + return; + } + + gc.save(); + try { + gc.setLineWidth(4.0); + gc.setStroke(paneSyncSelectMode ? PANE_SYNC_SELECT_BORDER : PANE_SYNC_COMMITTED_BORDER); + for (TerminalPane pane : panes) { + if (!highlighted.contains(pane)) { + continue; + } + gc.save(); + double x = Math.round(pane.x()) + 2.0; + double y = Math.round(pane.y()) + 2.0; + double width = Math.max(0.0, pane.width() - 4.0); + double height = Math.max(0.0, pane.height() - 4.0); + TerminalRenderer.clip(gc, Math.round(pane.x()), Math.round(pane.y()), pane.width(), pane.height(), pane.clip()); + gc.strokeRect(x, y, width, height); + gc.restore(); + } + } finally { + gc.restore(); + } + } + + private void removePaneFromSyncState(TerminalPane pane) { + boolean changed = paneSyncSelection.remove(pane); + changed |= paneSyncPanes.remove(pane); + if (paneSyncPanes.size() < 2) { + changed |= !paneSyncPanes.isEmpty(); + paneSyncPanes.clear(); + } + if (changed) { + layoutVersion++; + } + } + + private void prunePaneSyncState() { + Set live = livePanes(); + paneSyncSelection.retainAll(live); + paneSyncPanes.retainAll(live); + if (paneSyncPanes.size() < 2) { + paneSyncPanes.clear(); + } + } + + private Set livePanes() { + Set live = new HashSet<>(); + for (Tab tab : tabs) { + live.addAll(tab.allPanes()); + } + return live; + } + // ---- Input ---------------------------------------------------------------------- private void handleMousePressed(MouseEvent event) { diff --git a/src/main/java/com/gregor/jprototerm/TerminalWindow.java b/src/main/java/com/gregor/jprototerm/TerminalWindow.java index 1630375..9435451 100644 --- a/src/main/java/com/gregor/jprototerm/TerminalWindow.java +++ b/src/main/java/com/gregor/jprototerm/TerminalWindow.java @@ -72,6 +72,9 @@ final class TerminalWindow { keyActions.put("open_font_selector", this::openFontSelector); keyActions.put("open_scrollback", this::openScrollbackInEditor); keyActions.put("create_worktree", this::createWorktreeInEditor); + keyActions.put("pane_sync_select", compositor::togglePaneSyncSelection); + keyActions.put("pane_sync_commit", compositor::commitPaneSyncSelection); + keyActions.put("pane_sync_end", compositor::endPaneSync); keyActions.put("paste", this::pasteFromClipboard); StackPane root = new StackPane(compositor.canvas(), compositor.imageOverlay()); @@ -132,18 +135,47 @@ final class TerminalWindow { private void handlePressed(KeyEvent event) { for (Map.Entry action : keyActions.entrySet()) { if (config.keybindings().get(action.getKey()).matches(event)) { + if (compositor.isPaneSyncSelecting() && !allowedDuringPaneSyncSelection(action.getKey())) { + event.consume(); + return; + } action.getValue().run(); event.consume(); return; } } + if (compositor.isPaneSyncSelecting()) { + event.consume(); + return; + } String encoded = KeyEncoder.encode(event); if (encoded != null) { sendToActivePane(encoded, event); } } + private static boolean allowedDuringPaneSyncSelection(String action) { + return switch (action) { + case "navigate_left", + "navigate_down", + "navigate_up", + "navigate_right", + "toggle_floating", + "next_floating", + "previous_tab", + "next_tab", + "pane_sync_select", + "pane_sync_commit", + "pane_sync_end" -> true; + default -> false; + }; + } + private void handleTyped(KeyEvent event) { + if (compositor.isPaneSyncSelecting()) { + event.consume(); + return; + } if (event.isAltDown() || event.isControlDown() || event.isMetaDown()) { return; } @@ -160,6 +192,9 @@ final class TerminalWindow { TerminalPane active = compositor.activePane(); if (active != null) { active.send(text); + for (TerminalPane peer : compositor.paneSyncPeers(active)) { + peer.send(text); + } event.consume(); } } @@ -168,7 +203,11 @@ final class TerminalWindow { TerminalPane active = compositor.activePane(); Clipboard clipboard = Clipboard.getSystemClipboard(); if (active != null && clipboard.hasString()) { - active.paste(clipboard.getString()); + String text = clipboard.getString(); + active.paste(text); + for (TerminalPane peer : compositor.paneSyncPeers(active)) { + peer.paste(text); + } } } From 47b2daa782499bd01650122dcc2c3d1a5541f1e2 Mon Sep 17 00:00:00 2001 From: Gregor Lohaus Date: Fri, 19 Jun 2026 15:19:12 +0200 Subject: [PATCH 2/4] pane sync start != pane sync select --- README.md | 6 ++++-- config.example.toml | 3 ++- .../java/com/gregor/jprototerm/AppConfig.java | 4 +++- .../java/com/gregor/jprototerm/Compositor.java | 17 +++++++++++------ .../com/gregor/jprototerm/TerminalWindow.java | 9 +++++++-- 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index b4e4cd9..904a7c7 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,8 @@ next_tab = "ALT+SHIFT+L" open_font_selector = "ALT+T" open_scrollback = "ALT+S" create_worktree = "ALT+W" -pane_sync_select = "ALT+Y" +pane_sync_start = "ALT+Y" +pane_sync_select = "SPACE" pane_sync_commit = "ALT+SHIFT+Y" pane_sync_end = "ALT+U" paste = "CTRL+SHIFT+V" @@ -191,7 +192,8 @@ paste = "CTRL+SHIFT+V" - `Alt+s`: open the active pane scrollback in `$EDITOR` - `Alt+w`: edit a worktree name, then run `git worktree add /` from the previously focused pane's working directory -- `Alt+y`: enter pane-sync selection mode and toggle the focused pane in the sync set +- `Alt+y`: enter pane-sync selection mode +- `Space`: toggle the focused pane in the sync set while pane-sync selection mode is active - `Alt+Shift+y`: commit the current pane-sync selection; input typed or pasted into any synced pane is mirrored to the other synced panes - `Alt+u`: end pane sync diff --git a/config.example.toml b/config.example.toml index dc90429..ddde5f3 100644 --- a/config.example.toml +++ b/config.example.toml @@ -41,7 +41,8 @@ next_tab = "ALT+SHIFT+L" open_font_selector = "ALT+T" open_scrollback = "ALT+S" create_worktree = "ALT+W" -pane_sync_select = "ALT+Y" +pane_sync_start = "ALT+Y" +pane_sync_select = "SPACE" pane_sync_commit = "ALT+SHIFT+Y" pane_sync_end = "ALT+U" paste = "CTRL+SHIFT+V" diff --git a/src/main/java/com/gregor/jprototerm/AppConfig.java b/src/main/java/com/gregor/jprototerm/AppConfig.java index b390c95..6e1e3b3 100644 --- a/src/main/java/com/gregor/jprototerm/AppConfig.java +++ b/src/main/java/com/gregor/jprototerm/AppConfig.java @@ -50,6 +50,7 @@ public record AppConfig( "open_font_selector", "open_scrollback", "create_worktree", + "pane_sync_start", "pane_sync_select", "pane_sync_commit", "pane_sync_end", @@ -119,7 +120,8 @@ public record AppConfig( Map.entry("open_font_selector", KeyBinding.parse("ALT+T")), Map.entry("open_scrollback", KeyBinding.parse("ALT+S")), Map.entry("create_worktree", KeyBinding.parse("ALT+W")), - Map.entry("pane_sync_select", KeyBinding.parse("ALT+Y")), + Map.entry("pane_sync_start", KeyBinding.parse("ALT+Y")), + Map.entry("pane_sync_select", KeyBinding.parse("SPACE")), Map.entry("pane_sync_commit", KeyBinding.parse("ALT+SHIFT+Y")), Map.entry("pane_sync_end", KeyBinding.parse("ALT+U")), Map.entry("paste", KeyBinding.parse("CTRL+SHIFT+V")) diff --git a/src/main/java/com/gregor/jprototerm/Compositor.java b/src/main/java/com/gregor/jprototerm/Compositor.java index a14cf5a..694571c 100644 --- a/src/main/java/com/gregor/jprototerm/Compositor.java +++ b/src/main/java/com/gregor/jprototerm/Compositor.java @@ -144,14 +144,19 @@ public final class Compositor { return paneSyncSelectMode; } - public void togglePaneSyncSelection() { - TerminalPane active = activePane(); - if (active == null) { + public void startPaneSyncSelection() { + if (activePane() == null || paneSyncSelectMode) { return; } - if (!paneSyncSelectMode) { - paneSyncSelectMode = true; - paneSyncSelection.clear(); + paneSyncSelectMode = true; + paneSyncSelection.clear(); + layoutVersion++; + } + + public void togglePaneSyncSelection() { + TerminalPane active = activePane(); + if (active == null || !paneSyncSelectMode) { + return; } if (!paneSyncSelection.add(active)) { paneSyncSelection.remove(active); diff --git a/src/main/java/com/gregor/jprototerm/TerminalWindow.java b/src/main/java/com/gregor/jprototerm/TerminalWindow.java index 9435451..25adc8d 100644 --- a/src/main/java/com/gregor/jprototerm/TerminalWindow.java +++ b/src/main/java/com/gregor/jprototerm/TerminalWindow.java @@ -72,6 +72,7 @@ final class TerminalWindow { keyActions.put("open_font_selector", this::openFontSelector); keyActions.put("open_scrollback", this::openScrollbackInEditor); keyActions.put("create_worktree", this::createWorktreeInEditor); + keyActions.put("pane_sync_start", compositor::startPaneSyncSelection); keyActions.put("pane_sync_select", compositor::togglePaneSyncSelection); keyActions.put("pane_sync_commit", compositor::commitPaneSyncSelection); keyActions.put("pane_sync_end", compositor::endPaneSync); @@ -134,8 +135,12 @@ final class TerminalWindow { private void handlePressed(KeyEvent event) { for (Map.Entry action : keyActions.entrySet()) { - if (config.keybindings().get(action.getKey()).matches(event)) { - if (compositor.isPaneSyncSelecting() && !allowedDuringPaneSyncSelection(action.getKey())) { + String actionName = action.getKey(); + if (config.keybindings().get(actionName).matches(event)) { + if (actionName.equals("pane_sync_select") && !compositor.isPaneSyncSelecting()) { + continue; + } + if (compositor.isPaneSyncSelecting() && !allowedDuringPaneSyncSelection(actionName)) { event.consume(); return; } From a6a700b2c09945696c965db5affb1b1f3a99e0bd Mon Sep 17 00:00:00 2001 From: Gregor Lohaus Date: Fri, 19 Jun 2026 15:21:59 +0200 Subject: [PATCH 3/4] panesync start + panesync end = panesync toggle --- README.md | 6 ++---- config.example.toml | 3 +-- .../java/com/gregor/jprototerm/AppConfig.java | 6 ++---- .../com/gregor/jprototerm/Compositor.java | 21 ++++++++----------- .../com/gregor/jprototerm/TerminalWindow.java | 7 +++---- 5 files changed, 17 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 904a7c7..26bf827 100644 --- a/README.md +++ b/README.md @@ -170,10 +170,9 @@ next_tab = "ALT+SHIFT+L" open_font_selector = "ALT+T" open_scrollback = "ALT+S" create_worktree = "ALT+W" -pane_sync_start = "ALT+Y" +pane_sync_toggle = "ALT+Y" pane_sync_select = "SPACE" pane_sync_commit = "ALT+SHIFT+Y" -pane_sync_end = "ALT+U" paste = "CTRL+SHIFT+V" ``` @@ -192,11 +191,10 @@ paste = "CTRL+SHIFT+V" - `Alt+s`: open the active pane scrollback in `$EDITOR` - `Alt+w`: edit a worktree name, then run `git worktree add /` from the previously focused pane's working directory -- `Alt+y`: enter pane-sync selection mode +- `Alt+y`: enter pane-sync selection mode, cancel selection mode, or stop an active pane sync - `Space`: toggle the focused pane in the sync set while pane-sync selection mode is active - `Alt+Shift+y`: commit the current pane-sync selection; input typed or pasted into any synced pane is mirrored to the other synced panes -- `Alt+u`: end pane sync - `Ctrl+Shift+v`: paste - Font default: `JetBrainsMono Nerd Font` - Kitty graphics protocol parsing is enabled by default diff --git a/config.example.toml b/config.example.toml index ddde5f3..e1a0211 100644 --- a/config.example.toml +++ b/config.example.toml @@ -41,8 +41,7 @@ next_tab = "ALT+SHIFT+L" open_font_selector = "ALT+T" open_scrollback = "ALT+S" create_worktree = "ALT+W" -pane_sync_start = "ALT+Y" +pane_sync_toggle = "ALT+Y" pane_sync_select = "SPACE" pane_sync_commit = "ALT+SHIFT+Y" -pane_sync_end = "ALT+U" paste = "CTRL+SHIFT+V" diff --git a/src/main/java/com/gregor/jprototerm/AppConfig.java b/src/main/java/com/gregor/jprototerm/AppConfig.java index 6e1e3b3..016e2ad 100644 --- a/src/main/java/com/gregor/jprototerm/AppConfig.java +++ b/src/main/java/com/gregor/jprototerm/AppConfig.java @@ -50,10 +50,9 @@ public record AppConfig( "open_font_selector", "open_scrollback", "create_worktree", - "pane_sync_start", + "pane_sync_toggle", "pane_sync_select", "pane_sync_commit", - "pane_sync_end", "paste" ); @@ -120,10 +119,9 @@ public record AppConfig( Map.entry("open_font_selector", KeyBinding.parse("ALT+T")), Map.entry("open_scrollback", KeyBinding.parse("ALT+S")), Map.entry("create_worktree", KeyBinding.parse("ALT+W")), - Map.entry("pane_sync_start", KeyBinding.parse("ALT+Y")), + Map.entry("pane_sync_toggle", KeyBinding.parse("ALT+Y")), Map.entry("pane_sync_select", KeyBinding.parse("SPACE")), Map.entry("pane_sync_commit", KeyBinding.parse("ALT+SHIFT+Y")), - Map.entry("pane_sync_end", KeyBinding.parse("ALT+U")), Map.entry("paste", KeyBinding.parse("CTRL+SHIFT+V")) ) ); diff --git a/src/main/java/com/gregor/jprototerm/Compositor.java b/src/main/java/com/gregor/jprototerm/Compositor.java index 694571c..c16f310 100644 --- a/src/main/java/com/gregor/jprototerm/Compositor.java +++ b/src/main/java/com/gregor/jprototerm/Compositor.java @@ -144,8 +144,15 @@ public final class Compositor { return paneSyncSelectMode; } - public void startPaneSyncSelection() { - if (activePane() == null || paneSyncSelectMode) { + public void togglePaneSync() { + if (paneSyncSelectMode || !paneSyncPanes.isEmpty()) { + paneSyncSelectMode = false; + paneSyncSelection.clear(); + paneSyncPanes.clear(); + layoutVersion++; + return; + } + if (activePane() == null) { return; } paneSyncSelectMode = true; @@ -176,16 +183,6 @@ public final class Compositor { layoutVersion++; } - public void endPaneSync() { - if (!paneSyncSelectMode && paneSyncSelection.isEmpty() && paneSyncPanes.isEmpty()) { - return; - } - paneSyncSelectMode = false; - paneSyncSelection.clear(); - paneSyncPanes.clear(); - layoutVersion++; - } - public List paneSyncPeers(TerminalPane source) { prunePaneSyncState(); if (source == null || !paneSyncPanes.contains(source)) { diff --git a/src/main/java/com/gregor/jprototerm/TerminalWindow.java b/src/main/java/com/gregor/jprototerm/TerminalWindow.java index 25adc8d..ffea88d 100644 --- a/src/main/java/com/gregor/jprototerm/TerminalWindow.java +++ b/src/main/java/com/gregor/jprototerm/TerminalWindow.java @@ -72,10 +72,9 @@ final class TerminalWindow { keyActions.put("open_font_selector", this::openFontSelector); keyActions.put("open_scrollback", this::openScrollbackInEditor); keyActions.put("create_worktree", this::createWorktreeInEditor); - keyActions.put("pane_sync_start", compositor::startPaneSyncSelection); + keyActions.put("pane_sync_toggle", compositor::togglePaneSync); keyActions.put("pane_sync_select", compositor::togglePaneSyncSelection); keyActions.put("pane_sync_commit", compositor::commitPaneSyncSelection); - keyActions.put("pane_sync_end", compositor::endPaneSync); keyActions.put("paste", this::pasteFromClipboard); StackPane root = new StackPane(compositor.canvas(), compositor.imageOverlay()); @@ -169,9 +168,9 @@ final class TerminalWindow { "next_floating", "previous_tab", "next_tab", + "pane_sync_toggle", "pane_sync_select", - "pane_sync_commit", - "pane_sync_end" -> true; + "pane_sync_commit" -> true; default -> false; }; } From 7367b2f778cf1f168e114024b25435bad4993a8e Mon Sep 17 00:00:00 2001 From: Gregor Lohaus Date: Fri, 19 Jun 2026 15:26:00 +0200 Subject: [PATCH 4/4] panesync toggle <- panesync commit --- README.md | 6 ++---- config.example.toml | 1 - .../java/com/gregor/jprototerm/AppConfig.java | 2 -- .../com/gregor/jprototerm/Compositor.java | 21 +++++++------------ .../com/gregor/jprototerm/TerminalWindow.java | 4 +--- 5 files changed, 11 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 26bf827..60558a9 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,6 @@ open_scrollback = "ALT+S" create_worktree = "ALT+W" pane_sync_toggle = "ALT+Y" pane_sync_select = "SPACE" -pane_sync_commit = "ALT+SHIFT+Y" paste = "CTRL+SHIFT+V" ``` @@ -191,10 +190,9 @@ paste = "CTRL+SHIFT+V" - `Alt+s`: open the active pane scrollback in `$EDITOR` - `Alt+w`: edit a worktree name, then run `git worktree add /` from the previously focused pane's working directory -- `Alt+y`: enter pane-sync selection mode, cancel selection mode, or stop an active pane sync +- `Alt+y`: enter pane-sync selection mode, commit the selection, or stop an active pane sync - `Space`: toggle the focused pane in the sync set while pane-sync selection mode is active -- `Alt+Shift+y`: commit the current pane-sync selection; input typed or pasted into any synced - pane is mirrored to the other synced panes +- Once committed, input typed or pasted into any synced pane is mirrored to the other synced panes - `Ctrl+Shift+v`: paste - Font default: `JetBrainsMono Nerd Font` - Kitty graphics protocol parsing is enabled by default diff --git a/config.example.toml b/config.example.toml index e1a0211..a3875f6 100644 --- a/config.example.toml +++ b/config.example.toml @@ -43,5 +43,4 @@ open_scrollback = "ALT+S" create_worktree = "ALT+W" pane_sync_toggle = "ALT+Y" pane_sync_select = "SPACE" -pane_sync_commit = "ALT+SHIFT+Y" paste = "CTRL+SHIFT+V" diff --git a/src/main/java/com/gregor/jprototerm/AppConfig.java b/src/main/java/com/gregor/jprototerm/AppConfig.java index 016e2ad..876c875 100644 --- a/src/main/java/com/gregor/jprototerm/AppConfig.java +++ b/src/main/java/com/gregor/jprototerm/AppConfig.java @@ -52,7 +52,6 @@ public record AppConfig( "create_worktree", "pane_sync_toggle", "pane_sync_select", - "pane_sync_commit", "paste" ); @@ -121,7 +120,6 @@ public record AppConfig( Map.entry("create_worktree", KeyBinding.parse("ALT+W")), Map.entry("pane_sync_toggle", KeyBinding.parse("ALT+Y")), Map.entry("pane_sync_select", KeyBinding.parse("SPACE")), - Map.entry("pane_sync_commit", KeyBinding.parse("ALT+SHIFT+Y")), Map.entry("paste", KeyBinding.parse("CTRL+SHIFT+V")) ) ); diff --git a/src/main/java/com/gregor/jprototerm/Compositor.java b/src/main/java/com/gregor/jprototerm/Compositor.java index c16f310..f53ef55 100644 --- a/src/main/java/com/gregor/jprototerm/Compositor.java +++ b/src/main/java/com/gregor/jprototerm/Compositor.java @@ -145,9 +145,16 @@ public final class Compositor { } public void togglePaneSync() { - if (paneSyncSelectMode || !paneSyncPanes.isEmpty()) { + if (paneSyncSelectMode) { + paneSyncPanes.clear(); + paneSyncPanes.addAll(paneSyncSelection); paneSyncSelectMode = false; paneSyncSelection.clear(); + prunePaneSyncState(); + layoutVersion++; + return; + } + if (!paneSyncPanes.isEmpty()) { paneSyncPanes.clear(); layoutVersion++; return; @@ -171,18 +178,6 @@ public final class Compositor { layoutVersion++; } - public void commitPaneSyncSelection() { - if (!paneSyncSelectMode) { - return; - } - paneSyncPanes.clear(); - paneSyncPanes.addAll(paneSyncSelection); - paneSyncSelection.clear(); - paneSyncSelectMode = false; - prunePaneSyncState(); - layoutVersion++; - } - public List paneSyncPeers(TerminalPane source) { prunePaneSyncState(); if (source == null || !paneSyncPanes.contains(source)) { diff --git a/src/main/java/com/gregor/jprototerm/TerminalWindow.java b/src/main/java/com/gregor/jprototerm/TerminalWindow.java index ffea88d..0601b5a 100644 --- a/src/main/java/com/gregor/jprototerm/TerminalWindow.java +++ b/src/main/java/com/gregor/jprototerm/TerminalWindow.java @@ -74,7 +74,6 @@ final class TerminalWindow { keyActions.put("create_worktree", this::createWorktreeInEditor); keyActions.put("pane_sync_toggle", compositor::togglePaneSync); keyActions.put("pane_sync_select", compositor::togglePaneSyncSelection); - keyActions.put("pane_sync_commit", compositor::commitPaneSyncSelection); keyActions.put("paste", this::pasteFromClipboard); StackPane root = new StackPane(compositor.canvas(), compositor.imageOverlay()); @@ -169,8 +168,7 @@ final class TerminalWindow { "previous_tab", "next_tab", "pane_sync_toggle", - "pane_sync_select", - "pane_sync_commit" -> true; + "pane_sync_select" -> true; default -> false; }; }