Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.nbplugins</groupId>
<artifactId>netbeans-plugin-claude-code-gui</artifactId>
<version>1.3.18-SNAPSHOT</version>
<version>1.3.19-SNAPSHOT</version>
<packaging>nbm</packaging>
<name>Netbeans Plugin Claude Code GUI</name>
<description>NetBeans plugin that provides a GUI for Claude Code CLI — run claude code sessions directly inside the IDE.</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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). */
Expand Down
Original file line number Diff line number Diff line change
@@ -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(() -> {});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading