use crate::value::Value;
pub const DEFAULT_HISTORY_DEPTH: usize = 10;
pub const DEFAULT_HISTORY_CAPACITY: usize = DEFAULT_HISTORY_DEPTH + 1;
#[derive(Debug, Clone)]
pub struct History {
snapshots: Vec<Vec<Value>>,
position: usize,
capacity: usize,
}
impl Default for History {
fn default() -> Self {
Self::new()
}
}
impl History {
pub fn new() -> Self {
Self::with_capacity(DEFAULT_HISTORY_CAPACITY)
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
snapshots: Vec::new(),
position: 0,
capacity,
}
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn is_enabled(&self) -> bool {
self.capacity >= 2
}
pub fn position(&self) -> usize {
self.position
}
pub fn len(&self) -> usize {
self.snapshots.len()
}
pub fn is_empty(&self) -> bool {
self.snapshots.is_empty()
}
pub fn can_undo(&self) -> bool {
self.position > 0
}
pub fn can_redo(&self) -> bool {
self.position + 1 < self.snapshots.len()
}
pub fn undo_count(&self) -> usize {
self.position
}
pub fn redo_count(&self) -> usize {
if self.snapshots.is_empty() {
0
} else {
self.snapshots.len() - 1 - self.position
}
}
pub fn undo(&mut self) -> Option<Vec<Value>> {
if !self.is_enabled() || self.position == 0 {
return None;
}
self.position -= 1;
Some(self.snapshots[self.position].clone())
}
pub fn redo(&mut self) -> Option<Vec<Value>> {
if !self.is_enabled() || self.position + 1 >= self.snapshots.len() {
return None;
}
self.position += 1;
Some(self.snapshots[self.position].clone())
}
pub fn record(&mut self, snapshot: Vec<Value>) {
if !self.is_enabled() {
return;
}
self.snapshots.truncate(self.position + 1);
if !self.snapshots.is_empty() && snapshot == *self.snapshots.last().unwrap() {
return;
}
if self.snapshots.len() == self.capacity && self.capacity > 0 {
self.snapshots.remove(0);
if self.position > 0 {
self.position -= 1;
}
}
self.snapshots.push(snapshot);
self.position = self.snapshots.len() - 1;
}
pub fn seed(&mut self, snapshot: Vec<Value>) {
if !self.is_enabled() {
return;
}
self.snapshots = vec![snapshot];
self.position = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value::Value;
#[test]
fn undo_disabled_at_zero_capacity() {
let mut h = History::with_capacity(0);
assert!(!h.is_enabled());
h.record(vec![Value::Int(1.into())]);
assert_eq!(h.len(), 0);
assert!(h.undo().is_none());
assert!(h.redo().is_none());
}
#[test]
fn capacity_one_disables() {
let h = History::with_capacity(1);
assert!(!h.is_enabled());
}
#[test]
fn capacity_three_transition() {
let mut h = History::with_capacity(3);
h.seed(vec![Value::Int(0.into())]); h.record(vec![Value::Int(1.into())]); h.record(vec![Value::Int(2.into())]); assert_eq!(h.len(), 3);
assert_eq!(h.position(), 2);
h.record(vec![Value::Int(3.into())]); assert_eq!(h.len(), 3);
assert_eq!(h.position(), 2);
let v = h.undo().unwrap();
assert_eq!(v, vec![Value::Int(2.into())]);
assert_eq!(h.position(), 1);
let v = h.redo().unwrap();
assert_eq!(v, vec![Value::Int(3.into())]);
assert_eq!(h.position(), 2);
}
#[test]
fn mutation_after_undo_drops_redo() {
let mut h = History::with_capacity(5);
h.seed(vec![Value::Int(0.into())]);
h.record(vec![Value::Int(1.into())]);
h.record(vec![Value::Int(2.into())]);
h.undo(); h.record(vec![Value::Int(99.into())]); assert_eq!(h.redo_count(), 0);
}
#[test]
fn no_op_result_truncates_redo_no_append() {
let mut h = History::with_capacity(5);
h.seed(vec![Value::Int(1.into())]);
h.record(vec![Value::Int(2.into())]);
h.undo(); h.record(vec![Value::Int(1.into())]);
assert_eq!(h.len(), 1);
}
#[test]
fn redo_count_empty_buffer() {
let h = History::with_capacity(5);
assert_eq!(h.redo_count(), 0);
}
#[test]
fn seed_empty_stack() {
let mut h = History::with_capacity(5);
h.seed(vec![]);
assert_eq!(h.len(), 1);
assert_eq!(h.position(), 0);
let v = h.undo();
assert!(v.is_none()); }
#[test]
fn new_delegates() {
let h = History::new();
assert_eq!(h.capacity(), DEFAULT_HISTORY_CAPACITY);
}
}