1use std::time::{
2 Duration,
3 Instant,
4};
5
6use ropey::Rope;
7
8use crate::text_editor::TextSelection;
9
10#[derive(Clone, Debug, PartialEq)]
11pub enum HistoryChange {
12 InsertChar {
13 idx: usize,
14 len: usize,
15 ch: char,
16 selection: TextSelection,
17 },
18 InsertText {
19 idx: usize,
20 len: usize,
21 text: String,
22 selection: TextSelection,
23 },
24 Remove {
25 idx: usize,
26 len: usize,
27 text: String,
28 selection: TextSelection,
29 },
30}
31
32#[derive(Clone, Debug, PartialEq)]
33pub struct HistoryTransaction {
34 pub timestamp: Instant,
35 pub changes: Vec<HistoryChange>,
36}
37
38#[derive(Clone, Debug)]
39pub struct EditorHistory {
40 pub transactions: Vec<HistoryTransaction>,
41 pub current_transaction: usize,
42 pub version: usize,
44 transaction_threshold_groping: Duration,
46}
47
48impl EditorHistory {
49 pub fn new(transaction_threshold_groping: Duration) -> Self {
50 Self {
51 transactions: Vec::default(),
52 current_transaction: 0,
53 version: 0,
54 transaction_threshold_groping,
55 }
56 }
57
58 pub fn push_change(&mut self, change: HistoryChange) {
59 if self.can_redo() {
60 self.transactions.drain(self.current_transaction..);
61 }
62
63 let last_transaction = self
64 .transactions
65 .get_mut(self.current_transaction.saturating_sub(1));
66 if let Some(last_transaction) = last_transaction
67 && last_transaction.timestamp.elapsed() <= self.transaction_threshold_groping
68 {
69 last_transaction.changes.push(change);
70 last_transaction.timestamp = Instant::now();
71 return;
72 }
73
74 self.transactions.push(HistoryTransaction {
75 timestamp: Instant::now(),
76 changes: vec![change],
77 });
78
79 self.current_transaction = self.transactions.len();
80 self.version += 1;
81 }
82
83 pub fn current_change(&self) -> usize {
84 self.current_transaction
85 }
86
87 pub fn any_pending_changes(&self) -> usize {
88 self.transactions.len() - self.current_transaction
89 }
90
91 pub fn can_undo(&self) -> bool {
92 self.current_transaction > 0
93 }
94
95 pub fn can_redo(&self) -> bool {
96 self.current_transaction < self.transactions.len()
97 }
98
99 pub fn undo(&mut self, rope: &mut Rope) -> Option<TextSelection> {
100 if !self.can_undo() {
101 return None;
102 }
103
104 let last_transaction = self.transactions.get(self.current_transaction - 1);
105 if let Some(last_transaction) = last_transaction {
106 let mut selection = None;
107 for change in last_transaction.changes.iter().rev() {
108 match change {
109 HistoryChange::Remove {
110 idx,
111 text,
112 selection: previous_selection,
113 ..
114 } => {
115 let start = rope.utf16_cu_to_char(*idx);
116 rope.insert(start, text);
117 selection = Some(previous_selection.clone());
118 }
119 HistoryChange::InsertChar {
120 idx,
121 len,
122 selection: previous_selection,
123 ..
124 } => {
125 let start = rope.utf16_cu_to_char(*idx);
126 let end = rope.utf16_cu_to_char(*idx + len);
127 rope.remove(start..end);
128 selection = Some(previous_selection.clone());
129 }
130 HistoryChange::InsertText {
131 idx,
132 len,
133 selection: previous_selection,
134 ..
135 } => {
136 let start = rope.utf16_cu_to_char(*idx);
137 let end = rope.utf16_cu_to_char(*idx + len);
138 rope.remove(start..end);
139 selection = Some(previous_selection.clone());
140 }
141 }
142 }
143
144 self.current_transaction -= 1;
145 self.version += 1;
146 selection
147 } else {
148 None
149 }
150 }
151
152 pub fn redo(&mut self, rope: &mut Rope) -> Option<TextSelection> {
153 if !self.can_redo() {
154 return None;
155 }
156
157 let last_transaction = self.transactions.get(self.current_transaction);
158 if let Some(last_transaction) = last_transaction {
159 let mut cursor_pos = None;
160 for change in &last_transaction.changes {
161 cursor_pos = Some(match change {
162 HistoryChange::Remove { idx, len, .. } => {
163 let start = rope.utf16_cu_to_char(*idx);
164 let end = rope.utf16_cu_to_char(*idx + len);
165 rope.remove(start..end);
166 *idx
167 }
168 HistoryChange::InsertChar { idx, len, ch, .. } => {
169 let start = rope.utf16_cu_to_char(*idx);
170 rope.insert_char(start, *ch);
171 *idx + len
172 }
173 HistoryChange::InsertText { idx, text, len, .. } => {
174 let start = rope.utf16_cu_to_char(*idx);
175 rope.insert(start, text);
176 *idx + len
177 }
178 });
179 }
180 self.current_transaction += 1;
181 self.version += 1;
182 cursor_pos.map(TextSelection::new_cursor)
183 } else {
184 None
185 }
186 }
187
188 pub fn clear_redos(&mut self) {
189 if self.can_redo() {
190 self.transactions.drain(self.current_transaction..);
191 }
192 }
193
194 pub fn clear(&mut self) {
195 self.transactions.clear();
196 self.current_transaction = 0;
197 self.version = 0;
198 }
199}
200
201#[cfg(test)]
202mod test {
203 use std::time::Duration;
204
205 use ropey::Rope;
206
207 use super::{
208 EditorHistory,
209 HistoryChange,
210 };
211 use crate::text_editor::TextSelection;
212
213 #[test]
214 fn test_undo_redo() {
215 let mut rope = Rope::new();
216 let mut history = EditorHistory::new(Duration::ZERO);
217
218 rope.insert(0, "Hello World");
219
220 assert!(!history.can_undo());
221 assert!(!history.can_redo());
222
223 rope.insert(11, "\n!!!!");
224 history.push_change(HistoryChange::InsertText {
225 idx: 11,
226 text: "\n!!!!".to_owned(),
227 len: "\n!!!!".len(),
228 selection: TextSelection::new_cursor(11),
229 });
230
231 assert!(history.can_undo());
232 assert!(!history.can_redo());
233 assert_eq!(rope.to_string(), "Hello World\n!!!!");
234
235 history.undo(&mut rope);
236
237 assert!(!history.can_undo());
238 assert!(history.can_redo());
239 assert_eq!(rope.to_string(), "Hello World");
240
241 rope.insert(11, "\n!!!!");
242 history.push_change(HistoryChange::InsertText {
243 idx: 11,
244 text: "\n!!!!".to_owned(),
245 len: "\n!!!!".len(),
246 selection: TextSelection::new_cursor(11),
247 });
248 rope.insert(16, "\n!!!!");
249 history.push_change(HistoryChange::InsertText {
250 idx: 16,
251 text: "\n!!!!".to_owned(),
252 len: "\n!!!!".len(),
253 selection: TextSelection::new_cursor(16),
254 });
255 rope.insert(21, "\n!!!!");
256 history.push_change(HistoryChange::InsertText {
257 idx: 21,
258 text: "\n!!!!".to_owned(),
259 len: "\n!!!!".len(),
260 selection: TextSelection::new_cursor(21),
261 });
262
263 assert_eq!(history.any_pending_changes(), 0);
264 assert!(history.can_undo());
265 assert!(!history.can_redo());
266 assert_eq!(rope.to_string(), "Hello World\n!!!!\n!!!!\n!!!!");
267
268 history.undo(&mut rope);
269 assert_eq!(history.any_pending_changes(), 1);
270 assert_eq!(rope.to_string(), "Hello World\n!!!!\n!!!!");
271 history.undo(&mut rope);
272 assert_eq!(history.any_pending_changes(), 2);
273 assert_eq!(rope.to_string(), "Hello World\n!!!!");
274 history.undo(&mut rope);
275 assert_eq!(history.any_pending_changes(), 3);
276 assert_eq!(rope.to_string(), "Hello World");
277
278 assert!(!history.can_undo());
279 assert!(history.can_redo());
280
281 history.redo(&mut rope);
282 assert_eq!(rope.to_string(), "Hello World\n!!!!");
283 history.redo(&mut rope);
284 assert_eq!(rope.to_string(), "Hello World\n!!!!\n!!!!");
285 history.redo(&mut rope);
286 assert_eq!(rope.to_string(), "Hello World\n!!!!\n!!!!\n!!!!");
287
288 assert_eq!(history.any_pending_changes(), 0);
289 assert!(history.can_undo());
290 assert!(!history.can_redo());
291
292 history.undo(&mut rope);
293 assert_eq!(rope.to_string(), "Hello World\n!!!!\n!!!!");
294 assert_eq!(history.any_pending_changes(), 1);
295 history.undo(&mut rope);
296 assert_eq!(rope.to_string(), "Hello World\n!!!!");
297 assert_eq!(history.any_pending_changes(), 2);
298
299 rope.insert_char(0, '.');
300 history.push_change(HistoryChange::InsertChar {
301 idx: 0,
302 ch: '.',
303 len: 1,
304 selection: TextSelection::new_cursor(0),
305 });
306 assert_eq!(history.any_pending_changes(), 0);
307 }
308
309 #[test]
310 fn test_undo_restores_cursor_selection() {
311 let mut rope = Rope::new();
312 let mut history = EditorHistory::new(Duration::ZERO);
313
314 rope.insert(0, "Hello World");
315
316 rope.insert(11, "!");
317 history.push_change(HistoryChange::InsertChar {
318 idx: 11,
319 ch: '!',
320 len: 1,
321 selection: TextSelection::new_cursor(11),
322 });
323
324 let selection = history.undo(&mut rope).unwrap();
325 assert_eq!(selection, TextSelection::new_cursor(11));
326 assert_eq!(rope.to_string(), "Hello World");
327 }
328
329 #[test]
330 fn test_undo_restores_range_selection() {
331 let mut rope = Rope::new();
332 let mut history = EditorHistory::new(Duration::ZERO);
333
334 rope.insert(0, "Hello World");
335
336 let start = rope.utf16_cu_to_char(0);
337 let end = rope.utf16_cu_to_char(5);
338 rope.remove(start..end);
339 history.push_change(HistoryChange::Remove {
340 idx: 0,
341 text: "Hello".to_owned(),
342 len: 5,
343 selection: TextSelection::new_range((0, 5)),
344 });
345 assert_eq!(rope.to_string(), " World");
346
347 let selection = history.undo(&mut rope).unwrap();
348 assert_eq!(selection, TextSelection::new_range((0, 5)));
349 assert_eq!(rope.to_string(), "Hello World");
350 }
351
352 #[test]
353 fn test_redo_returns_cursor_at_end() {
354 let mut rope = Rope::new();
355 let mut history = EditorHistory::new(Duration::ZERO);
356
357 rope.insert(0, "Hello");
358
359 rope.insert(5, " World");
360 history.push_change(HistoryChange::InsertText {
361 idx: 5,
362 text: " World".to_owned(),
363 len: 6,
364 selection: TextSelection::new_cursor(5),
365 });
366
367 history.undo(&mut rope);
368 assert_eq!(rope.to_string(), "Hello");
369
370 let selection = history.redo(&mut rope).unwrap();
371 assert_eq!(selection, TextSelection::new_cursor(11));
372 assert_eq!(rope.to_string(), "Hello World");
373 }
374
375 #[test]
376 fn test_undo_redo_with_remove() {
377 let mut rope = Rope::new();
378 let mut history = EditorHistory::new(Duration::ZERO);
379
380 rope.insert(0, "Hello World");
381
382 let start = rope.utf16_cu_to_char(5);
383 let end = rope.utf16_cu_to_char(11);
384 rope.remove(start..end);
385 history.push_change(HistoryChange::Remove {
386 idx: 5,
387 text: " World".to_owned(),
388 len: 6,
389 selection: TextSelection::new_cursor(11),
390 });
391 assert_eq!(rope.to_string(), "Hello");
392
393 let selection = history.undo(&mut rope).unwrap();
394 assert_eq!(selection, TextSelection::new_cursor(11));
395 assert_eq!(rope.to_string(), "Hello World");
396
397 let selection = history.redo(&mut rope).unwrap();
398 assert_eq!(selection, TextSelection::new_cursor(5));
399 assert_eq!(rope.to_string(), "Hello");
400 }
401}