Prevent console from auto-scrolling to bottom#9008
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request introduces a mechanism in DaemonConsoleView to preserve the user's scroll position when new console output is printed, accompanied by unit tests for the scroll-end detection logic. The review feedback highlights a critical threading violation where UI components and editor geometry are accessed outside the Event Dispatch Thread (EDT). To resolve this, the reviewer suggests scheduling the scroll check on the EDT and throttling the requests using an AtomicBoolean to prevent performance degradation during rapid output.
| final Editor rawEditor = getEditor(); | ||
| final EditorEx editor = rawEditor instanceof EditorEx ? (EditorEx) rawEditor : null; | ||
| if (editor != null) { | ||
| wasAtScrollEnd = isAtScrollEnd(editor); | ||
| } |
There was a problem hiding this comment.
[MUST-FIX] Accessing the Editor and its ScrollingModel (via getEditor() and isAtScrollEnd(editor)) from a background thread violates the IntelliJ Platform threading model. UI components and editor geometry must only be accessed on the Event Dispatch Thread (EDT). Doing otherwise can lead to AssertionErrors or race conditions.
We can safely schedule this check on the EDT using ApplicationManager.getApplication().invokeLater(...), throttled by the scrollCheckPending flag to avoid performance degradation during rapid output.
if (com.intellij.openapi.application.ApplicationManager.getApplication().isDispatchThread()) {
final Editor rawEditor = getEditor();
final EditorEx editor = rawEditor instanceof EditorEx ? (EditorEx) rawEditor : null;
if (editor != null) {
wasAtScrollEnd = isAtScrollEnd(editor);
}
} else if (scrollCheckPending.compareAndSet(false, true)) {
com.intellij.openapi.application.ApplicationManager.getApplication().invokeLater(() -> {
scrollCheckPending.set(false);
final Editor rawEditor = getEditor();
final EditorEx editor = rawEditor instanceof EditorEx ? (EditorEx) rawEditor : null;
if (editor != null) {
wasAtScrollEnd = isAtScrollEnd(editor);
}
}, com.intellij.openapi.application.ModalityState.any());
}| // Whether the console was scrolled to the end before the most recent batch of output. | ||
| // Defaults to true so that the first output auto-scrolls normally. | ||
| private boolean wasAtScrollEnd = true; |
There was a problem hiding this comment.
[CONCERN] To support thread-safe scheduling of the scroll position check from background threads, we should introduce a throttled state indicator using an AtomicBoolean. This prevents flooding the Event Dispatch Thread (EDT) with redundant runnables when console output is rapid.
| // Whether the console was scrolled to the end before the most recent batch of output. | |
| // Defaults to true so that the first output auto-scrolls normally. | |
| private boolean wasAtScrollEnd = true; | |
| // Whether the console was scrolled to the end before the most recent batch of output. | |
| // Defaults to true so that the first output auto-scrolls normally. | |
| private boolean wasAtScrollEnd = true; | |
| private final java.util.concurrent.atomic.AtomicBoolean scrollCheckPending = new java.util.concurrent.atomic.AtomicBoolean(false); |
|
Sorry I didn't reply in the issue; I didn't see your message there until now! It looks like you were able to find the relevant file to make these changes anyway; thanks! I think the gemini comments above would be good to address. A gif/video of before and after would also be helpful. What happens (or should happen) if a user triggers an event that produces a cascade of errors, is looking at the root cause error at the top, and then triggers another event that produces errors? Will they just stay at the first error? |
PR Description
Prevent the logging console from auto-scrolling to the bottom when multiple errors are shown.
When a Flutter app throws a cascade of errors (e.g. an unbounded viewport), the console flooded with hundreds of lines and auto-scrolled to the bottom, burying the root-cause error at the top. This overrides
scrollToEnd()inDaemonConsoleViewto only scroll when the user was already at the bottom before new output arrived, preserving their position when they have scrolled up.Fixes #3778
To verify:
hasSizeassertion failuresReview the contribution guidelines below:
AUTHORSfile.CHANGELOG.mdif appropriate.Contribution guidelines:
our contributor guide and
the Flutter organization contributor guide
for general expectations for PRs.
dart format.practices (discussion).