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 terminal scrolling to the beginning and locking when a choice/confirmation menu appears (https://github.com/nbclaudecodegui/NetbeansClaudeCodeGui/issues/22)

# 0.21.50 (2026-04-19)

- Fixed diff panel appearing at wrong height (nearly invisible) after Reset Windows or on first use
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.21.56-SNAPSHOT</version>
<version>0.21.57-SNAPSHOT</version>
<packaging>nbm</packaging>
<name>Netbeans 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
63 changes: 59 additions & 4 deletions src/main/java/io/github/nbclaudecodegui/ui/ClaudeSessionTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,9 @@ public void onChoiceMenuChanged(ChoiceMenuModel menu) {
repaint();
controller.writePtyAnswer(answer);
});
LOG.fine(sessionTag + "[onChoiceMenuChanged pre-switch] " + termScrollState());
switchSouthCard(CARD_CHOICE);
LOG.fine(sessionTag + "[onChoiceMenuChanged post-switch] " + termScrollState());
} else {
if (CARD_CHOICE.equals(activeCard)) {
choiceMenuPanel.dismiss();
Expand Down Expand Up @@ -968,19 +970,46 @@ void switchSouthCard(String card) {
+ " choiceVisible=" + choiceMenuPanel.isVisible()
+ " choiceBounds=" + choiceMenuPanel.getBounds()
+ " southCardVisible=" + southCard.isVisible()
+ " southCardBounds=" + southCard.getBounds());
+ " southCardBounds=" + southCard.getBounds()
+ " divLoc=" + splitPane.getDividerLocation()
+ " " + termScrollState());
SwingUtilities.invokeLater(() -> LOG.fine(sessionTag + "[switchSouthCard CHOICE post-EDT]"
+ " choiceShowing=" + choiceMenuPanel.isShowing()
+ " choiceBounds=" + choiceMenuPanel.getBounds()
+ " componentCount=" + choiceMenuPanel.getComponentCount()));
+ " componentCount=" + choiceMenuPanel.getComponentCount()
+ " " + termScrollState()));
}
southCard.revalidate();
southCard.repaint();
// Keep the terminal scrolled to the bottom after layout changes caused by
// setDividerLocation() — JediTerm resets scroll position to top on resize.
if (terminalWidget != null) {
SwingUtilities.invokeLater(
() -> terminalWidget.getTerminalPanel().scrollToShowAllOutput());
SwingUtilities.invokeLater(() -> {
LOG.fine(sessionTag + "[scrollTerminalToBottom pre] " + termScrollState());
scrollTerminalToBottom();
LOG.fine(sessionTag + "[scrollTerminalToBottom post] " + termScrollState());
SwingUtilities.invokeLater(() ->
LOG.fine(sessionTag + "[scrollTerminalToBottom post-post] " + termScrollState()));
});
}
}

/** Scrolls the JediTerm widget to show the latest output (bottom of scrollback). */
private void scrollTerminalToBottom() {
if (terminalWidget == null) return;
scrollBottomOfComponents(terminalWidget);
}

private void scrollBottomOfComponents(java.awt.Container c) {
for (java.awt.Component child : c.getComponents()) {
if (child instanceof javax.swing.JScrollBar bar
&& bar.getOrientation() == javax.swing.JScrollBar.VERTICAL) {
bar.setValue(bar.getMaximum() - bar.getVisibleAmount());
return;
}
if (child instanceof java.awt.Container sub) {
scrollBottomOfComponents(sub);
}
}
}

Expand Down Expand Up @@ -1247,6 +1276,32 @@ private static JPanel makeSep() {
// Helpers
// -------------------------------------------------------------------------

/** Collects scroll-related state of the JediTerm terminal widget for diagnostics. */
private String termScrollState() {
if (terminalWidget == null) return "terminalWidget=null";
StringBuilder sb = new StringBuilder();
sb.append("termSize=").append(terminalWidget.getSize());
sb.append(" termEnabled=").append(terminalWidget.isEnabled());
walkScrollComponents(terminalWidget, sb, 0);
return sb.toString();
}

private void walkScrollComponents(java.awt.Container c, StringBuilder sb, int depth) {
for (java.awt.Component child : c.getComponents()) {
if (child instanceof javax.swing.JScrollBar bar) {
sb.append(" scrollBar[enabled=").append(bar.isEnabled())
.append(",val=").append(bar.getValue())
.append(",min=").append(bar.getMinimum())
.append(",max=").append(bar.getMaximum())
.append(",vis=").append(bar.getVisibleAmount())
.append("]");
}
if (child instanceof java.awt.Container sub && depth < 5) {
walkScrollComponents(sub, sb, depth + 1);
}
}
}

private List<String> getScreenLines() {
if (terminalWidget == null) return java.util.Collections.emptyList();
try {
Expand Down
Loading