use crate::buffer::Buffer;
use crate::selection::SelectionSet;
use crate::transaction::{apply, Committed, EditOp};
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum OpClass {
Type,
Delete,
Other,
}
#[derive(Copy, Clone, Debug)]
pub struct GroupingHint {
pub op: OpClass,
pub seal_before: bool,
pub seal_after: bool,
}
impl GroupingHint {
#[must_use]
pub fn mergeable(op: OpClass) -> Self {
Self { op, seal_before: false, seal_after: false }
}
#[must_use]
pub fn discrete() -> Self {
Self { op: OpClass::Other, seal_before: true, seal_after: true }
}
}
#[derive(Clone, Debug)]
struct Step {
forward: Vec<EditOp>,
inverse: Vec<EditOp>,
}
#[derive(Clone, Debug)]
struct Element {
steps: Vec<Step>,
op: OpClass,
alt_before: u64,
alt_after: u64,
}
type NodeId = usize;
#[derive(Clone, Debug)]
struct UndoNode {
parent: Option<NodeId>,
children: Vec<NodeId>,
preferred_child: Option<NodeId>,
elem: Option<Element>,
}
#[derive(Debug)]
pub(crate) struct History {
nodes: Vec<UndoNode>,
current: NodeId,
open: bool,
alt: u64,
alt_seq: u64,
saved_alt: u64,
max_undo: Option<usize>,
}
impl History {
pub(crate) fn new() -> Self {
Self {
nodes: vec![UndoNode { parent: None, children: Vec::new(), preferred_child: None, elem: None }],
current: 0,
open: false,
alt: 0,
alt_seq: 0,
saved_alt: 0,
max_undo: None,
}
}
pub(crate) fn set_max_undo(&mut self, limit: Option<usize>) {
self.max_undo = limit;
self.prune_if_needed();
}
pub(crate) fn undo_depth(&self) -> usize {
let mut depth = 0;
let mut node = self.current;
while let Some(parent) = self.nodes[node].parent {
node = parent;
depth += 1;
}
depth
}
pub(crate) fn can_undo(&self) -> bool {
self.current != 0
}
pub(crate) fn can_redo(&self) -> bool {
self.nodes[self.current].preferred_child.is_some()
}
pub(crate) fn record(&mut self, forward: Vec<EditOp>, inverse: Vec<EditOp>, hint: GroupingHint) {
if hint.seal_before {
self.open = false;
}
let alt_before = self.alt;
self.alt_seq += 1;
self.alt = self.alt_seq;
let alt_after = self.alt;
let step = Step { forward, inverse };
let can_merge = self.open
&& hint.op != OpClass::Other
&& self.nodes[self.current].elem.as_ref().is_some_and(|e| e.op == hint.op);
if can_merge {
let elem = self.nodes[self.current].elem.as_mut().expect("open implies an element");
elem.steps.push(step);
elem.alt_after = alt_after;
} else {
let parent = self.current;
let new_id = self.nodes.len();
self.nodes.push(UndoNode {
parent: Some(parent),
children: Vec::new(),
preferred_child: None,
elem: Some(Element { steps: vec![step], op: hint.op, alt_before, alt_after }),
});
let parent_node = &mut self.nodes[parent];
parent_node.children.push(new_id);
parent_node.preferred_child = Some(new_id);
self.current = new_id;
self.open = true;
}
if hint.seal_after {
self.open = false;
}
self.prune_if_needed();
}
fn prune_if_needed(&mut self) {
let Some(max) = self.max_undo else { return };
if self.undo_depth() <= max {
return;
}
let mut new_base = self.current;
for _ in 0..max {
new_base = self.nodes[new_base].parent.expect("depth > max implies enough ancestors");
}
self.rebuild_from(new_base);
}
fn rebuild_from(&mut self, new_base: NodeId) {
let mut remap = vec![usize::MAX; self.nodes.len()];
let mut order = vec![new_base];
remap[new_base] = 0;
let mut i = 0;
while i < order.len() {
let old = order[i];
for &child in &self.nodes[old].children {
if remap[child] == usize::MAX {
remap[child] = order.len();
order.push(child);
}
}
i += 1;
}
let fresh: Vec<UndoNode> = order
.iter()
.enumerate()
.map(|(new_id, &old)| {
let node = &self.nodes[old];
UndoNode {
parent: if new_id == 0 { None } else { node.parent.map(|p| remap[p]) },
children: node.children.iter().map(|&c| remap[c]).collect(),
preferred_child: node.preferred_child.map(|c| remap[c]),
elem: if new_id == 0 { None } else { node.elem.clone() },
}
})
.collect();
self.current = remap[self.current];
self.nodes = fresh;
}
pub(crate) fn undo(
&mut self,
buffer: &mut Buffer,
selections: &mut SelectionSet,
on_step: impl FnMut(&Committed, &Buffer),
) -> bool {
if self.current == 0 {
return false;
}
let elem = self.nodes[self.current].elem.clone().expect("non-root nodes carry an element");
replay(buffer, selections, elem.steps.iter().rev().map(|s| &s.inverse), on_step);
self.current = self.nodes[self.current].parent.expect("non-root nodes have a parent");
self.alt = elem.alt_before;
self.open = false;
true
}
pub(crate) fn redo(
&mut self,
buffer: &mut Buffer,
selections: &mut SelectionSet,
on_step: impl FnMut(&Committed, &Buffer),
) -> bool {
let Some(child) = self.nodes[self.current].preferred_child else {
return false;
};
let elem = self.nodes[child].elem.clone().expect("child nodes carry an element");
replay(buffer, selections, elem.steps.iter().map(|s| &s.forward), on_step);
self.current = child;
self.alt = elem.alt_after;
self.open = false;
true
}
pub(crate) fn redo_branch_count(&self) -> usize {
self.nodes[self.current].children.len()
}
pub(crate) fn select_redo_branch(&mut self, index: usize) -> bool {
let Some(&child) = self.nodes[self.current].children.get(index) else {
return false;
};
self.nodes[self.current].preferred_child = Some(child);
true
}
pub(crate) fn seal(&mut self) {
self.open = false;
}
pub(crate) fn is_dirty(&self) -> bool {
self.alt != self.saved_alt
}
pub(crate) fn mark_saved(&mut self) {
self.saved_alt = self.alt;
}
}
fn replay<'a>(
buffer: &mut Buffer,
selections: &mut SelectionSet,
steps: impl Iterator<Item = &'a Vec<EditOp>>,
mut on_step: impl FnMut(&Committed, &Buffer),
) {
for step in steps {
let committed = apply(buffer, step.clone()).expect("history ops are disjoint by construction");
selections.rebase(committed.patch());
on_step(&committed, buffer);
}
}