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}