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}