simple_undo/
lib.rs

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
8/// The `Undo` type wrapping a state that tracks updates and allows undoing or redoing them.
9pub struct Undo<'state, TState> {
10    /// The initial state used to regenerate the current one.
11    initial_state: TState,
12    /// The current state to update.
13    current_state: TState,
14    /// All recorded updates applied to the current state.
15    updates: Vec<Box<dyn Fn(&mut TState) + 'state>>,
16    /// Number of updates applied to the current state. Undoing reduces this number.
17    nb_updates: usize,
18}
19
20impl<'state, TState: Clone> Undo<'state, TState> {
21    /// Wraps the given state in an `Undo`, which will track all updates and allows undoing or redoing them.
22    ///
23    /// # Example
24    /// ```
25    /// use simple_undo::Undo;
26    ///
27    /// let mut wrapper = Undo::new(5);
28    /// ```
29    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    /// Unwraps the inner state to an owned value, disabling the undo/redo feature.
39    ///
40    /// # Example
41    /// ```
42    /// # use simple_undo::Undo;
43    /// let mut message = Undo::new(String::new());
44    /// message.update(|text| text.push_str("Hello "));
45    /// message.update(|text| text.push_str("world !"));
46    ///
47    /// let result: String = message.unwrap();
48    /// assert_eq!(result, "Hello world !");
49    /// ```
50    pub fn unwrap(self) -> TState {
51        self.current_state
52    }
53
54    /// Updates the current state with the given mutating function.
55    ///
56    /// Note that future [`Undo::redo`] are reset.
57    ///
58    /// # Example
59    /// ```
60    /// # use simple_undo::Undo;
61    /// let mut counter = Undo::new(0);
62    /// counter.update(|value| *value += 10);
63    /// counter.update(|value| *value -= 5);
64    /// counter.update(|value| *value += 3);
65    /// assert_eq!(*counter, 8);
66    /// ```
67    pub fn update(&mut self, update_fn: impl Fn(&mut TState) + 'state) {
68        if self.nb_updates != self.updates.len() {
69            // Discard previous updates when updating after an undo.
70            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    /// Undo the last update done to the current state.
78    ///
79    /// # Example
80    /// ```
81    /// # use simple_undo::Undo;
82    /// let mut counter = Undo::new(0);
83    /// counter.update(|value| *value += 1);
84    /// counter.update(|value| *value += 2);
85    /// assert_eq!(*counter, 3);
86    ///
87    /// counter.undo();
88    /// assert_eq!(*counter, 1);
89    /// counter.undo();
90    /// assert_eq!(*counter, 0);
91    /// counter.undo(); // does nothing
92    /// assert_eq!(*counter, 0);
93    /// ```
94    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    /// Redo the last update that have been undone using [`Undo::undo`].
107    ///
108    /// # Example
109    /// ```
110    /// # use simple_undo::Undo;
111    /// let mut counter = Undo::new(0);
112    /// counter.update(|value| *value += 1); // 1
113    /// counter.update(|value| *value += 2); // 3
114    /// counter.undo(); // 1
115    /// counter.undo(); // 0
116    /// assert_eq!(*counter, 0);
117    ///
118    /// counter.redo();
119    /// assert_eq!(*counter, 1);
120    /// counter.redo();
121    /// assert_eq!(*counter, 3);
122    /// counter.redo(); // does nothing
123    /// assert_eq!(*counter, 3);
124    /// ```
125    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(); // 8
201        counter.undo(); // 6
202        counter.undo(); // 4
203        counter.redo(); // 6
204        assert_eq!(counter.count, 6);
205        counter.update(|c| c.count += 10); // discard previous updates
206        assert_eq!(counter.count, 16);
207        counter.redo(); // nothing
208        counter.redo(); // nothing
209        counter.undo(); // 6
210        counter.undo(); // 4
211        assert_eq!(counter.count, 4);
212        counter.redo(); // 6
213        counter.redo(); // 16
214        counter.redo(); // nothing
215        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')); // H
231        input_text.update(|text| text.push('e')); // He
232        input_text.update(|text| text.push('l')); // Hel
233        input_text.update(|text| text.push('k')); // Helk
234        input_text.update(|text| text.push('o')); // Helko
235        input_text.undo(); // Helk
236        input_text.undo(); // Hel
237        input_text.undo(); // He
238        input_text.redo(); // Hel
239        input_text.update(|text| text.push('l')); // Hell
240        input_text.update(|text| text.push('o')); // Hello
241
242        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)); // borrow to_add
252
253        message.undo();
254        assert_eq!(*message, "Hello");
255        message.redo();
256        assert_eq!(*message, "Hello world !");
257    }
258}