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) {