use strop_core::Range;
use strop_grammar::{self as grammar, Command};
use crate::editor::{Editor, FindPending, LastSearch};
impl Editor {
pub(crate) fn note_search(&mut self, cmd: &Command) {
match &cmd.target {
grammar::Target::Motion(grammar::Motion::Search(query)) => {
self.last_search = Some(LastSearch {
query: query.clone(),
backward: false,
});
}
grammar::Target::Motion(grammar::Motion::SearchBackward(query)) => {
self.last_search = Some(LastSearch {
query: query.clone(),
backward: true,
});
}
grammar::Target::Motion(grammar::Motion::FindChar { ch, till, backward }) => {
self.last_find = Some((*ch, *backward, *till));
}
_ => {}
}
}
pub(crate) fn repeat_find(&mut self, reverse: bool) {
let Some((ch, backward, till)) = self.last_find else {
self.message = "no previous find".into();
return;
};
let backward = backward ^ reverse;
let seek = |buf: &strop_core::Buffer, cursor: usize| -> Option<usize> {
let landing = |target: usize| {
if !till {
target
} else if backward {
buf.ceil_boundary(target + 1)
} else {
buf.clamp_boundary(target.saturating_sub(1))
}
};
let mut target = grammar::find_character(buf, cursor.into(), ch, backward, 1)?;
while landing(target.get()) == cursor {
target = grammar::find_character(buf, target, ch, backward, 1)?;
}
Some(landing(target.get()))
};
let extras: Vec<usize> = self
.extra_selections()
.iter()
.map(|s| seek(self.buf(), s.head).unwrap_or(s.head))
.collect();
self.sels_mut().set_extras(extras);
match seek(self.buf(), self.head()) {
Some(h) => {
self.set_head(h);
self.flash(Range::charwise(self.head(), self.head()));
}
None => self.message = "find: no more matches".into(),
}
self.normalize_cursors();
}
pub(crate) fn repeat_search(&mut self, invert: bool) {
let Some(search) = self.last_search.clone() else {
self.message = "no previous search".into();
return;
};
let motion = if search.backward ^ invert {
grammar::Motion::SearchBackward(search.query.clone())
} else {
grammar::Motion::Search(search.query.clone())
};
let command = grammar::Command {
op: None,
register: None,
count: None,
target: grammar::Target::Motion(motion),
keys: if invert { "N" } else { "n" }.into(),
};
if self.defer_resolution(
&command,
self.all_cursors(),
super::super::resolution::ResolutionPurpose::RepeatSearch(invert),
) {
return;
}
self.move_cursor(&command);
self.last_search = Some(search);
}
pub(crate) fn search_word_under_cursor(&mut self, backward: bool) {
let word_char = |c: char| c.is_alphanumeric() || c == '_';
let buf_len = self.buf().len_bytes();
let head = self.buf().clamp_boundary(self.head());
if head >= buf_len {
self.message = "no word under cursor".into();
return;
}
let char_at = |position: usize| -> Option<char> {
(position < buf_len).then(|| {
self.buf()
.text()
.char(self.buf().text().byte_to_char(position))
})
};
if !char_at(head).is_some_and(word_char) {
self.message = "no word under cursor".into();
return;
}
let mut start = head;
while start > 0 {
let prev = self.buf().clamp_boundary(start - 1);
if char_at(prev).is_some_and(word_char) {
start = prev;
} else {
break;
}
}
let mut end = head;
while end < buf_len {
match char_at(end) {
Some(ch) if word_char(ch) => end += ch.len_utf8(),
_ => break,
}
}
let pattern = self.buf().text().byte_slice(start..end).to_string();
let query = match grammar::CompiledQuery::compile(&pattern, true) {
Ok(query) => query,
Err(error) => {
self.message = error.to_string();
return;
}
};
self.last_search = Some(LastSearch { query, backward });
if backward {
self.set_head(start);
}
self.repeat_search(false);
}
pub fn find_candidates(&self) -> Option<FindPending> {
let m = self.walker.pending_motion();
let ch = m.chars().next()?;
(m.chars().count() == 1 && matches!(ch, 'f' | 'F' | 't' | 'T')).then_some(FindPending {
ch,
backward: matches!(ch, 'F' | 'T'),
})
}
pub fn current_search_query(
&self,
) -> Result<Option<grammar::CompiledQuery>, grammar::QueryError> {
if let Some(pattern) = self.search_pattern() {
grammar::CompiledQuery::compile(pattern, false).map(Some)
} else {
Ok(self.last_search.as_ref().map(|search| search.query.clone()))
}
}
}