use std::borrow::Cow;
use std::collections::VecDeque;
pub type HistoryIndex = usize;
pub trait History {
fn get(&self, idx: HistoryIndex) -> Option<Cow<str>>;
fn last(&self) -> Option<HistoryIndex>;
fn add(&mut self, line: &str);
fn search(
&self,
idx: HistoryIndex,
style: SearchStyle,
direction: SearchDirection,
pattern: &str,
) -> Option<SearchResult>;
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct SearchResult<'a> {
pub line: Cow<'a, str>,
pub idx: HistoryIndex,
pub cursor: usize,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum SearchStyle {
Substring,
}
impl SearchStyle {
pub fn match_against(&self, pattern: &str, line: &str) -> Option<usize> {
match self {
Self::Substring => line.find(pattern),
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum SearchDirection {
Backwards,
Forwards,
}
impl SearchDirection {
pub fn next(self, idx: HistoryIndex) -> Option<HistoryIndex> {
let (next, overflow) = match self {
Self::Backwards => idx.overflowing_sub(1),
Self::Forwards => idx.overflowing_add(1),
};
if overflow {
None
} else {
Some(next)
}
}
}
#[derive(Default)]
pub struct BasicHistory {
entries: VecDeque<String>,
}
impl History for BasicHistory {
fn get(&self, idx: HistoryIndex) -> Option<Cow<str>> {
self.entries.get(idx).map(|s| Cow::Borrowed(s.as_str()))
}
fn last(&self) -> Option<HistoryIndex> {
if self.entries.is_empty() {
None
} else {
Some(self.entries.len() - 1)
}
}
fn add(&mut self, line: &str) {
if self.entries.back().map(String::as_str) == Some(line) {
return;
}
self.entries.push_back(line.to_owned());
}
fn search(
&self,
idx: HistoryIndex,
style: SearchStyle,
direction: SearchDirection,
pattern: &str,
) -> Option<SearchResult> {
let mut idx = idx;
loop {
let line = match self.entries.get(idx) {
Some(line) => line,
None => return None,
};
if let Some(cursor) = style.match_against(pattern, line) {
return Some(SearchResult {
line: Cow::Borrowed(line.as_str()),
idx,
cursor,
});
}
idx = match direction.next(idx) {
None => return None,
Some(idx) => idx,
};
}
}
}