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? |
|
Thanks @helin24 for the suggestions! I have addressed Gemini's comments above and amended the original commit with those changes. I have also attached the before/after videos. As for your question about the expected behavior, after my changes the user should be able to see the root cause error at the top and remain at the top even if another event produces more errors. I did some testing and this behavior is consistent across all errors, so you should be able to see the scroll button move up as the errors cascade and accumulate below, but the user will stay where they are in the console. |
|
I should have thought of this before, but I think we should make this an option that's added to settings, so that it is only enabled if someone wants to use this smart error scrolling. What do you think of that? I think it's going to be a matter of preference, where some users will prefer the window stay on the first error and some will want to see the bottom of the output. CC @pq if you have other thoughts. |
PR Description
Scroll the run console to the first of multiple errors instead of auto-scrolling to the bottom.
When a Flutter app throws a cascade of errors (e.g. an unbounded viewport), the logging console floods with hundreds of lines and auto-scrolls to the bottom, burying the root-cause error at the top. The reporter had to manually scroll back up to find it.
This changes
DaemonConsoleViewso that when the first error of a burst arrives (detected either from error-stream output or from the Flutter error bannerException caught by …), the console scrolls that first error to the top of the viewport and freezes auto-follow. The root-cause error stays visible while the rest of the stack traces stream in below. Normal auto-follow resumes once the user scrolls back to the bottom.Details:
invokeLater, throttled with anAtomicBoolean) to respect the platform threading model.clear()(hot reload/restart).Fixes #3778
To verify:
hasSizeassertion failuresA before/after demo video is attached in the comments.
Review 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).