Skip to main content

text_document_common/
undo_redo.rs

1// Generated by Qleany v1.4.8 from undo_redo.tera
2use crate::event::{Event, EventHub, Origin, UndoRedoEvent};
3use crate::types::EntityId;
4use anyhow::{Result, anyhow};
5use std::any::Any;
6use std::collections::HashMap;
7use std::sync::Arc;
8
9/// Trait for commands that can be undone and redone.
10///
11/// Implementors can optionally support command merging by overriding the
12/// `can_merge` and `merge` methods. This allows the UndoRedoManager to combine
13/// multiple commands of the same type into a single command, which is useful for
14/// operations like continuous typing or dragging.
15pub trait UndoRedoCommand: Send {
16    /// Undoes the command, reverting its effects
17    fn undo(&mut self) -> Result<()>;
18
19    /// Redoes the command, reapplying its effects
20    fn redo(&mut self) -> Result<()>;
21
22    /// Returns true if this command can be merged with the other command.
23    ///
24    /// By default, commands cannot be merged. Override this method to enable
25    /// merging for specific command types.
26    ///
27    /// # Example
28    /// ```test
29    /// fn can_merge(&self, other: &dyn UndoRedoCommand) -> bool {
30    ///     // Check if the other command is of the same type
31    ///     if let Some(_) = other.as_any().downcast_ref::<Self>() {
32    ///         return true;
33    ///     }
34    ///     false
35    /// }
36    /// ```
37    fn can_merge(&self, _other: &dyn UndoRedoCommand) -> bool {
38        false
39    }
40
41    /// Merges this command with the other command.
42    /// Returns true if the merge was successful.
43    ///
44    /// This method is called only if `can_merge` returns true.
45    ///
46    /// # Example
47    /// ```test
48    /// use common::undo_redo::UndoRedoCommand;
49    ///
50    /// fn merge(&mut self, other: &dyn UndoRedoCommand) -> bool {
51    ///     if let Some(other_cmd) = other.as_any().downcast_ref::<Self>() {
52    ///         // Merge the commands
53    ///         self.value += other_cmd.value;
54    ///         return true;
55    ///     }
56    ///     false
57    /// }
58    /// ```
59    fn merge(&mut self, _other: &dyn UndoRedoCommand) -> bool {
60        false
61    }
62
63    /// Returns the type ID of this command for type checking.
64    ///
65    /// This is used for downcasting in the `can_merge` and `merge` methods.
66    ///
67    /// # Example
68    /// ```test
69    /// fn as_any(&self) -> &dyn Any {
70    ///     self
71    /// }
72    /// ```
73    fn as_any(&self) -> &dyn Any;
74}
75
76/// A composite command that groups multiple commands as one.
77///
78/// This allows treating a sequence of commands as a single unit for undo/redo operations.
79/// When a composite command is undone or redone, all its contained commands are undone
80/// or redone in the appropriate order.
81///
82/// # Example
83/// ```test
84/// use common::undo_redo::CompositeCommand;
85/// let mut composite = CompositeCommand::new();
86/// composite.add_command(Box::new(Command1::new()));
87/// composite.add_command(Box::new(Command2::new()));
88/// // Now composite can be treated as a single command
89/// ```
90pub struct CompositeCommand {
91    commands: Vec<Box<dyn UndoRedoCommand>>,
92    pub stack_id: u64,
93}
94
95impl CompositeCommand {
96    /// Creates a new empty composite command.
97    pub fn new(stack_id: Option<u64>) -> Self {
98        CompositeCommand {
99            commands: Vec::new(),
100            stack_id: stack_id.unwrap_or(0),
101        }
102    }
103
104    /// Adds a command to this composite.
105    ///
106    /// Commands are executed, undone, and redone in the order they are added.
107    pub fn add_command(&mut self, command: Box<dyn UndoRedoCommand>) {
108        self.commands.push(command);
109    }
110
111    /// Returns true if this composite contains no commands.
112    pub fn is_empty(&self) -> bool {
113        self.commands.is_empty()
114    }
115}
116
117impl UndoRedoCommand for CompositeCommand {
118    fn undo(&mut self) -> Result<()> {
119        // Undo commands in reverse order
120        for command in self.commands.iter_mut().rev() {
121            command.undo()?;
122        }
123        Ok(())
124    }
125
126    fn redo(&mut self) -> Result<()> {
127        // Redo commands in original order
128        for command in self.commands.iter_mut() {
129            command.redo()?;
130        }
131        Ok(())
132    }
133
134    fn as_any(&self) -> &dyn Any {
135        self
136    }
137}
138/// Trait for commands that can be executed asynchronously with progress tracking and cancellation.
139///
140/// This trait extends the basic UndoRedoCommand trait with asynchronous capabilities.
141/// Implementors must also implement the UndoRedoCommand trait to ensure compatibility
142/// with the existing undo/redo system.
143pub trait AsyncUndoRedoCommand: UndoRedoCommand {
144    /// Starts the undo operation asynchronously and returns immediately.
145    /// Returns Ok(()) if the operation was successfully started.
146    fn start_undo(&mut self) -> Result<()>;
147
148    /// Starts the redo operation asynchronously and returns immediately.
149    /// Returns Ok(()) if the operation was successfully started.
150    fn start_redo(&mut self) -> Result<()>;
151
152    /// Checks the progress of the current operation.
153    /// Returns a value between 0.0 (not started) and 1.0 (completed).
154    fn check_progress(&self) -> f32;
155
156    /// Attempts to cancel the in-progress operation.
157    /// Returns Ok(()) if cancellation was successful or if no operation is in progress.
158    fn cancel(&mut self) -> Result<()>;
159
160    /// Checks if the current operation is complete.
161    /// Returns true if the operation has finished successfully.
162    fn is_complete(&self) -> bool;
163}
164
165#[derive(Default)]
166struct StackData {
167    undo_stack: Vec<Box<dyn UndoRedoCommand>>,
168    redo_stack: Vec<Box<dyn UndoRedoCommand>>,
169}
170
171/// Manager for undo and redo operations.
172///
173/// The UndoRedoManager maintains multiple stacks of commands:
174/// - Each stack has an undo stack for commands that can be undone
175/// - Each stack has a redo stack for commands that have been undone and can be redone
176///
177/// It also supports:
178/// - Grouping multiple commands as a single unit using begin_composite/end_composite
179/// - Merging commands of the same type when appropriate
180/// - Switching between different stacks
181pub struct UndoRedoManager {
182    stacks: HashMap<u64, StackData>,
183    next_stack_id: u64,
184    in_progress_composite: Option<CompositeCommand>,
185    composite_nesting_level: usize,
186    composite_stack_id: Option<u64>,
187    event_hub: Option<Arc<EventHub>>,
188}
189
190impl Default for UndoRedoManager {
191    fn default() -> Self {
192        Self::new()
193    }
194}
195
196impl UndoRedoManager {
197    /// Creates a new empty UndoRedoManager with one default stack (ID 0).
198    pub fn new() -> Self {
199        let mut stacks = HashMap::new();
200        stacks.insert(0, StackData::default());
201        UndoRedoManager {
202            stacks,
203            next_stack_id: 1,
204            in_progress_composite: None,
205            composite_nesting_level: 0,
206            composite_stack_id: None,
207            event_hub: None,
208        }
209    }
210
211    /// Inject the event hub to allow sending undo/redo related events
212    pub fn set_event_hub(&mut self, event_hub: &Arc<EventHub>) {
213        self.event_hub = Some(Arc::clone(event_hub));
214    }
215
216    /// Undoes the most recent command on the specified stack.
217    /// If `stack_id` is None, the global stack (ID 0) is used.
218    ///
219    /// The undone command is moved to the redo stack.
220    /// Returns Ok(()) if successful or if there are no commands to undo.
221    pub fn undo(&mut self, stack_id: Option<u64>) -> Result<()> {
222        let target_stack_id = stack_id.unwrap_or(0);
223        let stack = self
224            .stacks
225            .get_mut(&target_stack_id)
226            .ok_or_else(|| anyhow!("Stack with ID {} not found", target_stack_id))?;
227
228        if let Some(mut command) = stack.undo_stack.pop() {
229            if let Err(e) = command.undo() {
230                log::error!("Undo failed, dropping command: {e}");
231                // command dropped intentionally
232                return Err(e);
233            }
234            stack.redo_stack.push(command);
235            if let Some(event_hub) = &self.event_hub {
236                event_hub.send_event(Event {
237                    origin: Origin::UndoRedo(UndoRedoEvent::Undone),
238                    ids: Vec::<EntityId>::new(),
239                    data: None,
240                });
241            }
242        }
243        Ok(())
244    }
245
246    /// Redoes the most recently undone command on the specified stack.
247    /// If `stack_id` is None, the global stack (ID 0) is used.
248    ///
249    /// The redone command is moved back to the undo stack.
250    /// Returns Ok(()) if successful or if there are no commands to redo.
251    pub fn redo(&mut self, stack_id: Option<u64>) -> Result<()> {
252        let target_stack_id = stack_id.unwrap_or(0);
253        let stack = self
254            .stacks
255            .get_mut(&target_stack_id)
256            .ok_or_else(|| anyhow!("Stack with ID {} not found", target_stack_id))?;
257
258        if let Some(mut command) = stack.redo_stack.pop() {
259            if let Err(e) = command.redo() {
260                log::error!("Redo failed, dropping command: {e}");
261                // command dropped intentionally
262                return Err(e);
263            }
264            stack.undo_stack.push(command);
265            if let Some(event_hub) = &self.event_hub {
266                event_hub.send_event(Event {
267                    origin: Origin::UndoRedo(UndoRedoEvent::Redone),
268                    ids: Vec::<EntityId>::new(),
269                    data: None,
270                });
271            }
272        }
273        Ok(())
274    }
275
276    /// Begins a composite command group.
277    ///
278    /// All commands added between begin_composite and end_composite will be treated as a single command.
279    /// This is useful for operations that logically represent a single action but require multiple
280    /// commands to implement.
281    ///
282    /// # Example
283    /// ```test
284    /// let mut manager = UndoRedoManager::new();
285    /// manager.begin_composite();
286    /// manager.add_command(Box::new(Command1::new()));
287    /// manager.add_command(Box::new(Command2::new()));
288    /// manager.end_composite();
289    /// // Now undo() will undo both commands as a single unit
290    /// ```
291    pub fn begin_composite(&mut self, stack_id: Option<u64>) {
292        if self.composite_stack_id.is_some() && self.composite_stack_id != stack_id {
293            panic!(
294                "Cannot begin a composite on a different stack while another composite is in progress"
295            );
296        }
297
298        // Set the target stack ID for this composite
299        self.composite_stack_id = stack_id;
300
301        // Increment the nesting level
302        self.composite_nesting_level += 1;
303
304        // If there's no composite in progress, create one
305        if self.in_progress_composite.is_none() {
306            self.in_progress_composite = Some(CompositeCommand::new(stack_id));
307        }
308
309        // not sure if we want to send events for composites
310        if let Some(event_hub) = &self.event_hub {
311            event_hub.send_event(Event {
312                origin: Origin::UndoRedo(UndoRedoEvent::BeginComposite),
313                ids: Vec::<EntityId>::new(),
314                data: None,
315            });
316        }
317    }
318
319    /// Ends the current composite command group and adds it to the specified undo stack.
320    ///
321    /// If no commands were added to the composite, nothing is added to the undo stack.
322    /// If this is a nested composite, only the outermost composite is added to the undo stack.
323    pub fn end_composite(&mut self) {
324        // Decrement the nesting level
325        if self.composite_nesting_level > 0 {
326            self.composite_nesting_level -= 1;
327        }
328
329        // Only end the composite if we're at the outermost level
330        if self.composite_nesting_level == 0 {
331            if let Some(composite) = self.in_progress_composite.take()
332                && !composite.is_empty()
333            {
334                let target_stack_id = self.composite_stack_id.unwrap_or(0);
335                let stack = self
336                    .stacks
337                    .get_mut(&target_stack_id)
338                    .expect("Stack must exist");
339                stack.undo_stack.push(Box::new(composite));
340                stack.redo_stack.clear();
341            }
342            // not sure if we want to send events for composites
343            if let Some(event_hub) = &self.event_hub {
344                event_hub.send_event(Event {
345                    origin: Origin::UndoRedo(UndoRedoEvent::EndComposite),
346                    ids: Vec::<EntityId>::new(),
347                    data: None,
348                });
349            }
350        }
351    }
352
353    pub fn cancel_composite(&mut self) {
354        // Decrement the nesting level
355        if self.composite_nesting_level > 0 {
356            self.composite_nesting_level -= 1;
357        }
358
359        // Undo any sub-commands that were already executed in this composite
360        if let Some(ref mut composite) = self.in_progress_composite {
361            let _ = composite.undo();
362        }
363
364        self.in_progress_composite = None;
365        self.composite_stack_id = None;
366
367        // not sure if we want to send events for composites
368        if let Some(event_hub) = &self.event_hub {
369            event_hub.send_event(Event {
370                origin: Origin::UndoRedo(UndoRedoEvent::CancelComposite),
371                ids: Vec::<EntityId>::new(),
372                data: None,
373            });
374        }
375    }
376
377    /// Adds a command to the global undo stack (ID 0).
378    pub fn add_command(&mut self, command: Box<dyn UndoRedoCommand>) {
379        let _ = self.add_command_to_stack(command, None);
380    }
381
382    /// Adds a command to the specified undo stack.
383    /// If `stack_id` is None, the global stack (ID 0) is used.
384    ///
385    /// This method handles several cases:
386    /// 1. If a composite command is in progress, the command is added to the composite
387    /// 2. If the command can be merged with the last command on the specified undo stack, they are merged
388    /// 3. Otherwise, the command is added to the specified undo stack as a new entry
389    ///
390    /// In all cases, the redo stack of the stack is cleared when a new command is added.
391    pub fn add_command_to_stack(
392        &mut self,
393        command: Box<dyn UndoRedoCommand>,
394        stack_id: Option<u64>,
395    ) -> Result<()> {
396        // If we have a composite in progress, add the command to it
397        if let Some(composite) = &mut self.in_progress_composite {
398            // ensure that the stack_id is the same as the composite's stack
399            if composite.stack_id != stack_id.unwrap_or(0) {
400                return Err(anyhow!(
401                    "Cannot add command to composite with different stack ID"
402                ));
403            }
404            composite.add_command(command);
405            return Ok(());
406        }
407
408        let target_stack_id = stack_id.unwrap_or(0);
409        let stack = self
410            .stacks
411            .get_mut(&target_stack_id)
412            .ok_or_else(|| anyhow!("Stack with ID {} does not exist", target_stack_id))?;
413
414        // Try to merge with the last command if possible
415        if let Some(last_command) = stack.undo_stack.last_mut()
416            && last_command.can_merge(&*command)
417            && last_command.merge(&*command)
418        {
419            // Successfully merged, no need to add the new command
420            stack.redo_stack.clear();
421            return Ok(());
422        }
423
424        // If we couldn't merge, just add the command normally
425        stack.undo_stack.push(command);
426        stack.redo_stack.clear();
427        Ok(())
428    }
429
430    /// Returns true if there are commands that can be undone on the specified stack.
431    /// If `stack_id` is None, the global stack (ID 0) is used.
432    pub fn can_undo(&self, stack_id: Option<u64>) -> bool {
433        let target_stack_id = stack_id.unwrap_or(0);
434        self.stacks
435            .get(&target_stack_id)
436            .map(|s| !s.undo_stack.is_empty())
437            .unwrap_or(false)
438    }
439
440    /// Returns true if there are commands that can be redone on the specified stack.
441    /// If `stack_id` is None, the global stack (ID 0) is used.
442    pub fn can_redo(&self, stack_id: Option<u64>) -> bool {
443        let target_stack_id = stack_id.unwrap_or(0);
444        self.stacks
445            .get(&target_stack_id)
446            .map(|s| !s.redo_stack.is_empty())
447            .unwrap_or(false)
448    }
449
450    /// Clears the undo and redo history for a specific stack.
451    ///
452    /// This method removes all commands from both the undo and redo stacks of the specified stack.
453    pub fn clear_stack(&mut self, stack_id: u64) {
454        if let Some(stack) = self.stacks.get_mut(&stack_id) {
455            stack.undo_stack.clear();
456            stack.redo_stack.clear();
457        }
458    }
459
460    /// Clears all undo and redo history from all stacks.
461    pub fn clear_all_stacks(&mut self) {
462        for stack in self.stacks.values_mut() {
463            stack.undo_stack.clear();
464            stack.redo_stack.clear();
465        }
466        self.in_progress_composite = None;
467        self.composite_nesting_level = 0;
468    }
469
470    /// Creates a new undo/redo stack and returns its ID.
471    pub fn create_new_stack(&mut self) -> u64 {
472        let id = self.next_stack_id;
473        self.stacks.insert(id, StackData::default());
474        self.next_stack_id += 1;
475        id
476    }
477
478    /// Deletes an undo/redo stack by its ID.
479    ///
480    /// The default stack (ID 0) cannot be deleted.
481    pub fn delete_stack(&mut self, stack_id: u64) -> Result<()> {
482        if stack_id == 0 {
483            return Err(anyhow!("Cannot delete the default stack"));
484        }
485        if self.stacks.remove(&stack_id).is_some() {
486            Ok(())
487        } else {
488            Err(anyhow!("Stack with ID {} does not exist", stack_id))
489        }
490    }
491
492    /// Gets the size of the undo stack for a specific stack.
493    pub fn get_stack_size(&self, stack_id: u64) -> usize {
494        self.stacks
495            .get(&stack_id)
496            .map(|s| s.undo_stack.len())
497            .unwrap_or(0)
498    }
499}