fix(tui): drag-select auto-scrolls past edge; copy strips all rail decorations - #1239
Merged
5 commits merged intoMay 13, 2026
Merged
Conversation
Contributor
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
…ail (Hmbown#1163) When the user holds the left mouse button and drags past the top or bottom of the transcript rect, advance the viewport on a fixed cadence so a long passage can be selected in one drag. Also strip the visual tool-card left-rail glyph (`╭ │ ╰`) from copied text so it does not leak into the clipboard. Auto-scroll: * New `SelectionAutoscroll` state on `ViewportState` records direction and the last in-bounds column while a drag is held outside the rect. * Armed/disarmed by the existing `Drag(Left)` handler; cleared on `Up(Left)`, on a fresh `Down(Left)`, and when the cursor returns inside the viewport. * A new per-loop helper `tick_selection_autoscroll` advances `pending_scroll_delta` by ±1 line every 30 ms (~33 lines/sec) and extends the selection head to the matching edge row, so the visible selection rect stays glued to the cursor edge. * The main loop's `poll_timeout` is clamped to the next autoscroll tick so the loop wakes up on cadence even with no input events. Copy artifact: * `line_to_plain_for_copy` returns `(plain_text, rail_prefix_width)`, stripping a leading rail glyph span when present. * `selection_to_text` shifts recorded selection columns left by the rail width before slicing so visible selection bounds still match. Tests: * `drag_above/below_viewport_arms_autoscroll_*` — verify direction and clamped column for vertical-out drags. * `drag_back_inside_disarms_autoscroll` — re-entering clears state. * `mouse_up_clears_selection_autoscroll` — release ends autoscroll. * `tick_selection_autoscroll_advances_pending_scroll_when_due` — cadence advances scroll and head; `_respects_cadence` blocks early ticks; `_clears_when_drag_ended` self-heals if drag state is lost. * `line_to_plain_for_copy_*` — rail glyph stripping for top/middle/ bottom rails plus negative tests for plain spans, OSC-8 wrappers, and lines whose plain text legitimately starts with `│`. * Strengthened `selection_to_text_copies_rendered_transcript_block` to assert no `│ ` line-prefix leaks.
…tching (Hmbown#1163) Replace the hardcoded three-glyph TOOL_CARD_RAIL_PREFIXES set (only covered tool-card rails) with iterative structural detection that handles all TUI decoration glyphs: - Iterates through consecutive leading decorative spans so tool headers with multiple prefix spans (e.g. "• ▶ run issue") are fully stripped. - Pattern A: "<glyph>[<glyph>…]<space>" where all non-space chars are drawing characters — covers single-glyph (▏, ▶, ⌕) and multi-glyph prefixes (⋮⋮). - Pattern B: "<glyph>" + lone space span — covers assistant/user glyphs (●, ▎). - Covers Box Drawing, Block Elements, Geometric Shapes, and individual chars used as TUI decoration (•, …, ·, ⌕, ⋮). Store rail-prefix widths in TranscriptViewCache so the copy path reads metadata rather than guessing from glyphs.
Simulate a 30-turn complex session (user → thinking → assistant → tools) and assert the rail_prefix_widths vector stays under 1 MB even in pathological sessions. The test reports exact memory figures via eprintln! for diagnostic visibility.
…n#1163) - Wire mouse_hits_transcript_scrollbar into the Left-Down handler so the scrollbar thumb remains draggable after rebase onto upstream/main. - Fix clippy::useless_format in memory-overhead test.
Oliver-ZPLiu
force-pushed
the
fix/tui-selection-drag-edge-scroll
branch
from
May 8, 2026 19:51
ca019d7 to
f172641
Compare
…own#1163) Replace the upstream inert-scrollbar test (transcript_scrollbar_gutter_ is_not_draggable) with one that asserts the gutter starts a scrollbar drag rather than a text selection, matching the intended behaviour where the visible scrollbar thumb remains interactive. The other 17 test failures in the CI run are pre-existing Windows/local environment issues (Python REPL / skills fixture pollution) unrelated to this change.
This was referenced May 9, 2026
Owner
Triage / pre-merge note (deferred from v0.8.26 hotfix queue)State on this revisit:
The two changes are nicely separated and both have skill-level concerns I'd call out:
I haven't done a line-by-line read at the level I gave PRs #1287 / #1294 / #1320 because (a) the maintainer pre-vetted it in the v0.8.25 handoff, (b) CI is green across all three OSes including Windows, and (c) it's a pure UI/copy-path change with no new network or auth surface. Recommend: merge after a quick rebase if needed. 🤖 Surfaced during the v0.8.26 backlog sweep. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR fixes both problems reported in issue #1163:
╭│╰), transcript rails (▏), reasoning rails (╎), tool status symbols (·•◦), and tool family glyphs (▶⌕▷◆◐⋮⋮).Fixes #1163
Fixes #1255
Fixes #1292
Fixes #1298
Why this matters
I noticed that PR #1214 addressed a related interaction by making the transcript scrollbar inert — clicks and drags on the scrollbar gutter no longer trigger any action, which prevents the gutter from accidentally hijacking text selection. That is a clean fix for one class of scrollbar-selection interference.
However, making the scrollbar completely inert also removes a natural mouse-based navigation path, and user feedback in #1255 suggests this remains an issue — on Windows 10 the transcript area became entirely unscrollable for some users after the change.
The user in #1163 reported two concrete problems that are the focus of this PR:
Auto-scroll on edge-drag — when drag-selecting past the top or bottom of the viewport, the transcript does not scroll to follow the selection. This PR adds that behaviour directly in the selection-drag path, the same way text editors handle it. (also reported in deepseek tui 中不能滑动触控板/鼠标选择 AI 输出的文本 #1292 )
Rail decoration leakage into copied text — the extra vertical line the user saw in copied text is a visual-only decoration glyph that was not stripped by the copy path. This PR adds structural rail-prefix detection so every class of decoration is removed from clipboard output.
The scrollbar remains interactive — clicking the gutter starts a drag-to-scroll, which gives mouse-oriented users a direct way to navigate long transcripts. These changes are independent of #1214 and can coexist with it.
Approach
Edge auto-scroll
When the mouse is in drag-selection mode and approaches the top or bottom boundary of the viewport, the transcript scroll position adjusts to bring new content into view. This works similarly to text editors — the viewport follows the selection when it extends past the visible area.
Rail decoration stripping
Rail-prefix detection uses structural pattern matching instead of a hardcoded glyph list:
• ▶ run issue) are fully stripped."<glyph>[<glyph>…]<space>"where all non-space chars are drawing characters. Handles single-glyph (▏,▶,⌕) and multi-glyph (⋮⋮) prefixes."<glyph>"+ lone space span. Handles assistant/user glyphs (●,▎).Character coverage:
╭│╰╎…) — U+2500–U+257F▏▎▍…) — U+2580–U+259F●▶▷◆◐…) — U+25A0–U+25FF•(U+2022),…(U+2026),·(U+00B7),⌕(U+2315),⋮(U+22EE)Rail widths are stored in
TranscriptViewCacheduring flatten, so the copy path reads pre-computed metadata without guessing from glyphs at copy time. Memory overhead is negligible: a simulated 30-turn deep-reasoning session (599 rendered lines) adds 8 KB, scaling linearly at ~13 KB per 1,000 lines. A session would need ~131,000 rendered lines before this vector reaches 1 MB — far beyond any realistic transcript.Files changed
crates/tui/src/tui/transcript.rsrail_prefix_widthscache,compute_rail_prefix_width(),rail_prefix_width()crates/tui/src/tui/ui_text.rsline_to_plain_for_copy→line_to_plain; removed hardcoded glyph setcrates/tui/src/tui/ui.rsselection_to_text()to use cache-based rail width +slice_text()crates/tui/src/tui/ui/tests.rsTest plan
cargo fmt --all -- --checkpassescargo clippy -p deepseek-tui --all-targets --all-features --locked -- -D warningspassescargo test -p deepseek-tui selection_to_text— 2 passedcargo test -p deepseek-tui ui_text— 6 passedcargo test -p deepseek-tui transcript— 45 passed--mouse-capture: select transcript text containing tool cards, reasoning blocks, and tool call headers — copy via right-click menu, verify no rail glyphs leak