1#[allow(dead_code)]
7pub struct UndoCommand {
8 pub id: u64,
9 pub name: String,
10 pub data: Vec<u8>,
11 pub after: Vec<u8>,
12}
13
14#[allow(dead_code)]
15pub struct UndoStack {
16 pub history: Vec<UndoCommand>,
17 pub future: Vec<UndoCommand>,
18 pub max_depth: usize,
19 pub next_id: u64,
20}
21
22#[allow(dead_code)]
23pub fn new_undo_stack(max_depth: usize) -> UndoStack {
24 UndoStack {
25 history: Vec::new(),
26 future: Vec::new(),
27 max_depth,
28 next_id: 0,
29 }
30}
31
32#[allow(dead_code)]
33pub fn push_command(stack: &mut UndoStack, name: &str, before: Vec<u8>, after: Vec<u8>) {
34 stack.future.clear();
35 let id = stack.next_id;
36 stack.next_id += 1;
37 stack.history.push(UndoCommand {
38 id,
39 name: name.to_string(),
40 data: before,
41 after,
42 });
43 if stack.max_depth > 0 && stack.history.len() > stack.max_depth {
44 let overflow = stack.history.len() - stack.max_depth;
45 stack.history.drain(0..overflow);
46 }
47}
48
49#[allow(dead_code)]
50pub fn undo(stack: &mut UndoStack) -> Option<&UndoCommand> {
51 let cmd = stack.history.pop()?;
52 stack.future.push(cmd);
53 stack.future.last()
54}
55
56#[allow(dead_code)]
57pub fn redo(stack: &mut UndoStack) -> Option<&UndoCommand> {
58 let cmd = stack.future.pop()?;
59 stack.history.push(cmd);
60 stack.history.last()
61}
62
63#[allow(dead_code)]
64pub fn can_undo(stack: &UndoStack) -> bool {
65 !stack.history.is_empty()
66}
67
68#[allow(dead_code)]
69pub fn can_redo(stack: &UndoStack) -> bool {
70 !stack.future.is_empty()
71}
72
73#[allow(dead_code)]
74pub fn clear_undo_history(stack: &mut UndoStack) {
75 stack.history.clear();
76 stack.future.clear();
77}
78
79#[allow(dead_code)]
80pub fn history_depth(stack: &UndoStack) -> usize {
81 stack.history.len()
82}
83
84#[allow(dead_code)]
85pub fn future_depth(stack: &UndoStack) -> usize {
86 stack.future.len()
87}
88
89#[allow(dead_code)]
90pub fn peek_undo(stack: &UndoStack) -> Option<&UndoCommand> {
91 stack.history.last()
92}
93
94#[allow(dead_code)]
95pub fn peek_redo(stack: &UndoStack) -> Option<&UndoCommand> {
96 stack.future.last()
97}
98
99#[allow(dead_code)]
100pub fn command_names(stack: &UndoStack) -> Vec<&str> {
101 stack.history.iter().map(|c| c.name.as_str()).collect()
102}
103
104#[allow(dead_code)]
105pub fn truncate_history(stack: &mut UndoStack, keep: usize) {
106 if stack.history.len() > keep {
107 let drain_count = stack.history.len() - keep;
108 stack.history.drain(0..drain_count);
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_push_then_undo_returns_command() {
118 let mut stack = new_undo_stack(10);
119 push_command(&mut stack, "move", vec![1], vec![2]);
120 let cmd = undo(&mut stack);
121 assert!(cmd.is_some());
122 assert_eq!(cmd.expect("should succeed").name, "move");
123 }
124
125 #[test]
126 fn test_redo_after_undo() {
127 let mut stack = new_undo_stack(10);
128 push_command(&mut stack, "action", vec![0], vec![1]);
129 undo(&mut stack);
130 let redone = redo(&mut stack);
131 assert!(redone.is_some());
132 assert_eq!(redone.expect("should succeed").name, "action");
133 }
134
135 #[test]
136 fn test_can_undo_after_push() {
137 let mut stack = new_undo_stack(10);
138 assert!(!can_undo(&stack));
139 push_command(&mut stack, "a", vec![], vec![]);
140 assert!(can_undo(&stack));
141 }
142
143 #[test]
144 fn test_can_redo_after_undo() {
145 let mut stack = new_undo_stack(10);
146 push_command(&mut stack, "a", vec![], vec![]);
147 assert!(!can_redo(&stack));
148 undo(&mut stack);
149 assert!(can_redo(&stack));
150 }
151
152 #[test]
153 fn test_clear_history() {
154 let mut stack = new_undo_stack(10);
155 push_command(&mut stack, "a", vec![], vec![]);
156 push_command(&mut stack, "b", vec![], vec![]);
157 clear_undo_history(&mut stack);
158 assert!(!can_undo(&stack));
159 assert!(!can_redo(&stack));
160 }
161
162 #[test]
163 fn test_max_depth_enforced() {
164 let mut stack = new_undo_stack(3);
165 push_command(&mut stack, "a", vec![], vec![]);
166 push_command(&mut stack, "b", vec![], vec![]);
167 push_command(&mut stack, "c", vec![], vec![]);
168 push_command(&mut stack, "d", vec![], vec![]);
169 assert_eq!(history_depth(&stack), 3);
170 }
171
172 #[test]
173 fn test_redo_cleared_on_new_push() {
174 let mut stack = new_undo_stack(10);
175 push_command(&mut stack, "a", vec![], vec![]);
176 undo(&mut stack);
177 assert!(can_redo(&stack));
178 push_command(&mut stack, "b", vec![], vec![]);
179 assert!(!can_redo(&stack));
180 }
181
182 #[test]
183 fn test_peek_undo_does_not_pop() {
184 let mut stack = new_undo_stack(10);
185 push_command(&mut stack, "peek_test", vec![], vec![]);
186 let _ = peek_undo(&stack);
187 assert!(can_undo(&stack));
188 assert_eq!(history_depth(&stack), 1);
189 }
190
191 #[test]
192 fn test_peek_redo_does_not_pop() {
193 let mut stack = new_undo_stack(10);
194 push_command(&mut stack, "a", vec![], vec![]);
195 undo(&mut stack);
196 let _ = peek_redo(&stack);
197 assert!(can_redo(&stack));
198 assert_eq!(future_depth(&stack), 1);
199 }
200
201 #[test]
202 fn test_history_depth() {
203 let mut stack = new_undo_stack(10);
204 assert_eq!(history_depth(&stack), 0);
205 push_command(&mut stack, "a", vec![], vec![]);
206 assert_eq!(history_depth(&stack), 1);
207 }
208
209 #[test]
210 fn test_future_depth() {
211 let mut stack = new_undo_stack(10);
212 push_command(&mut stack, "a", vec![], vec![]);
213 push_command(&mut stack, "b", vec![], vec![]);
214 undo(&mut stack);
215 assert_eq!(future_depth(&stack), 1);
216 }
217
218 #[test]
219 fn test_command_names() {
220 let mut stack = new_undo_stack(10);
221 push_command(&mut stack, "first", vec![], vec![]);
222 push_command(&mut stack, "second", vec![], vec![]);
223 let names = command_names(&stack);
224 assert_eq!(names, vec!["first", "second"]);
225 }
226
227 #[test]
228 fn test_truncate_history() {
229 let mut stack = new_undo_stack(10);
230 push_command(&mut stack, "a", vec![], vec![]);
231 push_command(&mut stack, "b", vec![], vec![]);
232 push_command(&mut stack, "c", vec![], vec![]);
233 truncate_history(&mut stack, 2);
234 assert_eq!(history_depth(&stack), 2);
235 }
236}