1use std::ops::Range;
2use std::time::{Duration, Instant};
3
4use crate::editor::buffer::Version;
5use crate::editor::position::Position;
6use crate::editor::selection::Selection;
7
8const DEFAULT_MERGE_THRESHOLD_MS: u64 = 500;
9const MAX_HISTORY_SIZE: usize = 1000;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ChangeKind {
13 InsertText,
14 DeleteText,
15 Paste,
16 Replace,
17 CursorMove,
18 Structural,
19}
20
21#[derive(Debug, Clone)]
22pub enum BufferOp {
23 Replace {
24 range: Range<Position>,
25 text: String,
26 old_text: String,
27 end_position: Position,
28 },
29 MoveCursor {
30 position: Position,
31 },
32 SetSelection {
33 selection: Option<Selection>,
34 },
35}
36
37impl BufferOp {
38 pub fn invert(&self) -> BufferOp {
39 match self {
40 BufferOp::Replace {
41 range,
42 text,
43 old_text,
44 end_position,
45 } => {
46 BufferOp::Replace {
48 range: range.start..*end_position,
49 text: old_text.clone(),
50 old_text: text.clone(),
51 end_position: range.start,
52 }
53 }
54 BufferOp::MoveCursor { position } => BufferOp::MoveCursor {
55 position: *position,
56 },
57 BufferOp::SetSelection { selection } => BufferOp::SetSelection {
58 selection: *selection,
59 },
60 }
61 }
62}
63
64#[derive(Debug, Clone)]
65pub struct ChangeSet {
66 pub ops: Vec<BufferOp>,
67 pub before_cursor: Position,
68 pub after_cursor: Position,
69 pub before_selection: Option<Selection>,
70 pub after_selection: Option<Selection>,
71}
72
73impl ChangeSet {
74 pub fn new(
75 ops: Vec<BufferOp>,
76 before_cursor: Position,
77 after_cursor: Position,
78 before_selection: Option<Selection>,
79 after_selection: Option<Selection>,
80 ) -> Self {
81 Self {
82 ops,
83 before_cursor,
84 after_cursor,
85 before_selection,
86 after_selection,
87 }
88 }
89
90 pub fn invert(&self) -> ChangeSet {
91 let inverse_ops: Vec<BufferOp> = self.ops.iter().rev().map(|op| op.invert()).collect();
92
93 ChangeSet {
94 ops: inverse_ops,
95 before_cursor: self.after_cursor,
96 after_cursor: self.before_cursor,
97 before_selection: self.after_selection,
98 after_selection: self.before_selection,
99 }
100 }
101
102 pub fn is_empty(&self) -> bool {
103 self.ops.is_empty()
104 }
105
106 pub fn extend(&mut self, other: &mut ChangeSet) {
107 self.ops.append(&mut other.ops);
108 self.after_cursor = other.after_cursor;
109 self.after_selection = other.after_selection;
110 }
111}
112
113#[derive(Debug, Clone)]
114pub struct HistoryNode {
115 pub id: u64,
116 pub change: ChangeSet,
117 pub inverse: ChangeSet,
118 pub kind: ChangeKind,
119 pub timestamp: Instant,
120 pub version: Version,
121}
122
123#[derive(Debug)]
124pub struct History {
125 timeline: Vec<HistoryNode>,
126 cursor: usize,
127 merge_threshold: Duration,
128 next_id: u64,
129}
130
131impl History {
132 pub fn new() -> Self {
133 Self {
134 timeline: Vec::new(),
135 cursor: 0,
136 merge_threshold: Duration::from_millis(DEFAULT_MERGE_THRESHOLD_MS),
137 next_id: 1,
138 }
139 }
140
141 pub fn with_threshold(merge_threshold_ms: u64) -> Self {
142 Self {
143 timeline: Vec::new(),
144 cursor: 0,
145 merge_threshold: Duration::from_millis(merge_threshold_ms),
146 next_id: 1,
147 }
148 }
149
150 pub fn can_undo(&self) -> bool {
151 self.cursor > 0
152 }
153
154 pub fn can_redo(&self) -> bool {
155 self.cursor < self.timeline.len()
156 }
157
158 pub fn push(&mut self, change: ChangeSet, kind: ChangeKind, version: Version) {
159 let id = self.next_id;
160 self.next_id += 1;
161
162 let inverse = change.invert();
163 let timestamp = Instant::now();
164
165 let node = HistoryNode {
166 id,
167 change,
168 inverse,
169 kind,
170 timestamp,
171 version,
172 };
173
174 if self.cursor < self.timeline.len() {
175 self.timeline.truncate(self.cursor);
176 }
177
178 self.timeline.push(node);
179 self.cursor += 1;
180
181 if self.timeline.len() > MAX_HISTORY_SIZE {
182 self.timeline.remove(0);
183 self.cursor = self.cursor.saturating_sub(1);
184 }
185 }
186
187 pub fn try_merge(&mut self, new_change: ChangeSet, kind: ChangeKind, version: Version) -> bool {
188 if self.timeline.is_empty() || self.cursor == 0 {
189 return false;
190 }
191
192 let last_idx = self.cursor - 1;
193 let last_node = &self.timeline[last_idx];
194
195 if last_node.kind != kind {
196 return false;
197 }
198
199 if last_node.timestamp.elapsed() > self.merge_threshold {
200 return false;
201 }
202
203 if !can_merge_changes(&last_node.change, &new_change, kind) {
204 return false;
205 }
206
207 let last_node = &mut self.timeline[last_idx];
208 last_node.change.extend(&mut new_change.clone());
209 last_node.inverse = last_node.change.invert();
210 last_node.version = version;
211
212 true
213 }
214
215 pub fn undo(&mut self) -> Option<&HistoryNode> {
216 if !self.can_undo() {
217 return None;
218 }
219
220 self.cursor -= 1;
221 Some(&self.timeline[self.cursor])
222 }
223
224 pub fn redo(&mut self) -> Option<&HistoryNode> {
225 if !self.can_redo() {
226 return None;
227 }
228
229 let node = &self.timeline[self.cursor];
230 self.cursor += 1;
231 Some(node)
232 }
233
234 pub fn clear(&mut self) {
235 self.timeline.clear();
236 self.cursor = 0;
237 }
238
239 pub fn len(&self) -> usize {
240 self.timeline.len()
241 }
242
243 pub fn is_empty(&self) -> bool {
244 self.timeline.is_empty()
245 }
246
247 pub fn current_position(&self) -> usize {
248 self.cursor
249 }
250
251 pub fn restore_from_snapshots(
252 &mut self,
253 undo_stack: Vec<super::buffer::SerializableSnapshot>,
254 redo_stack: Vec<super::buffer::SerializableSnapshot>,
255 _current_version: super::buffer::Version,
256 ) {
257 self.timeline.clear();
258 let undo_count = undo_stack.len();
259 self.cursor = undo_count;
260 self.next_id = 1;
261
262 for (i, snapshot) in undo_stack.into_iter().enumerate() {
263 let change = ChangeSet::new(
264 vec![],
265 Position::new(0, 0),
266 snapshot.cursor(),
267 snapshot.selection(),
268 None,
269 );
270 let inverse = ChangeSet::new(
271 vec![],
272 snapshot.cursor(),
273 Position::new(0, 0),
274 None,
275 snapshot.selection(),
276 );
277 self.timeline.push(HistoryNode {
278 id: i as u64 + 1,
279 change,
280 inverse,
281 kind: ChangeKind::Structural,
282 timestamp: Instant::now(),
283 version: snapshot.version(),
284 });
285 self.next_id = (i as u64 + 2).max(self.next_id);
286 }
287
288 for snapshot in redo_stack {
289 let change = ChangeSet::new(
290 vec![],
291 Position::new(0, 0),
292 snapshot.cursor(),
293 snapshot.selection(),
294 None,
295 );
296 let inverse = ChangeSet::new(
297 vec![],
298 snapshot.cursor(),
299 Position::new(0, 0),
300 None,
301 snapshot.selection(),
302 );
303 self.timeline.push(HistoryNode {
304 id: self.next_id,
305 change,
306 inverse,
307 kind: ChangeKind::Structural,
308 timestamp: Instant::now(),
309 version: snapshot.version(),
310 });
311 self.next_id += 1;
312 }
313 }
314}
315
316impl Default for History {
317 fn default() -> Self {
318 Self::new()
319 }
320}
321
322fn can_merge_changes(a: &ChangeSet, b: &ChangeSet, kind: ChangeKind) -> bool {
323 if a.after_cursor != b.before_cursor || a.after_selection != b.before_selection {
324 return false;
325 }
326
327 match kind {
328 ChangeKind::InsertText => can_merge_op(a, b, true),
329 ChangeKind::DeleteText => can_merge_op(a, b, false),
330 _ => false,
331 }
332}
333
334fn can_merge_op(a: &ChangeSet, b: &ChangeSet, is_insert: bool) -> bool {
335 if a.ops.is_empty() || b.ops.len() != 1 {
339 return false;
340 }
341
342 let last_op_a = &a.ops[a.ops.len() - 1];
343 let op_b = &b.ops[0];
344
345 match (last_op_a, op_b) {
346 (
347 BufferOp::Replace {
348 range: ra,
349 text: ta,
350 old_text: oa,
351 end_position: ea,
352 },
353 BufferOp::Replace {
354 range: rb,
355 text: tb,
356 old_text: ob,
357 ..
358 },
359 ) => {
360 if is_insert {
361 if !oa.is_empty() || !ob.is_empty() {
363 return false;
364 }
365 if ta.contains('\n') || tb.contains('\n') {
366 return false;
367 }
368 *ea == rb.start
369 } else {
370 if !ta.is_empty() || !tb.is_empty() {
372 return false;
373 }
374 if oa.contains('\n') || ob.contains('\n') {
375 return false;
376 }
377 rb.end == ra.start
378 }
379 }
380 _ => false,
381 }
382}
383
384pub fn apply_changeset(buffer: &mut crate::editor::buffer::Buffer, cs: &ChangeSet) {
385 for op in &cs.ops {
386 apply_op(buffer, op);
387 }
388
389 buffer.set_cursor(cs.after_cursor);
390 buffer.normalize_cursor();
391 buffer.set_selection(cs.after_selection);
392}
393
394fn apply_op(buffer: &mut crate::editor::buffer::Buffer, op: &BufferOp) {
395 buffer.apply_op_without_history(op);
396}
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401
402 #[test]
403 fn test_changeset_invert_swaps_cursor() {
404 let cs = ChangeSet::new(
405 vec![],
406 Position::new(1, 2),
407 Position::new(3, 4),
408 Some(Selection::new(Position::new(1, 2), Position::new(1, 5))),
409 Some(Selection::new(Position::new(3, 4), Position::new(3, 7))),
410 );
411
412 let inv = cs.invert();
413
414 assert_eq!(inv.before_cursor, Position::new(3, 4));
415 assert_eq!(inv.after_cursor, Position::new(1, 2));
416 assert_eq!(inv.before_selection.unwrap().anchor, Position::new(3, 4));
417 assert_eq!(inv.after_selection.unwrap().anchor, Position::new(1, 2));
418 }
419
420 #[test]
421 fn test_history_push_truncates_future() {
422 let mut history = History::new();
423 let cs = create_empty_changeset(Position::new(0, 0));
424
425 history.push(cs.clone(), ChangeKind::InsertText, Version::new());
426 history.push(cs.clone(), ChangeKind::InsertText, Version::new());
427 assert_eq!(history.len(), 2);
428 assert!(history.can_undo());
429 assert!(!history.can_redo());
430
431 history.undo();
432 history.push(cs, ChangeKind::DeleteText, Version::new());
433 assert_eq!(history.len(), 2);
434 assert!(!history.can_redo());
435 }
436
437 #[test]
438 fn test_history_undo_redo() {
439 let mut history = History::new();
440 let _buffer = create_test_buffer();
441
442 let cs = create_changeset(
443 Position::new(0, 0),
444 Position::new(0, 5),
445 vec![BufferOp::Replace {
446 range: Position::new(0, 0)..Position::new(0, 0),
447 text: "hello".to_string(),
448 old_text: String::new(),
449 end_position: Position::new(0, 5),
450 }],
451 );
452
453 history.push(cs, ChangeKind::InsertText, Version::new());
454 assert!(history.undo().is_some());
455 assert!(history.redo().is_some());
456 }
457
458 #[test]
459 fn test_try_merge_same_kind_within_threshold() {
460 let mut history = History::new();
461
462 let cs1 = create_changeset(
463 Position::new(0, 0),
464 Position::new(0, 1),
465 vec![BufferOp::Replace {
466 range: Position::new(0, 0)..Position::new(0, 1),
467 text: "h".to_string(),
468 old_text: String::new(),
469 end_position: Position::new(0, 1),
470 }],
471 );
472
473 let cs2 = create_changeset(
474 Position::new(0, 1),
475 Position::new(0, 2),
476 vec![BufferOp::Replace {
477 range: Position::new(0, 1)..Position::new(0, 2),
478 text: "e".to_string(),
479 old_text: String::new(),
480 end_position: Position::new(0, 2),
481 }],
482 );
483
484 history.push(cs1, ChangeKind::InsertText, Version::new());
485 let merged = history.try_merge(cs2, ChangeKind::InsertText, Version::new());
486
487 assert!(merged);
488 assert_eq!(history.len(), 1);
489 }
490
491 #[test]
492 fn test_try_merge_different_kind_fails() {
493 let mut history = History::new();
494
495 let cs1 = create_empty_changeset(Position::new(0, 0));
496 let cs2 = create_empty_changeset(Position::new(0, 1));
497
498 history.push(cs1, ChangeKind::InsertText, Version::new());
499 let merged = history.try_merge(cs2, ChangeKind::DeleteText, Version::new());
500
501 assert!(!merged);
502 assert_eq!(history.len(), 1);
503 }
504
505 #[test]
506 fn test_try_merge_different_cursor_fails() {
507 let mut history = History::new();
508
509 let cs1 = create_changeset(Position::new(0, 0), Position::new(0, 1), vec![]);
510 let cs2 = create_changeset(Position::new(1, 0), Position::new(1, 1), vec![]);
511
512 history.push(cs1, ChangeKind::InsertText, Version::new());
513 let merged = history.try_merge(cs2, ChangeKind::InsertText, Version::new());
514
515 assert!(!merged);
516 }
517
518 fn create_test_buffer() -> crate::editor::buffer::Buffer {
519 crate::editor::buffer::Buffer::new()
520 }
521
522 fn create_empty_changeset(cursor: Position) -> ChangeSet {
523 ChangeSet::new(vec![], cursor, cursor, None, None)
524 }
525
526 fn create_changeset(
527 before_cursor: Position,
528 after_cursor: Position,
529 ops: Vec<BufferOp>,
530 ) -> ChangeSet {
531 ChangeSet::new(ops, before_cursor, after_cursor, None, None)
532 }
533}