1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3#![forbid(unsafe_code)]
4#![deny(clippy::all, clippy::pedantic, clippy::cargo, clippy::nursery)]
5
6use std::ops::Deref;
7
8pub struct Undo<'state, TState> {
10 initial_state: TState,
12 current_state: TState,
14 updates: Vec<Box<dyn Fn(&mut TState) + 'state>>,
16 nb_updates: usize,
18}
19
20impl<'state, TState: Clone> Undo<'state, TState> {
21 pub fn new(state: TState) -> Self {
30 Self {
31 current_state: state.clone(),
32 initial_state: state,
33 updates: Vec::new(),
34 nb_updates: 0,
35 }
36 }
37
38 pub fn unwrap(self) -> TState {
51 self.current_state
52 }
53
54 pub fn update(&mut self, update_fn: impl Fn(&mut TState) + 'state) {
68 if self.nb_updates != self.updates.len() {
69 self.updates.truncate(self.nb_updates);
71 }
72 update_fn(&mut self.current_state);
73 self.updates.push(Box::new(update_fn));
74 self.nb_updates += 1;
75 }
76
77 pub fn undo(&mut self) {
95 if self.nb_updates == 0 {
96 return;
97 }
98 self.nb_updates -= 1;
99
100 self.current_state = self.initial_state.clone();
101 for update_fn in self.updates[..self.nb_updates].iter() {
102 update_fn(&mut self.current_state);
103 }
104 }
105
106 pub fn redo(&mut self) {
126 if self.nb_updates == self.updates.len() {
127 return;
128 }
129 self.updates[self.nb_updates](&mut self.current_state);
130 self.nb_updates += 1;
131 }
132}
133
134impl<'state, TState: Clone> Deref for Undo<'state, TState> {
135 type Target = TState;
136
137 fn deref(&self) -> &Self::Target {
138 &self.current_state
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[derive(Clone)]
147 struct Counter {
148 count: u64,
149 }
150
151 #[test]
152 fn it_can_undo_and_redo_updates() {
153 let mut counter = Undo::new(Counter { count: 0 });
154 assert_eq!(counter.count, 0);
155 counter.update(|c| c.count = 5);
156 assert_eq!(counter.count, 5);
157 counter.update(|c| c.count += 3);
158 assert_eq!(counter.count, 8);
159
160 counter.undo();
161 assert_eq!(counter.count, 5);
162 counter.undo();
163 assert_eq!(counter.count, 0);
164
165 counter.redo();
166 assert_eq!(counter.count, 5);
167 counter.redo();
168 assert_eq!(counter.count, 8);
169 }
170
171 #[test]
172 fn it_does_nothing_on_too_many_undo_or_redo() {
173 let mut counter = Undo::new(Counter { count: 3 });
174 counter.undo();
175 assert_eq!(counter.count, 3);
176 counter.redo();
177 assert_eq!(counter.count, 3);
178 counter.update(|c| c.count = 8);
179 assert_eq!(counter.count, 8);
180 counter.undo();
181 counter.undo();
182 counter.undo();
183 assert_eq!(counter.count, 3);
184 counter.redo();
185 counter.redo();
186 counter.redo();
187 counter.redo();
188 assert_eq!(counter.count, 8);
189 }
190
191 #[test]
192 fn it_discards_previous_updates_when_updating_after_an_undo() {
193 let mut counter = Undo::new(Counter { count: 0 });
194 counter.update(|c| c.count += 2);
195 counter.update(|c| c.count += 2);
196 counter.update(|c| c.count += 2);
197 counter.update(|c| c.count += 2);
198 counter.update(|c| c.count += 2);
199 assert_eq!(counter.count, 10);
200 counter.undo(); counter.undo(); counter.undo(); counter.redo(); assert_eq!(counter.count, 6);
205 counter.update(|c| c.count += 10); assert_eq!(counter.count, 16);
207 counter.redo(); counter.redo(); counter.undo(); counter.undo(); assert_eq!(counter.count, 4);
212 counter.redo(); counter.redo(); counter.redo(); assert_eq!(counter.count, 16);
216 }
217
218 #[test]
219 fn it_unwraps_the_inner_value() {
220 let mut counter = Undo::new(Counter { count: 0 });
221 counter.update(|c| c.count = 5);
222
223 let counter: Counter = counter.unwrap();
224 assert_eq!(counter.count, 5);
225 }
226
227 #[test]
228 fn it_works_with_string() {
229 let mut input_text = Undo::new(String::new());
230 input_text.update(|text| text.push('H')); input_text.update(|text| text.push('e')); input_text.update(|text| text.push('l')); input_text.update(|text| text.push('k')); input_text.update(|text| text.push('o')); input_text.undo(); input_text.undo(); input_text.undo(); input_text.redo(); input_text.update(|text| text.push('l')); input_text.update(|text| text.push('o')); let result: String = input_text.unwrap();
243 assert_eq!(result, "Hello");
244 }
245
246 #[test]
247 fn it_works_with_capturing_closures() {
248 let to_add = String::from(" world !");
249 let mut message = Undo::new(String::from("Hello"));
250
251 message.update(|text| text.push_str(&to_add)); message.undo();
254 assert_eq!(*message, "Hello");
255 message.redo();
256 assert_eq!(*message, "Hello world !");
257 }
258}