1use std::collections::VecDeque;
9use std::time::{Duration, Instant};
10
11const MAX_DEPTH: usize = 64;
13const COALESCE_WINDOW: Duration = Duration::from_millis(1_200);
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum EditKind {
19 Typing,
21 Atomic,
23}
24
25#[derive(Debug, Clone)]
27pub struct History<T> {
28 undo: VecDeque<T>,
29 redo: VecDeque<T>,
30 last_edit: Option<(EditKind, Instant)>,
31}
32
33impl<T> Default for History<T> {
34 fn default() -> Self {
35 Self {
36 undo: VecDeque::new(),
37 redo: VecDeque::new(),
38 last_edit: None,
39 }
40 }
41}
42
43impl<T> History<T> {
44 pub fn new() -> Self {
45 Self::default()
46 }
47
48 pub fn will_coalesce(&self, kind: EditKind) -> bool {
50 kind == EditKind::Typing
51 && self.last_edit.is_some_and(|(last_kind, at)| {
52 last_kind == EditKind::Typing && at.elapsed() < COALESCE_WINDOW
53 })
54 && !self.undo.is_empty()
55 }
56
57 pub fn touch_coalesce(&mut self) {
59 if let Some((_, at)) = &mut self.last_edit {
60 *at = Instant::now();
61 }
62 }
63
64 pub fn push(&mut self, current: T, kind: EditKind) {
66 self.undo.push_back(current);
67 while self.undo.len() > MAX_DEPTH {
68 self.undo.pop_front();
69 }
70 self.redo.clear();
71 self.last_edit = Some((kind, Instant::now()));
72 }
73
74 pub fn before_edit_with(&mut self, kind: EditKind, current: impl FnOnce() -> T) {
76 if self.will_coalesce(kind) {
77 self.touch_coalesce();
78 return;
79 }
80 self.push(current(), kind);
81 }
82
83 pub fn break_coalesce(&mut self) {
85 self.last_edit = None;
86 }
87
88 pub fn undo(&mut self, current: T) -> Option<T> {
90 let prev = self.undo.pop_back()?;
91 self.redo.push_back(current);
92 self.break_coalesce();
93 Some(prev)
94 }
95
96 pub fn redo(&mut self, current: T) -> Option<T> {
98 let next = self.redo.pop_back()?;
99 self.undo.push_back(current);
100 self.break_coalesce();
101 Some(next)
102 }
103
104 pub fn can_redo(&self) -> bool {
105 !self.redo.is_empty()
106 }
107
108 pub fn for_each_mut(&mut self, mut update: impl FnMut(&mut T)) {
111 self.undo.iter_mut().for_each(&mut update);
112 self.redo.iter_mut().for_each(update);
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn typing_coalesces_into_one_step() {
122 let mut h: History<String> = History::new();
123 h.before_edit_with(EditKind::Typing, || "a".into());
124 h.before_edit_with(EditKind::Typing, || "ab".into());
125 h.before_edit_with(EditKind::Typing, || "abc".into());
126 assert_eq!(h.undo.len(), 1);
127 let restored = h.undo("abc".into()).unwrap();
128 assert_eq!(restored, "a");
129 }
130
131 #[test]
132 fn atomic_always_splits() {
133 let mut h: History<String> = History::new();
134 h.before_edit_with(EditKind::Typing, || "a".into());
135 h.before_edit_with(EditKind::Atomic, || "ab".into());
136 h.before_edit_with(EditKind::Typing, || "abc".into());
137 assert_eq!(h.undo.len(), 3);
138 }
139
140 #[test]
141 fn redo_clears_on_new_edit() {
142 let mut h: History<String> = History::new();
143 h.before_edit_with(EditKind::Atomic, || "0".into());
144 let _ = h.undo("1".into());
145 assert!(h.can_redo());
146 h.before_edit_with(EditKind::Atomic, || "2".into());
147 assert!(!h.can_redo());
148 }
149
150 #[test]
151 fn coalesce_skips_snapshot_fn() {
152 let mut h: History<String> = History::new();
153 h.before_edit_with(EditKind::Typing, || "a".into());
154 let mut built = 0;
155 h.before_edit_with(EditKind::Typing, || {
156 built += 1;
157 "ab".into()
158 });
159 assert_eq!(built, 0);
160 assert_eq!(h.undo.len(), 1);
161 }
162}