gpui_component/
history.rs

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