From da9618db97d462c3046a333898dc4aea0e7270ca Mon Sep 17 00:00:00 2001 From: Oleg Samarin Date: Mon, 20 Apr 2026 22:26:23 +0300 Subject: [PATCH 1/2] Added diagnostic on locking scrollbar of the PTY area --- pom.xml | 2 +- .../nbclaudecodegui/ui/ClaudeSessionTab.java | 44 +++++++++++++++++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index fdd7e86e..b7183f1d 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.github.nbclaudecodegui netbeans-claude-code-gui - 0.22.7-SNAPSHOT + 0.22.8-SNAPSHOT nbm Netbeans 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 b23aed89..f93f697c 100644 --- a/src/main/java/io/github/nbclaudecodegui/ui/ClaudeSessionTab.java +++ b/src/main/java/io/github/nbclaudecodegui/ui/ClaudeSessionTab.java @@ -501,7 +501,9 @@ public void onChoiceMenuChanged(ChoiceMenuModel menu) { repaint(); controller.writePtyAnswer(answer); }, grabFocus); + 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(); @@ -984,19 +986,27 @@ 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 + "[scrollToShowAllOutput pre] " + termScrollState()); + terminalWidget.getTerminalPanel().scrollToShowAllOutput(); + LOG.fine(sessionTag + "[scrollToShowAllOutput post] " + termScrollState()); + SwingUtilities.invokeLater(() -> + LOG.fine(sessionTag + "[scrollToShowAllOutput post-post] " + termScrollState())); + }); } } @@ -1263,6 +1273,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 getScreenLines() { if (terminalWidget == null) return java.util.Collections.emptyList(); try { From b12bc3a0b699ceef67d8a4692e85f0f1aff571a5 Mon Sep 17 00:00:00 2001 From: Oleg Samarin Date: Tue, 21 Apr 2026 09:01:36 +0300 Subject: [PATCH 2/2] Fixed terminal scrolling to the beginning and locking when a choice/confirmation menu appears (https://github.com/nbclaudecodegui/NetbeansClaudeCodeGui/issues/22) --- CHANGELOG.md | 1 + pom.xml | 2 +- .../nbclaudecodegui/ui/ClaudeSessionTab.java | 27 ++++++++++++++++--- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab025708..97bd214a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Fixed terminal scrolling to the beginning and locking when a choice/confirmation menu appears (https://github.com/nbclaudecodegui/NetbeansClaudeCodeGui/issues/22) - Added a setting to control whether the choice menu grabs keyboard focus (https://github.com/nbclaudecodegui/NetbeansClaudeCodeGui/issues/20) - Added a global setting to choose the default dock position of the Claude Code session tab (https://github.com/nbclaudecodegui/NetbeansClaudeCodeGui/issues/24) diff --git a/pom.xml b/pom.xml index b7183f1d..7a9b161a 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.github.nbclaudecodegui netbeans-claude-code-gui - 0.22.8-SNAPSHOT + 0.22.9-SNAPSHOT nbm Netbeans 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 f93f697c..33c7b7ae 100644 --- a/src/main/java/io/github/nbclaudecodegui/ui/ClaudeSessionTab.java +++ b/src/main/java/io/github/nbclaudecodegui/ui/ClaudeSessionTab.java @@ -1001,15 +1001,34 @@ void switchSouthCard(String card) { // setDividerLocation() — JediTerm resets scroll position to top on resize. if (terminalWidget != null) { SwingUtilities.invokeLater(() -> { - LOG.fine(sessionTag + "[scrollToShowAllOutput pre] " + termScrollState()); - terminalWidget.getTerminalPanel().scrollToShowAllOutput(); - LOG.fine(sessionTag + "[scrollToShowAllOutput post] " + termScrollState()); + LOG.fine(sessionTag + "[scrollTerminalToBottom pre] " + termScrollState()); + scrollTerminalToBottom(); + LOG.fine(sessionTag + "[scrollTerminalToBottom post] " + termScrollState()); SwingUtilities.invokeLater(() -> - LOG.fine(sessionTag + "[scrollToShowAllOutput post-post] " + termScrollState())); + 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); + } + } + } + /** * Handles the Save & Switch dialog confirmation. *