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