diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c96746..24bde98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Fixed Ctrl++/− terminal and Markdown Preview zoom shortcuts not working when using the numpad +/− keys - Renamed the "Subscription" connection type to "Claude Subscription" in Profile settings, to distinguish it from the new "ChatGPT Subscription" connection type - Added support for signing in with a ChatGPT Plus/Pro/Team subscription as a new profile connection type, routing sessions through OpenAI's Codex backend instead of requiring an OpenAI API key; click "Copy sign in link" to copy the sign-in URL, open it in your own browser, then paste the resulting code back to complete sign-in — the plugin never opens a browser or waits on it automatically. Once signed in, "Model Aliases…" is available for this profile and fetches the live list of models available to your account instead of a fixed list; if the fetch fails, the upstream error is shown in the dialog's status line diff --git a/pom.xml b/pom.xml index 71b5ba2..c948cd1 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.github.nbplugins netbeans-plugin-claude-code-gui - 1.3.18-SNAPSHOT + 1.3.19-SNAPSHOT nbm Netbeans Plugin Claude Code GUI NetBeans plugin that provides a GUI for Claude Code CLI — run claude code sessions directly inside the IDE. diff --git a/src/main/java/io/github/nbplugins/claudecodegui/ui/ClaudeSessionTab.java b/src/main/java/io/github/nbplugins/claudecodegui/ui/ClaudeSessionTab.java index 767bdc8..22eda07 100644 --- a/src/main/java/io/github/nbplugins/claudecodegui/ui/ClaudeSessionTab.java +++ b/src/main/java/io/github/nbplugins/claudecodegui/ui/ClaudeSessionTab.java @@ -191,9 +191,12 @@ public class ClaudeSessionTab extends TopComponent boolean ctrl = (e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0; if (!ctrl) return false; switch (e.getKeyCode()) { - case KeyEvent.VK_0: SwingUtilities.invokeLater(this::resetZoom); return true; - case KeyEvent.VK_MINUS: SwingUtilities.invokeLater(this::zoomOut); return true; - case KeyEvent.VK_EQUALS: SwingUtilities.invokeLater(this::zoomIn); return true; + case KeyEvent.VK_0: + case KeyEvent.VK_NUMPAD0: SwingUtilities.invokeLater(this::resetZoom); return true; + case KeyEvent.VK_MINUS: + case KeyEvent.VK_SUBTRACT: SwingUtilities.invokeLater(this::zoomOut); return true; + case KeyEvent.VK_EQUALS: + case KeyEvent.VK_ADD: SwingUtilities.invokeLater(this::zoomIn); return true; default: return false; } }; diff --git a/src/main/java/io/github/nbplugins/claudecodegui/ui/common/ZoomSupport.java b/src/main/java/io/github/nbplugins/claudecodegui/ui/common/ZoomSupport.java index 409fae5..2a7fa1c 100644 --- a/src/main/java/io/github/nbplugins/claudecodegui/ui/common/ZoomSupport.java +++ b/src/main/java/io/github/nbplugins/claudecodegui/ui/common/ZoomSupport.java @@ -138,9 +138,12 @@ public static void appendZoomMenu(JPopupMenu menu, Zoomable zoomable) { * populated so the shortcuts work whether the component itself or a child has focus. */ public static void bindZoomKeys(JComponent comp, Zoomable zoomable) { - bindKey(comp, KeyEvent.VK_0, "zoom-reset", zoomable::resetZoom); - bindKey(comp, KeyEvent.VK_MINUS, "zoom-out", zoomable::zoomOut); - bindKey(comp, KeyEvent.VK_EQUALS, "zoom-in", zoomable::zoomIn); + bindKey(comp, KeyEvent.VK_0, "zoom-reset", zoomable::resetZoom); + bindKey(comp, KeyEvent.VK_NUMPAD0, "zoom-reset", zoomable::resetZoom); + bindKey(comp, KeyEvent.VK_MINUS, "zoom-out", zoomable::zoomOut); + bindKey(comp, KeyEvent.VK_SUBTRACT, "zoom-out", zoomable::zoomOut); + bindKey(comp, KeyEvent.VK_EQUALS, "zoom-in", zoomable::zoomIn); + bindKey(comp, KeyEvent.VK_ADD, "zoom-in", zoomable::zoomIn); } /** Returns a MouseAdapter that resets zoom on Alt+middle-click (button 2). */ diff --git a/src/test/java/io/github/nbplugins/claudecodegui/ui/ClaudeSessionTabZoomKeyTest.java b/src/test/java/io/github/nbplugins/claudecodegui/ui/ClaudeSessionTabZoomKeyTest.java new file mode 100644 index 0000000..fc4e2d0 --- /dev/null +++ b/src/test/java/io/github/nbplugins/claudecodegui/ui/ClaudeSessionTabZoomKeyTest.java @@ -0,0 +1,65 @@ +package io.github.nbplugins.claudecodegui.ui; + +import java.awt.KeyEventDispatcher; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.lang.reflect.Field; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for {@link ClaudeSessionTab}'s {@code zoomKeyInterceptor}, the + * {@code KeyEventDispatcher} that handles Ctrl+zoom shortcuts while focus is + * inside the embedded JediTerm widget (which otherwise consumes key events + * before Swing's InputMap machinery sees them). + */ +class ClaudeSessionTabZoomKeyTest { + + @SuppressWarnings("unchecked") + private static KeyEventDispatcher getInterceptor(ClaudeSessionTab tab) throws Exception { + Field f = ClaudeSessionTab.class.getDeclaredField("zoomKeyInterceptor"); + f.setAccessible(true); + return (KeyEventDispatcher) f.get(tab); + } + + private static void setTerminalWidget(ClaudeSessionTab tab, ZoomableJediTermWidget widget) throws Exception { + Field f = ClaudeSessionTab.class.getDeclaredField("terminalWidget"); + f.setAccessible(true); + f.set(tab, widget); + } + + private static KeyEvent keyPress(java.awt.Component src, int keyCode) { + return new KeyEvent(src, KeyEvent.KEY_PRESSED, 0L, InputEvent.CTRL_DOWN_MASK, + keyCode, KeyEvent.CHAR_UNDEFINED); + } + + @Test + void dispatchKeyEvent_ctrlNumpadPlusAndMinusTriggerZoom() throws Exception { + ClaudeSessionTab tab = new ClaudeSessionTab(); + ZoomableJediTermWidget widget = new ZoomableJediTermWidget(new NetBeansSettingsProvider(), tab); + setTerminalWidget(tab, widget); + KeyEventDispatcher interceptor = getInterceptor(tab); + + int deltaBefore = tab.getZoomDelta(); + + assertTrue(interceptor.dispatchKeyEvent(keyPress(widget, KeyEvent.VK_ADD)), + "Ctrl+NumpadPlus must be consumed"); + java.awt.Toolkit.getDefaultToolkit().sync(); + waitForEdt(); + assertEquals(deltaBefore + 1, tab.getZoomDelta(), "Ctrl+NumpadPlus must zoom in"); + + assertTrue(interceptor.dispatchKeyEvent(keyPress(widget, KeyEvent.VK_SUBTRACT)), + "Ctrl+NumpadMinus must be consumed"); + waitForEdt(); + assertEquals(deltaBefore, tab.getZoomDelta(), "Ctrl+NumpadMinus must zoom back out"); + + assertTrue(interceptor.dispatchKeyEvent(keyPress(widget, KeyEvent.VK_NUMPAD0)), + "Ctrl+Numpad0 must be consumed"); + waitForEdt(); + assertEquals(0, tab.getZoomDelta(), "Ctrl+Numpad0 must reset zoom"); + } + + private static void waitForEdt() throws Exception { + javax.swing.SwingUtilities.invokeAndWait(() -> {}); + } +} diff --git a/src/test/java/io/github/nbplugins/claudecodegui/ui/common/ZoomSupportTest.java b/src/test/java/io/github/nbplugins/claudecodegui/ui/common/ZoomSupportTest.java index ef4529f..47c5acd 100644 --- a/src/test/java/io/github/nbplugins/claudecodegui/ui/common/ZoomSupportTest.java +++ b/src/test/java/io/github/nbplugins/claudecodegui/ui/common/ZoomSupportTest.java @@ -251,6 +251,40 @@ void bindZoomKeys_registersCtrl0CtrlMinusCtrlEqualsInBothMaps() { assertEquals(1, ins[0]); } + @Test + void bindZoomKeys_registersNumpadPlusMinusAndZeroInBothMaps() { + int[] resets = {0}, outs = {0}, ins = {0}; + Zoomable z = new Zoomable() { + @Override public void zoomIn() { ins[0]++; } + @Override public void zoomOut() { outs[0]++; } + @Override public void resetZoom() { resets[0]++; } + @Override public int getZoomDelta() { return 0; } + @Override public int getMinDelta() { return -8; } + @Override public int getMaxDelta() { return 20; } + }; + JLabel comp = new JLabel(); + ZoomSupport.bindZoomKeys(comp, z); + + int ctrl = InputEvent.CTRL_DOWN_MASK; + KeyStroke ctrlNumpad0 = KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD0, ctrl); + KeyStroke ctrlNumMinus = KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, ctrl); + KeyStroke ctrlNumPlus = KeyStroke.getKeyStroke(KeyEvent.VK_ADD, ctrl); + + for (int map : new int[]{JComponent.WHEN_FOCUSED, JComponent.WHEN_IN_FOCUSED_WINDOW}) { + assertEquals("zoom-reset", comp.getInputMap(map).get(ctrlNumpad0)); + assertEquals("zoom-out", comp.getInputMap(map).get(ctrlNumMinus)); + assertEquals("zoom-in", comp.getInputMap(map).get(ctrlNumPlus)); + } + + comp.getActionMap().get("zoom-reset").actionPerformed(null); + comp.getActionMap().get("zoom-out").actionPerformed(null); + comp.getActionMap().get("zoom-in").actionPerformed(null); + + assertEquals(1, resets[0]); + assertEquals(1, outs[0]); + assertEquals(1, ins[0]); + } + // --- createClickListener --------------------------------------------------- private static MouseEvent mouseButton(java.awt.Component src, int button, int modifiers) {