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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
- Fixed digits going into the "Type something." text field instead of selecting the intended numbered option in planning and permission-prompt choice menus
- Fixed "Yes, and tell Claude what to do next" option not showing a text input field in bash permission prompts
- Fixed "Yes, and tell Claude what to do next" option in bash permission prompts: now shows radio label "Yes" with input hint in the text field (not as a subtitle), and allows sending with empty text
- Fixed session tab showing the old project display name after IDE restart (tab now refreshes its name once all open projects finish loading)
- Fixed model discovery failing when Enter was absorbed as a newline in the prompt instead of submitting the /model command (race condition under load)

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.nbclaudecodegui</groupId>
<artifactId>netbeans-claude-code-gui</artifactId>
<version>0.22.43-SNAPSHOT</version>
<version>0.22.46-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 @@ -515,12 +515,13 @@ public void writePtyAnswer(String answer) {
int sep = answer.indexOf(':', 5);
String digit = answer.substring(5, sep);
String text = answer.substring(sep + 1);
ChoiceMenuModel current = model.getActiveChoiceMenu();
ChoiceMenuModel current = detectCurrentMenu();
int targetIdx = Integer.parseInt(digit) - 1;
// Scan for the text-input option index (same logic as plain-digit branch)
int typeIdx = findTextInputIdx(current);
final int currentIdx = typeIdx >= 0 ? typeIdx : targetIdx;
boolean currentHasTextInput = typeIdx >= 0;
// currentIdx = actual PTY cursor position (re-read from screen); currentHasTextInput
// = cursor is on a text-input option (digits go to its text field, not to navigation).
final int currentIdx = current != null ? current.defaultOptionIndex() : targetIdx;
boolean currentHasTextInput = current != null
&& isTextInputOption(current.options().get(currentIdx));
LOG.fine("[PTY write] type-input digit=" + digit + " text=" + text
+ " currentHasTextInput=" + currentHasTextInput);
Thread t = new Thread(() -> {
Expand Down Expand Up @@ -582,17 +583,16 @@ public void writePtyAnswer(String answer) {
t.start();
}
} else {
ChoiceMenuModel current = model.getActiveChoiceMenu();
// Find the text-input option index (if any). Claude Code routes ALL digit input
// to the text field regardless of which option the ❯ cursor is on, so we must
// check the whole option list, not just defaultOptionIndex.
int textInputIdx = findTextInputIdx(current);
boolean currentHasTextInput = textInputIdx >= 0;
LOG.fine("[PTY write] else-branch answer=" + answer + " textInputIdx=" + textInputIdx);
ChoiceMenuModel current = detectCurrentMenu();
// currentHasTextInput = PTY cursor (❯) is on a text-input option (re-read from
// screen); digits go into its text field, so use arrow-key navigation instead.
int currentIdx2 = current != null ? current.defaultOptionIndex() : 0;
boolean currentHasTextInput = current != null
&& isTextInputOption(current.options().get(currentIdx2));
LOG.fine("[PTY write] else-branch answer=" + answer + " currentIdx=" + currentIdx2 + " currentHasTextInput=" + currentHasTextInput);
if (currentHasTextInput && answer.matches("[0-9]")) {
// Claude Code routes digits to the text-input field — navigate with arrow keys instead
int targetIdx = answer.charAt(0) - '1';
int currentIdx = textInputIdx; // assume cursor is at the text-input option
int currentIdx = currentIdx2;
int delta = targetIdx - currentIdx;
LOG.fine("[PTY write] ARROW-digit targetIdx=" + targetIdx + " currentIdx=" + currentIdx + " delta=" + delta);
model.clearChoiceMenu();
Expand Down Expand Up @@ -1260,14 +1260,10 @@ private static boolean isTextInputOption(ChoiceMenuModel.Option opt) {
return opt.hasTextInput() || opt.display().trim().toLowerCase().startsWith("type");
}

/** Returns the 0-based index of the first text-input option in the menu, or -1 if none. */
private static int findTextInputIdx(ChoiceMenuModel menu) {
if (menu == null) return -1;
List<ChoiceMenuModel.Option> opts = menu.options();
for (int i = 0; i < opts.size(); i++) {
if (isTextInputOption(opts.get(i))) return i;
}
return -1;
/** Re-reads the current screen to get the up-to-date choice menu (PTY cursor may have moved). */
private ChoiceMenuModel detectCurrentMenu() {
List<String> lines = screenLines.get();
return screenContentDetector.detectChoiceMenu(lines).orElse(model.getActiveChoiceMenu());
}

private static boolean optionsEqual(List<ChoiceMenuModel.Option> a, List<ChoiceMenuModel.Option> b) {
Expand Down
23 changes: 15 additions & 8 deletions src/main/java/io/github/nbclaudecodegui/ui/ChoiceMenuPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,12 @@ public void show(ChoiceMenuModel model, Consumer<String> callback, boolean grabF
leftCol.add(typeRow);

} else if (isTypeInputOption(opt)) {
// Type-input: radio button + adjacent text field
JRadioButton rb = new JRadioButton(" ");
// Type-input: radio button + adjacent text field.
// When description is set, display is a label ("Yes") and description is
// the input hint ("and tell Claude what to do next"). Otherwise display
// itself is the hint ("Type something.") and the radio gets a blank label.
boolean hasLabel = opt.description() != null && !opt.description().isBlank();
JRadioButton rb = new JRadioButton(hasLabel ? opt.display().trim() : " ");
rb.setAlignmentX(Component.LEFT_ALIGNMENT);
rb.setName("typeInputRb");
group.add(rb);
Expand All @@ -188,7 +192,8 @@ public void show(ChoiceMenuModel model, Consumer<String> callback, boolean grabF
tf.setMaximumSize(new java.awt.Dimension(
Integer.MAX_VALUE, tf.getPreferredSize().height));
tf.setEnabled(false);
setPlaceholder(tf, opt.display().trim());
String hintText = hasLabel ? opt.description().trim() : opt.display().trim();
setPlaceholder(tf, hintText);
rb.addChangeListener(e -> tf.setEnabled(rb.isSelected()));
tf.addFocusListener(new java.awt.event.FocusAdapter() {
@Override public void focusGained(java.awt.event.FocusEvent e) {
Expand Down Expand Up @@ -221,8 +226,8 @@ public void show(ChoiceMenuModel model, Consumer<String> callback, boolean grabF
leftCol.add(rb);
}

// Description subtitle
if (opt.description() != null && !opt.description().isBlank()) {
// Description subtitle — skip for type-input options (description is the field hint)
if (opt.description() != null && !opt.description().isBlank() && !isTypeInputOption(opt)) {
JLabel desc = new JLabel("<html><small>" + escapeHtml(opt.description()) + "</small></html>");
desc.setForeground(Color.GRAY);
desc.setAlignmentX(Component.LEFT_ALIGNMENT);
Expand Down Expand Up @@ -313,14 +318,16 @@ public void keyPressed(java.awt.event.KeyEvent e) {
if (rb != null && rb.isSelected()) {
ChoiceMenuModel.Option opt = otherOptions.get(i);
if (isTypeInputOption(opt) && finalTypeFields[i] != null) {
String hint = opt.display().trim();
boolean hasLabel = opt.description() != null && !opt.description().isBlank();
String hint = hasLabel ? opt.description().trim() : opt.display().trim();
String typed = finalTypeFields[i].getText().trim();
if (!typed.isEmpty() && !typed.equals(hint)) {
// Bug 4: Claude needs the option number sent first to activate
// text-entry mode, then the typed text + \r.
// Encode as "TYPE:N:text" for writePtyAnswer to handle.
int optNum = finalModel.options().indexOf(opt) + 1;
submitAnswer("TYPE:" + optNum + ":" + typed);
} else if (hasLabel) {
// "Yes" label with no text typed — select the option with no text.
submitAnswer(opt.response());
}
} else {
submitAnswer(opt.response());
Expand Down
Loading