freya_hooks/
editor_history.rs

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