Skip to main content

oo_ide/editor/
history.rs

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                // Inverse: replace range.start..*end_position with old_text.
47                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    // The new change must be a single-op edit.  The existing entry may already hold
336    // multiple ops from prior merges, so we only require it to be non-empty and
337    // check its *last* op for adjacency.
338    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                // For insert: old_text must be empty, and new text can't span lines.
362                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                // For delete: new text must be empty, and old text can't span lines.
371                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}