fix(terminal): keep selections aligned while scrolling - #258
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes terminal selection alignment during scrolling and adds continuous out-of-body auto-scroll.
Changes:
- Remaps selection endpoints after viewport scrolling.
- Adds timed auto-scroll repainting.
- Adds regression tests for scrolling, boundaries, and event ordering.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
input.rs |
Integrates scroll-aware selection handling. |
input/viewport_scroll.rs |
Adds selection scrolling and endpoint helpers. |
input/selection_tests.rs |
Adds selection-scroll regression coverage. |
Comments suppressed due to low confidence (1)
crates/horizon-ui/src/terminal_widget/input.rs:453
- This schedules a repaint after 16 ms but does not throttle the scroll operation itself. Any earlier repaint (for example, while another UI animation calls
request_repaint) invokes this block again and scrolls immediately, so out-of-body selection speed becomes frame-rate dependent and can substantially exceed the intended 16 ms cadence. Track the next eligible auto-scroll time in the drag state and only scroll once that deadline has elapsed; the repaint test should also advance frame time to verify the throttle.
if auto_scrolled {
pointer.ui_ctx.request_repaint_after(SELECTION_AUTO_SCROLL_INTERVAL);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Process primary release events in frame order so a release followed by a same-frame press hands the still-held pointer to the restarted drag instead of finishing it, and replay only local-selection anchors after same-frame scrollback changes so the claimed-press replay can never reopen Ctrl+click targets. Split terminal input into keyboard, routing, and frame-events leaf modules to stay inside the maintainability budget.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
crates/horizon-ui/src/terminal_widget/input.rs:349
- Primary transitions are still not fully order-aware. For an input batch
press inside → release → press outside,body_primary_press_posretains the first press while the final pointer state is down. This setsdrag_restarted_by_press, suppresses completion, and leaves a selection drag active toward the outside pointer even though the local press was already released. Track the primary-button lifecycle in event order so a later non-local press cannot revive an earlier in-body press.
let mut drag_restarted_by_press = false;
if let Some(pos) = frame_events.body_primary_press_pos
Code review (max effort): findings fixed in 5efd1dbOverviewThe PR correctly identifies why selections drift while scrolling: Findings (all fixed)
Regression coverage added
The two harness tests fail on f73091b and pass on 5efd1db. These orderings live inside a single ~16 ms frame, so coverage is deterministic-test-based by necessity. Verified good (no action)
Known pre-existing limitations (out of scope, noted for the record)
Test evidence (worktree of this branch)
|
|
SMOKE-TEST REQUEST macOS/Metal — plan: docs/testing/2026-07-31-macos-terminal-selection-scrollback-smoke.md — scope: selection/scrollback ordering, outside auto-scroll motion, release/re-entry, and mouse-reporting routing |
There was a problem hiding this comment.
🟡 Not ready to approve
An outside release can trigger an additional auto-scroll step after its cadence deadline.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (1)
crates/horizon-ui/src/terminal_widget/input.rs:437
- A release outside the body can still perform another auto-scroll step when its cadence deadline is ready, because the release path calls the same scrolling helper used while the button is held. This makes the viewport and final endpoint move on the release event, despite auto-scroll being required to stop on release. Finalize the endpoint without scrolling here; wheel-induced remapping is still handled later by the event replay.
update_active_selection_drag(panel, pointer, selection_drag, pos, false);
- Files reviewed: 9/10 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
|
SMOKE-TEST REPORT (macOS 26.5.2 / Apple M4 Max Metal)
|
There was a problem hiding this comment.
🟡 Not ready to approve
Same-frame release-then-press handling can lose or incorrectly replace the completed Linux primary selection.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (1)
crates/horizon-ui/src/terminal_widget/input.rs:412
- A release that precedes the last local press is discarded here:
PointerFrameEventsreports no completing release, andstart_local_selection_athas already overwritten the old terminal selection. Consequently, a completed drag followed by a new press in the same frame never copies the completed text to the Linux primary selection (ordrag_stopped_bycopies the newly started selection instead). Preserve/copy the selection at each release before processing a later press, while still leaving the restarted drag active.
if let Some(pos) = frame_events.body_primary_press_pos {
pointer.interaction.body.request_focus();
// Collection verified this exact event's modifiers and terminal mode
// route it to local selection. The frame's final modifiers may belong
// to a later outside press.
start_local_selection_at(panel, pointer, pos);
selection_drag.start(panel.id, pos);
- Files reviewed: 8/9 changed files
- Comments generated: 0 new
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
Purpose
Fix terminal text selections falling behind the pointer when scrolling down, or extending beyond it when scrolling up. Selection endpoints were mapped before same-frame wheel events changed the viewport, and a held pointer outside the terminal body had no repaint cadence to keep auto-scrolling.
Behavior impact
Test evidence
cargo fmt --all -- --check./scripts/check-maintainability.shRUSTFLAGS="-D warnings" cargo test --offline --workspaceRUSTFLAGS="-D warnings" cargo test --offline --workspace --features speechcargo clippy --offline --all-targets --features speech,trace-profiling -- -D warningscargo clippy --offline --workspace --lib --bins --examples --features speech -- -D warnings -D clippy::unwrap_used -D clippy::expect_usedcargo clippy --offline --workspace --all-targets --features speech -- -D warnings -W clippy::pedantic