use std::{
fmt::Debug,
time::{Duration, Instant},
};
pub trait HistoryItem: Clone + PartialEq {
fn version(&self) -> usize;
fn set_version(&mut self, version: usize);
}
#[derive(Debug)]
pub struct History<I: HistoryItem> {
undos: Vec<I>,
redos: Vec<I>,
last_changed_at: Instant,
version: usize,
pub(crate) ignore: bool,
max_undos: usize,
group_interval: Option<Duration>,
grouping: bool,
unique: bool,
}
impl<I> History<I>
where
I: HistoryItem,
{
pub fn new() -> Self {
Self {
undos: Default::default(),
redos: Default::default(),
ignore: false,
last_changed_at: Instant::now(),
version: 0,
max_undos: 1000,
group_interval: None,
grouping: false,
unique: false,
}
}
pub fn max_undos(mut self, max_undos: usize) -> Self {
self.max_undos = max_undos;
self
}
pub fn unique(mut self) -> Self {
self.unique = true;
self
}
pub fn group_interval(mut self, group_interval: Duration) -> Self {
self.group_interval = Some(group_interval);
self
}
pub fn start_grouping(&mut self) {
self.grouping = true;
}
pub fn end_grouping(&mut self) {
self.grouping = false;
}
fn inc_version(&mut self) -> usize {
let t = Instant::now();
if !self.grouping && Some(self.last_changed_at.elapsed()) > self.group_interval {
self.version += 1;
}
self.last_changed_at = t;
self.version
}
pub fn version(&self) -> usize {
self.version
}
pub fn push(&mut self, item: I) {
let version = self.inc_version();
if self.undos.len() >= self.max_undos {
self.undos.remove(0);
}
if self.unique {
self.undos.retain(|c| *c != item);
self.redos.retain(|c| *c != item);
}
let mut item = item;
item.set_version(version);
self.undos.push(item);
}
pub fn undos(&self) -> &Vec<I> {
&self.undos
}
pub fn redos(&self) -> &Vec<I> {
&self.redos
}
pub fn clear(&mut self) {
self.undos.clear();
self.redos.clear();
}
pub fn undo(&mut self) -> Option<Vec<I>> {
if let Some(first_change) = self.undos.pop() {
let mut changes = vec![first_change.clone()];
while self
.undos
.iter()
.filter(|c| c.version() == first_change.version())
.count()
> 0
{
let change = self.undos.pop().unwrap();
changes.push(change);
}
self.redos.extend(changes.clone());
Some(changes)
} else {
None
}
}
pub fn redo(&mut self) -> Option<Vec<I>> {
if let Some(first_change) = self.redos.pop() {
let mut changes = vec![first_change.clone()];
while self
.redos
.iter()
.filter(|c| c.version() == first_change.version())
.count()
> 0
{
let change = self.redos.pop().unwrap();
changes.push(change);
}
self.undos.extend(changes.clone());
Some(changes)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone)]
struct TabIndex {
tab_index: usize,
version: usize,
}
impl PartialEq for TabIndex {
fn eq(&self, other: &Self) -> bool {
self.tab_index == other.tab_index
}
}
impl From<usize> for TabIndex {
fn from(value: usize) -> Self {
TabIndex {
tab_index: value,
version: 0,
}
}
}
impl HistoryItem for TabIndex {
fn version(&self) -> usize {
self.version
}
fn set_version(&mut self, version: usize) {
self.version = version;
}
}
#[test]
fn test_history() {
let mut history: History<TabIndex> = History::new().max_undos(100);
history.push(0.into());
history.push(3.into());
history.push(2.into());
history.push(1.into());
assert_eq!(history.version(), 4);
let changes = history.undo().unwrap();
assert_eq!(changes.len(), 1);
assert_eq!(changes[0].tab_index, 1);
let changes = history.undo().unwrap();
assert_eq!(changes.len(), 1);
assert_eq!(changes[0].tab_index, 2);
history.push(5.into());
let changes = history.redo().unwrap();
assert_eq!(changes[0].tab_index, 2);
let changes = history.redo().unwrap();
assert_eq!(changes[0].tab_index, 1);
let changes = history.undo().unwrap();
assert_eq!(changes[0].tab_index, 1);
let changes = history.undo().unwrap();
assert_eq!(changes[0].tab_index, 2);
let changes = history.undo().unwrap();
assert_eq!(changes[0].tab_index, 5);
let changes = history.undo().unwrap();
assert_eq!(changes[0].tab_index, 3);
let changes = history.undo().unwrap();
assert_eq!(changes[0].tab_index, 0);
assert_eq!(history.undo().is_none(), true);
}
#[test]
fn test_unique_history() {
let mut history: History<TabIndex> = History::new().max_undos(100).unique();
history.push(0.into());
history.push(1.into());
history.push(1.into()); history.push(2.into());
history.push(1.into());
assert_eq!(history.version(), 5);
assert_eq!(history.undos().len(), 3);
assert_eq!(history.undos().last().unwrap().tab_index, 1);
let changes = history.undo().unwrap();
assert_eq!(changes.len(), 1);
assert_eq!(changes[0].tab_index, 1);
assert_eq!(history.redos().len(), 1);
history.push(2.into());
assert_eq!(history.undos().len(), 2);
assert_eq!(history.redos().len(), 1);
let changes = history.redo().unwrap();
assert_eq!(changes.len(), 1);
assert_eq!(changes[0].tab_index, 1);
history.push(3.into());
assert_eq!(history.version(), 7);
assert_eq!(history.undos().len(), 4);
for _ in 0..4 {
history.undo();
}
assert_eq!(history.undos().len(), 0);
assert_eq!(history.redos().len(), 4);
}
}