#[derive(Debug)]
pub(crate) struct UndoStack<T> {
past: Vec<T>,
future: Vec<T>,
stashed_future: Vec<T>,
}
impl<T> Default for UndoStack<T> {
fn default() -> Self {
Self {
past: vec![],
future: vec![],
stashed_future: vec![],
}
}
}
impl<T: Clone + PartialEq> UndoStack<T> {
const CAP: usize = 64;
pub(crate) fn record(&mut self, before: &T) {
if self.past.last() == Some(before) {
return;
}
self.past.push(before.clone());
if self.past.len() > Self::CAP {
self.past.remove(0);
}
self.stashed_future = std::mem::take(&mut self.future);
}
pub(crate) fn undo(&mut self, current: &mut T) -> bool {
let Some(previous) = self.past.pop() else {
return false;
};
self.stashed_future.clear();
self.future.push(std::mem::replace(current, previous));
true
}
pub(crate) fn redo(&mut self, current: &mut T) -> bool {
let Some(next) = self.future.pop() else {
return false;
};
self.stashed_future.clear();
self.past.push(std::mem::replace(current, next));
true
}
pub(crate) fn can_undo(&self) -> bool {
!self.past.is_empty()
}
pub(crate) fn can_redo(&self) -> bool {
!self.future.is_empty()
}
pub(crate) fn discard_if_unchanged(&mut self, current: &T) {
if self.past.last() == Some(current) {
self.past.pop();
if self.future.is_empty() {
self.future = std::mem::take(&mut self.stashed_future);
}
}
}
pub(crate) fn clear(&mut self) {
self.past.clear();
self.future.clear();
self.stashed_future.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn undo_and_redo_walk_the_edits() {
let mut stack = UndoStack::default();
let mut value = 1;
stack.record(&value);
value = 2;
assert!(stack.undo(&mut value));
assert_eq!(value, 1);
assert!(stack.redo(&mut value));
assert_eq!(value, 2);
assert!(!stack.redo(&mut value));
}
#[test]
fn a_new_edit_drops_what_was_undone() {
let mut stack = UndoStack::default();
let mut value = 1;
stack.record(&value);
value = 2;
stack.undo(&mut value);
stack.record(&value);
assert!(!stack.can_redo());
}
#[test]
fn only_the_last_edits_are_kept() {
let mut stack = UndoStack::default();
let mut value = 0;
for next in 1..=70 {
stack.record(&value);
value = next;
}
let mut undone = 0;
while stack.undo(&mut value) {
undone += 1;
}
assert_eq!(undone, UndoStack::<i32>::CAP);
}
#[test]
fn recording_the_same_state_twice_is_one_step() {
let mut stack = UndoStack::default();
let mut value = 1;
stack.record(&value);
stack.record(&value);
value = 2;
stack.undo(&mut value);
assert!(!stack.can_undo());
}
#[test]
fn an_unchanged_edit_keeps_the_redo_history() {
let mut stack = UndoStack::default();
let mut value = 1;
stack.record(&value);
value = 2;
stack.undo(&mut value);
stack.record(&value);
stack.discard_if_unchanged(&value);
assert!(stack.can_redo());
assert!(stack.redo(&mut value));
assert_eq!(value, 2);
}
#[test]
fn a_real_change_after_undo_drops_the_redo_history() {
let mut stack = UndoStack::default();
let mut value = 1;
stack.record(&value);
value = 2;
stack.undo(&mut value);
stack.record(&value);
value = 3;
stack.discard_if_unchanged(&value);
assert!(!stack.can_redo());
assert!(stack.undo(&mut value));
assert_eq!(value, 1);
}
#[test]
fn a_snapshot_nothing_changed_is_dropped() {
let mut stack = UndoStack::default();
let value = 1;
stack.record(&value);
stack.discard_if_unchanged(&value);
assert!(!stack.can_undo());
}
}