type Snapshot = Vec<serde_json::Value>;
const MAX_DEPTH: usize = 64;
#[derive(Default)]
pub(crate) struct History {
past: Vec<Snapshot>,
future: Vec<Snapshot>,
}
impl History {
pub(crate) fn record(&mut self, before: Snapshot) {
self.future.clear();
if self.past.len() == MAX_DEPTH {
self.past.remove(0);
}
self.past.push(before);
}
pub(crate) fn undo(&mut self, current: Snapshot) -> Option<Snapshot> {
let snap = self.past.pop()?;
self.future.push(current);
Some(snap)
}
pub(crate) fn redo(&mut self, current: Snapshot) -> Option<Snapshot> {
let snap = self.future.pop()?;
self.past.push(current);
Some(snap)
}
pub(crate) fn can_undo(&self) -> bool {
!self.past.is_empty()
}
pub(crate) fn can_redo(&self) -> bool {
!self.future.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn snap(tag: &str) -> Snapshot {
vec![serde_json::json!({ "name": tag })]
}
#[test]
fn empty_history_has_nothing_to_step() {
let mut h = History::default();
assert!(!h.can_undo());
assert!(!h.can_redo());
assert_eq!(h.undo(snap("now")), None);
assert_eq!(h.redo(snap("now")), None);
}
#[test]
fn undo_returns_the_recorded_state_and_arms_redo() {
let mut h = History::default();
h.record(snap("v1"));
assert!(h.can_undo());
let restored = h.undo(snap("v2")).expect("one step to undo");
assert_eq!(restored, snap("v1"));
assert!(!h.can_undo());
assert!(h.can_redo());
let forward = h.redo(snap("v1")).expect("one step to redo");
assert_eq!(forward, snap("v2"));
assert!(h.can_undo());
assert!(!h.can_redo());
}
#[test]
fn a_fresh_edit_clears_the_redo_branch() {
let mut h = History::default();
h.record(snap("v1"));
h.undo(snap("v2")).unwrap();
assert!(h.can_redo());
h.record(snap("v1"));
assert!(!h.can_redo());
assert_eq!(h.undo(snap("v3")), Some(snap("v1")));
}
#[test]
fn multiple_steps_unwind_in_order() {
let mut h = History::default();
h.record(snap("v1"));
h.record(snap("v2"));
h.record(snap("v3"));
assert_eq!(h.undo(snap("v4")), Some(snap("v3")));
assert_eq!(h.undo(snap("v3")), Some(snap("v2")));
assert_eq!(h.redo(snap("v2")), Some(snap("v3")));
assert_eq!(h.undo(snap("v3")), Some(snap("v2")));
assert_eq!(h.undo(snap("v2")), Some(snap("v1")));
assert_eq!(h.undo(snap("v1")), None);
}
#[test]
fn depth_is_bounded_by_dropping_the_oldest() {
let mut h = History::default();
for i in 0..(MAX_DEPTH + 10) {
h.record(snap(&format!("v{i}")));
}
let mut last = None;
let mut current = snap("current");
while let Some(s) = h.undo(current.clone()) {
current = s.clone();
last = Some(s);
}
assert_eq!(last, Some(snap("v10")));
}
}