#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Edit {
pub at: usize,
pub text: String,
pub kind: EditKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum EditKind {
Insert,
Delete,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
struct Revision {
parent: usize,
last_child: Option<usize>,
undo: Vec<Edit>,
redo: Vec<Edit>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct History {
revisions: Vec<Revision>,
current: usize,
pending: Option<(Vec<Edit>, Vec<Edit>)>,
}
impl Default for History {
fn default() -> Self {
Self {
revisions: vec![Revision {
parent: 0,
last_child: None,
undo: vec![],
redo: vec![],
}],
current: 0,
pending: None,
}
}
}
#[derive(Debug, Clone)]
pub struct RevisionRow {
pub index: usize,
pub parent: usize,
pub depth: usize,
pub summary: String,
pub is_current: bool,
pub branches: bool,
}
impl History {
pub fn tree_rows(&self) -> Vec<RevisionRow> {
let mut depths = vec![0usize; self.revisions.len()];
for i in 1..self.revisions.len() {
depths[i] = depths[self.revisions[i].parent] + 1;
}
let mut out: Vec<RevisionRow> = (1..self.revisions.len())
.rev()
.map(|i| {
let rev = &self.revisions[i];
let first = rev.redo.first();
let summary = match first {
Some(e) => {
let sign = match e.kind {
EditKind::Insert => "+",
EditKind::Delete => "-",
};
let text: String = e
.text
.chars()
.take(24)
.map(|c| if c == '\n' { '↵' } else { c })
.collect();
let more = if e.text.chars().count() > 24 {
"…"
} else {
""
};
format!("{sign} \"{text}{more}\"")
}
None => "(empty)".into(),
};
RevisionRow {
index: i,
parent: rev.parent,
depth: depths[i],
summary,
is_current: i == self.current,
branches: self.revisions[..i].iter().any(|r| r.parent == rev.parent),
}
})
.collect();
out.sort_by_key(|r| std::cmp::Reverse(r.index));
out
}
pub fn ops_to(&mut self, target: usize) -> Option<Vec<Edit>> {
if target >= self.revisions.len() {
return None;
}
let mut anc_cur = Vec::new();
let mut at = self.current;
loop {
anc_cur.push(at);
if at == 0 {
break;
}
at = self.revisions[at].parent;
}
let mut up_path = Vec::new(); let mut t = target;
while !anc_cur.contains(&t) {
up_path.push(t);
t = self.revisions[t].parent;
}
let fork = t;
let mut ops = Vec::new();
let mut c = self.current;
while c != fork {
let mut rev_undo = self.revisions[c].undo.clone();
rev_undo.reverse();
ops.extend(rev_undo);
c = self.revisions[c].parent;
}
for &r in up_path.iter().rev() {
ops.extend(self.revisions[r].redo.clone());
}
let mut c = self.current;
while c != fork {
let p = self.revisions[c].parent;
self.revisions[p].last_child = Some(c);
c = p;
}
let mut p = fork;
for &r in up_path.iter().rev() {
self.revisions[p].last_child = Some(r);
p = r;
}
self.current = target;
Some(ops)
}
}
impl History {
pub fn begin(&mut self) {
if self.pending.is_none() {
self.pending = Some((Vec::new(), Vec::new()));
}
}
pub fn commit(&mut self) {
let Some((undo, redo)) = self.pending.take() else {
return;
};
if undo.is_empty() {
return;
}
let rev = Revision {
parent: self.current,
last_child: None,
undo,
redo,
};
self.revisions.push(rev);
let idx = self.revisions.len() - 1;
self.revisions[self.current].last_child = Some(idx);
self.current = idx;
}
pub fn record(&mut self, undo: Edit, redo: Edit) {
if self.pending.is_none() {
self.begin();
}
if let Some((u, r)) = &mut self.pending {
u.push(undo);
r.push(redo);
}
}
pub fn can_undo(&self) -> bool {
self.current > 0
}
pub fn can_redo(&self) -> bool {
self.revisions
.get(self.current)
.and_then(|r| r.last_child)
.is_some()
}
pub fn undo_ops(&mut self) -> Option<Vec<Edit>> {
if self.current == 0 {
return None;
}
let rev = &self.revisions[self.current];
let parent = rev.parent;
let mut ops = rev.undo.clone();
ops.reverse();
self.revisions[parent].last_child = Some(self.current);
self.current = parent;
Some(ops)
}
pub fn redo_ops(&mut self) -> Option<Vec<Edit>> {
let child = self.revisions.get(self.current)?.last_child?;
let ops = self.revisions[child].redo.clone();
self.current = child;
Some(ops)
}
pub fn depth(&self) -> usize {
self.revisions.len()
}
pub fn cap(&mut self, cap: usize) {
if self.revisions.len() <= cap {
return;
}
let mut chain = Vec::new();
let mut at = self.current;
loop {
chain.push(at);
if at == 0 {
break;
}
at = self.revisions[at].parent;
}
chain.reverse();
if chain.len() > cap {
chain = chain[chain.len() - cap..].to_vec();
}
let mut remap = std::collections::HashMap::new();
let mut new_revisions = Vec::with_capacity(chain.len());
for (new_idx, &old_idx) in chain.iter().enumerate() {
remap.insert(old_idx, new_idx);
let mut rev = self.revisions[old_idx].clone();
rev.parent = if new_idx == 0 { 0 } else { new_idx - 1 };
rev.last_child = rev.last_child.and_then(|c| remap.get(&c).copied());
new_revisions.push(rev);
}
self.revisions = new_revisions;
self.current = *remap.get(&self.current).unwrap_or(&0);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn edit(at: usize, text: &str, kind: EditKind) -> Edit {
Edit {
at,
text: text.into(),
kind,
}
}
#[test]
fn linear_undo_redo() {
let mut h = History::default();
h.begin();
h.record(
edit(0, "", EditKind::Delete),
edit(0, "x", EditKind::Insert),
);
h.commit();
assert!(h.can_undo());
let ops = h.undo_ops().unwrap();
assert_eq!(ops, vec![edit(0, "", EditKind::Delete)]);
assert!(h.can_redo());
let ops = h.redo_ops().unwrap();
assert_eq!(ops, vec![edit(0, "x", EditKind::Insert)]);
assert!(!h.can_undo() || h.can_undo());
}
#[test]
fn edit_after_undo_forks_a_branch() {
let mut h = History::default();
h.begin();
h.record(
edit(0, "", EditKind::Delete),
edit(0, "a", EditKind::Insert),
);
h.commit();
h.undo_ops();
h.begin();
h.record(
edit(0, "", EditKind::Delete),
edit(0, "b", EditKind::Insert),
);
h.commit();
assert!(h.depth() >= 2);
}
}