1use super::{CommandError, Selection, SelectionSetSnapshot};
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6use std::time::{Duration, Instant};
7
8const DEFAULT_UNDO_COALESCING_TIMEOUT: Duration = Duration::from_secs(1);
9
10#[derive(Debug, Clone)]
11pub(super) struct TextEdit {
12 pub(super) start_before: usize,
13 pub(super) start_after: usize,
14 pub(super) deleted_text: String,
15 pub(super) inserted_text: String,
16}
17
18impl TextEdit {
19 pub(super) fn deleted_len(&self) -> usize {
20 self.deleted_text.chars().count()
21 }
22
23 pub(super) fn inserted_len(&self) -> usize {
24 self.inserted_text.chars().count()
25 }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29enum UndoEditKind {
30 Insert,
31 Explicit,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35struct CoalescingEdit {
36 start_before: usize,
37 start_after: usize,
38 deleted_len: usize,
39 inserted_len: usize,
40}
41
42#[derive(Debug, Clone)]
43struct UndoCoalescingState {
44 group_id: usize,
45 last_at: Instant,
46 edit_kind: UndoEditKind,
47 after_selection: SelectionSetSnapshot,
48 edits: Vec<CoalescingEdit>,
49}
50
51impl UndoCoalescingState {
52 fn from_step(
53 group_id: usize,
54 edit_kind: UndoEditKind,
55 step: &UndoStep,
56 last_at: Instant,
57 ) -> Option<Self> {
58 Some(Self {
59 group_id,
60 last_at,
61 edit_kind,
62 after_selection: step.after_selection.clone(),
63 edits: coalescing_edits_for_kind(edit_kind, step)?,
64 })
65 }
66
67 fn extend_with(
68 &self,
69 edit_kind: UndoEditKind,
70 step: &UndoStep,
71 now: Instant,
72 timeout: Duration,
73 ) -> Option<Vec<CoalescingEdit>> {
74 if self.edit_kind != edit_kind {
75 return None;
76 }
77 if edit_kind == UndoEditKind::Insert
78 && now.saturating_duration_since(self.last_at) >= timeout
79 {
80 return None;
81 }
82 if self.after_selection != step.before_selection {
83 return None;
84 }
85
86 let next_edits = coalescing_edits_for_kind(edit_kind, step)?;
87 if self.edits.len() != next_edits.len() {
88 return None;
89 }
90
91 for (previous, next) in self.edits.iter().zip(&next_edits) {
92 match edit_kind {
93 UndoEditKind::Insert => {
94 let previous_end = previous.start_after.saturating_add(previous.inserted_len);
95 if previous_end != next.start_before {
96 return None;
97 }
98 }
99 UndoEditKind::Explicit => {
100 if previous.start_after != next.start_before
101 || previous.inserted_len != next.deleted_len
102 {
103 return None;
104 }
105 }
106 }
107 }
108
109 Some(next_edits)
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub(super) enum UndoCoalescingMode {
115 None,
116 Insert,
117 Explicit,
118}
119
120fn coalescing_edits_for_kind(
121 edit_kind: UndoEditKind,
122 step: &UndoStep,
123) -> Option<Vec<CoalescingEdit>> {
124 if step.edits.is_empty() {
125 return None;
126 }
127
128 let mut edits = Vec::with_capacity(step.edits.len());
129 for edit in &step.edits {
130 let deleted_len = edit.deleted_len();
131 let inserted_len = edit.inserted_len();
132 match edit_kind {
133 UndoEditKind::Insert => {
134 if deleted_len != 0 || inserted_len == 0 || edit.inserted_text.contains('\n') {
135 return None;
136 }
137 }
138 UndoEditKind::Explicit => {
139 if deleted_len == 0 && inserted_len == 0 {
140 return None;
141 }
142 }
143 }
144
145 edits.push(CoalescingEdit {
146 start_before: edit.start_before,
147 start_after: edit.start_after,
148 deleted_len,
149 inserted_len,
150 });
151 }
152
153 edits.sort_by_key(|edit| (edit.start_before, edit.start_after));
154 Some(edits)
155}
156
157#[derive(Debug, Clone)]
158pub(super) struct UndoStep {
159 pub(super) group_id: usize,
160 pub(super) edits: Vec<TextEdit>,
161 pub(super) before_selection: SelectionSetSnapshot,
162 pub(super) after_selection: SelectionSetSnapshot,
163}
164
165#[derive(Debug)]
166pub(super) struct UndoRedoManager {
167 nodes: Vec<UndoNode>,
172 current: UndoNodeId,
174 step_count: usize,
176 max_undo: usize,
177 clean_node: Option<UndoNodeId>,
179 next_group_id: usize,
180 open_group: Option<UndoCoalescingState>,
181 coalescing_timeout: Duration,
182}
183
184type UndoNodeId = usize;
185
186#[derive(Debug)]
187struct UndoNode {
188 parent: Option<UndoNodeId>,
189 children: Vec<UndoNodeId>,
190 preferred_child: Option<UndoNodeId>,
192 step: Option<UndoStep>,
194}
195
196impl UndoRedoManager {
197 pub(super) fn new(max_undo: usize) -> Self {
198 let max_undo = max_undo.max(1);
199 Self {
200 nodes: vec![UndoNode {
201 parent: None,
202 children: Vec::new(),
203 preferred_child: None,
204 step: None,
205 }],
206 current: 0,
207 step_count: 0,
208 max_undo,
209 clean_node: Some(0),
210 next_group_id: 0,
211 open_group: None,
212 coalescing_timeout: DEFAULT_UNDO_COALESCING_TIMEOUT,
213 }
214 }
215
216 fn invalid_history_error(context: &str, node: UndoNodeId) -> CommandError {
217 CommandError::Other(format!("Invalid undo history: {context} (node={node})"))
218 }
219
220 fn live_current_node(&self) -> bool {
221 self.current == 0
222 || self
223 .nodes
224 .get(self.current)
225 .is_some_and(|node| node.step.is_some())
226 }
227
228 pub(super) fn can_undo(&self) -> bool {
229 self.current != 0
230 && self
231 .nodes
232 .get(self.current)
233 .is_some_and(|node| node.step.is_some())
234 }
235
236 pub(super) fn can_redo(&self) -> bool {
237 self.selected_child(self.current).is_some()
238 }
239
240 pub(super) fn undo_depth(&self) -> usize {
241 let mut depth = 0usize;
242 let mut node = self.current;
243 let mut remaining = self.nodes.len();
244 while node != 0 && remaining > 0 {
245 let Some(current) = self.nodes.get(node) else {
246 break;
247 };
248 let has_step = current.step.is_some();
249 if !has_step {
250 break;
251 }
252 depth = depth.saturating_add(1);
253 let Some(parent) = current.parent else {
254 break;
255 };
256 node = parent;
257 remaining -= 1;
258 }
259 depth
260 }
261
262 pub(super) fn redo_depth(&self) -> usize {
263 let mut depth = 0usize;
264 let mut node = self.current;
265 let mut remaining = self.nodes.len();
266 while remaining > 0 {
267 let Some(child) = self.selected_child(node) else {
268 break;
269 };
270 depth = depth.saturating_add(1);
271 node = child;
272 remaining -= 1;
273 }
274 depth
275 }
276
277 pub(super) fn current_group_id(&self) -> Option<usize> {
278 self.open_group.as_ref().map(|group| group.group_id)
279 }
280
281 pub(super) fn coalescing_timeout(&self) -> Duration {
282 self.coalescing_timeout
283 }
284
285 pub(super) fn set_coalescing_timeout(&mut self, timeout: Duration) {
286 self.coalescing_timeout = timeout;
287 }
288
289 pub(super) fn is_clean(&self) -> bool {
290 self.clean_node == Some(self.current)
291 }
292
293 pub(super) fn mark_clean(&mut self) {
294 self.clean_node = Some(self.current);
295 self.end_group();
296 }
297
298 pub(super) fn end_group(&mut self) {
299 self.open_group = None;
300 }
301
302 pub(super) fn push_step(&mut self, step: UndoStep, coalescible_insert: bool) -> usize {
303 let mode = if coalescible_insert {
304 UndoCoalescingMode::Insert
305 } else {
306 UndoCoalescingMode::None
307 };
308 self.push_step_with_mode(step, mode)
309 }
310
311 pub(super) fn push_explicit_coalescing_step(&mut self, step: UndoStep) -> usize {
312 self.push_step_with_mode(step, UndoCoalescingMode::Explicit)
313 }
314
315 fn push_step_with_mode(&mut self, mut step: UndoStep, mode: UndoCoalescingMode) -> usize {
316 let now = Instant::now();
317 let edit_kind = match mode {
318 UndoCoalescingMode::None => None,
319 UndoCoalescingMode::Insert => Some(UndoEditKind::Insert),
320 UndoCoalescingMode::Explicit => Some(UndoEditKind::Explicit),
321 };
322 let next_edits = edit_kind.and_then(|kind| coalescing_edits_for_kind(kind, &step));
323 let reuse_open_group = next_edits.is_some()
324 && self.clean_node != Some(self.current)
325 && edit_kind.is_some_and(|kind| {
326 self.open_group
327 .as_ref()
328 .and_then(|group| group.extend_with(kind, &step, now, self.coalescing_timeout))
329 .is_some()
330 });
331
332 if reuse_open_group {
333 if let Some(group) = &self.open_group {
334 step.group_id = group.group_id;
335 }
336 } else {
337 step.group_id = self.next_group_id;
338 self.next_group_id = self.next_group_id.wrapping_add(1);
339 }
340
341 if let Some(kind) = edit_kind.filter(|_| next_edits.is_some()) {
342 self.open_group = UndoCoalescingState::from_step(step.group_id, kind, &step, now);
343 } else {
344 self.open_group = None;
345 }
346
347 let group_id = step.group_id;
348 let parent = if self.live_current_node() {
349 self.current
350 } else {
351 debug_assert!(
352 self.live_current_node(),
353 "undo history current node is stale"
354 );
355 self.current = 0;
356 self.open_group = None;
357 0
358 };
359 let new_id = self.nodes.len();
360 self.nodes.push(UndoNode {
361 parent: Some(parent),
362 children: Vec::new(),
363 preferred_child: None,
364 step: Some(step),
365 });
366
367 if let Some(parent_node) = self.nodes.get_mut(parent) {
368 parent_node.children.push(new_id);
369 parent_node.preferred_child = Some(new_id);
370 } else {
371 debug_assert!(
372 parent < self.nodes.len(),
373 "undo history parent node is invalid"
374 );
375 self.current = new_id;
376 return group_id;
377 }
378 self.current = new_id;
379 self.step_count = self.step_count.saturating_add(1);
380
381 self.prune_if_needed();
382 group_id
383 }
384
385 pub(super) fn pop_undo_group(&mut self) -> Result<Option<Vec<UndoStep>>, CommandError> {
386 let mut node = self.current;
387 if node == 0 {
388 return Ok(None);
389 }
390 let group_id = self
391 .nodes
392 .get(node)
393 .ok_or_else(|| Self::invalid_history_error("current node is out of bounds", node))?
394 .step
395 .as_ref()
396 .ok_or_else(|| Self::invalid_history_error("current node is stale", node))?
397 .group_id;
398
399 let mut steps: Vec<UndoStep> = Vec::new();
400 while node != 0 {
401 let (step, parent) = {
402 let current = self.nodes.get(node).ok_or_else(|| {
403 Self::invalid_history_error("undo path node is out of bounds", node)
404 })?;
405 let step = current
406 .step
407 .as_ref()
408 .ok_or_else(|| Self::invalid_history_error("undo path node is stale", node))?;
409 if step.group_id != group_id {
410 break;
411 }
412 let parent = current.parent.ok_or_else(|| {
413 Self::invalid_history_error("undo path node has no parent", node)
414 })?;
415 (step.clone(), parent)
416 };
417
418 if self.nodes.get(parent).is_none() {
419 return Err(Self::invalid_history_error(
420 "undo path parent is out of bounds",
421 parent,
422 ));
423 }
424
425 steps.push(step);
426
427 if let Some(parent_node) = self.nodes.get_mut(parent) {
429 parent_node.preferred_child = Some(node);
430 }
431
432 node = parent;
433 }
434
435 if steps.is_empty() {
436 return Ok(None);
437 }
438
439 self.current = node;
440 Ok(Some(steps))
441 }
442
443 pub(super) fn pop_redo_group(&mut self) -> Result<Option<Vec<UndoStep>>, CommandError> {
444 let Some(first) = self.selected_child_checked(self.current)? else {
445 return Ok(None);
446 };
447 let group_id = self
448 .nodes
449 .get(first)
450 .and_then(|node| node.step.as_ref())
451 .ok_or_else(|| Self::invalid_history_error("redo child node is stale", first))?
452 .group_id;
453
454 let mut node = self.current;
455 let mut steps: Vec<UndoStep> = Vec::new();
456 while let Some(child) = self.selected_child_checked(node)? {
457 let step = {
458 let child_node = self.nodes.get(child).ok_or_else(|| {
459 Self::invalid_history_error("redo child node is out of bounds", child)
460 })?;
461 let step = child_node.step.as_ref().ok_or_else(|| {
462 Self::invalid_history_error("redo child node is stale", child)
463 })?;
464 if step.group_id != group_id {
465 break;
466 }
467 step.clone()
468 };
469
470 let parent = self.nodes.get_mut(node).ok_or_else(|| {
472 Self::invalid_history_error("redo parent node is out of bounds", node)
473 })?;
474 parent.preferred_child = Some(child);
475
476 steps.push(step);
477 node = child;
478 }
479
480 if steps.is_empty() {
481 return Ok(None);
482 }
483
484 self.current = node;
485 Ok(Some(steps))
486 }
487
488 pub(super) fn redo_branch_count(&self) -> usize {
489 self.nodes
490 .get(self.current)
491 .map_or(0, |node| node.children.len())
492 }
493
494 pub(super) fn selected_redo_branch_index(&self) -> Option<usize> {
495 let node = self.nodes.get(self.current)?;
496 let selected = self.selected_child(self.current)?;
497 node.children.iter().position(|c| *c == selected)
498 }
499
500 pub(super) fn select_redo_branch(&mut self, index: usize) -> Result<(), CommandError> {
501 let children = self
502 .nodes
503 .get(self.current)
504 .ok_or_else(|| {
505 Self::invalid_history_error("current redo node is out of bounds", self.current)
506 })?
507 .children
508 .clone();
509 if index >= children.len() {
510 return Err(CommandError::Other(format!(
511 "Invalid redo branch index {} (count={})",
512 index,
513 children.len()
514 )));
515 }
516 let child = children[index];
517 self.ensure_live_redo_child(child)?;
518 self.nodes
519 .get_mut(self.current)
520 .ok_or_else(|| {
521 Self::invalid_history_error("current redo node is out of bounds", self.current)
522 })?
523 .preferred_child = Some(child);
524 Ok(())
525 }
526
527 pub(super) fn selected_child(&self, node: UndoNodeId) -> Option<UndoNodeId> {
528 self.selected_child_checked(node).ok().flatten()
529 }
530
531 fn selected_child_checked(&self, node: UndoNodeId) -> Result<Option<UndoNodeId>, CommandError> {
532 let n = self.nodes.get(node).ok_or_else(|| {
533 Self::invalid_history_error("redo parent node is out of bounds", node)
534 })?;
535 if n.children.is_empty() {
536 return Ok(None);
537 }
538 let selected = if let Some(pref) = n.preferred_child
539 && n.children.contains(&pref)
540 {
541 pref
542 } else {
543 match n.children.last().copied() {
544 Some(child) => child,
545 None => return Ok(None),
546 }
547 };
548 self.ensure_live_redo_child(selected)?;
549 Ok(Some(selected))
550 }
551
552 fn ensure_live_redo_child(&self, child: UndoNodeId) -> Result<(), CommandError> {
553 let child_node = self.nodes.get(child).ok_or_else(|| {
554 Self::invalid_history_error("redo child node is out of bounds", child)
555 })?;
556 if child_node.step.is_none() {
557 return Err(Self::invalid_history_error(
558 "redo child node is stale",
559 child,
560 ));
561 }
562 Ok(())
563 }
564
565 pub(super) fn prune_if_needed(&mut self) {
566 while self.step_count > self.max_undo {
567 if let Some(leaf) = self.find_prunable_leaf() {
568 self.remove_leaf_node(leaf);
569 continue;
570 }
571
572 if self.prune_root_child() {
573 continue;
574 }
575
576 break;
578 }
579 }
580
581 pub(super) fn find_prunable_leaf(&self) -> Option<UndoNodeId> {
582 (1..self.nodes.len())
583 .filter(|id| *id != self.current)
584 .filter(|id| self.nodes.get(*id).is_some_and(|node| node.step.is_some()))
585 .filter(|id| {
586 self.nodes
587 .get(*id)
588 .is_some_and(|node| node.children.is_empty())
589 })
590 .min()
591 }
592
593 pub(super) fn remove_leaf_node(&mut self, id: UndoNodeId) {
594 let parent = match self.nodes.get(id).and_then(|node| node.parent) {
595 Some(parent) => parent,
596 None => {
597 debug_assert!(self.nodes.get(id).and_then(|node| node.parent).is_some());
598 return;
599 }
600 };
601 let Some(parent_node) = self.nodes.get_mut(parent) else {
602 debug_assert!(
603 parent < self.nodes.len(),
604 "undo history leaf parent is invalid"
605 );
606 return;
607 };
608 parent_node.children.retain(|c| *c != id);
609 if parent_node.preferred_child == Some(id) {
610 parent_node.preferred_child = parent_node.children.last().copied();
611 }
612
613 if self.clean_node == Some(id) {
614 self.clean_node = None;
615 }
616
617 let Some(node) = self.nodes.get_mut(id) else {
618 debug_assert!(id < self.nodes.len(), "undo history leaf node is invalid");
619 return;
620 };
621 node.parent = None;
622 node.children.clear();
623 node.preferred_child = None;
624 node.step = None;
625
626 self.step_count = self.step_count.saturating_sub(1);
627 }
628
629 pub(super) fn prune_root_child(&mut self) -> bool {
630 let Some(root) = self.nodes.first() else {
631 debug_assert!(!self.nodes.is_empty(), "undo history is missing root node");
632 return false;
633 };
634 let children = root.children.clone();
635 if children.len() != 1 {
636 return false;
637 }
638 let doomed = children[0];
639 if doomed == self.current {
640 return false;
641 }
642 if self
643 .nodes
644 .get(doomed)
645 .is_none_or(|node| node.step.is_none())
646 {
647 return false;
648 }
649
650 let adopted = match self.nodes.get_mut(doomed) {
653 Some(doomed_node) => std::mem::take(&mut doomed_node.children),
654 None => {
655 debug_assert!(
656 doomed < self.nodes.len(),
657 "undo history pruned node is invalid"
658 );
659 return false;
660 }
661 };
662 for child in &adopted {
663 if let Some(child_node) = self.nodes.get_mut(*child) {
664 child_node.parent = Some(0);
665 } else {
666 debug_assert!(
667 *child < self.nodes.len(),
668 "undo history child node is invalid"
669 );
670 return false;
671 }
672 }
673
674 let Some(root) = self.nodes.get_mut(0) else {
675 debug_assert!(!self.nodes.is_empty(), "undo history is missing root node");
676 return false;
677 };
678 root.children = adopted;
679 root.preferred_child = root.children.last().copied();
680
681 if self.clean_node == Some(doomed) {
682 self.clean_node = Some(0);
683 }
684
685 let Some(doomed_node) = self.nodes.get_mut(doomed) else {
686 debug_assert!(
687 doomed < self.nodes.len(),
688 "undo history pruned node is invalid"
689 );
690 return false;
691 };
692 doomed_node.parent = None;
693 doomed_node.children.clear();
694 doomed_node.preferred_child = None;
695 doomed_node.step = None;
696
697 self.step_count = self.step_count.saturating_sub(1);
698 true
699 }
700}
701
702#[derive(Debug, Clone, PartialEq, Eq)]
712#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
713pub struct UndoHistorySnapshot {
714 pub format_version: u32,
716 pub undo_stack: Vec<UndoHistoryStep>,
718 pub redo_stack: Vec<UndoHistoryStep>,
720 pub max_undo: usize,
722 pub clean_index: Option<usize>,
724 pub next_group_id: usize,
726 pub open_group_id: Option<usize>,
728}
729
730#[derive(Debug, Clone, PartialEq, Eq)]
731#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
732pub struct UndoHistoryStep {
734 pub group_id: usize,
736 pub edits: Vec<UndoHistoryTextEdit>,
738 pub before_selection: UndoHistorySelectionSet,
740 pub after_selection: UndoHistorySelectionSet,
742}
743
744#[derive(Debug, Clone, PartialEq, Eq)]
745#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
746pub struct UndoHistoryTextEdit {
748 pub start_before: usize,
750 pub start_after: usize,
752 pub deleted_text: String,
754 pub inserted_text: String,
756}
757
758#[derive(Debug, Clone, PartialEq, Eq)]
759#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
760pub struct UndoHistorySelectionSet {
762 pub selections: Vec<Selection>,
764 pub primary_index: usize,
766}
767
768#[derive(Debug, Clone, PartialEq, Eq)]
770pub enum UndoHistoryRestoreError {
771 UnsupportedFormatVersion(u32),
773 InvalidCleanIndex {
775 clean_index: usize,
777 max_index: usize,
779 },
780}
781
782impl std::fmt::Display for UndoHistoryRestoreError {
783 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
784 match self {
785 Self::UnsupportedFormatVersion(v) => {
786 write!(f, "Unsupported undo history snapshot format_version={}", v)
787 }
788 Self::InvalidCleanIndex {
789 clean_index,
790 max_index,
791 } => write!(
792 f,
793 "Invalid undo history snapshot clean_index={} (max={})",
794 clean_index, max_index
795 ),
796 }
797 }
798}
799
800impl std::error::Error for UndoHistoryRestoreError {}
801
802const UNDO_HISTORY_SNAPSHOT_FORMAT_VERSION: u32 = 1;
803
804impl UndoRedoManager {
805 pub(super) fn snapshot(&self) -> UndoHistorySnapshot {
806 let undo_nodes = self.undo_path_nodes_oldest_first();
807 let redo_nodes_nearest_first = self.redo_path_nodes_nearest_first();
808
809 let clean_index = self.clean_node.and_then(|clean| {
810 if clean == 0 {
811 return Some(0);
812 }
813 if let Some(pos) = undo_nodes.iter().position(|id| *id == clean) {
814 return Some(pos + 1);
815 }
816 if let Some(pos) = redo_nodes_nearest_first.iter().position(|id| *id == clean) {
817 return Some(undo_nodes.len() + pos + 1);
818 }
819 None
820 });
821
822 UndoHistorySnapshot {
823 format_version: UNDO_HISTORY_SNAPSHOT_FORMAT_VERSION,
824 undo_stack: self
825 .undo_path_steps_oldest_first()
826 .iter()
827 .map(|step| UndoHistoryStep {
828 group_id: step.group_id,
829 edits: step
830 .edits
831 .iter()
832 .map(|edit| UndoHistoryTextEdit {
833 start_before: edit.start_before,
834 start_after: edit.start_after,
835 deleted_text: edit.deleted_text.clone(),
836 inserted_text: edit.inserted_text.clone(),
837 })
838 .collect(),
839 before_selection: UndoHistorySelectionSet {
840 selections: step.before_selection.selections.clone(),
841 primary_index: step.before_selection.primary_index,
842 },
843 after_selection: UndoHistorySelectionSet {
844 selections: step.after_selection.selections.clone(),
845 primary_index: step.after_selection.primary_index,
846 },
847 })
848 .collect(),
849 redo_stack: self
850 .redo_path_steps_newest_first()
851 .iter()
852 .map(|step| UndoHistoryStep {
853 group_id: step.group_id,
854 edits: step
855 .edits
856 .iter()
857 .map(|edit| UndoHistoryTextEdit {
858 start_before: edit.start_before,
859 start_after: edit.start_after,
860 deleted_text: edit.deleted_text.clone(),
861 inserted_text: edit.inserted_text.clone(),
862 })
863 .collect(),
864 before_selection: UndoHistorySelectionSet {
865 selections: step.before_selection.selections.clone(),
866 primary_index: step.before_selection.primary_index,
867 },
868 after_selection: UndoHistorySelectionSet {
869 selections: step.after_selection.selections.clone(),
870 primary_index: step.after_selection.primary_index,
871 },
872 })
873 .collect(),
874 max_undo: self.max_undo,
875 clean_index,
876 next_group_id: self.next_group_id,
877 open_group_id: self.current_group_id(),
878 }
879 }
880
881 pub(super) fn restore_from_snapshot(
882 &mut self,
883 snapshot: UndoHistorySnapshot,
884 ) -> Result<(), UndoHistoryRestoreError> {
885 if snapshot.format_version != UNDO_HISTORY_SNAPSHOT_FORMAT_VERSION {
886 return Err(UndoHistoryRestoreError::UnsupportedFormatVersion(
887 snapshot.format_version,
888 ));
889 }
890
891 let undo_steps = snapshot
892 .undo_stack
893 .into_iter()
894 .map(|step| UndoStep {
895 group_id: step.group_id,
896 edits: step
897 .edits
898 .into_iter()
899 .map(|edit| TextEdit {
900 start_before: edit.start_before,
901 start_after: edit.start_after,
902 deleted_text: edit.deleted_text,
903 inserted_text: edit.inserted_text,
904 })
905 .collect(),
906 before_selection: SelectionSetSnapshot {
907 selections: step.before_selection.selections,
908 primary_index: step.before_selection.primary_index,
909 },
910 after_selection: SelectionSetSnapshot {
911 selections: step.after_selection.selections,
912 primary_index: step.after_selection.primary_index,
913 },
914 })
915 .collect::<Vec<_>>();
916
917 let redo_steps = snapshot
918 .redo_stack
919 .into_iter()
920 .map(|step| UndoStep {
921 group_id: step.group_id,
922 edits: step
923 .edits
924 .into_iter()
925 .map(|edit| TextEdit {
926 start_before: edit.start_before,
927 start_after: edit.start_after,
928 deleted_text: edit.deleted_text,
929 inserted_text: edit.inserted_text,
930 })
931 .collect(),
932 before_selection: SelectionSetSnapshot {
933 selections: step.before_selection.selections,
934 primary_index: step.before_selection.primary_index,
935 },
936 after_selection: SelectionSetSnapshot {
937 selections: step.after_selection.selections,
938 primary_index: step.after_selection.primary_index,
939 },
940 })
941 .collect::<Vec<_>>();
942
943 let total_steps = undo_steps.len() + redo_steps.len();
944 let max_undo = snapshot.max_undo.max(total_steps).max(1);
945 if let Some(clean_index) = snapshot.clean_index
946 && clean_index > total_steps
947 {
948 return Err(UndoHistoryRestoreError::InvalidCleanIndex {
949 clean_index,
950 max_index: total_steps,
951 });
952 }
953
954 self.nodes = vec![UndoNode {
956 parent: None,
957 children: Vec::new(),
958 preferred_child: None,
959 step: None,
960 }];
961 self.current = 0;
962 self.step_count = 0;
963
964 let mut node = 0usize;
965 let mut undo_node_ids: Vec<UndoNodeId> = Vec::new();
966 for step in undo_steps {
967 let new_id = self.nodes.len();
968 self.nodes.push(UndoNode {
969 parent: Some(node),
970 children: Vec::new(),
971 preferred_child: None,
972 step: Some(step),
973 });
974 if let Some(parent_node) = self.nodes.get_mut(node) {
975 parent_node.children.push(new_id);
976 parent_node.preferred_child = Some(new_id);
977 } else {
978 debug_assert!(
979 node < self.nodes.len(),
980 "undo history restore parent is invalid"
981 );
982 }
983 node = new_id;
984 undo_node_ids.push(new_id);
985 self.step_count = self.step_count.saturating_add(1);
986 }
987
988 let current_node = node;
989
990 let mut redo_node_ids_nearest_first: Vec<UndoNodeId> = Vec::new();
993 let mut redo_parent = current_node;
994 for step in redo_steps.into_iter().rev() {
995 let new_id = self.nodes.len();
996 self.nodes.push(UndoNode {
997 parent: Some(redo_parent),
998 children: Vec::new(),
999 preferred_child: None,
1000 step: Some(step),
1001 });
1002 if let Some(parent_node) = self.nodes.get_mut(redo_parent) {
1003 parent_node.children.push(new_id);
1004 parent_node.preferred_child = Some(new_id);
1005 } else {
1006 debug_assert!(
1007 redo_parent < self.nodes.len(),
1008 "undo history restore redo parent is invalid"
1009 );
1010 }
1011 redo_parent = new_id;
1012 redo_node_ids_nearest_first.push(new_id);
1013 self.step_count = self.step_count.saturating_add(1);
1014 }
1015
1016 self.current = current_node;
1017 self.max_undo = max_undo;
1018 self.next_group_id = snapshot.next_group_id;
1019 self.open_group = snapshot.open_group_id.and_then(|group_id| {
1020 let step = self.nodes.get(self.current)?.step.as_ref()?;
1021 if step.group_id != group_id {
1022 return None;
1023 }
1024 let kind = if coalescing_edits_for_kind(UndoEditKind::Insert, step).is_some() {
1025 UndoEditKind::Insert
1026 } else {
1027 UndoEditKind::Explicit
1028 };
1029 UndoCoalescingState::from_step(group_id, kind, step, Instant::now())
1030 });
1031
1032 self.clean_node = snapshot.clean_index.and_then(|idx| {
1033 if idx == 0 {
1034 return Some(0);
1035 }
1036 let undo_len = undo_node_ids.len();
1037 if idx <= undo_len {
1038 return undo_node_ids.get(idx - 1).copied();
1039 }
1040 let redo_pos = idx.saturating_sub(undo_len + 1);
1041 redo_node_ids_nearest_first.get(redo_pos).copied()
1042 });
1043
1044 Ok(())
1045 }
1046
1047 pub(super) fn undo_path_nodes_oldest_first(&self) -> Vec<UndoNodeId> {
1048 let mut nodes: Vec<UndoNodeId> = Vec::new();
1049 let mut node = self.current;
1050 let mut remaining = self.nodes.len();
1051 while node != 0 && remaining > 0 {
1052 let Some(current) = self.nodes.get(node) else {
1053 break;
1054 };
1055 if current.step.is_none() {
1056 break;
1057 }
1058 nodes.push(node);
1059 let Some(parent) = current.parent else {
1060 break;
1061 };
1062 node = parent;
1063 remaining -= 1;
1064 }
1065 nodes.reverse();
1066 nodes
1067 }
1068
1069 pub(super) fn redo_path_nodes_nearest_first(&self) -> Vec<UndoNodeId> {
1070 let mut out: Vec<UndoNodeId> = Vec::new();
1071 let mut node = self.current;
1072 while let Some(child) = self.selected_child(node) {
1073 out.push(child);
1074 node = child;
1075 }
1076 out
1077 }
1078
1079 pub(super) fn undo_path_steps_oldest_first(&self) -> Vec<UndoStep> {
1080 self.undo_path_nodes_oldest_first()
1081 .into_iter()
1082 .filter_map(|id| {
1083 self.nodes
1084 .get(id)
1085 .and_then(|node| node.step.as_ref())
1086 .cloned()
1087 })
1088 .collect()
1089 }
1090
1091 pub(super) fn redo_path_steps_newest_first(&self) -> Vec<UndoStep> {
1094 let mut steps: Vec<UndoStep> = self
1095 .redo_path_nodes_nearest_first()
1096 .into_iter()
1097 .filter_map(|id| {
1098 self.nodes
1099 .get(id)
1100 .and_then(|node| node.step.as_ref())
1101 .cloned()
1102 })
1103 .collect();
1104 steps.reverse();
1105 steps
1106 }
1107}
1108
1109#[cfg(test)]
1110mod tests {
1111 use super::super::{Position, SelectionDirection};
1112 use super::*;
1113
1114 fn selection_snapshot() -> SelectionSetSnapshot {
1115 SelectionSetSnapshot {
1116 selections: vec![Selection {
1117 start: Position::new(0, 0),
1118 end: Position::new(0, 0),
1119 direction: SelectionDirection::Forward,
1120 }],
1121 primary_index: 0,
1122 }
1123 }
1124
1125 fn undo_step(group_id: usize) -> UndoStep {
1126 UndoStep {
1127 group_id,
1128 edits: vec![TextEdit {
1129 start_before: 0,
1130 start_after: 0,
1131 deleted_text: String::new(),
1132 inserted_text: "x".to_string(),
1133 }],
1134 before_selection: selection_snapshot(),
1135 after_selection: selection_snapshot(),
1136 }
1137 }
1138
1139 fn tombstone_node(parent: Option<UndoNodeId>) -> UndoNode {
1140 UndoNode {
1141 parent,
1142 children: Vec::new(),
1143 preferred_child: None,
1144 step: None,
1145 }
1146 }
1147
1148 #[test]
1149 fn pop_undo_group_reports_stale_current_node() {
1150 let mut manager = UndoRedoManager::new(10);
1151 manager.nodes.push(tombstone_node(Some(0)));
1152 manager.current = 1;
1153
1154 let err = manager
1155 .pop_undo_group()
1156 .expect_err("stale current node should be an explicit error");
1157 assert!(err.to_string().contains("current node is stale"));
1158 }
1159
1160 #[test]
1161 fn pop_redo_group_reports_stale_child_node() {
1162 let mut manager = UndoRedoManager::new(10);
1163 manager.nodes.push(tombstone_node(Some(0)));
1164 manager.nodes[0].children.push(1);
1165 manager.nodes[0].preferred_child = Some(1);
1166
1167 let err = manager
1168 .pop_redo_group()
1169 .expect_err("stale redo child should be an explicit error");
1170 assert!(err.to_string().contains("redo child node is stale"));
1171 }
1172
1173 #[test]
1174 fn checked_history_accessors_do_not_panic_on_invalid_current_node() {
1175 let mut manager = UndoRedoManager::new(10);
1176 manager.current = 999;
1177
1178 assert!(!manager.can_undo());
1179 assert!(!manager.can_redo());
1180 assert_eq!(manager.undo_depth(), 0);
1181 assert_eq!(manager.redo_depth(), 0);
1182 assert_eq!(manager.redo_branch_count(), 0);
1183 assert_eq!(manager.selected_redo_branch_index(), None);
1184 }
1185
1186 #[test]
1187 fn valid_undo_redo_group_paths_still_work() {
1188 let mut manager = UndoRedoManager::new(10);
1189 let group = manager.push_step(undo_step(0), false);
1190 assert_eq!(group, 0);
1191
1192 let undo_steps = manager.pop_undo_group().unwrap().unwrap();
1193 assert_eq!(undo_steps.len(), 1);
1194 assert_eq!(manager.current, 0);
1195
1196 let redo_steps = manager.pop_redo_group().unwrap().unwrap();
1197 assert_eq!(redo_steps.len(), 1);
1198 assert_eq!(manager.current, 1);
1199 }
1200}