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: 128,
offset: 0,
saved_absolute_index: None,
}
}
pub fn with_limit(limit: usize) -> Self {
Self {
limit,
..Self::new()
}
}
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::History;
#[test]
fn undo_restores_previous_frame() {
let mut h = History::with_limit(8);
h.record(&['a']);
h.record(&['b']);
let mut cells = vec!['b'];
h.undo(&mut cells);
assert_eq!(cells, vec!['a']);
}
#[test]
fn redo_reapplies_undone_frame() {
let mut h = History::with_limit(8);
h.record(&['a']);
h.record(&['b']);
let mut cells = vec!['b'];
h.undo(&mut cells);
h.redo(&mut cells);
assert_eq!(cells, vec!['b']);
}
#[test]
fn identical_frames_are_not_recorded_twice() {
let mut h = History::with_limit(8);
h.record(&['a']);
h.record(&['a']);
assert_eq!(h.frames.len(), 1);
}
#[test]
fn oldest_frame_is_evicted_beyond_limit() {
let mut h = History::with_limit(2);
h.record(&['a']);
h.record(&['b']);
h.record(&['c']);
assert_eq!(h.frames.len(), 2);
assert_eq!(h.offset, 1);
}
#[test]
fn zero_limit_disables_recording() {
let mut h = History::with_limit(0);
h.record(&['a']);
assert!(h.frames.is_empty());
}
}