diff --git a/CHANGELOG.md b/CHANGELOG.md
index 92544b68..716855cf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,5 @@
- Fixed F5 not refreshing the Markdown Preview from disk
-- Added font zoom (Alt+Scroll) for Terminal and Markdown Preview; Ctrl+0 resets; right-click "Zoom" submenu
+- Added font zoom (Alt+Scroll / Ctrl+= / Ctrl+−) for Terminal and Markdown Preview; Ctrl+0 or Alt+Click Wheel resets; right-click "Zoom" submenu
- Added Find bar (Ctrl+F / F3 / Shift+F3) to Markdown Preview
- Added automatic Markdown Preview tab when Claude writes a plan file (configurable in Settings → General)
- Added `show_markdown` MCP tool: displays markdown text in a NetBeans Markdown Preview tab
diff --git a/pom.xml b/pom.xml
index 1c3659a7..93bc4b32 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
io.github.nbclaudecodegui
netbeans-claude-code-gui
- 0.23.18-SNAPSHOT
+ 0.23.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/nbclaudecodegui/ui/ClaudeSessionTab.java b/src/main/java/io/github/nbclaudecodegui/ui/ClaudeSessionTab.java
index f73b519a..74cde8d2 100644
--- a/src/main/java/io/github/nbclaudecodegui/ui/ClaudeSessionTab.java
+++ b/src/main/java/io/github/nbclaudecodegui/ui/ClaudeSessionTab.java
@@ -188,12 +188,14 @@ public class ClaudeSessionTab extends TopComponent
private final KeyEventDispatcher zoomKeyInterceptor = e -> {
if (e.getID() != KeyEvent.KEY_PRESSED || terminalWidget == null) return false;
if (!SwingUtilities.isDescendingFrom(e.getComponent(), terminalWidget)) return false;
- if (e.getKeyCode() == KeyEvent.VK_0
- && (e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0) {
- SwingUtilities.invokeLater(this::resetZoom);
- return true;
+ 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;
+ default: return false;
}
- return false;
};
private JSplitPane splitPane;
private JPanel errorPanel;
@@ -904,9 +906,10 @@ public void componentResized(ComponentEvent e) {
widget.getTerminalPanel().addMouseWheelListener(
ZoomSupport.createWheelListener(this));
+ widget.getTerminalPanel().addMouseListener(ZoomSupport.createClickListener(this));
// The Zoom submenu is spliced into JediTerm's native context menu by
// ZoomableJediTermWidget (JediTerm ignores setComponentPopupMenu).
- ZoomSupport.bindResetKey(this, this);
+ ZoomSupport.bindZoomKeys(this, this);
add(splitPane, BorderLayout.CENTER);
revalidate();
diff --git a/src/main/java/io/github/nbclaudecodegui/ui/MarkdownPreviewTab.java b/src/main/java/io/github/nbclaudecodegui/ui/MarkdownPreviewTab.java
index 529e1611..70d8aeeb 100644
--- a/src/main/java/io/github/nbclaudecodegui/ui/MarkdownPreviewTab.java
+++ b/src/main/java/io/github/nbclaudecodegui/ui/MarkdownPreviewTab.java
@@ -174,7 +174,8 @@ public static void openLive(String filePath, String content, FileObject fo) {
tab.pane.setComponentPopupMenu(tab.buildContextMenu());
tab.initFindBar(tab.pane);
tab.pane.addMouseWheelListener(ZoomSupport.createWheelListener(tab, true));
- ZoomSupport.bindResetKey(tab.pane, tab);
+ tab.pane.addMouseListener(ZoomSupport.createClickListener(tab));
+ ZoomSupport.bindZoomKeys(tab.pane, tab);
tab.applyMdZoom(tab.mdZoomDelta);
if (fo != null) {
@@ -704,7 +705,8 @@ protected void componentOpened() {
pane.setComponentPopupMenu(buildContextMenu());
initFindBar(pane);
pane.addMouseWheelListener(ZoomSupport.createWheelListener(this, true));
- ZoomSupport.bindResetKey(pane, this);
+ pane.addMouseListener(ZoomSupport.createClickListener(this));
+ ZoomSupport.bindZoomKeys(pane, this);
applyMdZoom(mdZoomDelta);
}
diff --git a/src/main/java/io/github/nbclaudecodegui/ui/common/ZoomSupport.java b/src/main/java/io/github/nbclaudecodegui/ui/common/ZoomSupport.java
index 397cef9d..9319e7fa 100644
--- a/src/main/java/io/github/nbclaudecodegui/ui/common/ZoomSupport.java
+++ b/src/main/java/io/github/nbclaudecodegui/ui/common/ZoomSupport.java
@@ -4,6 +4,8 @@
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.AbstractAction;
@@ -92,13 +94,13 @@ private static void forwardToScrollAncestor(MouseWheelEvent e) {
public static JMenu buildZoomMenu(Zoomable zoomable) {
JMenu menu = new JMenu("Zoom");
- JMenuItem increase = new JMenuItem("Increase (Alt+Scroll Up)");
+ JMenuItem increase = new JMenuItem("Increase (Alt+Scroll Up / Ctrl+=)");
increase.addActionListener(e -> zoomable.zoomIn());
- JMenuItem decrease = new JMenuItem("Decrease (Alt+Scroll Down)");
+ JMenuItem decrease = new JMenuItem("Decrease (Alt+Scroll Down / Ctrl+−)");
decrease.addActionListener(e -> zoomable.zoomOut());
- JMenuItem reset = new JMenuItem("Reset (Ctrl+0)");
+ JMenuItem reset = new JMenuItem("Reset (Ctrl+0 / Alt+Click Wheel)");
reset.addActionListener(e -> zoomable.resetZoom());
menu.add(increase);
@@ -130,14 +132,36 @@ public static void appendZoomMenu(JPopupMenu menu, Zoomable zoomable) {
menu.add(buildZoomMenu(zoomable));
}
- /** Binds Ctrl+0 → resetZoom() on the given component via InputMap/ActionMap. */
- public static void bindResetKey(JComponent comp, Zoomable zoomable) {
- KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_0, InputEvent.CTRL_DOWN_MASK);
- // WHEN_FOCUSED overrides the NetBeans global Ctrl+0 action while comp is focused
- comp.getInputMap(JComponent.WHEN_FOCUSED).put(ks, "zoom-reset");
- comp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ks, "zoom-reset");
- comp.getActionMap().put("zoom-reset", new AbstractAction() {
- @Override public void actionPerformed(ActionEvent e) { zoomable.resetZoom(); }
+ /**
+ * Binds Ctrl+0 (reset), Ctrl+− (out), and Ctrl+= (in) on the given component
+ * via InputMap/ActionMap. Both WHEN_FOCUSED and WHEN_IN_FOCUSED_WINDOW maps are
+ * 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);
+ }
+
+ /** Returns a MouseAdapter that resets zoom on Alt+middle-click (button 2). */
+ public static MouseAdapter createClickListener(Zoomable zoomable) {
+ return new MouseAdapter() {
+ @Override public void mousePressed(MouseEvent e) {
+ if (e.getButton() == MouseEvent.BUTTON2
+ && (e.getModifiersEx() & InputEvent.ALT_DOWN_MASK) != 0) {
+ e.consume();
+ zoomable.resetZoom();
+ }
+ }
+ };
+ }
+
+ private static void bindKey(JComponent comp, int vk, String actionKey, Runnable action) {
+ KeyStroke ks = KeyStroke.getKeyStroke(vk, InputEvent.CTRL_DOWN_MASK);
+ comp.getInputMap(JComponent.WHEN_FOCUSED).put(ks, actionKey);
+ comp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ks, actionKey);
+ comp.getActionMap().put(actionKey, new AbstractAction() {
+ @Override public void actionPerformed(ActionEvent e) { action.run(); }
});
}
}
diff --git a/src/test/java/io/github/nbclaudecodegui/ui/common/ZoomSupportTest.java b/src/test/java/io/github/nbclaudecodegui/ui/common/ZoomSupportTest.java
index 730cccf6..724958a1 100644
--- a/src/test/java/io/github/nbclaudecodegui/ui/common/ZoomSupportTest.java
+++ b/src/test/java/io/github/nbclaudecodegui/ui/common/ZoomSupportTest.java
@@ -2,6 +2,8 @@
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.Action;
@@ -213,11 +215,51 @@ void createWheelListener_forwardTrue_altWheelStillZoomsAndIsNotForwarded() {
assertTrue(alt.isConsumed(), "zoom event must be consumed");
}
- // --- bindResetKey: Ctrl+0 must reach the focused surface (NetBeans steals
- // WHEN_IN_FOCUSED_WINDOW), so it must also be in WHEN_FOCUSED -----------
+ // --- bindZoomKeys: all three shortcuts registered in both input maps --------
@Test
- void bindResetKey_registersCtrl0InFocusedAndWindowMaps() {
+ void bindZoomKeys_registersCtrl0CtrlMinusCtrlEqualsInBothMaps() {
+ 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 ctrl0 = KeyStroke.getKeyStroke(KeyEvent.VK_0, ctrl);
+ KeyStroke ctrlMinus = KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, ctrl);
+ KeyStroke ctrlEqual = KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, ctrl);
+
+ for (int map : new int[]{JComponent.WHEN_FOCUSED, JComponent.WHEN_IN_FOCUSED_WINDOW}) {
+ assertEquals("zoom-reset", comp.getInputMap(map).get(ctrl0));
+ assertEquals("zoom-out", comp.getInputMap(map).get(ctrlMinus));
+ assertEquals("zoom-in", comp.getInputMap(map).get(ctrlEqual));
+ }
+
+ 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) {
+ return new MouseEvent(src, MouseEvent.MOUSE_PRESSED, 0L,
+ modifiers, 0, 0, 1, false, button);
+ }
+
+ @Test
+ void createClickListener_altMiddleClickResetsZoom() {
int[] resets = {0};
Zoomable z = new Zoomable() {
@Override public void zoomIn() {}
@@ -227,19 +269,43 @@ void bindResetKey_registersCtrl0InFocusedAndWindowMaps() {
@Override public int getMinDelta() { return -8; }
@Override public int getMaxDelta() { return 20; }
};
- JLabel comp = new JLabel();
+ MouseAdapter listener = ZoomSupport.createClickListener(z);
+ listener.mousePressed(mouseButton(new JLabel(), MouseEvent.BUTTON2,
+ InputEvent.ALT_DOWN_MASK | InputEvent.getMaskForButton(MouseEvent.BUTTON2)));
+ assertEquals(1, resets[0]);
+ }
- ZoomSupport.bindResetKey(comp, z);
-
- KeyStroke ctrl0 = KeyStroke.getKeyStroke(KeyEvent.VK_0, InputEvent.CTRL_DOWN_MASK);
- assertEquals("zoom-reset",
- comp.getInputMap(JComponent.WHEN_FOCUSED).get(ctrl0),
- "Ctrl+0 must be bound in WHEN_FOCUSED to beat the NetBeans global action");
- assertEquals("zoom-reset",
- comp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).get(ctrl0));
- Action action = comp.getActionMap().get("zoom-reset");
- assertNotNull(action);
- action.actionPerformed(null);
- assertEquals(1, resets[0], "the bound action must invoke resetZoom()");
+ @Test
+ void createClickListener_plainMiddleClickIgnored() {
+ int[] resets = {0};
+ Zoomable z = new Zoomable() {
+ @Override public void zoomIn() {}
+ @Override public void zoomOut() {}
+ @Override public void resetZoom() { resets[0]++; }
+ @Override public int getZoomDelta() { return 0; }
+ @Override public int getMinDelta() { return -8; }
+ @Override public int getMaxDelta() { return 20; }
+ };
+ MouseAdapter listener = ZoomSupport.createClickListener(z);
+ listener.mousePressed(mouseButton(new JLabel(), MouseEvent.BUTTON2,
+ InputEvent.getMaskForButton(MouseEvent.BUTTON2)));
+ assertEquals(0, resets[0]);
+ }
+
+ @Test
+ void createClickListener_altLeftClickIgnored() {
+ int[] resets = {0};
+ Zoomable z = new Zoomable() {
+ @Override public void zoomIn() {}
+ @Override public void zoomOut() {}
+ @Override public void resetZoom() { resets[0]++; }
+ @Override public int getZoomDelta() { return 0; }
+ @Override public int getMinDelta() { return -8; }
+ @Override public int getMaxDelta() { return 20; }
+ };
+ MouseAdapter listener = ZoomSupport.createClickListener(z);
+ listener.mousePressed(mouseButton(new JLabel(), MouseEvent.BUTTON1,
+ InputEvent.ALT_DOWN_MASK | InputEvent.getMaskForButton(MouseEvent.BUTTON1)));
+ assertEquals(0, resets[0]);
}
}