use crate::editor::EditorState;
use crate::search::SearchState;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IncsearchSession {
prior: Option<SearchState>,
saved_cursor: usize,
saved_scroll: usize,
}
pub fn update_incsearch(
editor: &mut EditorState,
session: &mut Option<IncsearchSession>,
input: &str,
forward: bool,
viewport_height: usize,
viewport_width: usize,
) {
if session.is_none() {
*session = Some(IncsearchSession {
prior: editor.search.take(),
saved_cursor: editor.cursor.offset,
saved_scroll: editor.scroll,
});
}
let (saved_cursor, saved_scroll) = {
let s = session.as_ref().expect("session ensured above");
(s.saved_cursor, s.saved_scroll)
};
let Ok(mut state) = SearchState::new(input.to_owned(), None) else {
editor.search = None;
editor.restore_view(saved_cursor, Some(saved_scroll));
return;
};
state.ensure_fresh(&editor.buffer.contents(), editor.buffer.version());
if state.matches.is_empty() {
editor.search = None;
editor.restore_view(saved_cursor, Some(saved_scroll));
return;
}
let cursor_byte = editor
.buffer
.rope()
.char_to_byte(saved_cursor.min(editor.buffer.len_chars()));
state.focus_relative_to(cursor_byte, forward);
editor.search = Some(state);
editor.sync_cursor_to_search_focus();
editor.scroll_cursor_comfortably_into_view(viewport_height, viewport_width);
}
pub fn end_incsearch(editor: &mut EditorState, session: &mut Option<IncsearchSession>) -> bool {
let Some(s) = session.take() else {
return false;
};
editor.search = s.prior;
editor.restore_view(s.saved_cursor, Some(s.saved_scroll));
true
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Theme;
use crate::document::Buffer;
fn theme() -> &'static Theme {
Box::leak(Box::new(Theme::default()))
}
fn editor(text: &str) -> EditorState {
let mut st = EditorState::new(Buffer::from_str(text), theme());
st.update_cursor_block();
st
}
#[test]
fn update_focuses_the_first_match_after_the_origin() {
let mut st = editor("foo bar\nfoo");
let mut session = None;
update_incsearch(&mut st, &mut session, "foo", true, 24, 80);
let s = st.search.as_ref().expect("session live");
assert_eq!(s.focused_range(), Some(8..11));
assert_eq!(st.cursor.offset, 8, "cursor parked on the focus");
}
#[test]
fn backward_search_focuses_the_last_match_before_the_origin() {
let mut st = editor("foo bar\nfoo");
st.place_cursor(8); let mut session = None;
update_incsearch(&mut st, &mut session, "foo", false, 24, 80);
let s = st.search.as_ref().expect("session live");
assert_eq!(s.focused_range(), Some(0..3));
}
#[test]
fn every_keystroke_resolves_from_the_saved_cursor_not_the_parked_one() {
let mut st = editor("aa ab ac");
let mut session = None;
update_incsearch(&mut st, &mut session, "a", true, 24, 80);
assert_eq!(st.search.as_ref().unwrap().focused_range(), Some(1..2));
update_incsearch(&mut st, &mut session, "ab", true, 24, 80);
assert_eq!(st.search.as_ref().unwrap().focused_range(), Some(3..5));
}
#[test]
fn a_matchless_input_clears_highlights_and_restores_the_view() {
let mut st = editor("foo bar");
st.scroll = 0;
let mut session = None;
update_incsearch(&mut st, &mut session, "bar", true, 24, 80);
assert!(st.search.is_some());
update_incsearch(&mut st, &mut session, "barz", true, 24, 80);
assert!(st.search.is_none(), "no match → no highlights");
assert_eq!(st.cursor.offset, 0, "view back at the origin");
assert!(session.is_some(), "the session survives for later keys");
update_incsearch(&mut st, &mut session, "bar", true, 24, 80);
assert!(st.search.is_some());
}
#[test]
fn end_restores_prior_session_cursor_and_scroll() {
let mut st = editor("foo bar\nfoo");
let mut prior = SearchState::new("bar".to_owned(), None).expect("valid");
prior.ensure_fresh(&st.buffer.contents(), st.buffer.version());
st.search = Some(prior.clone());
st.place_cursor(5);
st.scroll = 1;
let mut session = None;
update_incsearch(&mut st, &mut session, "foo", true, 24, 80);
assert_eq!(st.search.as_ref().unwrap().query, "foo");
assert!(end_incsearch(&mut st, &mut session));
assert_eq!(
st.search.as_ref().map(|s| s.query.as_str()),
Some("bar"),
"prior hlsearch session restored"
);
assert_eq!(st.cursor.offset, 5);
assert_eq!(st.scroll, 1);
assert!(!end_incsearch(&mut st, &mut session), "already ended");
}
}