use crate::{ArrayBoundsCheck, ArrayOverflowResult, HISTORY_SIZE_MAX,
core::coordinates::{Index, idx, len}};
use std::collections::VecDeque;
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
#[derive(Debug)]
pub struct History {
pub entries: VecDeque<String>,
pub max_size: usize,
pub sender: UnboundedSender<String>,
current_position: Option<Index>,
}
impl History {
#[must_use]
pub fn new() -> (Self, UnboundedReceiver<String>) {
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel::<String>();
(
Self {
entries: VecDeque::default(),
max_size: HISTORY_SIZE_MAX,
sender,
current_position: Option::default(),
},
receiver,
)
}
}
impl History {
pub fn update(&mut self, maybe_line: Option<String>) {
if let Some(line) = maybe_line {
if self.entries.front() == Some(&line) || line.is_empty() {
return;
}
self.entries.push_front(line);
self.current_position = None;
if self.entries.len() > self.max_size {
self.entries.pop_back();
}
}
}
pub fn search_next(&mut self) -> Option<&str> {
if let Some(index) = &mut self.current_position {
let entries_length = len(self.entries.len());
let next_index: Index = *index + 1;
if next_index.overflows(entries_length) == ArrayOverflowResult::Within {
*index = next_index;
}
Some(&self.entries[index.as_usize()])
} else if !self.entries.is_empty() {
self.current_position = Some(idx(0));
Some(&self.entries[0])
} else {
None
}
}
pub fn search_previous(&mut self) -> Option<&str> {
if let Some(index) = &mut self.current_position {
if *index == idx(0) {
self.current_position = None;
return Some("");
}
*index -= 1;
Some(&self.entries[index.as_usize()])
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
#[allow(clippy::needless_return)]
async fn test_update() {
let (mut history, _) = History::new();
history.max_size = 2;
history.update(Some("test1".into()));
assert_eq!(history.entries.front(), Some(&"test1".to_string()));
history.update(None);
assert_eq!(history.entries.front(), Some(&"test1".to_string()));
history.update(Some("test1".into()));
assert_eq!(history.entries.front(), Some(&"test1".to_string()));
history.update(Some("test2".into()));
assert_eq!(history.entries.front(), Some(&"test2".to_string()));
assert_eq!(history.entries.len(), 2);
history.update(Some("test3".into()));
assert_eq!(history.entries.len(), 2);
assert!(history.entries.contains(&"test2".to_string()));
assert!(history.entries.contains(&"test3".to_string()));
}
#[tokio::test]
#[allow(clippy::needless_return)]
async fn test_search_next() {
let (mut history, _) = History::new();
history.max_size = 2;
history.update(Some("test1".into()));
history.update(Some("test2".into()));
history.update(Some("test3".into()));
assert_eq!(history.search_next(), Some("test3"));
assert_eq!(history.search_next(), Some("test2"));
assert_eq!(history.search_next(), Some("test2"));
assert_eq!(history.search_next(), Some("test2"));
}
#[tokio::test]
#[allow(clippy::needless_return)]
async fn test_search_previous() {
let (mut history, _) = History::new();
history.max_size = 2;
history.update(Some("test1".into()));
history.update(Some("test2".into()));
history.update(Some("test3".into()));
assert_eq!(history.search_previous(), None);
assert_eq!(history.search_next(), Some("test3"));
assert_eq!(history.search_previous(), Some(""));
assert_eq!(history.search_previous(), None);
}
}