Skip to content

Fix semantic drag selection over-running non-word ranges into the next word#12985

Merged
acarl005 merged 3 commits into
masterfrom
factory/semantic-selection-nonword-range
Jun 25, 2026
Merged

Fix semantic drag selection over-running non-word ranges into the next word#12985
acarl005 merged 3 commits into
masterfrom
factory/semantic-selection-nonword-range

Conversation

@warp-dev-github-integration

@warp-dev-github-integration warp-dev-github-integration Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Human Remarks (Andy)

double-click-and-drag works differently in the block list vs rich text views and the block list behavior is the correct one. This PR resolves that difference and makes rich text behave the same. Added some more tests for this case.

Description

Semantic (double-click) + drag selection in agent conversations over-extended: when the drag tail landed on a non-word character (e.g. , or ...), the selection jumped to the end of the next word instead of stopping at the end of the non-word run. The terminal block list already behaved correctly.

Root cause: rich-text semantic expansion used word-boundary iterators that start outside a word — word_ends_from_offset_exclusive (forward) and word_starts_backward_from_offset_exclusive (backward) in crates/warpui_core/src/text/word_boundaries.rs. When the tail sits on a boundary char, the iterator skips the whole boundary run and continues into the adjacent word. The buggy pattern was duplicated in two call sites: FormattedTextElement::expand_selection (the AI/agent/markdown/code-block path) and Text::expand_selection (plain Text, e.g. table cells). The terminal block list uses a separate, correct path (grid handler semantic_search_left/right) that stops at the first boundary char.

Fix: add a shared TextBuffer::semantic_expansion_target(position, direction, policy) that snaps to the maximal contiguous run of the same character class as the char under the tail:

  • tail on a word char → end/start of that word (unchanged);
  • tail on a non-word char → end (forward) / start (backward) of the contiguous boundary run, never crossing into the adjacent word.

Both expand_selection call sites now route through this helper (kept in sync), and word_boundaries::is_word_boundary is exposed for reuse. The terminal block list is unaffected (separate path).

Intended behavior change: double-clicking directly on punctuation now selects the punctuation run rather than prevWord punct nextWord, consistent with the grid.

Linked Issue

Reported via the factory-client bug-triage Slack thread (linked below).

  • The linked issue is labeled ready-to-spec or ready-to-implement.
  • Where appropriate, screenshots or a short video of the implementation are included below.

Testing

  • Added regression unit test crates/warpui_core/src/text/word_boundaries_tests.rs::test_semantic_expansion_stops_at_boundary_run (fails before / passes after): for foo... bar baz, forward target from offsets 3/4/5 == col 7 and backward from 3/4/5 == col 3 (buggy values were 10 and 0); word-char cases unchanged.

  • ./script/format clean; cargo clippy --workspace --all-targets --all-features --tests -- -D warnings clean; full warpui_core test suite passes (293 tests).

  • Manual UI verification on the running app (agent conversation): double-click foo, drag onto ... → highlights foo... (no longer foo... bar); double-click alpha, drag onto the comma → alpha, (no longer alpha, bravo); dragging onto a word still selects the whole word (alpha, bravo).

  • I have manually tested my changes locally

Screenshots / Videos

Verified on the agent-conversation surface (screenshot posted in the Slack thread).

Agent Mode

  • Warp Agent Mode - This PR was created via Warp's AI Agent Mode

Slack thread: https://warpdev.slack.com/archives/C0BCE7AELJ2/p1782281505938299?thread_ts=1782281505.938299&cid=C0BCE7AELJ2
Conversation: https://staging.warp.dev/conversation/a72ed78e-011f-4eb4-9072-e7dede99c407
Run: https://oz.staging.warp.dev/runs/019ef841-92c2-739f-bb00-7e91a9dd20eb

This PR was generated with Oz.

…t word

Semantic (double-click) + drag selection in rich-text surfaces (agent
conversations, markdown, code blocks, table cells) over-extended when the
drag tail landed on a non-word character: the word-boundary iterators start
"outside a word", so they skipped the whole boundary run AND continued to the
end of the next word.

Add a shared `TextBuffer::semantic_expansion_target` that snaps to the maximal
contiguous run of the same character class as the char under the tail (a word,
or a run of boundary chars), matching the terminal block list's grid behavior
(`semantic_search_left/right`). Route both `FormattedTextElement::expand_selection`
and `Text::expand_selection` through it, and expose a reusable
`word_boundaries::is_word_boundary`.

Co-Authored-By: Oz <oz-agent@warp.dev>
@cla-bot cla-bot Bot added the cla-signed label Jun 24, 2026
@acarl005 acarl005 marked this pull request as ready for review June 24, 2026 17:13
@acarl005 acarl005 self-requested a review June 24, 2026 17:13
@oz-for-oss

oz-for-oss Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

@acarl005

I'm starting a first review of this pull request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@oz-for-oss oz-for-oss Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

This PR routes semantic drag expansion for formatted and plain rich text through a shared helper so boundary-character tails stop before the next word, and adds unit coverage for non-word tails. I found no diff-line code correctness, security, or spec-context issues in the provided artifacts.

Concerns

  • This is a user-facing selection behavior change, but the PR context only says a screenshot was posted in Slack; no screenshot or screen recording is attached or embedded in the PR description. For this user-facing change, please include screenshots or a screen recording demonstrating it working end to end.

Verdict

Found: 0 critical, 1 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

oz-agent and others added 2 commits June 24, 2026 18:54
… is_word_boundary method

- semantic_expansion_target now faithfully mirrors the terminal grid's
  semantic_search_left/right instead of grouping a contiguous boundary run.
  This fixes the reviewer-reported divergence: dragging the tail onto a
  non-word char no longer includes trailing whitespace (e.g. "alpha," and
  "foo...", not "alpha, " / "foo... ").
- Make is_word_boundary a method on WordBoundariesPolicy instead of a free
  function.
- Add direct unit tests for FormattedTextElement::expand_selection, backed by a
  new TextFrame::mock_with_positions that lays out glyphs/carets with real
  positions so geometry is assertable.
- Update the buffer-level test to assert block-list-accurate values, including
  explicit "no trailing whitespace" cases.

Co-Authored-By: Oz <oz-agent@warp.dev>
@acarl005 acarl005 requested a review from peicodes June 24, 2026 21:26
@acarl005 acarl005 merged commit 0009e7c into master Jun 25, 2026
36 checks passed
@acarl005 acarl005 deleted the factory/semantic-selection-nonword-range branch June 25, 2026 00:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants