Skip to content

feat: add regex mode to search (#209) - #224

Merged
matheuswhite merged 2 commits into
mainfrom
feat/209-regex-search
Jul 19, 2026
Merged

feat: add regex mode to search (#209)#224
matheuswhite merged 2 commits into
mainfrom
feat/209-regex-search

Conversation

@matheuswhite

Copy link
Copy Markdown
Owner

Closes #209.

Problem

Search mode only matched raw substrings, so patterns (digit runs, alternations, anchored matches, …) couldn't be found.

Solution

A regex toggle is added to search mode:

  • Ctrl+E toggles regex on/off (mirrors the existing Ctrl+W case toggle).
  • A new box in the search bar shows the state next to the case box: [.*] when on, [ ] when off — e.g. [Aa][.*][1/4] Search Mode.
  • When on, each line is matched with a regular expression instead of a literal substring. Case sensitivity is folded into the compiled regex (RegexBuilder::case_insensitive), so the case toggle and regex toggle compose.
  • An invalid or in-progress pattern simply matches nothing; the existing red "0 matches" bar ([--/--]) signals it — no log spam.

A note on the keybinding

The issue proposed Ctrl+Shift+F, but control combos collapse Shift on most terminals — without the Kitty keyboard protocol, Ctrl+Shift+F is indistinguishable from Ctrl+F and the toggle would silently never fire. Ctrl+E was chosen instead: reliable on every terminal, works in headless-adjacent decoders, and follows the same pattern as Ctrl+W.

Implementation

The two previously-independent, substring-only match computations — the navigation entries built in update_search_state and the highlighted spans built in search_line — now share a single SearchMatcher stored in ScreenMode::Search:

  • Compiled once per search change, not per rendered line.
  • Returns character-offset match positions (start, len), so the navigation entries and highlight columns stay aligned (the "current" match's black-on-yellow style depends on that alignment).
  • Matches on the whole decoded, ANSI-stripped line, so regex anchors (^, $, \b) behave against real line boundaries — the old tail-based to_special_char closure would have mis-anchored them.
  • Skips zero-width matches (e.g. a trailing .*) so they don't inflate the count with nothing to highlight.

is_regex is threaded from InputsShared through the three update_search_state call sites, ChangeToSearchMode, and the search-bar renderer, following the existing is_case_sensitive path exactly.

Testing

  • 9 unit tests on SearchMatcher (screen.rs): plain case-sensitive/insensitive, char (not byte) columns for multi-byte input, regex digit-runs, case-insensitive flag, ^ anchoring, zero-width-match skipping, invalid regex → no match, empty query.
  • New e2e test regex_search_toggles_with_ctrl_e_and_matches_each_line (tests/tui_e2e.rs): literal \d+ matches nothing → Ctrl+E → two digit runs match, box reads [.*].
  • Full suite green: 194 unit, 12 e2e (1 pre-existing macOS-only ignore), cargo fmt clean, no new clippy lints.
  • Verified live in the TUI: toggle + box, per-line regex highlighting (current match emphasized), Ctrl+W×regex interaction, ^ anchoring, invalid/no-match red bar, and match navigation (Up/Down).

🤖 Generated with Claude Code

matheuswhite and others added 2 commits July 19, 2026 14:24
Search mode gains a regex toggle bound to `Ctrl+E` (mirroring the `Ctrl+W`
case-sensitivity toggle). A new box in the search bar shows the state next to
the case box: `[.*]` when on, `[  ]` when off. When on, each line is matched
with a regular expression instead of a literal substring; case sensitivity is
folded into the compiled regex, so the two toggles compose.

The two match computations — the navigation entries built in
`update_search_state` and the highlighted spans built in `search_line` — now
share a single `SearchMatcher` that is compiled once per search change (not per
rendered line) and returns character-offset match positions. Matching happens
on the whole decoded line so regex anchors (`^`, `$`, `\b`) behave against the
real line boundaries and the two paths stay column-aligned. An invalid /
in-progress pattern matches nothing, which the existing red "0 matches" bar
already signals.

The issue asked for `Ctrl+Shift+F`, but control combos collapse Shift on most
terminals (indistinguishable from `Ctrl+F` without the Kitty keyboard
protocol), so `Ctrl+E` is used instead for a reliable, cross-platform toggle.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The e2e test searched for a bare \d+, but on Linux the PTY serial port
connects and adds a "Connected at .../dev/pts/N with 115200bps" log line
whose digits also matched, giving 4 hits instead of the 2 asserted (macOS
never connects, so it saw 2 and passed). Anchor the pattern to `err ` so
the connection line is excluded and the count is a deterministic 2 on both.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@matheuswhite
matheuswhite force-pushed the feat/209-regex-search branch from 0360746 to c2e0ac9 Compare July 19, 2026 17:25
@matheuswhite
matheuswhite merged commit 45fd898 into main Jul 19, 2026
8 checks passed
@matheuswhite
matheuswhite deleted the feat/209-regex-search branch July 19, 2026 17:39
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.

Regex on search mode

1 participant