use std::collections::VecDeque;
#[derive(Debug)]
pub struct History {
pub frames: VecDeque<Vec<char>>,
pub index: usize,
pub limit: usize,
pub offset: usize,
pub saved_absolute_index: Option<usize>,
}
impl History {
pub fn new() -> Self {
Self {
frames: VecDeque::with_capacity(32),
index: 0,
limit: 100,
offset: 0,
saved_absolute_index: None,
}
}
pub fn record(&mut self, cells: &[char]) {
if self.limit == 0 {
return;
}
if !self.frames.is_empty()
&& self.index < self.frames.len()
&& self.frames[self.index] == cells
{
return;
}
if self.index + 1 < self.frames.len() {
if let Some(saved) = self.saved_absolute_index
&& saved > self.offset + self.index
{
self.saved_absolute_index = None;
}
self.frames.truncate(self.index + 1);
}
self.frames.push_back(cells.to_vec());
if self.frames.len() > self.limit {
self.frames.pop_front();
self.offset += 1;
}
self.index = self.frames.len().saturating_sub(1);
}
pub fn undo(&mut self, cells: &mut Vec<char>) {
if self.index > 0 {
self.index -= 1;
*cells = self.frames[self.index].clone();
}
}
pub fn redo(&mut self, cells: &mut Vec<char>) {
if self.index + 1 < self.frames.len() {
self.index += 1;
*cells = self.frames[self.index].clone();
}
}
pub fn clear(&mut self) {
self.frames.clear();
self.index = 0;
self.offset = 0;
self.saved_absolute_index = None;
}
}
impl Default for History {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_history_record_dedup() {
let mut hist = History::new();
let state1 = vec!['a'];
let state2 = vec!['b'];
hist.record(&state1);
assert_eq!(hist.frames.len(), 1);
hist.record(&state1);
assert_eq!(hist.frames.len(), 1);
hist.record(&state2);
assert_eq!(hist.frames.len(), 2);
}
#[test]
fn test_history_limit() {
let mut hist = History::new();
hist.limit = 30;
for i in 0..40 {
hist.record(&vec![std::char::from_u32(i + 65).unwrap()]);
}
assert_eq!(hist.frames.len(), 30);
assert_eq!(hist.index, 29);
assert_eq!(hist.frames[0], vec![std::char::from_u32(10 + 65).unwrap()]);
assert_eq!(hist.frames[29], vec![std::char::from_u32(39 + 65).unwrap()]);
}
#[test]
fn test_history_undo_redo() {
let mut hist = History::new();
let mut current = vec!['0'];
hist.record(&vec!['1']);
hist.record(&vec!['2']);
hist.record(&vec!['3']);
assert_eq!(hist.index, 2);
hist.undo(&mut current);
assert_eq!(current, vec!['2']);
assert_eq!(hist.index, 1);
hist.undo(&mut current);
assert_eq!(current, vec!['1']);
assert_eq!(hist.index, 0);
hist.undo(&mut current);
assert_eq!(current, vec!['1']);
hist.redo(&mut current);
assert_eq!(current, vec!['2']);
assert_eq!(hist.index, 1);
hist.redo(&mut current);
assert_eq!(current, vec!['3']);
assert_eq!(hist.index, 2);
hist.redo(&mut current);
assert_eq!(current, vec!['3']);
}
#[test]
fn test_history_fork() {
let mut hist = History::new();
let mut current = vec!['0'];
hist.record(&vec!['1']);
hist.record(&vec!['2']);
hist.record(&vec!['3']);
hist.undo(&mut current);
hist.undo(&mut current);
hist.record(&vec!['A']);
assert_eq!(hist.frames.len(), 2);
assert_eq!(hist.frames[0], vec!['1']);
assert_eq!(hist.frames[1], vec!['A']);
assert_eq!(hist.index, 1);
}
#[test]
fn test_history_clear() {
let mut hist = History::new();
hist.record(&vec!['C']);
hist.record(&vec!['h']);
hist.record(&vec!['a']);
hist.record(&vec!['r']);
hist.record(&vec!['l']);
hist.record(&vec!['o']);
hist.record(&vec!['t']);
hist.record(&vec!['t']);
hist.record(&vec!['e']);
hist.clear();
assert_eq!(hist.frames.len(), 0);
assert_eq!(hist.index, 0);
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn prop_history_never_exceeds_limit(
limit in 0usize..50,
pushes in 0usize..100
) {
let mut hist = History::new();
hist.limit = limit;
for i in 0..pushes {
hist.record(&[std::char::from_digit((i % 36) as u32, 36).unwrap_or('0')]);
}
assert!(hist.frames.len() <= limit);
if limit > 0 && pushes > 0 {
assert!(hist.index < limit);
} else {
assert_eq!(hist.index, 0);
}
}
}
}