Skip to content

Commit 59b66d9

Browse files
ctruedenclaude
andcommitted
Follow console output only while scrolled to the bottom
The combined view never scrolled as text arrived, and the other consoles always jumped to the end, even when the user had scrolled upward. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent 78a5237 commit 59b66d9

3 files changed

Lines changed: 26 additions & 5 deletions

File tree

‎src/main/java/org/scijava/ui/swing/script/ConsoleArea.java‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,15 @@
3232

3333
import java.awt.Color;
3434
import java.awt.Graphics;
35+
import java.awt.Rectangle;
3536
import java.util.List;
3637
import java.util.concurrent.CopyOnWriteArrayList;
3738

3839
import javax.swing.event.DocumentEvent;
3940
import javax.swing.event.DocumentListener;
4041
import javax.swing.JTextArea;
42+
import javax.swing.JViewport;
43+
import javax.swing.SwingUtilities;
4144
import javax.swing.plaf.basic.BasicTextAreaUI;
4245
import javax.swing.text.BadLocationException;
4346
import javax.swing.text.Document;
@@ -127,10 +130,30 @@ public void append(final String text, final boolean error) {
127130
if (text == null || text.isEmpty()) return;
128131
synchronized (appendLock) {
129132
chunks.add(new Mark(getDocument().getLength(), error));
130-
append(text);
133+
appendFollowing(this, text);
131134
}
132135
}
133136

137+
/**
138+
* Appends text to the given area, then scrolls to the end of the text if the
139+
* area was already scrolled to the bottom (or is not currently displayed).
140+
* If the user has scrolled upward, the view is left alone.
141+
*/
142+
public static void appendFollowing(final JTextArea area, final String text) {
143+
final boolean follow = isAtBottom(area);
144+
area.append(text);
145+
if (follow) {
146+
SwingUtilities.invokeLater(() -> area.setCaretPosition(area.getDocument().getLength()));
147+
}
148+
}
149+
150+
private static boolean isAtBottom(final JTextArea area) {
151+
if (!(area.getParent() instanceof JViewport)) return true;
152+
final Rectangle visible = area.getVisibleRect();
153+
// Note: allow a little slack for rounding and partially visible lines.
154+
return visible.y + visible.height >= area.getHeight() - 2;
155+
}
156+
134157
/** Sets foreground, background and caret colors together. */
135158
public void setColors(final Color fg, final Color bg) {
136159
setForeground(fg);

‎src/main/java/org/scijava/ui/swing/script/JTextAreaOutputStream.java‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,7 @@ public void flushQueue() {
104104
catch (final BadLocationException e) {
105105
e.printStackTrace();
106106
}
107-
textArea.append(sb.toString());
108-
textArea.setCaretPosition(textArea.getDocument().getLength());
107+
ConsoleArea.appendFollowing(textArea, sb.toString());
109108
}
110109
}
111110

‎src/main/java/org/scijava/ui/swing/script/JTextAreaWriter.java‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,7 @@ public void flushQueue() {
114114
catch (final BadLocationException e) {
115115
log.error(e);
116116
}
117-
textArea.append(sb.toString());
118-
textArea.setCaretPosition(textArea.getDocument().getLength());
117+
ConsoleArea.appendFollowing(textArea, sb.toString());
119118
}
120119
}
121120

0 commit comments

Comments
 (0)