use std::collections::VecDeque;
#[derive(Debug, Clone)]
pub struct UndoHistory<T> {
past: VecDeque<T>,
present: T,
future: Vec<T>,
limit: usize,
}
impl<T: Clone> UndoHistory<T> {
pub fn new(initial: T) -> Self {
Self {
past: VecDeque::new(),
present: initial,
future: Vec::new(),
limit: 100,
}
}
pub fn set_limit(&mut self, limit: usize) {
self.limit = limit;
while self.past.len() > self.limit {
self.past.pop_front();
}
}
pub fn current(&self) -> &T {
&self.present
}
pub fn current_mut(&mut self) -> &mut T {
self.future.clear();
&mut self.present
}
pub fn can_undo(&self) -> bool {
!self.past.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.future.is_empty()
}
pub fn undo_depth(&self) -> usize {
self.past.len()
}
pub fn commit(&mut self, next: T) {
let previous = std::mem::replace(&mut self.present, next);
self.past.push_back(previous);
if self.past.len() > self.limit {
self.past.pop_front();
}
self.future.clear();
}
pub fn edit(&mut self, mutate: impl FnOnce(&mut T)) {
let mut next = self.present.clone();
mutate(&mut next);
self.commit(next);
}
pub fn coalesce(&mut self, mutate: impl FnOnce(&mut T)) {
mutate(&mut self.present);
self.future.clear();
}
pub fn undo(&mut self) -> bool {
if let Some(previous) = self.past.pop_back() {
let current = std::mem::replace(&mut self.present, previous);
self.future.push(current);
true
} else {
false
}
}
pub fn redo(&mut self) -> bool {
if let Some(next) = self.future.pop() {
let current = std::mem::replace(&mut self.present, next);
self.past.push_back(current);
if self.past.len() > self.limit {
self.past.pop_front();
}
true
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn commit_undo_redo_restores_states() {
let mut history = UndoHistory::new(0i32);
history.commit(1);
history.commit(2);
assert_eq!(*history.current(), 2);
assert!(history.undo());
assert_eq!(*history.current(), 1);
assert!(history.undo());
assert_eq!(*history.current(), 0);
assert!(!history.undo());
assert!(history.redo());
assert_eq!(*history.current(), 1);
assert!(history.redo());
assert_eq!(*history.current(), 2);
assert!(!history.redo());
}
#[test]
fn edit_applies_one_transaction() {
let mut history = UndoHistory::new(vec![1, 2, 3]);
history.edit(|v| v.push(4));
assert_eq!(history.current(), &vec![1, 2, 3, 4]);
history.undo();
assert_eq!(history.current(), &vec![1, 2, 3]);
}
#[test]
fn commit_after_undo_clears_redo() {
let mut history = UndoHistory::new(0);
history.commit(1);
history.commit(2);
history.undo();
assert!(history.can_redo());
history.commit(9);
assert!(!history.can_redo());
assert_eq!(*history.current(), 9);
}
#[test]
fn coalesce_does_not_grow_history() {
let mut history = UndoHistory::new(0);
history.commit(1);
history.coalesce(|v| *v = 5);
history.coalesce(|v| *v = 7);
assert_eq!(*history.current(), 7);
assert_eq!(history.undo_depth(), 1);
history.undo();
assert_eq!(*history.current(), 0);
}
#[test]
fn limit_drops_oldest_steps() {
let mut history = UndoHistory::new(0);
history.set_limit(2);
for value in 1..=5 {
history.commit(value);
}
assert_eq!(history.undo_depth(), 2);
history.undo();
history.undo();
assert!(!history.can_undo());
assert_eq!(*history.current(), 3);
}
#[test]
fn redo_and_direct_mutation_preserve_history_invariants() {
let mut history = UndoHistory::new(0);
history.commit(1);
history.commit(2);
history.commit(3);
assert!(history.undo());
assert!(history.undo());
history.set_limit(1);
assert!(history.redo());
assert!(history.redo());
assert_eq!(history.undo_depth(), 1);
assert!(history.undo());
assert!(history.can_redo());
*history.current_mut() = 9;
assert!(!history.can_redo());
assert_eq!(*history.current(), 9);
history.set_limit(0);
history.commit(10);
assert!(!history.can_undo());
}
}