1use std::collections::HashMap;
2
3#[derive(Debug, Clone)]
5enum Entry<T: Clone> {
6 Event(T),
7 Checkpoint,
8}
9
10pub struct EventLog<T: Clone> {
19 events: Vec<Entry<T>>,
20 cursor: usize,
21 checkpoints: HashMap<String, usize>,
22}
23
24impl<T: Clone> EventLog<T> {
25 pub fn new() -> Self {
27 EventLog {
28 events: Vec::new(),
29 cursor: 0,
30 checkpoints: HashMap::new(),
31 }
32 }
33
34 pub fn append(&mut self, event: T) {
36 if self.cursor < self.events.len() {
37 self.events.truncate(self.cursor);
38 self.checkpoints.retain(|_, idx| *idx <= self.cursor);
40 }
41 self.events.push(Entry::Event(event));
42 self.cursor = self.events.len();
43 }
44
45 pub fn checkpoint(&mut self, name: &str) {
48 if self.cursor < self.events.len() {
49 self.events.truncate(self.cursor);
50 self.checkpoints.retain(|_, idx| *idx <= self.cursor);
52 }
53 self.checkpoints.insert(name.to_string(), self.cursor);
54 self.events.push(Entry::Checkpoint);
55 self.cursor = self.events.len();
56 }
57
58 pub fn undo(&mut self, count: usize) -> Vec<T> {
61 let mut result = Vec::new();
62 let mut pos = self.cursor as isize - 1;
63 let mut undone = 0;
64
65 while pos >= 0 && undone < count {
66 if let Entry::Event(ref ev) = self.events[pos as usize] {
67 result.push(ev.clone());
68 undone += 1;
69 }
70 pos -= 1;
71 }
72
73 self.cursor = (pos + 1) as usize;
74 result
75 }
76
77 pub fn undo_to(&mut self, name: &str) -> Result<Vec<T>, String> {
80 let target = match self.checkpoints.get(name) {
81 Some(&t) if t < self.cursor => t,
82 _ => return Err(format!("cannot undo to {name:?}")),
83 };
84
85 let mut result = Vec::new();
86 for i in (target..self.cursor).rev() {
87 if let Entry::Event(ref ev) = self.events[i] {
88 result.push(ev.clone());
89 }
90 }
91 self.cursor = target;
92 Ok(result)
93 }
94
95 pub fn redo(&mut self, count: usize) -> Vec<T> {
98 let mut result = Vec::new();
99 let mut pos = self.cursor;
100 let mut redone = 0;
101
102 while pos < self.events.len() && redone < count {
103 if let Entry::Event(ref ev) = self.events[pos] {
104 result.push(ev.clone());
105 redone += 1;
106 }
107 pos += 1;
108 }
109
110 self.cursor = pos;
111 result
112 }
113
114 pub fn recent(&self, count: usize) -> Vec<T> {
117 let limit = if count == 0 { self.cursor } else { count };
118 let mut result = Vec::new();
119 let mut i = self.cursor as isize - 1;
120 while i >= 0 && result.len() < limit {
121 if let Entry::Event(ref ev) = self.events[i as usize] {
122 result.push(ev.clone());
123 }
124 i -= 1;
125 }
126 result.reverse();
127 result
128 }
129
130 pub fn cursor(&self) -> usize {
132 self.cursor
133 }
134
135 pub fn length(&self) -> usize {
137 self.events.len()
138 }
139
140 pub fn can_undo(&self) -> bool {
142 self.events[..self.cursor]
143 .iter()
144 .any(|e| matches!(e, Entry::Event(_)))
145 }
146
147 pub fn can_redo(&self) -> bool {
149 self.cursor < self.events.len()
150 }
151}
152
153impl<T: Clone> Default for EventLog<T> {
154 fn default() -> Self {
155 Self::new()
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn test_append_and_cursor() {
165 let mut log = EventLog::new();
166 log.append("a");
167 log.append("b");
168 assert_eq!(log.cursor(), 2);
169 assert_eq!(log.length(), 2);
170 }
171
172 #[test]
173 fn test_recent() {
174 let mut log = EventLog::new();
175 log.append("a");
176 log.append("b");
177 log.append("c");
178
179 let got = log.recent(2);
180 assert_eq!(got, vec!["b", "c"]);
181
182 let all = log.recent(0);
183 assert_eq!(all, vec!["a", "b", "c"]);
184 }
185
186 #[test]
187 fn test_undo_most_recent() {
188 let mut log = EventLog::new();
189 log.append("a");
190 log.append("b");
191 let undone = log.undo(1);
192 assert_eq!(undone, vec!["b"]);
193 assert_eq!(log.cursor(), 1);
194 }
195
196 #[test]
197 fn test_undo_multiple() {
198 let mut log = EventLog::new();
199 log.append("a");
200 log.append("b");
201 log.append("c");
202 let undone = log.undo(2);
203 assert_eq!(undone, vec!["c", "b"]);
204 assert_eq!(log.cursor(), 1);
205 }
206
207 #[test]
208 fn test_undo_empty() {
209 let mut log: EventLog<&str> = EventLog::new();
210 let undone = log.undo(1);
211 assert!(undone.is_empty());
212 }
213
214 #[test]
215 fn test_undo_skips_checkpoints() {
216 let mut log = EventLog::new();
217 log.append("a");
218 log.checkpoint("cp1");
219 log.append("b");
220 let undone = log.undo(2);
221 assert_eq!(undone, vec!["b", "a"]);
222 }
223
224 #[test]
225 fn test_redo() {
226 let mut log = EventLog::new();
227 log.append("a");
228 log.append("b");
229 log.undo(2);
230 let redone = log.redo(2);
231 assert_eq!(redone, vec!["a", "b"]);
232 assert_eq!(log.cursor(), 2);
233 }
234
235 #[test]
236 fn test_redo_empty() {
237 let mut log = EventLog::new();
238 log.append("a");
239 let redone = log.redo(1);
240 assert!(redone.is_empty());
241 }
242
243 #[test]
244 fn test_redo_skips_checkpoints() {
245 let mut log = EventLog::new();
246 log.append("a");
247 log.checkpoint("cp1");
248 log.append("b");
249 log.undo(2);
250 let redone = log.redo(2);
251 assert_eq!(redone, vec!["a", "b"]);
252 }
253
254 #[test]
255 fn test_truncate_on_append() {
256 let mut log = EventLog::new();
257 log.append("a");
258 log.append("b");
259 log.undo(1); log.append("c"); assert_eq!(log.length(), 2);
262 let redone = log.redo(1);
263 assert!(redone.is_empty());
264 let all = log.recent(0);
265 assert_eq!(all, vec!["a", "c"]);
266 }
267
268 #[test]
269 fn test_checkpoint_undo_to() {
270 let mut log = EventLog::new();
271 log.append("a");
272 log.checkpoint("v1");
273 log.append("b");
274 log.append("c");
275 let undone = log.undo_to("v1").unwrap();
276 assert_eq!(undone, vec!["c", "b"]);
277 let recent = log.recent(0);
278 assert_eq!(recent, vec!["a"]);
279 }
280
281 #[test]
282 fn test_checkpoint_unknown_name() {
283 let mut log = EventLog::new();
284 log.append("a");
285 let result = log.undo_to("nonexistent");
286 assert!(result.is_err());
287 }
288
289 #[test]
290 fn test_checkpoint_truncates_redo_tail() {
291 let mut log = EventLog::new();
297 log.append("a");
298 log.append("b");
299 log.append("c");
300 log.undo(2); log.checkpoint("cp1");
302 assert_eq!(log.length(), 2, "checkpoint should truncate the redo tail before appending its sentinel");
303 assert_eq!(log.recent(0), vec!["a"]);
304 assert!(!log.can_redo(), "redo tail must be gone after checkpoint");
305
306 let undone = log.undo(1);
307 assert_eq!(undone, vec!["a"], "undo after checkpoint must only see truly-applied events");
308 }
309
310 #[test]
311 fn test_checkpoint_removed_on_truncation() {
312 let mut log = EventLog::new();
313 log.append("a");
314 log.checkpoint("v1");
315 log.append("b");
316 log.undo(2); log.append("x"); let result = log.undo_to("v1");
319 assert!(result.is_err(), "checkpoint should be removed after truncation");
320 }
321
322 #[test]
323 fn test_cursor_starts_at_zero() {
324 let log: EventLog<&str> = EventLog::new();
325 assert_eq!(log.cursor(), 0);
326 }
327
328 #[test]
329 fn test_cursor_advances_on_append() {
330 let mut log = EventLog::new();
331 log.append("a");
332 assert_eq!(log.cursor(), 1);
333 log.append("b");
334 assert_eq!(log.cursor(), 2);
335 }
336
337 #[test]
338 fn test_cursor_moves_back_on_undo() {
339 let mut log = EventLog::new();
340 log.append("a");
341 log.append("b");
342 log.undo(1);
343 assert_eq!(log.cursor(), 1);
344 }
345
346 #[test]
347 fn test_cursor_moves_forward_on_redo() {
348 let mut log = EventLog::new();
349 log.append("a");
350 log.append("b");
351 log.undo(1);
352 log.redo(1);
353 assert_eq!(log.cursor(), 2);
354 }
355
356 #[test]
357 fn test_can_undo() {
358 let mut log: EventLog<&str> = EventLog::new();
359 assert!(!log.can_undo(), "CanUndo() should be false for empty log");
360 log.append("a");
361 assert!(log.can_undo(), "CanUndo() should be true after append");
362 }
363
364 #[test]
365 fn test_can_redo() {
366 let mut log: EventLog<&str> = EventLog::new();
367 assert!(!log.can_redo(), "CanRedo() should be false for empty log");
368 log.append("a");
369 assert!(!log.can_redo(), "CanRedo() should be false at end");
370 log.undo(1);
371 assert!(log.can_redo(), "CanRedo() should be true after undo");
372 }
373}