use std::rc::Rc;
use super::cursor::Cursor;
#[derive(Clone)]
pub struct Change {
pub start: usize,
pub before: String,
pub after: String,
pub cursor_before: Cursor,
pub cursor_after: Cursor,
}
#[derive(Default, Clone)]
pub struct Step {
pub changes: Vec<Change>,
pub cursor_start: Option<Cursor>,
}
pub struct History {
undo: Vec<(u64, Rc<Step>)>,
redo: Vec<(u64, Rc<Step>)>,
current: Step,
pending_start: Option<Cursor>,
grouping: bool,
next_id: u64,
}
impl Default for History {
fn default() -> Self {
History {
undo: Vec::new(),
redo: Vec::new(),
current: Step::default(),
pending_start: None,
grouping: false,
next_id: 1,
}
}
}
impl History {
pub fn record(&mut self, change: Change) {
self.redo.clear();
self.current.changes.push(change);
if !self.grouping {
self.seal();
}
}
fn seal(&mut self) {
if !self.current.changes.is_empty() {
let mut step = std::mem::take(&mut self.current);
step.cursor_start = self.pending_start.take();
self.undo.push((self.next_id, Rc::new(step)));
self.next_id += 1;
}
}
pub fn split(&mut self) {
self.seal();
}
pub fn state(&self) -> u64 {
if !self.current.changes.is_empty() {
return self.next_id;
}
self.undo.last().map(|(id, _)| *id).unwrap_or(0)
}
pub fn begin_group(&mut self, cursor: Option<Cursor>) {
self.seal();
if let Some(c) = cursor {
self.pending_start = Some(c);
}
self.grouping = true;
}
pub fn end_group(&mut self) {
self.grouping = false;
self.seal();
}
pub fn undo(&mut self) -> Option<Rc<Step>> {
self.seal();
let entry = self.undo.pop()?;
let step = Rc::clone(&entry.1);
self.redo.push(entry);
Some(step)
}
pub fn redo(&mut self) -> Option<Rc<Step>> {
let entry = self.redo.pop()?;
let step = Rc::clone(&entry.1);
self.undo.push(entry);
Some(step)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn change(start: usize, before: &str, after: &str) -> Change {
Change {
start,
before: before.to_string(),
after: after.to_string(),
cursor_before: Cursor::new(0, start),
cursor_after: Cursor::new(0, start + after.chars().count()),
}
}
#[test]
fn a_group_is_one_step_however_many_edits_it_made() {
let mut h = History::default();
h.record(change(0, "", "a"));
h.record(change(1, "", "b"));
assert!(h.undo().is_some());
assert!(h.undo().is_some());
assert!(h.undo().is_none(), "two ungrouped edits are two steps");
let mut h = History::default();
h.begin_group(Some(Cursor::new(0, 0)));
h.record(change(0, "", "a"));
h.record(change(1, "", "b"));
h.end_group();
let step = h.undo().expect("one step");
assert_eq!(step.changes.len(), 2);
assert!(h.undo().is_none(), "the group was one step");
}
#[test]
fn the_step_remembers_where_its_command_began() {
let mut h = History::default();
h.begin_group(Some(Cursor::new(7, 3)));
h.begin_group(None);
h.record(change(0, "abc", ""));
h.end_group();
let step = h.undo().unwrap();
assert_eq!(step.cursor_start, Some(Cursor::new(7, 3)));
}
#[test]
fn a_split_sub_step_takes_no_anchor() {
let mut h = History::default();
h.begin_group(Some(Cursor::new(1, 1)));
h.record(change(0, "", "one"));
h.split();
h.record(change(3, "", " two"));
h.end_group();
let second = h.undo().unwrap();
assert_eq!(second.cursor_start, None, "the sub-step starts where typing left off");
let first = h.undo().unwrap();
assert_eq!(first.cursor_start, Some(Cursor::new(1, 1)));
}
#[test]
fn undoing_back_to_a_saved_position_compares_equal() {
let mut h = History::default();
h.record(change(0, "", "a"));
let saved = h.state();
h.record(change(1, "", "b"));
assert_ne!(h.state(), saved, "an edit moves off the saved position");
h.undo();
assert_eq!(h.state(), saved, "undoing lands back on it");
h.redo();
assert_ne!(h.state(), saved, "and redoing leaves again");
}
#[test]
fn a_new_edit_discards_the_redo_stack() {
let mut h = History::default();
h.record(change(0, "", "a"));
h.record(change(1, "", "b"));
h.undo();
assert!(h.redo().is_some());
h.undo();
h.record(change(1, "", "c"));
assert!(h.redo().is_none(), "the forked future is gone");
}
#[test]
fn history_positions_are_never_reused() {
let mut h = History::default();
let mut seen = Vec::new();
for i in 0..4 {
h.record(change(i, "", "x"));
seen.push(h.state());
}
h.undo();
h.record(change(9, "", "y"));
seen.push(h.state());
let mut sorted = seen.clone();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(sorted.len(), seen.len(), "an id came back: {seen:?}");
}
#[test]
fn a_group_that_changed_nothing_is_not_a_step() {
let mut h = History::default();
h.begin_group(Some(Cursor::new(0, 0)));
h.end_group();
assert!(h.undo().is_none());
assert_eq!(h.state(), 0);
}
}