Skip to content

feat: smart double-click selection (OSC 8 links + regex rules)#1629

Open
nikicat wants to merge 5 commits into
raphamorim:mainfrom
nikicat:feat/smart-selection
Open

feat: smart double-click selection (OSC 8 links + regex rules)#1629
nikicat wants to merge 5 commits into
raphamorim:mainfrom
nikicat:feat/smart-selection

Conversation

@nikicat
Copy link
Copy Markdown

@nikicat nikicat commented May 30, 2026

Summary

Makes double-click selection URL- and token-aware so it stops truncating at
semantic boundary characters (: in particular). Implements #1621.

Details

Two layers in front of the existing semantic selection:

  1. OSC 8 fast path — double-clicking inside an OSC 8 hyperlink span selects
    the whole link across soft-wrapped rows. The span walk lives in
    crosswords::hyperlink::hyperlink_span_at and is now shared with the hint path
    so both agree on link boundaries.
  2. Smart-selection rules — six precision-tagged regex rules ship as defaults
    (URL, file:line:col, UUID, IPv4, email, git SHA). The engine scans the
    soft-wrapped logical line containing the click and returns the highest-precision
    match covering it, falling back to the previous semantic behavior on no match.
    URL precision sits above IPv4 so https://192.168.1.1/admin selects whole.

A [smart-selection] config table lets users disable the engine, remove/override
built-in rules, or append their own (e.g. JIRA tickets) with hot reload. The
runtime rule list is the merge of defaults and user [[smart-selection.rules]]
layered by name (same name replaces, enabled = false removes, fresh name
appends); bad regexes log a warning and skip just that rule. SmartSelector owns
the compiled rules and the reload step, so a config change swaps the rule set
without rebuilding Screen. Documented in rio.5.

Testing

cargo test -p rio-backend — rule merge/disable/override on reload, plus a
worst-case timing sentinel that fills an 8192-cell line and asserts one
select_at() stays well under budget.

Closes #1621

🤖 Generated with Claude Code

nikicat and others added 5 commits May 30, 2026 17:19
Double-clicking inside an OSC 8 link span now selects the whole link
(across soft-wrapped rows) instead of the semantic-word boundaries,
which truncate URLs whose anchor text contains characters like `:` or
spaces. Falls back to the existing semantic selection when the click
cell has no hyperlink, so plain text behavior is unchanged.

The span walk lives in `crosswords::hyperlink::hyperlink_span_at` and
is now also used by `find_hyperlink_at_point` in the hint path, so the
two places agree on link boundaries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(cherry picked from commit 25c7287)
Adds an iTerm2-style smart-selection layer between the OSC 8 fast path
and the existing semantic selection. Six precision-tagged regex rules
ship as defaults — URL, file:line:col, UUID, IPv4, email, git SHA — so
a double-click on any of them selects the whole token instead of
truncating at the semantic boundary characters (`:` in particular).

The engine scans the soft-wrapped logical line containing the click,
returns the highest-precision match that covers it, and falls back to
the previous semantic behavior when no rule matches. URL precision is
above IPv4 so a URL like `https://192.168.1.1/admin` selects whole
instead of being shadowed by its embedded address.

A `Vec<SmartRule>` lives on `Screen` so DFA compilation happens once
at startup. Config exposure (user rules, overrides) is the next phase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(cherry picked from commit b24c6f3)
Adds a `[smart-selection]` config section so users can disable the
engine, remove built-in rules, override their precision/pattern, or
append fresh rules (e.g. JIRA tickets, internal ID schemes). The
runtime rule list is the merge of the hardcoded defaults and the
user's `[[smart-selection.rules]]` entries, layered by name: same
name replaces, `enabled = false` removes, fresh name appends. Bad
user regexes log a warning and skip just that rule.

`SmartSelector` owns the compiled rules and the reload step, so a
config change picked up by `Screen::update_config` swaps the rule
set without rebuilding `Screen`. Splitting the reload into its own
type also gets it covered by unit tests — three new cases assert
that the master switch, a rule disable, and a fresh user rule all
take effect on reload.

Documents the table in `rio.5.scd` next to the other config
sections.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(cherry picked from commit b6325e6)
Adds a sentinel test that fills an 8192-cell (MAX_SCAN_CELLS) logical
line with content that frequently kicks off but rarely completes a
match for every built-in rule, then asserts one select_at() stays
under 50ms. Typical wall-clock is sub-millisecond; the 50× headroom
is meant to catch obvious regressions (a per-click DFA recompile, an
accidental O(n²)) without being CI-flaky.

Alt-bypass from the plan is intentionally skipped — Alt+click is
already bound to OSC 8 / hint activation, so the bypass would
conflict with the existing modifier role.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(cherry picked from commit a5a50c2)
Formatting-only; no behavior change. (CI `cargo fmt --check` gate.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: URL-aware double-click word selection (don't break on : in ://)

1 participant