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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +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 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.39-SNAPSHOT</version>
<version>0.22.43-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,14 +515,36 @@ public void writePtyAnswer(String answer) {
int sep = answer.indexOf(':', 5);
String digit = answer.substring(5, sep);
String text = answer.substring(sep + 1);
LOG.fine("[PTY write] type-input digit=" + digit + " text=" + text);
ChoiceMenuModel current = model.getActiveChoiceMenu();
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;
LOG.fine("[PTY write] type-input digit=" + digit + " text=" + text
+ " currentHasTextInput=" + currentHasTextInput);
Thread t = new Thread(() -> {
try {
connector.write(digit);
Thread.sleep(200);
connector.write(text);
Thread.sleep(200);
connector.write("\r");
if (currentHasTextInput) {
int delta = targetIdx - currentIdx;
if (delta != 0) {
byte[] arrow = delta < 0 ? new byte[]{0x1b, '[', 'A'} : new byte[]{0x1b, '[', 'B'};
for (int i = 0; i < Math.abs(delta); i++) {
connector.write(arrow);
Thread.sleep(80);
}
Thread.sleep(100);
}
connector.write(text);
Thread.sleep(200);
connector.write("\r");
} else {
connector.write(digit);
Thread.sleep(200);
connector.write(text);
Thread.sleep(200);
connector.write("\r");
}
} catch (IOException ex) {
LOG.warning("writePtyAnswer TYPE write failed: " + ex.getMessage());
} catch (InterruptedException ie) {
Expand Down Expand Up @@ -560,11 +582,47 @@ public void writePtyAnswer(String answer) {
t.start();
}
} else {
boolean isMenuDigit = answer.matches("[0-9]");
String toWrite = isMenuDigit ? answer : answer + "\r";
LOG.fine("[PTY write] " + toWrite.replace("\r", "\\r").replace("\n", "\\n"));
connector.write(toWrite);
model.clearChoiceMenu();
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);
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 delta = targetIdx - currentIdx;
LOG.fine("[PTY write] ARROW-digit targetIdx=" + targetIdx + " currentIdx=" + currentIdx + " delta=" + delta);
model.clearChoiceMenu();
if (delta == 0) {
connector.write("\r");
} else {
byte[] arrow = delta < 0 ? new byte[]{0x1b, '[', 'A'} : new byte[]{0x1b, '[', 'B'};
Thread t = new Thread(() -> {
try {
for (int i = 0; i < Math.abs(delta); i++) {
connector.write(arrow);
Thread.sleep(80);
}
connector.write("\r");
} catch (IOException ex) {
LOG.warning("writePtyAnswer ARROW-digit write failed: " + ex.getMessage());
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}, "pty-arrow-digit");
t.setDaemon(true);
t.start();
}
} else {
boolean isMenuDigit = answer.matches("[0-9]");
String toWrite = isMenuDigit ? answer : answer + "\r";
LOG.fine("[PTY write] " + toWrite.replace("\r", "\\r").replace("\n", "\\n"));
connector.write(toWrite);
model.clearChoiceMenu();
}
}
} catch (IOException ex) {
LOG.warning("writePtyAnswer failed: " + ex.getMessage());
Expand Down Expand Up @@ -1198,6 +1256,20 @@ void flushPendingPrompt() {
*
* <p>For numbered menus, full {@link java.util.Objects#equals} semantics apply.
*/
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;
}

private static boolean optionsEqual(List<ChoiceMenuModel.Option> a, List<ChoiceMenuModel.Option> b) {
if (a.size() != b.size()) return false;
if (!a.isEmpty() && a.get(0).response().startsWith("ARROW:")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ public void setSessionTag(String tag) {
private static final Pattern PLAN_NAME_DASHES_PATTERN =
Pattern.compile("\u2500{2,}\\s+([\\w][\\w\\-]*)\\s+\u2500+");

/**
* Matches permission-prompt options that embed a text-input hint after a comma:
* "Yes, and tell Claude what to do next" \u2192 group 1 = "Yes", group 2 = "and tell Claude what to do next".
* The phrase "tell Claude" is the key discriminator: it indicates the user must supply text,
* unlike "Yes, and don't ask again for: ..." which is a normal boolean option.
*/
private static final Pattern AND_TELL_CLAUDE_PATTERN =
Pattern.compile("^(.*?),\\s+(and tell Claude\\b.*)$", Pattern.CASE_INSENSITIVE);

// -------------------------------------------------------------------------
// Session state
// -------------------------------------------------------------------------
Expand Down Expand Up @@ -736,6 +745,13 @@ static ChoiceMenuModel.Option extractOption(String line, int index) {
checked = true;
afterDot = afterDot.substring(3).stripLeading();
}
// "Yes, and tell Claude what to do next" → label + text-input hint
Matcher tellMatcher = AND_TELL_CLAUDE_PATTERN.matcher(afterDot);
if (tellMatcher.matches()) {
return new ChoiceMenuModel.Option(
tellMatcher.group(1).strip(), String.valueOf(num),
tellMatcher.group(2).strip(), checked, hasCheckbox, true);
}
return new ChoiceMenuModel.Option(afterDot.strip(), String.valueOf(num), null, checked, hasCheckbox);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"text": "Do you want to proceed?",
"options": [
{"display": "Yes", "response": "1", "description": "and tell Claude what to do next", "hasTextInput": true},
{"display": "Yes, and don't ask again for: javap -classpath lib/intellij-core-1.0-PATCHED.jar -c com.intellij.util.containers.ContainerUtil 2>&1 | grep -A 10 \"createConcurrentWeakMap\"", "response": "2"},
{"display": "No", "response": "3"}
],
"defaultOptionIndex": 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Bash command

javap -classpath lib/intellij-core-1.0-PATCHED.jar -c com.intellij.util.containers.ContainerUtil 2>&1 | grep -A 10 "createConcurrentWeakMap"
Run shell command

Do you want to proceed?
❯ 1. Yes, and tell Claude what to do next
2. Yes, and don't ask again for: javap -classpath lib/intellij-core-1.0-PATCHED.jar -c com.intellij.util.containers.ContainerUtil 2>&1 | grep -A 10 "createConcurrentWeakMap"
3. No

Esc to cancel · ctrl+e to explain
Loading