freya_edit/
editor_history.rs

1use std::time::{
2    Duration,
3    Instant,
4};
5
6use ropey::Rope;
7
8#[derive(Clone, Debug, PartialEq)]
9pub enum HistoryChange {
10    InsertChar {
11        idx: usize,
12        len: usize,
13        ch: char,
14    },
15    InsertText {
16        idx: usize,
17        len: usize,
18        text: String,
19    },
20    Remove {
21        idx: usize,
22        len: usize,
23        text: String,
24    },
25}
26
27#[derive(Clone, Debug, PartialEq)]
28pub struct HistoryTransaction {
29    pub timestamp: Instant,
30    pub changes: Vec<HistoryChange>,
31}
32
33#[derive(Clone, Debug)]
34pub struct EditorHistory {
35    pub transactions: Vec<HistoryTransaction>,
36    pub current_transaction: usize,
37    // Incremental counter for every transaction.
38    pub version: usize,
39    /// After how many seconds since the last transaction a change should be grouped with the last transaction.
40    transaction_treshold_groping: Duration,
41}
42
43impl EditorHistory {
44    pub fn new(transaction_treshold_groping: Duration) -> Self {
45        Self {
46            transactions: Vec::default(),
47            current_transaction: 0,
48            version: 0,
49            transaction_treshold_groping,
50        }
51    }
52
53    pub fn push_change(&mut self, change: HistoryChange) {
54        if self.can_redo() {
55            self.transactions.drain(self.current_transaction..);
56        }
57
58        let last_transaction = self
59            .transactions
60            .get_mut(self.current_transaction.saturating_sub(1));
61        if let Some(last_transaction) = last_transaction
62            && last_transaction.timestamp.elapsed() <= self.transaction_treshold_groping
63        {
64            last_transaction.changes.push(change);
65            last_transaction.timestamp = Instant::now();
66            return;
67        }
68
69        self.transactions.push(HistoryTransaction {
70            timestamp: Instant::now(),
71            changes: vec![change],
72        });
73
74        self.current_transaction = self.transactions.len();
75        self.version += 1;
76    }
77
78    pub fn current_change(&self) -> usize {
79        self.current_transaction
80    }
81
82    pub fn any_pending_changes(&self) -> usize {
83        self.transactions.len() - self.current_transaction
84    }
85
86    pub fn can_undo(&self) -> bool {
87        self.current_transaction > 0
88    }
89
90    pub fn can_redo(&self) -> bool {
91        self.current_transaction < self.transactions.len()
92    }
93
94    pub fn undo(&mut self, rope: &mut Rope) -> Option<usize> {
95        if !self.can_undo() {
96            return None;
97        }
98
99        let last_transaction = self.transactions.get(self.current_transaction - 1);
100        if let Some(last_transaction) = last_transaction {
101            let mut idx_end = None;
102            for change in last_transaction.changes.iter().rev() {
103                idx_end.replace(match change {
104                    HistoryChange::Remove { idx, text, len } => {
105                        let start = rope.utf16_cu_to_char(*idx);
106                        rope.insert(start, text);
107                        *idx + len
108                    }
109                    HistoryChange::InsertChar { idx, len, .. } => {
110                        let start = rope.utf16_cu_to_char(*idx);
111                        let end = rope.utf16_cu_to_char(*idx + len);
112                        rope.remove(start..end);
113                        *idx
114                    }
115                    HistoryChange::InsertText { idx, len, .. } => {
116                        let start = rope.utf16_cu_to_char(*idx);
117                        let end = rope.utf16_cu_to_char(*idx + len);
118                        rope.remove(start..end);
119                        *idx
120                    }
121                });
122            }
123
124            self.current_transaction -= 1;
125            self.version += 1;
126            idx_end
127        } else {
128            None
129        }
130    }
131
132    pub fn redo(&mut self, rope: &mut Rope) -> Option<usize> {
133        if !self.can_redo() {
134            return None;
135        }
136
137        let last_transaction = self.transactions.get(self.current_transaction);
138        if let Some(last_transaction) = last_transaction {
139            let mut idx_end = None;
140            for change in &last_transaction.changes {
141                idx_end.replace(match change {
142                    HistoryChange::Remove { idx, len, .. } => {
143                        let start = rope.utf16_cu_to_char(*idx);
144                        let end = rope.utf16_cu_to_char(*idx + len);
145                        rope.remove(start..end);
146                        *idx
147                    }
148                    HistoryChange::InsertChar { idx, ch, len } => {
149                        let start = rope.utf16_cu_to_char(*idx);
150                        rope.insert_char(start, *ch);
151                        *idx + len
152                    }
153                    HistoryChange::InsertText { idx, text, len } => {
154                        let start = rope.utf16_cu_to_char(*idx);
155                        rope.insert(start, text);
156                        *idx + len
157                    }
158                });
159            }
160            self.current_transaction += 1;
161            self.version += 1;
162            idx_end
163        } else {
164            None
165        }
166    }
167
168    pub fn clear_redos(&mut self) {
169        if self.can_redo() {
170            self.transactions.drain(self.current_transaction..);
171        }
172    }
173
174    pub fn clear(&mut self) {
175        self.transactions.clear();
176        self.current_transaction = 0;
177        self.version = 0;
178    }
179}
180
181#[cfg(test)]
182mod test {
183    use std::time::Duration;
184
185    use ropey::Rope;
186
187    use super::{
188        EditorHistory,
189        HistoryChange,
190    };
191
192    #[test]
193    fn test() {
194        let mut rope = Rope::new();
195        let mut history = EditorHistory::new(Duration::ZERO);
196
197        // Initial text
198        rope.insert(0, "Hello World");
199
200        assert!(!history.can_undo());
201        assert!(!history.can_redo());
202
203        // New change
204        rope.insert(11, "\n!!!!");
205        history.push_change(HistoryChange::InsertText {
206            idx: 11,
207            text: "\n!!!!".to_owned(),
208            len: "\n!!!!".len(),
209        });
210
211        assert!(history.can_undo());
212        assert!(!history.can_redo());
213        assert_eq!(rope.to_string(), "Hello World\n!!!!");
214
215        // Undo last change
216        history.undo(&mut rope);
217
218        assert!(!history.can_undo());
219        assert!(history.can_redo());
220        assert_eq!(rope.to_string(), "Hello World");
221
222        // More changes
223        rope.insert(11, "\n!!!!");
224        history.push_change(HistoryChange::InsertText {
225            idx: 11,
226            text: "\n!!!!".to_owned(),
227            len: "\n!!!!".len(),
228        });
229        rope.insert(16, "\n!!!!");
230        history.push_change(HistoryChange::InsertText {
231            idx: 16,
232            text: "\n!!!!".to_owned(),
233            len: "\n!!!!".len(),
234        });
235        rope.insert(21, "\n!!!!");
236        history.push_change(HistoryChange::InsertText {
237            idx: 21,
238            text: "\n!!!!".to_owned(),
239            len: "\n!!!!".len(),
240        });
241
242        assert_eq!(history.any_pending_changes(), 0);
243        assert!(history.can_undo());
244        assert!(!history.can_redo());
245        assert_eq!(rope.to_string(), "Hello World\n!!!!\n!!!!\n!!!!");
246
247        // Undo last changes
248        history.undo(&mut rope);
249        assert_eq!(history.any_pending_changes(), 1);
250        assert_eq!(rope.to_string(), "Hello World\n!!!!\n!!!!");
251        history.undo(&mut rope);
252        assert_eq!(history.any_pending_changes(), 2);
253        assert_eq!(rope.to_string(), "Hello World\n!!!!");
254        history.undo(&mut rope);
255        assert_eq!(history.any_pending_changes(), 3);
256        assert_eq!(rope.to_string(), "Hello World");
257
258        assert!(!history.can_undo());
259        assert!(history.can_redo());
260
261        // Redo last changes
262        history.redo(&mut rope);
263        assert_eq!(rope.to_string(), "Hello World\n!!!!");
264        history.redo(&mut rope);
265        assert_eq!(rope.to_string(), "Hello World\n!!!!\n!!!!");
266        history.redo(&mut rope);
267        assert_eq!(rope.to_string(), "Hello World\n!!!!\n!!!!\n!!!!");
268
269        assert_eq!(history.any_pending_changes(), 0);
270        assert!(history.can_undo());
271        assert!(!history.can_redo());
272
273        // Undo last change
274        history.undo(&mut rope);
275        assert_eq!(rope.to_string(), "Hello World\n!!!!\n!!!!");
276        assert_eq!(history.any_pending_changes(), 1);
277        history.undo(&mut rope);
278        assert_eq!(rope.to_string(), "Hello World\n!!!!");
279        assert_eq!(history.any_pending_changes(), 2);
280
281        // Dischard any changes that could have been redone
282        rope.insert_char(0, '.');
283        history.push_change(HistoryChange::InsertChar {
284            idx: 0,
285            ch: '.',
286            len: 1,
287        });
288        assert_eq!(history.any_pending_changes(), 0);
289    }
290}