Skip to main content

reovim_kernel/block/
undo.rs

1//! Undo tree with branching support.
2//!
3//! Provides vim-style branching undo history where undoing and then
4//! making new changes creates a new branch rather than losing history.
5
6use {
7    crate::mm::{Edit, Position},
8    std::time::{Duration, Instant},
9};
10
11/// Origin of an edit for multi-client tracking (#471).
12///
13/// Tracks which client made an edit, enabling per-client undo where
14/// each client only undoes their own changes.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
16pub enum EditOrigin {
17    /// Edit made by a specific client.
18    ///
19    /// The `usize` value corresponds to `ClientId::as_usize()`.
20    Client(usize),
21    /// System-generated edit (auto-format, LSP, macro playback, etc.).
22    ///
23    /// This is the default origin for backward compatibility.
24    #[default]
25    System,
26}
27
28/// A node in the undo tree.
29///
30/// Each node represents a set of edits made at a point in time,
31/// along with cursor positions for proper restoration.
32#[derive(Debug, Clone)]
33pub struct UndoNode {
34    /// Edits that were made (in order applied).
35    edits: Vec<Edit>,
36    /// Cursor position before these edits were applied.
37    cursor_before: Position,
38    /// Cursor position after these edits were applied.
39    cursor_after: Position,
40    /// When this node was created.
41    timestamp: Instant,
42    /// Parent node index (None for root).
43    parent: Option<usize>,
44    /// Child node indices (branches).
45    children: Vec<usize>,
46    /// Sequential change number.
47    seq_num: u64,
48    /// Origin of this edit (which client made it).
49    origin: EditOrigin,
50}
51
52impl UndoNode {
53    /// Get the edits in this node.
54    #[must_use]
55    pub fn edits(&self) -> &[Edit] {
56        &self.edits
57    }
58
59    /// Get cursor position before edits.
60    #[must_use]
61    pub const fn cursor_before(&self) -> Position {
62        self.cursor_before
63    }
64
65    /// Get cursor position after edits.
66    #[must_use]
67    pub const fn cursor_after(&self) -> Position {
68        self.cursor_after
69    }
70
71    /// Get timestamp when this node was created.
72    #[must_use]
73    pub const fn timestamp(&self) -> Instant {
74        self.timestamp
75    }
76
77    /// Get sequential change number.
78    #[must_use]
79    pub const fn seq_num(&self) -> u64 {
80        self.seq_num
81    }
82
83    /// Check if this is the root node.
84    #[must_use]
85    pub const fn is_root(&self) -> bool {
86        self.parent.is_none()
87    }
88
89    /// Get number of branches (children) from this node.
90    #[must_use]
91    pub const fn branch_count(&self) -> usize {
92        self.children.len()
93    }
94
95    /// Get parent node index.
96    #[must_use]
97    pub const fn parent(&self) -> Option<usize> {
98        self.parent
99    }
100
101    /// Get children node indices.
102    #[must_use]
103    pub fn children(&self) -> &[usize] {
104        &self.children
105    }
106
107    /// Get the origin of this edit.
108    ///
109    /// Returns which client made this edit, or `EditOrigin::System` for
110    /// system-generated edits.
111    #[must_use]
112    pub const fn origin(&self) -> EditOrigin {
113        self.origin
114    }
115}
116
117/// Result from an undo or redo operation.
118///
119/// Contains the edits to apply and the cursor position to restore.
120#[derive(Debug, Clone)]
121pub struct UndoResult {
122    /// Edits to apply (already inverted for undo).
123    pub edits: Vec<Edit>,
124    /// Cursor position to restore.
125    pub cursor: Position,
126}
127
128/// Undo tree with branching support.
129///
130/// Unlike a simple undo stack, an undo tree preserves all history.
131/// When you undo and then make new changes, a new branch is created
132/// rather than discarding the undone changes.
133///
134/// # Vim Comparison
135///
136/// This is similar to vim's `:undotree` feature. The `g-` and `g+`
137/// commands traverse changes in time order (`seq_num`), while `u` and
138/// `Ctrl-R` traverse the tree structure.
139///
140/// # Memory Management
141///
142/// The tree has a configurable maximum number of nodes. When exceeded,
143/// the oldest nodes are pruned (but the path from root to current is
144/// always preserved).
145#[derive(Debug, Clone)]
146pub struct UndoTree {
147    /// All nodes in the tree.
148    nodes: Vec<UndoNode>,
149    /// Index of current position in the tree.
150    current: usize,
151    /// Sequential change counter.
152    seq_counter: u64,
153    /// Maximum number of nodes to retain.
154    max_nodes: usize,
155    /// Index of the preferred/active branch at each node.
156    /// Used to remember which branch to follow on redo.
157    active_branches: Vec<usize>,
158}
159
160impl Default for UndoTree {
161    fn default() -> Self {
162        Self::new()
163    }
164}
165
166impl UndoTree {
167    /// Default maximum number of nodes.
168    pub const DEFAULT_MAX_NODES: usize = 10000;
169
170    /// Create a new undo tree.
171    ///
172    /// The tree starts with a root node representing the initial state.
173    #[must_use]
174    pub fn new() -> Self {
175        Self::with_max_nodes(Self::DEFAULT_MAX_NODES)
176    }
177
178    /// Create a new undo tree with custom maximum nodes.
179    #[must_use]
180    pub fn with_max_nodes(max_nodes: usize) -> Self {
181        let root = UndoNode {
182            edits: Vec::new(),
183            cursor_before: Position::default(),
184            cursor_after: Position::default(),
185            timestamp: Instant::now(),
186            parent: None,
187            children: Vec::new(),
188            seq_num: 0,
189            origin: EditOrigin::System,
190        };
191
192        Self {
193            nodes: vec![root],
194            current: 0,
195            seq_counter: 0,
196            max_nodes: max_nodes.max(1), // At least 1 node
197            active_branches: vec![0],
198        }
199    }
200
201    /// Push a new change onto the tree with system origin.
202    ///
203    /// Creates a new node as a child of the current node.
204    /// If the current node already has children (we're in the middle
205    /// of the tree after an undo), this creates a new branch.
206    ///
207    /// This is equivalent to `push_with_origin(edits, cursor_before, cursor_after, EditOrigin::System)`.
208    pub fn push(&mut self, edits: Vec<Edit>, cursor_before: Position, cursor_after: Position) {
209        self.push_with_origin(edits, cursor_before, cursor_after, EditOrigin::System);
210    }
211
212    /// Push a new change onto the tree with specified origin.
213    ///
214    /// Creates a new node as a child of the current node, tagged with the
215    /// specified origin. This enables per-client undo where each client
216    /// can undo only their own changes.
217    ///
218    /// If the current node already has children (we're in the middle
219    /// of the tree after an undo), this creates a new branch.
220    pub fn push_with_origin(
221        &mut self,
222        edits: Vec<Edit>,
223        cursor_before: Position,
224        cursor_after: Position,
225        origin: EditOrigin,
226    ) {
227        if edits.is_empty() {
228            return;
229        }
230
231        self.seq_counter += 1;
232
233        let new_node = UndoNode {
234            edits,
235            cursor_before,
236            cursor_after,
237            timestamp: Instant::now(),
238            parent: Some(self.current),
239            children: Vec::new(),
240            seq_num: self.seq_counter,
241            origin,
242        };
243
244        let new_idx = self.nodes.len();
245        self.nodes.push(new_node);
246
247        // Add as child of current node
248        self.nodes[self.current].children.push(new_idx);
249
250        // Update active branch for current node
251        let branch_idx = self.nodes[self.current].children.len() - 1;
252        // Extend active_branches if needed
253        while self.active_branches.len() <= self.current {
254            self.active_branches.push(0);
255        }
256        self.active_branches[self.current] = branch_idx;
257
258        // Ensure active_branches has entry for new node
259        while self.active_branches.len() <= new_idx {
260            self.active_branches.push(0);
261        }
262
263        // Move to new node
264        self.current = new_idx;
265
266        // Prune if over limit
267        self.prune_if_needed();
268    }
269
270    /// Undo the current change.
271    ///
272    /// Moves to the parent node and returns the inverse edits
273    /// along with the cursor position to restore.
274    ///
275    /// Returns `None` if at the root (nothing to undo).
276    pub fn undo(&mut self) -> Option<UndoResult> {
277        let current_node = &self.nodes[self.current];
278
279        // Can't undo past root
280        let parent_idx = current_node.parent?;
281
282        // Get inverse edits and cursor to restore
283        let result = UndoResult {
284            edits: current_node.edits.iter().rev().map(Edit::inverse).collect(),
285            cursor: current_node.cursor_before,
286        };
287
288        // Move to parent
289        self.current = parent_idx;
290
291        Some(result)
292    }
293
294    /// Redo the last undone change.
295    ///
296    /// Moves to the first child node (or the active branch) and
297    /// returns the edits along with cursor position to restore.
298    ///
299    /// Returns `None` if there's nothing to redo.
300    pub fn redo(&mut self) -> Option<UndoResult> {
301        let current_node = &self.nodes[self.current];
302
303        if current_node.children.is_empty() {
304            return None;
305        }
306
307        // Get active branch index
308        let branch_idx = self
309            .active_branches
310            .get(self.current)
311            .copied()
312            .unwrap_or(0)
313            .min(current_node.children.len() - 1);
314
315        let child_idx = current_node.children[branch_idx];
316        let child_node = &self.nodes[child_idx];
317
318        let result = UndoResult {
319            edits: child_node.edits.clone(),
320            cursor: child_node.cursor_after,
321        };
322
323        // Move to child
324        self.current = child_idx;
325
326        Some(result)
327    }
328
329    /// Redo a specific branch.
330    ///
331    /// Like `redo()` but follows a specific branch index.
332    #[cfg_attr(coverage_nightly, coverage(off))]
333    pub fn redo_branch(&mut self, branch_idx: usize) -> Option<UndoResult> {
334        let current_node = &self.nodes[self.current];
335
336        if branch_idx >= current_node.children.len() {
337            return None;
338        }
339
340        // Update active branch
341        if self.current < self.active_branches.len() {
342            self.active_branches[self.current] = branch_idx;
343        }
344
345        let child_idx = current_node.children[branch_idx];
346        let child_node = &self.nodes[child_idx];
347
348        let result = UndoResult {
349            edits: child_node.edits.clone(),
350            cursor: child_node.cursor_after,
351        };
352
353        self.current = child_idx;
354
355        Some(result)
356    }
357
358    /// Get the available branches (children) from current node.
359    ///
360    /// Returns indices that can be passed to `redo_branch()`.
361    #[must_use]
362    pub fn branches(&self) -> &[usize] {
363        &self.nodes[self.current].children
364    }
365
366    /// Switch the active branch at current node.
367    ///
368    /// This affects which branch `redo()` will follow.
369    /// Returns `false` if the branch index is invalid.
370    pub fn switch_branch(&mut self, branch_idx: usize) -> bool {
371        let current_node = &self.nodes[self.current];
372
373        if branch_idx >= current_node.children.len() {
374            return false;
375        }
376
377        while self.active_branches.len() <= self.current {
378            self.active_branches.push(0);
379        }
380        self.active_branches[self.current] = branch_idx;
381
382        true
383    }
384
385    /// Check if undo is possible.
386    #[must_use]
387    pub fn can_undo(&self) -> bool {
388        self.nodes[self.current].parent.is_some()
389    }
390
391    /// Check if redo is possible.
392    #[must_use]
393    pub fn can_redo(&self) -> bool {
394        !self.nodes[self.current].children.is_empty()
395    }
396
397    /// Get the current node.
398    #[must_use]
399    pub fn current_node(&self) -> &UndoNode {
400        &self.nodes[self.current]
401    }
402
403    /// Get the current node index.
404    #[must_use]
405    pub const fn current_index(&self) -> usize {
406        self.current
407    }
408
409    /// Get total number of nodes in the tree.
410    #[must_use]
411    pub const fn node_count(&self) -> usize {
412        self.nodes.len()
413    }
414
415    /// Get node by index.
416    ///
417    /// Returns `None` if index is out of bounds.
418    #[must_use]
419    pub fn node(&self, index: usize) -> Option<&UndoNode> {
420        self.nodes.get(index)
421    }
422
423    /// Get all node indices for iteration.
424    ///
425    /// Returns a range that can be used to iterate over all valid node indices.
426    #[must_use]
427    pub const fn node_indices(&self) -> std::ops::Range<usize> {
428        0..self.nodes.len()
429    }
430
431    /// Get active branch index at a node.
432    ///
433    /// Returns the preferred child index that `redo()` would follow
434    /// from the specified node. Returns `None` if the node doesn't exist
435    /// or has no active branch set.
436    #[must_use]
437    pub fn active_branch_at(&self, node_index: usize) -> Option<usize> {
438        self.active_branches.get(node_index).copied()
439    }
440
441    /// Get the maximum nodes limit.
442    #[must_use]
443    pub const fn max_nodes(&self) -> usize {
444        self.max_nodes
445    }
446
447    /// Set the maximum nodes limit.
448    pub fn set_max_nodes(&mut self, max: usize) {
449        self.max_nodes = max.max(1);
450        self.prune_if_needed();
451    }
452
453    /// Get the current sequential change number.
454    #[must_use]
455    pub const fn seq_counter(&self) -> u64 {
456        self.seq_counter
457    }
458
459    /// Collect all edits applied between a node and the current head.
460    ///
461    /// Walks from the current position back to `from_idx` via parent links,
462    /// collecting all edits from intermediate nodes in forward (application)
463    /// order. The edits from the `from_idx` node itself are NOT included.
464    ///
465    /// Returns `Some(empty_vec)` if `from_idx` is the current position.
466    /// Returns `None` if `from_idx` is not an ancestor of the current position
467    /// or is out of bounds.
468    #[must_use]
469    pub fn edits_since(&self, from_idx: usize) -> Option<Vec<&Edit>> {
470        if from_idx >= self.nodes.len() {
471            return None;
472        }
473
474        if from_idx == self.current {
475            return Some(Vec::new());
476        }
477
478        // Walk from current back to from_idx via parent links.
479        let mut path_indices = Vec::new();
480        let mut idx = self.current;
481
482        loop {
483            if idx == from_idx {
484                break;
485            }
486            path_indices.push(idx);
487            match self.nodes[idx].parent {
488                Some(parent) => idx = parent,
489                None => return None, // Reached root without finding from_idx
490            }
491        }
492
493        // path_indices is in reverse order (current -> ... -> from_idx+1).
494        // Reverse to get forward order (from_idx+1 -> ... -> current).
495        path_indices.reverse();
496
497        // Collect non-empty edits from each node in forward order.
498        let mut edits = Vec::new();
499        for node_idx in path_indices {
500            for edit in &self.nodes[node_idx].edits {
501                if !edit.is_empty() {
502                    edits.push(edit);
503                }
504            }
505        }
506
507        Some(edits)
508    }
509
510    /// Prune old nodes if over the limit.
511    ///
512    /// This is a simple pruning strategy that removes the oldest
513    /// nodes that are not on the path from root to current.
514    #[cfg_attr(coverage_nightly, coverage(off))]
515    fn prune_if_needed(&mut self) {
516        if self.nodes.len() <= self.max_nodes {
517            return;
518        }
519
520        // Build set of nodes on path from root to current
521        let mut protected = vec![false; self.nodes.len()];
522        let mut idx = self.current;
523        loop {
524            protected[idx] = true;
525            match self.nodes[idx].parent {
526                Some(parent) => idx = parent,
527                None => break,
528            }
529        }
530
531        // Find nodes to remove (oldest first, not protected)
532        let to_remove = self.nodes.len() - self.max_nodes;
533        let mut removed = 0;
534        let mut remove_indices = Vec::new();
535
536        for (i, node) in self.nodes.iter().enumerate() {
537            if removed >= to_remove {
538                break;
539            }
540            if !protected[i] && node.children.is_empty() {
541                remove_indices.push(i);
542                removed += 1;
543            }
544        }
545
546        // Actually remove nodes (in reverse order to maintain indices)
547        for &idx in remove_indices.iter().rev() {
548            self.remove_node(idx);
549        }
550    }
551
552    /// Remove a node from the tree.
553    ///
554    /// Updates parent's children list and adjusts indices.
555    #[cfg_attr(coverage_nightly, coverage(off))]
556    fn remove_node(&mut self, idx: usize) {
557        if idx == 0 || idx == self.current {
558            return; // Never remove root or current
559        }
560
561        // Remove from parent's children
562        if let Some(parent_idx) = self.nodes[idx].parent {
563            self.nodes[parent_idx].children.retain(|&c| c != idx);
564        }
565
566        // Update indices in all nodes
567        for node in &mut self.nodes {
568            if let Some(ref mut parent) = node.parent
569                && *parent > idx
570            {
571                *parent -= 1;
572            }
573            for child in &mut node.children {
574                if *child > idx {
575                    *child -= 1;
576                }
577            }
578        }
579
580        // Update current if needed
581        if self.current > idx {
582            self.current -= 1;
583        }
584
585        // Update active_branches
586        for branch in &mut self.active_branches {
587            if *branch > idx {
588                *branch -= 1;
589            }
590        }
591
592        // Remove the node
593        self.nodes.remove(idx);
594
595        // Trim active_branches
596        while self.active_branches.len() > self.nodes.len() {
597            self.active_branches.pop();
598        }
599    }
600
601    /// Clear all history except root.
602    pub fn clear(&mut self) {
603        let root_cursor = self.nodes[0].cursor_after;
604        *self = Self::with_max_nodes(self.max_nodes);
605        self.nodes[0].cursor_after = root_cursor;
606    }
607
608    /// Reconstruct an `UndoTree` from serialized data.
609    ///
610    /// This is used by the persistence layer to restore undo history from disk.
611    /// Timestamps are reconstructed relative to "now" using the provided durations.
612    ///
613    /// # Arguments
614    ///
615    /// * `nodes_data` - Vector of tuples containing node data:
616    ///   - `Vec<Edit>`: The edits in this node
617    ///   - `Position`: Cursor before edits
618    ///   - `Position`: Cursor after edits
619    ///   - `Duration`: Relative time from tree creation
620    ///   - `Option<usize>`: Parent node index
621    ///   - `Vec<usize>`: Child node indices
622    ///   - `u64`: Sequential change number
623    ///   - `EditOrigin`: Origin of this edit
624    /// * `current` - Index of current position in tree
625    /// * `seq_counter` - Current sequential change counter
626    /// * `max_nodes` - Maximum nodes to retain
627    /// * `active_branches` - Preferred branch at each node
628    ///
629    /// # Panics
630    ///
631    /// Panics if `nodes_data` is empty (tree must have at least a root node).
632    #[must_use]
633    #[allow(clippy::type_complexity)] // Complex tuple is intentional for persistence API
634    pub fn from_serializable(
635        nodes_data: Vec<(
636            Vec<Edit>,     // edits
637            Position,      // cursor_before
638            Position,      // cursor_after
639            Duration,      // relative_time from root
640            Option<usize>, // parent
641            Vec<usize>,    // children
642            u64,           // seq_num
643            EditOrigin,    // origin
644        )>,
645        current: usize,
646        seq_counter: u64,
647        max_nodes: usize,
648        active_branches: Vec<usize>,
649    ) -> Self {
650        assert!(!nodes_data.is_empty(), "UndoTree must have at least a root node");
651
652        let now = Instant::now();
653
654        let nodes: Vec<UndoNode> = nodes_data
655            .into_iter()
656            .map(
657                |(
658                    edits,
659                    cursor_before,
660                    cursor_after,
661                    relative_time,
662                    parent,
663                    children,
664                    seq_num,
665                    origin,
666                )| {
667                    UndoNode {
668                        edits,
669                        cursor_before,
670                        cursor_after,
671                        // Reconstruct timestamp: now + relative offset
672                        // Note: For a tree loaded at time T, all nodes have timestamps
673                        // relative to T. The ordering is preserved.
674                        timestamp: now + relative_time,
675                        parent,
676                        children,
677                        seq_num,
678                        origin,
679                    }
680                },
681            )
682            .collect();
683
684        Self {
685            nodes,
686            current,
687            seq_counter,
688            max_nodes: max_nodes.max(1), // At least 1 node
689            active_branches,
690        }
691    }
692}