Skip to main content

gpui_base/
history.rs

1/// A browser-style linear trail with a current entry.
2///
3/// Entries before the current one can be revisited with [`back`](Self::back),
4/// and entries left behind by going back can be restored with
5/// [`forward`](Self::forward). Pushing after going back starts a new branch
6/// and drops the forward entries.
7#[derive(Debug)]
8pub struct History<T> {
9    entries: Vec<T>,
10    forward_entries: Vec<T>,
11    max_entries: usize,
12}
13
14impl<T> History<T> {
15    pub fn new() -> Self {
16        Self {
17            entries: Vec::new(),
18            forward_entries: Vec::new(),
19            max_entries: 1000,
20        }
21    }
22
23    /// Sets the maximum number of root-to-current entries to keep, defaults to 1000.
24    ///
25    /// Lowering the limit immediately removes the oldest entries.
26    pub fn max_entries(mut self, max_entries: usize) -> Self {
27        self.max_entries = max_entries;
28        self.enforce_max_entries();
29        self
30    }
31
32    /// Pushes an entry and drops the forward branch.
33    pub fn push(&mut self, entry: T) {
34        self.forward_entries.clear();
35        if self.max_entries == 0 {
36            return;
37        }
38        self.entries.push(entry);
39        self.enforce_max_entries();
40    }
41
42    /// Returns the current entry.
43    pub fn current(&self) -> Option<&T> {
44        self.entries.last()
45    }
46
47    /// Replaces the current entry, or pushes when the trail is empty.
48    pub fn replace_current(&mut self, entry: T) {
49        match self.entries.last_mut() {
50            Some(current) => *current = entry,
51            None => self.push(entry),
52        }
53    }
54
55    /// Removes and returns the current entry without changing the forward branch.
56    pub fn remove_current(&mut self) -> Option<T> {
57        self.entries.pop()
58    }
59
60    /// Returns whether moving back would keep a root entry current.
61    pub fn can_back(&self) -> bool {
62        self.entries.len() > 1
63    }
64
65    /// Returns whether a forward entry is available.
66    pub fn can_forward(&self) -> bool {
67        !self.forward_entries.is_empty()
68    }
69
70    /// Moves back one entry and returns the new current entry.
71    pub fn back(&mut self) -> Option<T>
72    where
73        T: Clone,
74    {
75        if self.entries.len() <= 1 {
76            return None;
77        }
78        self.forward_entries.push(self.entries.pop().unwrap());
79        self.current().cloned()
80    }
81
82    /// Moves forward one entry and returns the restored entry.
83    pub fn forward(&mut self) -> Option<T>
84    where
85        T: Clone,
86    {
87        if self.max_entries == 0 {
88            return None;
89        }
90        let entry = self.forward_entries.pop()?;
91        self.entries.push(entry);
92        self.enforce_max_entries();
93        self.current().cloned()
94    }
95
96    /// Iterates from the root entry to the current entry.
97    pub fn entries(&self) -> impl DoubleEndedIterator<Item = &T> + ExactSizeIterator {
98        self.entries.iter()
99    }
100
101    /// Iterates from the nearest forward entry to the furthest.
102    pub fn forward_entries(&self) -> impl DoubleEndedIterator<Item = &T> + ExactSizeIterator {
103        self.forward_entries.iter().rev()
104    }
105
106    /// Keeps only entries accepted by `keep` on both sides of the current position.
107    pub fn retain(&mut self, mut keep: impl FnMut(&T) -> bool) {
108        self.entries.retain(&mut keep);
109        self.forward_entries.retain(&mut keep);
110    }
111
112    /// Clears current, back, and forward entries.
113    pub fn clear(&mut self) {
114        self.entries.clear();
115        self.forward_entries.clear();
116    }
117
118    fn enforce_max_entries(&mut self) {
119        let excess = self.entries.len().saturating_sub(self.max_entries);
120        if excess > 0 {
121            self.entries.drain(..excess);
122        }
123    }
124}
125
126impl<T> Default for History<T> {
127    fn default() -> Self {
128        Self::new()
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn navigation_moves_between_entries_without_backing_past_the_root() {
138        let mut history = History::new().max_entries(3);
139        history.push(1);
140        history.push(2);
141        history.push(3);
142
143        assert_eq!(history.current(), Some(&3));
144        assert_eq!(history.back(), Some(2));
145        assert_eq!(history.back(), Some(1));
146        assert_eq!(history.back(), None);
147        assert_eq!(history.forward(), Some(2));
148        assert_eq!(history.entries().copied().collect::<Vec<_>>(), [1, 2]);
149        assert_eq!(history.entries().rev().copied().collect::<Vec<_>>(), [2, 1]);
150        assert_eq!(history.forward_entries().copied().collect::<Vec<_>>(), [3]);
151        assert!(history.can_back());
152        assert!(history.can_forward());
153    }
154
155    #[test]
156    fn pushing_after_back_truncates_the_forward_branch() {
157        let mut history = History::new();
158        history.push(1);
159        history.push(2);
160        history.push(3);
161        assert_eq!(history.back(), Some(2));
162
163        history.push(4);
164
165        assert_eq!(history.entries().copied().collect::<Vec<_>>(), [1, 2, 4]);
166        assert!(!history.can_forward());
167        assert_eq!(history.forward(), None);
168    }
169
170    #[test]
171    fn repeated_entries_preserve_every_navigation_step() {
172        let mut history = History::new();
173        history.push(1);
174        history.push(2);
175        history.push(1);
176
177        assert_eq!(history.entries().copied().collect::<Vec<_>>(), [1, 2, 1]);
178        assert_eq!(history.back(), Some(2));
179        assert_eq!(history.back(), Some(1));
180    }
181
182    #[test]
183    fn max_entries_evicts_the_oldest_entry() {
184        let mut history = History::new().max_entries(2);
185        history.push(1);
186        history.push(2);
187        history.push(3);
188
189        assert_eq!(history.entries().copied().collect::<Vec<_>>(), [2, 3]);
190        assert_eq!(history.back(), Some(2));
191        assert_eq!(history.back(), None);
192    }
193
194    #[test]
195    fn lowering_max_entries_truncates_populated_entries_and_caps_forward_restores() {
196        let mut history = History::new();
197        history.push(1);
198        history.push(2);
199        history.push(3);
200        assert_eq!(history.back(), Some(2));
201
202        history = history.max_entries(1);
203
204        assert_eq!(history.entries().copied().collect::<Vec<_>>(), [2]);
205        assert_eq!(history.forward(), Some(3));
206        assert_eq!(history.entries().copied().collect::<Vec<_>>(), [3]);
207        assert_eq!(history.back(), None);
208    }
209
210    #[test]
211    fn zero_max_entries_retains_nothing() {
212        let mut history = History::new().max_entries(0);
213        history.push(1);
214
215        assert_eq!(history.current(), None);
216        assert_eq!(history.entries().len(), 0);
217        assert!(!history.can_back());
218        assert!(!history.can_forward());
219    }
220
221    #[test]
222    fn replace_current_updates_in_place_and_pushes_when_empty() {
223        let mut history = History::new();
224        history.replace_current(1);
225        history.push(2);
226        history.replace_current(3);
227
228        assert_eq!(history.entries().copied().collect::<Vec<_>>(), [1, 3]);
229    }
230
231    #[test]
232    fn remove_current_preserves_forward_entries() {
233        let mut history = History::new();
234        history.push(1);
235        history.push(2);
236        history.push(3);
237        assert_eq!(history.back(), Some(2));
238
239        assert_eq!(history.remove_current(), Some(2));
240
241        assert_eq!(history.current(), Some(&1));
242        assert_eq!(history.forward_entries().copied().collect::<Vec<_>>(), [3]);
243        assert_eq!(history.forward(), Some(3));
244    }
245
246    #[test]
247    fn retain_filters_back_and_forward_entries_without_reordering() {
248        let mut history = History::new();
249        for entry in 1..=8 {
250            history.push(entry);
251        }
252        history.back();
253        history.back();
254        history.back();
255        history.back();
256
257        history.retain(|entry| entry % 2 == 0);
258
259        assert_eq!(history.entries().copied().collect::<Vec<_>>(), [2, 4]);
260        assert_eq!(
261            history.forward_entries().copied().collect::<Vec<_>>(),
262            [6, 8]
263        );
264        assert_eq!(history.forward(), Some(6));
265        assert_eq!(history.forward(), Some(8));
266    }
267
268    #[test]
269    fn clear_removes_back_and_forward_entries() {
270        let mut history = History::new();
271        history.push(1);
272        history.push(2);
273        history.back();
274
275        history.clear();
276
277        assert_eq!(history.current(), None);
278        assert_eq!(history.entries().len(), 0);
279        assert_eq!(history.forward_entries().len(), 0);
280        assert!(!history.can_back());
281        assert!(!history.can_forward());
282    }
283}