freya_hooks/
editor_history.rs1use 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 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 rope.insert(0, "Hello World");
158
159 assert!(!history.can_undo());
160 assert!(!history.can_redo());
161
162 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 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 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 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 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 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 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}