use crate::transaction::Transaction;
#[derive(Debug)]
pub struct History {
undo_stack: Vec<Transaction>,
redo_stack: Vec<Transaction>,
clean_depth: Option<usize>,
force_new_group: bool,
}
impl History {
pub fn new() -> Self {
Self {
undo_stack: Vec::new(),
redo_stack: Vec::new(),
clean_depth: Some(0),
force_new_group: false,
}
}
pub fn push(&mut self, tx: Transaction) {
if let Some(depth) = self.clean_depth {
if depth > self.undo_stack.len() {
self.clean_depth = None;
}
}
self.redo_stack.clear();
if !self.force_new_group {
if let Some(last) = self.undo_stack.last_mut() {
if last.can_coalesce(&tx) {
last.merge(&tx);
return;
}
}
}
self.force_new_group = false;
self.undo_stack.push(tx);
}
pub fn undo(&mut self) -> Option<Transaction> {
let tx = self.undo_stack.pop()?;
let inverse = tx.inverse();
self.redo_stack.push(tx);
Some(inverse)
}
pub fn redo(&mut self) -> Option<Transaction> {
let tx = self.redo_stack.pop()?;
let re_apply = tx.clone();
self.undo_stack.push(tx);
Some(re_apply)
}
pub fn mark_clean(&mut self) {
self.clean_depth = Some(self.undo_stack.len());
self.force_new_group = true;
}
pub fn is_dirty(&self) -> bool {
self.clean_depth != Some(self.undo_stack.len())
}
pub fn can_undo(&self) -> bool {
!self.undo_stack.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.redo_stack.is_empty()
}
pub fn clear(&mut self) {
self.undo_stack.clear();
self.redo_stack.clear();
self.clean_depth = Some(0);
self.force_new_group = false;
}
}
impl Default for History {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transaction::EditStep;
#[test]
fn undo_redo_basic() {
let mut history = History::new();
let tx = Transaction::single(EditStep::insert(0, "hello"));
history.push(tx);
assert!(history.can_undo());
assert!(!history.can_redo());
let undo_tx = history.undo().unwrap();
assert_eq!(undo_tx.steps[0].offset, 0);
assert_eq!(undo_tx.steps[0].deleted, "hello");
assert!(undo_tx.steps[0].inserted.is_empty());
assert!(!history.can_undo());
assert!(history.can_redo());
let redo_tx = history.redo().unwrap();
assert_eq!(redo_tx.steps[0].inserted, "hello");
}
#[test]
fn new_edit_clears_redo() {
let mut history = History::new();
history.push(Transaction::single(EditStep::insert(0, "a")));
history.push(Transaction::single(EditStep::insert(1, "b")));
history.undo();
assert!(history.can_redo());
history.push(Transaction::single(EditStep::insert(0, "x")));
assert!(!history.can_redo());
}
#[test]
fn coalescing_inserts() {
let mut history = History::new();
history.push(Transaction::single(EditStep::insert(0, "h")));
history.push(Transaction::single(EditStep::insert(1, "i")));
assert_eq!(history.undo_stack.len(), 1);
assert_eq!(history.undo_stack[0].steps[0].inserted, "hi");
}
#[test]
fn newline_breaks_coalescing() {
let mut history = History::new();
history.push(Transaction::single(EditStep::insert(0, "a")));
history.push(Transaction::single(EditStep::insert(1, "\n")));
assert_eq!(history.undo_stack.len(), 2);
}
#[test]
fn dirty_tracking() {
let mut history = History::new();
assert!(!history.is_dirty());
history.push(Transaction::single(EditStep::insert(0, "x")));
assert!(history.is_dirty());
history.mark_clean();
assert!(!history.is_dirty());
history.push(Transaction::single(EditStep::insert(1, "y")));
assert!(history.is_dirty());
history.undo();
assert!(!history.is_dirty());
}
}