Skip to main content

indigo_core/
history.rs

1use std::{iter, marker::PhantomData};
2
3pub trait FromItem<I> {
4    fn from_item(item: I) -> Self;
5}
6
7impl<I> FromItem<I> for I {
8    fn from_item(item: I) -> Self {
9        item
10    }
11}
12
13impl<I> FromItem<I> for Vec<I>
14where
15    I: Clone,
16{
17    fn from_item(item: I) -> Self {
18        vec![item]
19    }
20}
21
22#[derive(Clone, Debug)]
23pub struct History<I, T = Vec<I>>
24where
25    T: FromItem<I> + Extend<I> + Clone,
26{
27    transactions: Vec<T>,
28    index: usize,
29    last_is_committed: bool,
30    _phantom: PhantomData<fn(I)>,
31}
32
33impl<I, T> History<I, T>
34where
35    T: FromItem<I> + Extend<I> + Clone,
36{
37    #[must_use]
38    pub fn new() -> Self {
39        Self::default()
40    }
41
42    // NOTE: Replace `extend` + `iter::once` with `extend_one` once it stabilizes.
43    // https://github.com/rust-lang/rust/issues/72631
44    pub fn push(&mut self, item: I) {
45        // If forking the timeline in the past, forget the future.
46        if self.index < self.transactions.len() {
47            self.transactions.truncate(self.index);
48            self.last_is_committed = true;
49        }
50        // If transaction is committed, start a new one.
51        if self.last_is_committed {
52            self.transactions.push(T::from_item(item));
53            self.index += 1;
54            self.last_is_committed = false;
55        } else {
56            // Extend transaction with item.
57            self.transactions
58                .last_mut()
59                .unwrap()
60                .extend(iter::once(item));
61        }
62    }
63
64    pub fn commit(&mut self) {
65        self.last_is_committed = true;
66    }
67
68    /// Returns undone change.
69    pub fn undo(&mut self) -> Option<&T> {
70        if self.index == 0 {
71            return None;
72        }
73        self.index -= 1;
74        self.last_is_committed = true;
75        let transaction = &self.transactions[self.index];
76        Some(transaction)
77    }
78
79    /// Returns redone change.
80    pub fn redo(&mut self) -> Option<&T> {
81        if self.index == self.transactions.len() {
82            return None;
83        }
84        let transaction = &self.transactions[self.index];
85        self.index += 1;
86        self.last_is_committed = true;
87        Some(transaction)
88    }
89}
90
91impl<I, T> Default for History<I, T>
92where
93    T: FromItem<I> + Extend<I> + Clone,
94{
95    fn default() -> Self {
96        Self {
97            transactions: Vec::new(),
98            index: 0,
99            last_is_committed: true,
100            _phantom: PhantomData,
101        }
102    }
103}
104
105#[derive(Clone, Debug, Default, PartialEq)]
106pub struct First<T>(pub Option<T>);
107
108impl<T> FromItem<T> for First<T> {
109    fn from_item(item: T) -> Self {
110        Self(Some(item))
111    }
112}
113
114impl<T> Extend<T> for First<T> {
115    fn extend<I>(&mut self, iter: I)
116    where
117        I: IntoIterator<Item = T>,
118    {
119        if self.0.is_none() {
120            self.0 = iter.into_iter().next();
121        }
122    }
123}
124
125#[derive(Clone, Debug, Default, PartialEq)]
126pub struct Last<T>(pub Option<T>);
127
128impl<T> FromItem<T> for Last<T> {
129    fn from_item(item: T) -> Self {
130        Self(Some(item))
131    }
132}
133
134impl<T> Extend<T> for Last<T> {
135    fn extend<I>(&mut self, iter: I)
136    where
137        I: IntoIterator<Item = T>,
138    {
139        self.0 = iter.into_iter().last();
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use std::ops::AddAssign;
147
148    #[test]
149    fn undo_returns_undone() {
150        let mut history: History<u8> = History::new();
151        history.push(1);
152        history.push(2);
153        assert_eq!(history.undo(), Some(&vec![1, 2]));
154        assert_eq!(history.undo(), None);
155    }
156
157    #[test]
158    fn redo_returns_redone() {
159        let mut history: History<u8> = History::new();
160        history.push(1);
161        history.push(2);
162        history.undo();
163        assert_eq!(history.redo(), Some(&vec![1, 2]));
164        assert_eq!(history.redo(), None);
165    }
166
167    #[test]
168    fn undo_redo_symmetry() {
169        let mut history: History<u8> = History::new();
170        history.push(1);
171        history.push(2);
172        history.push(3);
173        history.commit();
174        history.push(4);
175        history.push(5);
176        history.push(6);
177        history.commit();
178        assert_eq!(history.undo(), Some(&vec![4, 5, 6]));
179        assert_eq!(history.undo(), Some(&vec![1, 2, 3]));
180        assert_eq!(history.redo(), Some(&vec![1, 2, 3]));
181        assert_eq!(history.redo(), Some(&vec![4, 5, 6]));
182    }
183
184    #[test]
185    fn no_movement_no_commit() {
186        let mut history: History<u8> = History::new();
187        history.push(1);
188        // Doesn't move because there's nothing to redo.
189        history.redo();
190        history.push(2);
191        // No movement -> no commit.
192        assert_eq!(history.undo(), Some(&vec![1, 2]));
193    }
194
195    #[test]
196    fn movement_commits() {
197        let mut history: History<u8> = History::new();
198        history.push(1);
199        // Moves to the past and then back to the present.
200        history.undo();
201        history.redo();
202        history.push(2);
203        // Movement -> commit.
204        assert_eq!(history.undo(), Some(&vec![2]));
205    }
206
207    #[test]
208    fn fork_timeline_discards_future() {
209        let mut history: History<u8> = History::new();
210        history.push(1);
211        history.commit();
212        history.push(2);
213        history.commit();
214        history.undo();
215        history.push(3);
216        assert_eq!(history.undo(), Some(&vec![3]));
217    }
218
219    #[derive(Clone, Debug, Default, PartialEq)]
220    struct Sum<T>(T);
221
222    impl<T> FromItem<T> for Sum<T> {
223        fn from_item(item: T) -> Self {
224            Self(item)
225        }
226    }
227
228    impl<T> Extend<T> for Sum<T>
229    where
230        T: AddAssign,
231    {
232        fn extend<I>(&mut self, ns: I)
233        where
234            I: IntoIterator<Item = T>,
235        {
236            for n in ns {
237                self.0 += n;
238            }
239        }
240    }
241
242    #[test]
243    fn custom_transaction_type_sum() {
244        let mut history: History<u8, Sum<u8>> = History::new();
245        history.push(1);
246        history.push(1);
247        history.push(1);
248        history.commit();
249        history.push(2);
250        history.push(2);
251        history.push(2);
252        history.commit();
253        assert_eq!(history.undo(), Some(&Sum(6)));
254        assert_eq!(history.undo(), Some(&Sum(3)));
255    }
256
257    #[test]
258    fn custom_transaction_type_first() {
259        let mut history: History<u8, First<u8>> = History::new();
260        history.push(1);
261        history.push(2);
262        history.push(3);
263        history.commit();
264        history.push(4);
265        history.push(5);
266        history.push(6);
267        history.commit();
268        assert_eq!(history.undo(), Some(&First(Some(4))));
269        assert_eq!(history.undo(), Some(&First(Some(1))));
270    }
271
272    #[test]
273    fn custom_transaction_type_last() {
274        let mut history: History<u8, Last<u8>> = History::new();
275        history.push(1);
276        history.push(2);
277        history.push(3);
278        history.commit();
279        history.push(4);
280        history.push(5);
281        history.push(6);
282        history.commit();
283        assert_eq!(history.undo(), Some(&Last(Some(6))));
284        assert_eq!(history.undo(), Some(&Last(Some(3))));
285    }
286}