gpui_component/
history.rs

1use std::{
2    fmt::Debug,
3    time::{Duration, Instant},
4};
5
6/// A HistoryItem represents a single change in the history.
7/// It must implement Clone and PartialEq to be used in the History.
8pub trait HistoryItem: Clone + PartialEq {
9    fn version(&self) -> usize;
10    fn set_version(&mut self, version: usize);
11}
12
13/// The History is used to keep track of changes to a model and to allow undo and redo operations.
14///
15/// This is now used in Input for undo/redo operations. You can also use this in
16/// your own models to keep track of changes, for example to track the tab
17/// history for prev/next features.
18///
19/// ## Use cases
20///
21/// - Undo/redo operations in Input
22/// - Tracking tab history for prev/next features
23#[derive(Debug)]
24pub struct History<I: HistoryItem> {
25    undos: Vec<I>,
26    redos: Vec<I>,
27    last_changed_at: Instant,
28    version: usize,
29    pub(crate) ignore: bool,
30    max_undos: usize,
31    group_interval: Option<Duration>,
32    grouping: bool,
33    unique: bool,
34}
35
36impl<I> History<I>
37where
38    I: HistoryItem,
39{
40    pub fn new() -> Self {
41        Self {
42            undos: Default::default(),
43            redos: Default::default(),
44            ignore: false,
45            last_changed_at: Instant::now(),
46            version: 0,
47            max_undos: 1000,
48            group_interval: None,
49            grouping: false,
50            unique: false,
51        }
52    }
53
54    /// Set the maximum number of undo steps to keep, defaults to 1000.
55    pub fn max_undos(mut self, max_undos: usize) -> Self {
56        self.max_undos = max_undos;
57        self
58    }
59
60    /// Set the history to be unique, defaults to false.
61    /// If set to true, the history will only keep unique changes.
62    pub fn unique(mut self) -> Self {
63        self.unique = true;
64        self
65    }
66
67    /// Set the interval in milliseconds to group changes, defaults to None.
68    pub fn group_interval(mut self, group_interval: Duration) -> Self {
69        self.group_interval = Some(group_interval);
70        self
71    }
72
73    /// Start grouping changes, this will prevent the version from being incremented until `end_grouping` is called.
74    pub fn start_grouping(&mut self) {
75        self.grouping = true;
76    }
77
78    /// End grouping changes, this will allow the version to be incremented again.
79    pub fn end_grouping(&mut self) {
80        self.grouping = false;
81    }
82
83    /// Increment the version number if the last change was made more than `GROUP_INTERVAL` milliseconds ago.
84    fn inc_version(&mut self) -> usize {
85        let t = Instant::now();
86        if !self.grouping && Some(self.last_changed_at.elapsed()) > self.group_interval {
87            self.version += 1;
88        }
89
90        self.last_changed_at = t;
91        self.version
92    }
93
94    /// Get the current version number.
95    pub fn version(&self) -> usize {
96        self.version
97    }
98
99    /// Push a new change to the history.
100    pub fn push(&mut self, item: I) {
101        let version = self.inc_version();
102
103        if self.undos.len() >= self.max_undos {
104            self.undos.remove(0);
105        }
106
107        if self.unique {
108            self.undos.retain(|c| *c != item);
109            self.redos.retain(|c| *c != item);
110        }
111
112        let mut item = item;
113        item.set_version(version);
114        self.undos.push(item);
115    }
116
117    /// Get the undo stack.
118    pub fn undos(&self) -> &Vec<I> {
119        &self.undos
120    }
121
122    /// Get the redo stack.
123    pub fn redos(&self) -> &Vec<I> {
124        &self.redos
125    }
126
127    /// Clear the undo and redo stacks.
128    pub fn clear(&mut self) {
129        self.undos.clear();
130        self.redos.clear();
131    }
132
133    /// Undo the last change and return the changes that were undone.
134    pub fn undo(&mut self) -> Option<Vec<I>> {
135        if let Some(first_change) = self.undos.pop() {
136            let mut changes = vec![first_change.clone()];
137            // pick the next all changes with the same version
138            while self
139                .undos
140                .iter()
141                .filter(|c| c.version() == first_change.version())
142                .count()
143                > 0
144            {
145                let change = self.undos.pop().unwrap();
146                changes.push(change);
147            }
148
149            self.redos.extend(changes.clone());
150            Some(changes)
151        } else {
152            None
153        }
154    }
155
156    /// Redo the last undone change and return the changes that were redone.
157    pub fn redo(&mut self) -> Option<Vec<I>> {
158        if let Some(first_change) = self.redos.pop() {
159            let mut changes = vec![first_change.clone()];
160            // pick the next all changes with the same version
161            while self
162                .redos
163                .iter()
164                .filter(|c| c.version() == first_change.version())
165                .count()
166                > 0
167            {
168                let change = self.redos.pop().unwrap();
169                changes.push(change);
170            }
171            self.undos.extend(changes.clone());
172            Some(changes)
173        } else {
174            None
175        }
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182
183    #[derive(Clone)]
184    struct TabIndex {
185        tab_index: usize,
186        version: usize,
187    }
188
189    impl PartialEq for TabIndex {
190        fn eq(&self, other: &Self) -> bool {
191            self.tab_index == other.tab_index
192        }
193    }
194
195    impl From<usize> for TabIndex {
196        fn from(value: usize) -> Self {
197            TabIndex {
198                tab_index: value,
199                version: 0,
200            }
201        }
202    }
203
204    impl HistoryItem for TabIndex {
205        fn version(&self) -> usize {
206            self.version
207        }
208        fn set_version(&mut self, version: usize) {
209            self.version = version;
210        }
211    }
212
213    #[test]
214    fn test_history() {
215        let mut history: History<TabIndex> = History::new().max_undos(100);
216        history.push(0.into());
217        history.push(3.into());
218        history.push(2.into());
219        history.push(1.into());
220
221        assert_eq!(history.version(), 4);
222        let changes = history.undo().unwrap();
223        assert_eq!(changes.len(), 1);
224        assert_eq!(changes[0].tab_index, 1);
225
226        let changes = history.undo().unwrap();
227        assert_eq!(changes.len(), 1);
228        assert_eq!(changes[0].tab_index, 2);
229
230        history.push(5.into());
231
232        let changes = history.redo().unwrap();
233        assert_eq!(changes[0].tab_index, 2);
234
235        let changes = history.redo().unwrap();
236        assert_eq!(changes[0].tab_index, 1);
237
238        let changes = history.undo().unwrap();
239        assert_eq!(changes[0].tab_index, 1);
240
241        let changes = history.undo().unwrap();
242        assert_eq!(changes[0].tab_index, 2);
243
244        let changes = history.undo().unwrap();
245        assert_eq!(changes[0].tab_index, 5);
246
247        let changes = history.undo().unwrap();
248        assert_eq!(changes[0].tab_index, 3);
249
250        let changes = history.undo().unwrap();
251        assert_eq!(changes[0].tab_index, 0);
252
253        assert_eq!(history.undo().is_none(), true);
254    }
255
256    #[test]
257    fn test_unique_history() {
258        let mut history: History<TabIndex> = History::new().max_undos(100).unique();
259
260        // Push some items
261        history.push(0.into());
262        history.push(1.into());
263        history.push(1.into()); // Duplicate, should be ignored
264        history.push(2.into());
265        history.push(1.into()); // Duplicate, should be remove old, and add new
266
267        // Check the version and undo stack
268        assert_eq!(history.version(), 5);
269        assert_eq!(history.undos().len(), 3);
270        assert_eq!(history.undos().last().unwrap().tab_index, 1);
271
272        // Undo the last change
273        let changes = history.undo().unwrap();
274        assert_eq!(changes.len(), 1);
275        assert_eq!(changes[0].tab_index, 1);
276
277        assert_eq!(history.redos().len(), 1);
278        // Push duplicate, should be ignored
279        history.push(2.into());
280
281        assert_eq!(history.undos().len(), 2);
282        assert_eq!(history.redos().len(), 1);
283
284        // Redo the last undone change
285        let changes = history.redo().unwrap();
286        assert_eq!(changes.len(), 1);
287        assert_eq!(changes[0].tab_index, 1);
288
289        // Push another item
290        history.push(3.into());
291
292        // Check the version and undo stack
293        assert_eq!(history.version(), 7);
294        assert_eq!(history.undos().len(), 4);
295
296        // Undo all changes
297        for _ in 0..4 {
298            history.undo();
299        }
300
301        // Check the undo stack is empty and redo stack has all changes
302        assert_eq!(history.undos().len(), 0);
303        assert_eq!(history.redos().len(), 4);
304    }
305}