use std::collections::VecDeque;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UndoStack<T> {
past: VecDeque<T>,
future: Vec<T>,
limit: usize,
}
impl<T> Default for UndoStack<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> UndoStack<T> {
pub fn new() -> Self {
Self::with_limit(usize::MAX)
}
pub fn with_limit(limit: usize) -> Self {
Self {
past: VecDeque::new(),
future: Vec::new(),
limit,
}
}
pub fn record(&mut self, before: T) {
if self.limit == 0 {
return;
}
self.past.push_back(before);
while self.past.len() > self.limit {
self.past.pop_front();
}
self.future.clear();
}
pub fn undo(&mut self, current: T) -> Option<T> {
let prev = self.past.pop_back()?;
self.future.push(current);
Some(prev)
}
pub fn redo(&mut self, current: T) -> Option<T> {
let next = self.future.pop()?;
self.past.push_back(current);
Some(next)
}
pub fn can_undo(&self) -> bool {
!self.past.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.future.is_empty()
}
pub fn clear(&mut self) {
self.past.clear();
self.future.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn undo_with_empty_history_returns_none() {
let mut stack: UndoStack<i32> = UndoStack::new();
assert_eq!(stack.undo(0), None);
assert!(!stack.can_undo());
}
#[test]
fn redo_with_empty_future_returns_none() {
let mut stack: UndoStack<i32> = UndoStack::new();
assert_eq!(stack.redo(0), None);
assert!(!stack.can_redo());
}
#[test]
fn record_then_undo_restores_prior_state() {
let mut stack = UndoStack::new();
stack.record(0); let restored = stack.undo(1);
assert_eq!(restored, Some(0));
}
#[test]
fn undo_then_redo_round_trips() {
let mut stack = UndoStack::new();
stack.record(0);
let after_undo = stack.undo(1).unwrap();
assert_eq!(after_undo, 0);
let after_redo = stack.redo(after_undo).unwrap();
assert_eq!(after_redo, 1);
}
#[test]
fn record_clears_redo_history() {
let mut stack = UndoStack::new();
stack.record(0);
stack.undo(1); assert!(stack.can_redo());
stack.record(5); assert!(!stack.can_redo());
}
#[test]
fn multiple_undo_steps_pop_in_lifo_order() {
let mut stack = UndoStack::new();
stack.record(0); stack.record(1); stack.record(2); assert_eq!(stack.undo(3), Some(2));
assert_eq!(stack.undo(2), Some(1));
assert_eq!(stack.undo(1), Some(0));
assert_eq!(stack.undo(0), None);
}
#[test]
fn with_limit_zero_disables_history() {
let mut stack = UndoStack::with_limit(0);
stack.record(0);
assert!(!stack.can_undo());
assert_eq!(stack.undo(1), None);
}
#[test]
fn with_limit_evicts_oldest_entry() {
let mut stack = UndoStack::with_limit(2);
stack.record(0);
stack.record(1);
stack.record(2); assert_eq!(stack.undo(3), Some(2));
assert_eq!(stack.undo(2), Some(1));
assert_eq!(
stack.undo(1),
None,
"oldest entry (0) should have been evicted"
);
}
#[test]
fn clear_drops_both_stacks() {
let mut stack = UndoStack::new();
stack.record(0);
stack.undo(1);
assert!(stack.can_redo());
stack.clear();
assert!(!stack.can_undo());
assert!(!stack.can_redo());
}
}