#[derive(Clone, Debug)]
#[cfg_attr(
feature = "serialization",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct History {
entries: Vec<String>,
max_entries: usize,
browse_index: Option<usize>,
stash: Option<String>,
}
impl Default for History {
fn default() -> Self {
Self::new(100)
}
}
impl PartialEq for History {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl History {
pub fn new(max_entries: usize) -> Self {
Self {
entries: Vec::new(),
max_entries,
browse_index: None,
stash: None,
}
}
pub fn push(&mut self, entry: String) {
if entry.is_empty() {
return;
}
if self.entries.last() == Some(&entry) {
return;
}
self.entries.push(entry);
while self.entries.len() > self.max_entries {
self.entries.remove(0);
}
self.exit_browse();
}
pub fn prev(&mut self, current_buffer: &str) -> Option<&str> {
if self.entries.is_empty() {
return None;
}
let new_index = match self.browse_index {
None => {
self.stash = Some(current_buffer.to_string());
self.entries.len() - 1
}
Some(0) => return None, Some(i) => i - 1,
};
self.browse_index = Some(new_index);
Some(&self.entries[new_index])
}
pub fn next(&mut self) -> Option<String> {
let i = self.browse_index?;
if i + 1 >= self.entries.len() {
let stash = self.stash.take().unwrap_or_default();
self.browse_index = None;
Some(stash)
} else {
self.browse_index = Some(i + 1);
Some(self.entries[i + 1].clone())
}
}
pub fn exit_browse(&mut self) {
self.browse_index = None;
self.stash = None;
}
pub fn is_browsing(&self) -> bool {
self.browse_index.is_some()
}
pub fn count(&self) -> usize {
self.entries.len()
}
pub fn max_entries(&self) -> usize {
self.max_entries
}
pub fn set_max_entries(&mut self, max: usize) {
self.max_entries = max;
if self.entries.len() > max {
let excess = self.entries.len() - max;
self.entries.drain(..excess);
}
}
pub fn entries(&self) -> &[String] {
&self.entries
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let h = History::new(10);
assert_eq!(h.count(), 0);
assert!(!h.is_browsing());
}
#[test]
fn test_push() {
let mut h = History::new(10);
h.push("hello".to_string());
assert_eq!(h.count(), 1);
assert_eq!(h.entries()[0], "hello");
}
#[test]
fn test_push_ignores_empty() {
let mut h = History::new(10);
h.push(String::new());
assert_eq!(h.count(), 0);
}
#[test]
fn test_push_ignores_consecutive_duplicates() {
let mut h = History::new(10);
h.push("hello".to_string());
h.push("hello".to_string());
assert_eq!(h.count(), 1);
}
#[test]
fn test_push_allows_non_consecutive_duplicates() {
let mut h = History::new(10);
h.push("hello".to_string());
h.push("world".to_string());
h.push("hello".to_string());
assert_eq!(h.count(), 3);
}
#[test]
fn test_max_entries() {
let mut h = History::new(3);
h.push("a".to_string());
h.push("b".to_string());
h.push("c".to_string());
h.push("d".to_string());
assert_eq!(h.count(), 3);
assert_eq!(h.entries()[0], "b");
}
#[test]
fn test_prev_empty_history() {
let mut h = History::new(10);
assert_eq!(h.prev("buffer"), None);
}
#[test]
fn test_prev_starts_browsing() {
let mut h = History::new(10);
h.push("first".to_string());
h.push("second".to_string());
let entry = h.prev("current");
assert_eq!(entry, Some("second"));
assert!(h.is_browsing());
}
#[test]
fn test_prev_traverses_backwards() {
let mut h = History::new(10);
h.push("first".to_string());
h.push("second".to_string());
assert_eq!(h.prev("current"), Some("second"));
assert_eq!(h.prev("current"), Some("first"));
assert_eq!(h.prev("current"), None); }
#[test]
fn test_next_not_browsing() {
let mut h = History::new(10);
assert_eq!(h.next(), None);
}
#[test]
fn test_next_restores_stash() {
let mut h = History::new(10);
h.push("first".to_string());
h.prev("my buffer");
let restored = h.next();
assert_eq!(restored, Some("my buffer".to_string()));
assert!(!h.is_browsing());
}
#[test]
fn test_prev_next_cycle() {
let mut h = History::new(10);
h.push("a".to_string());
h.push("b".to_string());
h.push("c".to_string());
assert_eq!(h.prev("current"), Some("c"));
assert_eq!(h.prev("current"), Some("b"));
assert_eq!(h.next(), Some("c".to_string()));
assert_eq!(h.next(), Some("current".to_string()));
}
#[test]
fn test_exit_browse() {
let mut h = History::new(10);
h.push("first".to_string());
h.prev("current");
assert!(h.is_browsing());
h.exit_browse();
assert!(!h.is_browsing());
}
#[test]
fn test_set_max_entries_evicts_oldest() {
let mut h = History::new(10);
h.push("a".to_string());
h.push("b".to_string());
h.push("c".to_string());
h.push("d".to_string());
h.push("e".to_string());
assert_eq!(h.count(), 5);
h.set_max_entries(2);
assert_eq!(h.count(), 2);
assert_eq!(h.entries()[0], "d");
assert_eq!(h.entries()[1], "e");
}
#[test]
fn test_set_max_entries_no_eviction_when_under_limit() {
let mut h = History::new(10);
h.push("a".to_string());
h.push("b".to_string());
assert_eq!(h.count(), 2);
h.set_max_entries(10);
assert_eq!(h.count(), 2);
}
}