pub struct UndoStack<T: Clone> {
undo_stack: Vec<T>,
redo_stack: Vec<T>,
max_history: usize,
}
impl<T: Clone> UndoStack<T> {
pub fn new(max_history: usize) -> Self {
Self {
undo_stack: Vec::new(),
redo_stack: Vec::new(),
max_history,
}
}
pub fn with_default_capacity() -> Self {
Self::new(100)
}
pub fn push(&mut self, state: T) {
self.undo_stack.push(state);
self.redo_stack.clear();
if self.undo_stack.len() > self.max_history {
self.undo_stack.remove(0);
}
}
pub fn undo(&mut self) -> Option<&T> {
if self.undo_stack.len() <= 1 {
return None;
}
let current = self.undo_stack.pop().unwrap();
self.redo_stack.push(current);
self.undo_stack.last()
}
pub fn redo(&mut self) -> Option<&T> {
if self.redo_stack.is_empty() {
return None;
}
let state = self.redo_stack.pop().unwrap();
self.undo_stack.push(state);
self.undo_stack.last()
}
pub fn can_undo(&self) -> bool {
self.undo_stack.len() > 1
}
pub fn can_redo(&self) -> bool {
!self.redo_stack.is_empty()
}
pub fn clear(&mut self) {
self.undo_stack.clear();
self.redo_stack.clear();
}
pub fn current(&self) -> Option<&T> {
self.undo_stack.last()
}
pub fn undo_depth(&self) -> usize {
self.undo_stack.len().saturating_sub(1)
}
pub fn redo_depth(&self) -> usize {
self.redo_stack.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let stack: UndoStack<String> = UndoStack::new(50);
assert!(!stack.can_undo());
assert!(!stack.can_redo());
assert_eq!(stack.current(), None);
assert_eq!(stack.undo_depth(), 0);
assert_eq!(stack.redo_depth(), 0);
}
#[test]
fn test_with_default_capacity() {
let stack: UndoStack<i32> = UndoStack::with_default_capacity();
assert_eq!(stack.max_history, 100);
}
#[test]
fn test_push_and_current() {
let mut stack = UndoStack::new(10);
stack.push("first".to_string());
assert_eq!(stack.current(), Some(&"first".to_string()));
stack.push("second".to_string());
assert_eq!(stack.current(), Some(&"second".to_string()));
}
#[test]
fn test_undo() {
let mut stack = UndoStack::new(10);
stack.push("initial".to_string());
stack.push("modified".to_string());
assert!(stack.can_undo());
let result = stack.undo();
assert_eq!(result, Some(&"initial".to_string()));
assert_eq!(stack.current(), Some(&"initial".to_string()));
}
#[test]
fn test_redo() {
let mut stack = UndoStack::new(10);
stack.push("initial".to_string());
stack.push("modified".to_string());
stack.undo();
assert!(stack.can_redo());
let result = stack.redo();
assert_eq!(result, Some(&"modified".to_string()));
assert_eq!(stack.current(), Some(&"modified".to_string()));
}
#[test]
fn test_undo_clears_on_push() {
let mut stack = UndoStack::new(10);
stack.push("a".to_string());
stack.push("b".to_string());
stack.push("c".to_string());
stack.undo(); assert!(stack.can_redo());
stack.push("d".to_string());
assert!(!stack.can_redo());
assert_eq!(stack.current(), Some(&"d".to_string()));
}
#[test]
fn test_undo_nothing_to_undo() {
let mut stack: UndoStack<String> = UndoStack::new(10);
assert_eq!(stack.undo(), None);
stack.push("only".to_string());
assert_eq!(stack.undo(), None);
}
#[test]
fn test_redo_nothing_to_redo() {
let mut stack: UndoStack<String> = UndoStack::new(10);
assert_eq!(stack.redo(), None);
stack.push("a".to_string());
assert_eq!(stack.redo(), None);
}
#[test]
fn test_max_history_trimming() {
let mut stack = UndoStack::new(3);
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
assert_eq!(stack.undo_depth(), 2); assert_eq!(stack.current(), Some(&4));
stack.undo();
assert_eq!(stack.current(), Some(&3));
stack.undo();
assert_eq!(stack.current(), Some(&2));
assert!(!stack.can_undo());
}
#[test]
fn test_clear() {
let mut stack = UndoStack::new(10);
stack.push("a".to_string());
stack.push("b".to_string());
stack.undo();
stack.clear();
assert!(!stack.can_undo());
assert!(!stack.can_redo());
assert_eq!(stack.current(), None);
assert_eq!(stack.undo_depth(), 0);
assert_eq!(stack.redo_depth(), 0);
}
#[test]
fn test_undo_redo_depths() {
let mut stack = UndoStack::new(10);
assert_eq!(stack.undo_depth(), 0);
assert_eq!(stack.redo_depth(), 0);
stack.push("a".to_string());
assert_eq!(stack.undo_depth(), 0);
assert_eq!(stack.redo_depth(), 0);
stack.push("b".to_string());
assert_eq!(stack.undo_depth(), 1);
assert_eq!(stack.redo_depth(), 0);
stack.push("c".to_string());
assert_eq!(stack.undo_depth(), 2);
assert_eq!(stack.redo_depth(), 0);
stack.undo();
assert_eq!(stack.undo_depth(), 1);
assert_eq!(stack.redo_depth(), 1);
stack.undo();
assert_eq!(stack.undo_depth(), 0);
assert_eq!(stack.redo_depth(), 2);
}
#[test]
fn test_multiple_undo_redo_cycles() {
let mut stack = UndoStack::new(10);
stack.push(1);
stack.push(2);
stack.push(3);
assert_eq!(stack.undo(), Some(&2));
assert_eq!(stack.undo(), Some(&1));
assert_eq!(stack.undo(), None);
assert_eq!(stack.redo(), Some(&2));
assert_eq!(stack.redo(), Some(&3));
assert_eq!(stack.redo(), None);
}
#[test]
fn test_can_undo_and_can_redo() {
let mut stack = UndoStack::new(10);
assert!(!stack.can_undo());
assert!(!stack.can_redo());
stack.push("a".to_string());
assert!(!stack.can_undo()); assert!(!stack.can_redo());
stack.push("b".to_string());
assert!(stack.can_undo());
assert!(!stack.can_redo());
stack.undo();
assert!(!stack.can_undo()); assert!(stack.can_redo());
stack.redo();
assert!(stack.can_undo());
assert!(!stack.can_redo());
}
#[test]
fn test_with_complex_type() {
#[derive(Clone, Debug, PartialEq)]
struct State {
name: String,
value: i32,
}
let mut stack = UndoStack::new(10);
stack.push(State {
name: "first".into(),
value: 1,
});
stack.push(State {
name: "second".into(),
value: 2,
});
let prev = stack.undo().unwrap();
assert_eq!(prev.name, "first");
assert_eq!(prev.value, 1);
}
}