Files
codex/codex-rs/tui/src/vim_search.rs
T
Benjamin Carlsson 63d213884d Add Vim search motions to the composer (#41586)
## What changed

- Add draft-local forward and backward literal search with `/` and `?`, plus
  wrapped repeat navigation with `n` and `N`.
- Support search motions after delete, change, and yank operators, while keeping
  queries separate from the draft and skipping atomic elements and partial
  grapheme matches.
- Render and edit the query in the composer footer, highlight matches, and add
  configurable `tui.keymap.vim_search` bindings to the keymap picker and schema.

## Testing

- Cover navigation, wrapping, operator composition, repeat behavior, Unicode
  graphemes, atomic elements, query editing, paste handling, and rendering.

GitOrigin-RevId: 087b080ff8fd74e48456c6b899c07ea5eed38e0c
2026-08-30 00:42:00 +00:00

45 lines
1.3 KiB
Rust

//! Surface-independent literal Vim search queries. Matches span complete graphemes;
//! callers own navigation, operator ranges, and the lifetime of accepted versus pending input.
use std::ops::Range;
use unicode_segmentation::UnicodeSegmentation;
#[derive(Clone, Copy, Debug, Default)]
pub(crate) enum SearchDirection {
#[default]
Forward,
Backward,
}
impl SearchDirection {
pub(crate) fn reversed(self) -> Self {
match self {
Self::Forward => Self::Backward,
Self::Backward => Self::Forward,
}
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct SearchQuery {
pub(crate) text: String,
pub(crate) direction: SearchDirection,
}
pub(crate) fn matching_ranges<'a>(
text: &'a str,
query: &'a str,
) -> impl Iterator<Item = Range<usize>> + 'a {
let text = if query.is_empty() { "" } else { text };
text.grapheme_indices(/*is_extended*/ true)
.filter(move |&(start, _)| text[start..].starts_with(query))
.map(move |(start, _)| {
let end = text[start..]
.grapheme_indices(/*is_extended*/ true)
.map(|(offset, grapheme)| start + offset + grapheme.len())
.find(|&end| end >= start + query.len())
.unwrap_or(text.len());
start..end
})
}