Skip to main content

ass_editor/core/document/
undo_redo.rs

1//! Undo/redo execution and history manager access
2//!
3//! Implements reversing and replaying recorded operations (including
4//! stream-feature delta operations) and exposes the underlying
5//! `UndoManager` for configuration and inspection.
6
7use super::EditorDocument;
8use crate::commands::CommandResult;
9use crate::core::errors::{EditorError, Result};
10use crate::core::history::UndoManager;
11use crate::core::position::{Position, Range};
12
13#[cfg(not(feature = "std"))]
14use alloc::string::ToString;
15
16impl EditorDocument {
17    /// Perform an undo operation
18    ///
19    /// Retrieves the most recent operation from the undo stack and reverses it.
20    /// If the operation includes a script delta, it will be applied for efficient updates.
21    pub fn undo(&mut self) -> Result<CommandResult> {
22        use crate::core::history::Operation;
23
24        // Pop from undo stack
25        if let Some(entry) = self.history.pop_undo_entry() {
26            let mut result = CommandResult::success();
27            result.content_changed = true;
28
29            // Execute the inverse of the operation
30            match &entry.operation {
31                Operation::Insert { position, text } => {
32                    // Undo insert by deleting the inserted text
33                    let end_pos = Position::new(position.offset + text.len());
34                    let range = Range::new(*position, end_pos);
35                    self.delete_raw(range)?;
36                    result.modified_range = Some(Range::new(*position, *position));
37                    result.new_cursor = entry.cursor_before;
38                }
39                Operation::Delete {
40                    range,
41                    deleted_text,
42                } => {
43                    // Undo delete by inserting the deleted text
44                    self.insert_raw(range.start, deleted_text)?;
45                    let end_pos = Position::new(range.start.offset + deleted_text.len());
46                    result.modified_range = Some(Range::new(range.start, end_pos));
47                    result.new_cursor = entry.cursor_before;
48                }
49                Operation::Replace {
50                    range, old_text, ..
51                } => {
52                    // Undo replace by restoring old text
53                    self.replace_raw(*range, old_text)?;
54                    let end_pos = Position::new(range.start.offset + old_text.len());
55                    result.modified_range = Some(Range::new(range.start, end_pos));
56                    result.new_cursor = entry.cursor_before;
57                }
58                #[cfg(feature = "stream")]
59                Operation::Delta { forward, undo_data } => {
60                    // Restore removed sections
61                    for (index, section_text) in undo_data.removed_sections.iter() {
62                        self.insert_section_at(*index, section_text)?;
63                    }
64
65                    // Restore modified sections
66                    for (index, original_text) in undo_data.modified_sections.iter() {
67                        self.replace_section(*index, original_text)?;
68                    }
69
70                    // Remove added sections
71                    for _ in 0..forward.added.len() {
72                        self.remove_last_section()?;
73                    }
74
75                    result.message = Some("Delta operation undone".to_string());
76                }
77            }
78
79            // Push to redo stack for future redo
80            self.history.push_redo_entry(entry);
81
82            // Apply script delta if available
83            #[cfg(feature = "stream")]
84            if let Some(delta) = result.script_delta.as_ref() {
85                self.apply_script_delta(delta.clone())?;
86            }
87
88            result.message = Some("Undo successful".to_string());
89            Ok(result)
90        } else {
91            Err(EditorError::NothingToUndo)
92        }
93    }
94
95    /// Perform a redo operation
96    ///
97    /// Retrieves the most recent operation from the redo stack and re-executes it.
98    /// If the operation includes a script delta, it will be applied for efficient updates.
99    pub fn redo(&mut self) -> Result<CommandResult> {
100        use crate::core::history::Operation;
101
102        // Pop from redo stack
103        if let Some(entry) = self.history.pop_redo_entry() {
104            let mut result = CommandResult::success();
105            result.content_changed = true;
106
107            // Re-execute the original operation
108            match &entry.operation {
109                Operation::Insert { position, text } => {
110                    // Redo insert
111                    self.insert_raw(*position, text)?;
112                    let end_pos = Position::new(position.offset + text.len());
113                    result.modified_range = Some(Range::new(*position, end_pos));
114                    result.new_cursor = entry.cursor_after;
115                }
116                Operation::Delete { range, .. } => {
117                    // Redo delete
118                    self.delete_raw(*range)?;
119                    result.modified_range = Some(Range::new(range.start, range.start));
120                    result.new_cursor = entry.cursor_after;
121                }
122                Operation::Replace {
123                    range, new_text, ..
124                } => {
125                    // Redo replace
126                    self.replace_raw(*range, new_text)?;
127                    let end_pos = Position::new(range.start.offset + new_text.len());
128                    result.modified_range = Some(Range::new(range.start, end_pos));
129                    result.new_cursor = entry.cursor_after;
130                }
131                #[cfg(feature = "stream")]
132                Operation::Delta {
133                    forward,
134                    undo_data: _,
135                } => {
136                    // Re-apply the delta
137                    self.apply_script_delta(forward.clone())?;
138                    result.message = Some("Delta re-applied".to_string());
139                }
140            }
141
142            // Record in history without using the public methods (to avoid recursion)
143            // We need to manually update the history manager's cursor
144            if let Some(cursor) = result.new_cursor {
145                self.history.set_cursor(Some(cursor));
146            }
147
148            // Create a new history entry for the redo operation
149            let new_entry = crate::core::history::HistoryEntry::new(
150                entry.operation,
151                entry.description,
152                &result,
153                entry.cursor_before,
154            );
155
156            // Push back to undo stack
157            self.history.stack_mut().push(new_entry);
158
159            result.message = Some("Redo successful".to_string());
160            Ok(result)
161        } else {
162            Err(EditorError::NothingToRedo)
163        }
164    }
165
166    /// Check if undo is available
167    pub fn can_undo(&self) -> bool {
168        self.history.can_undo()
169    }
170
171    /// Check if redo is available
172    pub fn can_redo(&self) -> bool {
173        self.history.can_redo()
174    }
175
176    /// Get description of the next undo operation
177    pub fn next_undo_description(&self) -> Option<&str> {
178        self.history.next_undo_description()
179    }
180
181    /// Get description of the next redo operation
182    pub fn next_redo_description(&self) -> Option<&str> {
183        self.history.next_redo_description()
184    }
185
186    /// Get mutable reference to the undo manager for configuration
187    pub fn undo_manager_mut(&mut self) -> &mut UndoManager {
188        &mut self.history
189    }
190
191    /// Get reference to the undo manager
192    pub fn undo_manager(&self) -> &UndoManager {
193        &self.history
194    }
195}