use std::collections::VecDeque;
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
use crate::HISTORY_SIZE_MAX;
pub struct History {
pub entries: VecDeque<String>,
pub max_size: usize,
pub sender: UnboundedSender<String>,
current_position: Option<usize>,
}
impl History {
pub fn new() -> (Self, UnboundedReceiver<String>) {
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel::<String>();
(
Self {
entries: Default::default(),
max_size: HISTORY_SIZE_MAX,
sender,
current_position: Default::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 {
if *index < self.entries.len() - 1 {
*index += 1;
}
Some(&self.entries[*index])
} else if !self.entries.is_empty() {
self.current_position = Some(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 == 0 {
self.current_position = None;
return Some("");
}
*index -= 1;
Some(&self.entries[*index])
} 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);
}
}