history_stack/
lib.rs

1//! A crate implementing generic history managers that can act as building blocks for transactional
2//! state and reversible computations
3
4#![no_std]
5#![forbid(unsafe_code)]
6#![warn(clippy::alloc_instead_of_core, clippy::std_instead_of_alloc)]
7#![warn(clippy::pedantic, clippy::cargo)]
8#![allow(clippy::module_name_repetitions)]
9#![warn(missing_docs, clippy::missing_docs_in_private_items)]
10extern crate alloc;
11
12use core::{cmp, fmt, hash, ops};
13
14use alloc::vec::Vec;
15
16/// A wrapper over a `T` that provides a primitive history mechanism by use of a stack of `T`. It
17/// can be pushed to or popped from to save the current value or pop out a previously saved value
18/// in LIFO (stack) order.
19///
20/// `HistoryStack` is also "transparently T", meaning the default traits it implements all act like
21/// the current value of T, so hashing `HistoryStack<T>` and T produce the same hash, Eq and Ord work
22/// the same etc. This also includes `Display`, but does not include `Debug`.
23#[derive(Clone, Default, Debug)]
24pub struct HistoryStack<T> {
25    /// The history stack, this starts out empty and should only be modified via pushing and popping
26    stack: Vec<T>,
27    /// The current value, since `HistoryStack<T>` acts like a T, this is always initialized to
28    /// some value
29    current: T,
30}
31
32impl<T: fmt::Display> fmt::Display for HistoryStack<T> {
33    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34        self.current.fmt(f)
35    }
36}
37
38impl<T> HistoryStack<T> {
39    /// Create a new `HistoryStack` whose current value is set to `v`, with no history
40    pub const fn new(v: T) -> Self {
41        Self {
42            stack: Vec::new(),
43            current: v,
44        }
45    }
46
47    /// Pop a value from the stack and set it as the current value, returning the previous current
48    /// value.
49    ///
50    /// Returns `None` in the case that there was no previous value from the stack, current is also
51    /// unchanged.
52    pub fn pop(&mut self) -> Option<T> {
53        match self.stack.pop() {
54            Some(last) => Some(core::mem::replace(&mut self.current, last)),
55            None => None,
56        }
57    }
58
59    /// Pushes a value into the current value, and pushes the previously current value into the
60    /// stack.
61    pub fn push_value(&mut self, v: T) {
62        self.stack.push(core::mem::replace(&mut self.current, v));
63    }
64
65    /// Makes a [`Clone::clone`] of the current value and pushes it to the stack, leaving the
66    /// current value untouched
67    pub fn push(&mut self)
68    where
69        T: Clone,
70    {
71        self.stack.push(self.current.clone());
72    }
73}
74
75impl<T> ops::Deref for HistoryStack<T> {
76    type Target = T;
77
78    fn deref(&self) -> &Self::Target {
79        &self.current
80    }
81}
82
83impl<T> ops::DerefMut for HistoryStack<T> {
84    fn deref_mut(&mut self) -> &mut Self::Target {
85        &mut self.current
86    }
87}
88
89impl<T: PartialEq> PartialEq<T> for HistoryStack<T> {
90    fn eq(&self, other: &T) -> bool {
91        &self.current == other
92    }
93}
94
95impl<T: PartialEq> PartialEq for HistoryStack<T> {
96    fn eq(&self, other: &Self) -> bool {
97        self.current == other.current
98    }
99}
100
101impl<T: Eq> Eq for HistoryStack<T> {}
102
103impl<T: PartialOrd> PartialOrd for HistoryStack<T> {
104    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
105        self.current.partial_cmp(&other.current)
106    }
107}
108
109impl<T: PartialOrd> PartialOrd<T> for HistoryStack<T> {
110    fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
111        self.current.partial_cmp(other)
112    }
113}
114
115impl<T: Ord> Ord for HistoryStack<T> {
116    fn cmp(&self, other: &Self) -> cmp::Ordering {
117        self.current.cmp(&other.current)
118    }
119}
120
121impl<T: hash::Hash> hash::Hash for HistoryStack<T> {
122    fn hash<H: hash::Hasher>(&self, state: &mut H) {
123        self.current.hash(state);
124    }
125}
126
127/// A structure which allows you to undo and redo changes based on saved states of `T`.
128///
129/// To use, simply [`save`](UndoStack::save), [`undo`](UndoStack::undo), and
130/// [`redo`](UndoStack::redo) later if needed;
131/// ```rust
132/// # use history_stack::UndoStack;
133/// // Create with initial state
134/// let mut undo = UndoStack::new(5u8);
135///
136/// // make a savepoint and get a reference to the new current value
137/// // our stack looks like [5, 5] currently, our current value being the second
138/// let newref = undo.save();
139///
140/// // we modified the new current value, our stack looks like [5, 10] now
141/// *newref *= 2;
142///
143/// // but we made a mistake! we want to go back now, and since we are
144/// // sure we saved earlier we can unwrap here to get the Ok variant
145/// // our stack still looks like [5, 10], but now we point to the 5
146/// let oldref = undo.undo().unwrap();
147///
148/// // turns out it wasnt a mistake, lets redo and unwrap to be sure we got the newer value
149/// undo.redo().unwrap();
150///
151/// // UndoStack implements Deref and DerefMut, we can make sure we got the new value like this
152/// assert_eq!(undo, 10);
153/// ```
154///
155/// This is useful when you want to be able to make changes in a way where you can undo a change,
156/// and then reapply it later, but do not wish to write a complex incremental structure that could
157/// track changes like that. This type provides a generic (read: you can use it on anything)
158/// interface to achieve that effect, even if it may use more memory than a more targeted approach.
159///
160/// `UndoStack` is also "transparently T", meaning the default traits it implements all act like
161/// the current value of T, so hashing `UndoStack<T>` and T produce the same hash, Eq and Ord work
162/// the same etc. This also includes `Display`, but does not include `Debug`.
163#[derive(Clone, Debug)]
164pub struct UndoStack<T> {
165    /// History of the undostack that includes the current value somewhere within
166    history: Vec<T>,
167    /// Index into history that represents the current value
168    current: usize,
169}
170
171impl<T: Default> Default for UndoStack<T> {
172    fn default() -> Self {
173        Self {
174            history: alloc::vec![T::default()],
175            current: 0,
176        }
177    }
178}
179
180impl<T: fmt::Display> fmt::Display for UndoStack<T> {
181    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
182        self.inner().fmt(f)
183    }
184}
185
186impl<T> UndoStack<T> {
187    /// Creates a new `UndoStack` with a starting value to act as the current value
188    pub fn new(start: T) -> Self {
189        Self {
190            history: alloc::vec![start],
191            current: 0,
192        }
193    }
194
195    /// Drops any values that exist after the current value
196    fn invalidate_future(&mut self) {
197        // we have hit undo if these values do not match up, so we must invalidate the redo stack
198        // it is safe to do current+1 because current is <history.len() which is stored
199        // as usize aswell
200        if self.current + 1 != self.history.len() {
201            // see above for +1 safety
202            self.history.truncate(self.current + 1);
203        }
204    }
205
206    /// Pushes a value assuming the current value is the last value
207    /// returns a reference to the new current value (the value that was just pushed)
208    fn push_unchecked(&mut self, val: T) -> &mut T {
209        self.history.push(val);
210
211        // +1 safety: current is always less than history.len(), which would panic on overflow
212        self.current += 1;
213
214        &mut self.history[self.current]
215    }
216
217    /// Saves the current T to history and invalidates any data that may be used to redo
218    /// This will [`Drop`] any T that exist later in history than the current edit point.
219    ///
220    /// Returns a reference to the new current value
221    ///
222    /// # Panics
223    /// This will panic if allocation failed
224    pub fn save(&mut self) -> &mut T
225    where
226        T: Clone,
227    {
228        self.invariant_ck();
229
230        self.invalidate_future();
231
232        // safe to unwrap here because history is always nonempty
233        let val = self.history.last().unwrap().clone();
234
235        self.push_unchecked(val)
236    }
237
238    /// Pushes the given value to the stack, making it the new current value and invalidating
239    /// future history, returns a reference to the new current value
240    ///
241    /// This is functionally identical to [`save`](UndoStack::save) but does not have a `Clone`
242    /// bound, instead sourcing its new value from the caller.
243    ///
244    /// # Panics
245    /// This will panic if allocation failed
246    pub fn push(&mut self, new_current: T) -> &mut T {
247        self.invariant_ck();
248
249        self.invalidate_future();
250
251        self.push_unchecked(new_current)
252    }
253
254    /// If there is a previous state in the history stack, backtrack to that and return `Ok(&mut T)`
255    /// to the new current value, otherwise return `Err(&mut T)` to the unchanged current value.
256    #[allow(clippy::missing_errors_doc)]
257    pub fn undo(&mut self) -> Result<&mut T, &mut T> {
258        self.invariant_ck();
259
260        match self.current.checked_sub(1) {
261            Some(n) => {
262                self.current = n;
263                Ok(&mut self.history[self.current])
264            }
265            None => {
266                // current was 0
267                Err(&mut self.history[0])
268            }
269        }
270    }
271
272    /// If there is a future state in the history stack that has been undone from, redo to that
273    /// position and return `Ok(&mut T)` of the new current value after advancing, else return
274    /// `Err(&mut T)` of the current unchanged value, if there was no future history.
275    #[allow(clippy::missing_errors_doc)]
276    pub fn redo(&mut self) -> Result<&mut T, &mut T> {
277        self.invariant_ck();
278
279        if self.current + 1 == self.history.len() {
280            Err(&mut self.history[self.current])
281        } else {
282            self.current += 1;
283
284            Ok(&mut self.history[self.current])
285        }
286    }
287
288    /// function that runs in debug and checks all trivial invariants of `UndoStack`
289    fn invariant_ck(&self) {
290        debug_assert!(
291            !self.history.is_empty(),
292            "UndoStack: history was empty, this indicates a bug in UndoStack"
293        );
294        debug_assert!(self.current < self.history.len(), "UndoStack: current was not less than history length, this indicates a bug in UndoStack");
295    }
296
297    /// Gets a reference to the current value
298    /// used to implement traits via T without accidental recursion
299    fn inner(&self) -> &T {
300        self
301    }
302}
303
304impl<T> ops::Deref for UndoStack<T> {
305    type Target = T;
306
307    fn deref(&self) -> &Self::Target {
308        &self.history[self.current]
309    }
310}
311
312impl<T> ops::DerefMut for UndoStack<T> {
313    fn deref_mut(&mut self) -> &mut Self::Target {
314        &mut self.history[self.current]
315    }
316}
317
318impl<T: PartialEq> PartialEq<T> for UndoStack<T> {
319    fn eq(&self, other: &T) -> bool {
320        self.inner() == other
321    }
322}
323
324impl<T: PartialEq> PartialEq for UndoStack<T> {
325    fn eq(&self, other: &Self) -> bool {
326        self.inner() == other.inner()
327    }
328}
329
330impl<T: Eq> Eq for UndoStack<T> {}
331
332impl<T: PartialOrd> PartialOrd for UndoStack<T> {
333    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
334        self.inner().partial_cmp(other.inner())
335    }
336}
337
338impl<T: PartialOrd> PartialOrd<T> for UndoStack<T> {
339    fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
340        self.inner().partial_cmp(other)
341    }
342}
343
344impl<T: Ord> Ord for UndoStack<T> {
345    fn cmp(&self, other: &Self) -> cmp::Ordering {
346        self.inner().cmp(other.inner())
347    }
348}
349
350impl<T: hash::Hash> hash::Hash for UndoStack<T> {
351    fn hash<H: hash::Hasher>(&self, state: &mut H) {
352        self.inner().hash(state);
353    }
354}
355
356#[test]
357fn undo_stack() {
358    let mut g = UndoStack::new(0u8);
359
360    *g.save() += 1;
361
362    assert_eq!(g, 1);
363
364    assert_eq!(*g.undo().unwrap(), 0);
365
366    assert_eq!(*g.redo().unwrap(), 1);
367
368    assert!(g.undo().is_ok());
369
370    *g.save() += 2;
371
372    assert!(g.redo().is_err());
373}
374
375#[test]
376fn history_stack() {
377    let mut g = HistoryStack::new(0u8);
378
379    g.push_value(5);
380
381    assert_eq!(g, 5);
382
383    assert_eq!(g.pop(), Some(5));
384
385    assert_eq!(g, 0);
386}