1use std::any::Any;
12use std::collections::HashMap;
13use std::sync::Arc;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct CommandId(u64);
18
19impl CommandId {
20 pub fn as_u64(self) -> u64 {
21 self.0
22 }
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub struct GroupId(u64);
28
29impl GroupId {
30 pub fn as_u64(self) -> u64 {
31 self.0
32 }
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub struct CheckpointId(u64);
38
39impl CheckpointId {
40 pub fn as_u64(self) -> u64 {
41 self.0
42 }
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum CommandResult {
48 Success,
50 Failed(String),
52 Cancelled,
54 NeedsConfirmation(String),
56}
57
58impl CommandResult {
59 pub fn is_success(&self) -> bool {
60 matches!(self, Self::Success)
61 }
62
63 pub fn is_failed(&self) -> bool {
64 matches!(self, Self::Failed(_))
65 }
66}
67
68pub trait Command: Send + Sync {
70 fn execute(&mut self) -> CommandResult;
72
73 fn undo(&mut self) -> CommandResult;
75
76 fn redo(&mut self) -> CommandResult {
78 self.execute()
79 }
80
81 fn description(&self) -> &str;
83
84 fn can_merge(&self, _other: &dyn Command) -> bool {
86 false
87 }
88
89 fn merge(&mut self, _other: Box<dyn Command>) -> Option<Box<dyn Command>> {
91 None
92 }
93
94 fn memory_size(&self) -> usize {
96 64
98 }
99
100 fn metadata(&self) -> Option<&dyn Any> {
102 None
103 }
104}
105
106struct HistoryEntry {
108 #[allow(dead_code)]
109 id: CommandId,
110 command: Box<dyn Command>,
111 group_id: Option<GroupId>,
112 timestamp: u64,
113 #[allow(dead_code)]
114 executed: bool,
115}
116
117#[derive(Debug, Clone)]
119pub struct HistoryConfig {
120 pub max_commands: usize,
122 pub max_memory: usize,
124 pub enable_merging: bool,
126 pub auto_group_interval_ms: u64,
128}
129
130impl Default for HistoryConfig {
131 fn default() -> Self {
132 Self {
133 max_commands: 1000,
134 max_memory: 10 * 1024 * 1024, enable_merging: true,
136 auto_group_interval_ms: 500,
137 }
138 }
139}
140
141#[derive(Debug, Clone)]
143pub struct Checkpoint {
144 pub id: CheckpointId,
145 pub name: String,
146 pub position: usize,
147 pub timestamp: u64,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
152pub enum HistoryEvent {
153 Executed(CommandId),
155 Undone(CommandId),
157 Redone(CommandId),
159 Cleared,
161 CheckpointCreated(CheckpointId),
163 CheckpointRestored(CheckpointId),
165 Trimmed(usize),
167}
168
169pub type HistoryCallback = Arc<dyn Fn(HistoryEvent) + Send + Sync>;
171
172pub struct CommandHistory {
174 config: HistoryConfig,
175 next_command_id: u64,
176 next_group_id: u64,
177 next_checkpoint_id: u64,
178
179 undo_stack: Vec<HistoryEntry>,
181 redo_stack: Vec<HistoryEntry>,
183
184 current_group: Option<GroupId>,
186 checkpoints: HashMap<CheckpointId, Checkpoint>,
188
189 timestamp: u64,
191 last_command_time: u64,
193
194 listeners: Vec<HistoryCallback>,
196
197 current_memory: usize,
199
200 recording: bool,
202}
203
204impl Default for CommandHistory {
205 fn default() -> Self {
206 Self::new(HistoryConfig::default())
207 }
208}
209
210impl CommandHistory {
211 pub fn new(config: HistoryConfig) -> Self {
212 Self {
213 config,
214 next_command_id: 1,
215 next_group_id: 1,
216 next_checkpoint_id: 1,
217 undo_stack: Vec::new(),
218 redo_stack: Vec::new(),
219 current_group: None,
220 checkpoints: HashMap::new(),
221 timestamp: 0,
222 last_command_time: 0,
223 listeners: Vec::new(),
224 current_memory: 0,
225 recording: true,
226 }
227 }
228
229 pub fn execute(&mut self, mut command: Box<dyn Command>) -> CommandResult {
231 if !self.recording {
232 return command.execute();
233 }
234
235 let result = command.execute();
236 if !result.is_success() {
237 return result;
238 }
239
240 self.redo_stack.clear();
242
243 let id = CommandId(self.next_command_id);
244 self.next_command_id += 1;
245
246 let group_id = if self.current_group.is_some() {
248 self.current_group
249 } else if self.config.auto_group_interval_ms > 0
250 && self.timestamp.saturating_sub(self.last_command_time)
251 < self.config.auto_group_interval_ms
252 && !self.undo_stack.is_empty()
253 {
254 self.undo_stack.last().and_then(|e| e.group_id)
256 } else {
257 None
258 };
259
260 if self.config.enable_merging && !self.undo_stack.is_empty() {
262 let can_merge = self
263 .undo_stack
264 .last()
265 .is_some_and(|last| last.command.can_merge(command.as_ref()));
266
267 if can_merge {
268 if let Some(last_entry) = self.undo_stack.last_mut() {
269 if let Some(merged) = last_entry.command.merge(command) {
270 self.current_memory -= last_entry.command.memory_size();
272 self.current_memory += merged.memory_size();
273 last_entry.command = merged;
274 self.emit(&HistoryEvent::Executed(id));
275 return result;
276 }
277 }
278 return result;
281 }
282 }
283
284 let memory = command.memory_size();
285 self.current_memory += memory;
286
287 let entry = HistoryEntry {
288 id,
289 command,
290 group_id,
291 timestamp: self.timestamp,
292 executed: true,
293 };
294
295 self.undo_stack.push(entry);
296 self.last_command_time = self.timestamp;
297
298 self.trim_if_needed();
300
301 self.emit(&HistoryEvent::Executed(id));
302 result
303 }
304
305 pub fn undo(&mut self) -> Option<CommandResult> {
307 let entry = self.undo_stack.pop()?;
308 let id = entry.id;
309 let group_id = entry.group_id;
310
311 let mut command = entry.command;
312 let result = command.undo();
313
314 if result.is_success() {
315 self.current_memory -= command.memory_size();
316
317 let redo_entry = HistoryEntry {
318 id,
319 command,
320 group_id,
321 timestamp: entry.timestamp,
322 executed: false,
323 };
324 self.current_memory += redo_entry.command.memory_size();
325 self.redo_stack.push(redo_entry);
326
327 self.emit(&HistoryEvent::Undone(id));
328
329 if let Some(gid) = group_id {
331 while let Some(last) = self.undo_stack.last() {
332 if last.group_id == Some(gid) {
333 self.undo();
334 } else {
335 break;
336 }
337 }
338 }
339 }
340
341 Some(result)
342 }
343
344 pub fn redo(&mut self) -> Option<CommandResult> {
346 let entry = self.redo_stack.pop()?;
347 let id = entry.id;
348 let group_id = entry.group_id;
349
350 let mut command = entry.command;
351 let result = command.redo();
352
353 if result.is_success() {
354 self.current_memory -= command.memory_size();
355
356 let undo_entry = HistoryEntry {
357 id,
358 command,
359 group_id,
360 timestamp: entry.timestamp,
361 executed: true,
362 };
363 self.current_memory += undo_entry.command.memory_size();
364 self.undo_stack.push(undo_entry);
365
366 self.emit(&HistoryEvent::Redone(id));
367
368 if let Some(gid) = group_id {
370 while let Some(last) = self.redo_stack.last() {
371 if last.group_id == Some(gid) {
372 self.redo();
373 } else {
374 break;
375 }
376 }
377 }
378 }
379
380 Some(result)
381 }
382
383 pub fn can_undo(&self) -> bool {
385 !self.undo_stack.is_empty()
386 }
387
388 pub fn can_redo(&self) -> bool {
390 !self.redo_stack.is_empty()
391 }
392
393 pub fn undo_count(&self) -> usize {
395 self.undo_stack.len()
396 }
397
398 pub fn redo_count(&self) -> usize {
400 self.redo_stack.len()
401 }
402
403 pub fn undo_description(&self) -> Option<&str> {
405 self.undo_stack.last().map(|e| e.command.description())
406 }
407
408 pub fn redo_description(&self) -> Option<&str> {
410 self.redo_stack.last().map(|e| e.command.description())
411 }
412
413 pub fn begin_group(&mut self) -> GroupId {
415 let id = GroupId(self.next_group_id);
416 self.next_group_id += 1;
417 self.current_group = Some(id);
418 id
419 }
420
421 pub fn end_group(&mut self) {
423 self.current_group = None;
424 }
425
426 pub fn execute_group<I>(&mut self, commands: I) -> Vec<CommandResult>
428 where
429 I: IntoIterator<Item = Box<dyn Command>>,
430 {
431 let _group = self.begin_group();
432 let results: Vec<_> = commands.into_iter().map(|cmd| self.execute(cmd)).collect();
433 self.end_group();
434 results
435 }
436
437 pub fn create_checkpoint(&mut self, name: impl Into<String>) -> CheckpointId {
439 let id = CheckpointId(self.next_checkpoint_id);
440 self.next_checkpoint_id += 1;
441
442 let checkpoint = Checkpoint {
443 id,
444 name: name.into(),
445 position: self.undo_stack.len(),
446 timestamp: self.timestamp,
447 };
448
449 self.checkpoints.insert(id, checkpoint);
450 self.emit(&HistoryEvent::CheckpointCreated(id));
451 id
452 }
453
454 pub fn restore_checkpoint(&mut self, id: CheckpointId) -> bool {
456 let checkpoint = match self.checkpoints.get(&id) {
457 Some(c) => c.clone(),
458 None => return false,
459 };
460
461 while self.undo_stack.len() > checkpoint.position {
463 if self.undo().is_none() {
464 break;
465 }
466 }
467
468 self.emit(&HistoryEvent::CheckpointRestored(id));
469 true
470 }
471
472 pub fn get_checkpoint(&self, id: CheckpointId) -> Option<&Checkpoint> {
474 self.checkpoints.get(&id)
475 }
476
477 pub fn checkpoints(&self) -> impl Iterator<Item = &Checkpoint> {
479 self.checkpoints.values()
480 }
481
482 pub fn clear(&mut self) {
484 self.undo_stack.clear();
485 self.redo_stack.clear();
486 self.checkpoints.clear();
487 self.current_memory = 0;
488 self.emit(&HistoryEvent::Cleared);
489 }
490
491 pub fn memory_usage(&self) -> usize {
493 self.current_memory
494 }
495
496 pub fn tick(&mut self, delta_ms: u64) {
498 self.timestamp += delta_ms;
499 }
500
501 pub fn pause(&mut self) {
503 self.recording = false;
504 }
505
506 pub fn resume(&mut self) {
508 self.recording = true;
509 }
510
511 pub fn is_recording(&self) -> bool {
513 self.recording
514 }
515
516 pub fn on_event(&mut self, callback: HistoryCallback) {
518 self.listeners.push(callback);
519 }
520
521 fn emit(&self, event: &HistoryEvent) {
522 for listener in &self.listeners {
523 listener(event.clone());
524 }
525 }
526
527 fn trim_if_needed(&mut self) {
528 let mut trimmed = 0;
529
530 while self.undo_stack.len() > self.config.max_commands {
532 if let Some(entry) = self.undo_stack.first() {
533 self.current_memory -= entry.command.memory_size();
534 }
535 self.undo_stack.remove(0);
536 trimmed += 1;
537 }
538
539 while self.current_memory > self.config.max_memory && !self.undo_stack.is_empty() {
541 if let Some(entry) = self.undo_stack.first() {
542 self.current_memory -= entry.command.memory_size();
543 }
544 self.undo_stack.remove(0);
545 trimmed += 1;
546 }
547
548 if trimmed > 0 {
549 for checkpoint in self.checkpoints.values_mut() {
551 checkpoint.position = checkpoint.position.saturating_sub(trimmed);
552 }
553 self.emit(&HistoryEvent::Trimmed(trimmed));
554 }
555 }
556}
557
558pub struct CompositeCommand {
560 description: String,
561 commands: Vec<Box<dyn Command>>,
562 executed: Vec<bool>,
563}
564
565impl CompositeCommand {
566 pub fn new(description: impl Into<String>) -> Self {
567 Self {
568 description: description.into(),
569 commands: Vec::new(),
570 executed: Vec::new(),
571 }
572 }
573
574 pub fn with_command(mut self, command: Box<dyn Command>) -> Self {
575 self.commands.push(command);
576 self
577 }
578
579 pub fn build(self) -> Box<dyn Command> {
580 Box::new(CompositeCommandImpl {
581 description: self.description,
582 commands: self.commands,
583 executed: self.executed,
584 })
585 }
586}
587
588struct CompositeCommandImpl {
589 description: String,
590 commands: Vec<Box<dyn Command>>,
591 executed: Vec<bool>,
592}
593
594impl Command for CompositeCommandImpl {
595 fn execute(&mut self) -> CommandResult {
596 self.executed.clear();
597 for cmd in &mut self.commands {
598 let result = cmd.execute();
599 if !result.is_success() {
600 for (i, was_executed) in self.executed.iter().enumerate().rev() {
602 if *was_executed {
603 self.commands[i].undo();
604 }
605 }
606 return result;
607 }
608 self.executed.push(true);
609 }
610 CommandResult::Success
611 }
612
613 fn undo(&mut self) -> CommandResult {
614 for (i, cmd) in self.commands.iter_mut().enumerate().rev() {
615 if i < self.executed.len() && self.executed[i] {
616 let result = cmd.undo();
617 if !result.is_success() {
618 return result;
619 }
620 }
621 }
622 self.executed.clear();
623 CommandResult::Success
624 }
625
626 fn description(&self) -> &str {
627 &self.description
628 }
629
630 fn memory_size(&self) -> usize {
631 std::mem::size_of::<Self>() + self.commands.iter().map(|c| c.memory_size()).sum::<usize>()
632 }
633}
634
635pub struct SetValueCommand<T: Clone + Send + Sync + 'static> {
637 description: String,
638 value: Arc<std::sync::RwLock<T>>,
639 old_value: Option<T>,
640 new_value: T,
641}
642
643impl<T: Clone + Send + Sync + 'static> SetValueCommand<T> {
644 pub fn new(
645 description: impl Into<String>,
646 value: Arc<std::sync::RwLock<T>>,
647 new_value: T,
648 ) -> Self {
649 Self {
650 description: description.into(),
651 value,
652 old_value: None,
653 new_value,
654 }
655 }
656}
657
658impl<T: Clone + Send + Sync + 'static> Command for SetValueCommand<T> {
659 fn execute(&mut self) -> CommandResult {
660 let Ok(mut guard) = self.value.write() else {
661 return CommandResult::Failed("Lock poisoned".into());
662 };
663 self.old_value = Some(guard.clone());
664 *guard = self.new_value.clone();
665 CommandResult::Success
666 }
667
668 fn undo(&mut self) -> CommandResult {
669 let Some(old) = self.old_value.clone() else {
670 return CommandResult::Failed("No old value".into());
671 };
672 let Ok(mut guard) = self.value.write() else {
673 return CommandResult::Failed("Lock poisoned".into());
674 };
675 *guard = old;
676 CommandResult::Success
677 }
678
679 fn description(&self) -> &str {
680 &self.description
681 }
682}
683
684#[cfg(test)]
685#[allow(clippy::unwrap_used, clippy::disallowed_methods)]
686mod tests {
687 use super::*;
688 use std::sync::atomic::{AtomicI32, Ordering};
689
690 struct IncrementCommand {
692 counter: Arc<AtomicI32>,
693 amount: i32,
694 }
695
696 impl Command for IncrementCommand {
697 fn execute(&mut self) -> CommandResult {
698 self.counter.fetch_add(self.amount, Ordering::SeqCst);
699 CommandResult::Success
700 }
701
702 fn undo(&mut self) -> CommandResult {
703 self.counter.fetch_sub(self.amount, Ordering::SeqCst);
704 CommandResult::Success
705 }
706
707 fn description(&self) -> &'static str {
708 "Increment counter"
709 }
710 }
711
712 struct FailingCommand {
714 should_fail: bool,
715 }
716
717 impl Command for FailingCommand {
718 fn execute(&mut self) -> CommandResult {
719 if self.should_fail {
720 CommandResult::Failed("Intentional failure".into())
721 } else {
722 CommandResult::Success
723 }
724 }
725
726 fn undo(&mut self) -> CommandResult {
727 CommandResult::Success
728 }
729
730 fn description(&self) -> &'static str {
731 "Failing command"
732 }
733 }
734
735 #[test]
736 fn test_basic_execute() {
737 let mut history = CommandHistory::default();
738 let counter = Arc::new(AtomicI32::new(0));
739
740 let cmd = Box::new(IncrementCommand {
741 counter: counter.clone(),
742 amount: 5,
743 });
744
745 let result = history.execute(cmd);
746 assert!(result.is_success());
747 assert_eq!(counter.load(Ordering::SeqCst), 5);
748 assert_eq!(history.undo_count(), 1);
749 }
750
751 #[test]
752 fn test_undo() {
753 let mut history = CommandHistory::default();
754 let counter = Arc::new(AtomicI32::new(0));
755
756 history.execute(Box::new(IncrementCommand {
757 counter: counter.clone(),
758 amount: 10,
759 }));
760 assert_eq!(counter.load(Ordering::SeqCst), 10);
761
762 let result = history.undo();
763 assert!(result.is_some());
764 assert!(result.unwrap().is_success());
765 assert_eq!(counter.load(Ordering::SeqCst), 0);
766 }
767
768 #[test]
769 fn test_redo() {
770 let mut history = CommandHistory::default();
771 let counter = Arc::new(AtomicI32::new(0));
772
773 history.execute(Box::new(IncrementCommand {
774 counter: counter.clone(),
775 amount: 7,
776 }));
777 history.undo();
778 assert_eq!(counter.load(Ordering::SeqCst), 0);
779
780 let result = history.redo();
781 assert!(result.is_some());
782 assert!(result.unwrap().is_success());
783 assert_eq!(counter.load(Ordering::SeqCst), 7);
784 }
785
786 #[test]
787 fn test_multiple_undo_redo() {
788 let mut history = CommandHistory::default();
789 let counter = Arc::new(AtomicI32::new(0));
790
791 history.execute(Box::new(IncrementCommand {
792 counter: counter.clone(),
793 amount: 1,
794 }));
795 history.execute(Box::new(IncrementCommand {
796 counter: counter.clone(),
797 amount: 2,
798 }));
799 history.execute(Box::new(IncrementCommand {
800 counter: counter.clone(),
801 amount: 3,
802 }));
803
804 assert_eq!(counter.load(Ordering::SeqCst), 6);
805 assert_eq!(history.undo_count(), 3);
806
807 history.undo();
808 assert_eq!(counter.load(Ordering::SeqCst), 3);
809
810 history.undo();
811 assert_eq!(counter.load(Ordering::SeqCst), 1);
812
813 history.redo();
814 assert_eq!(counter.load(Ordering::SeqCst), 3);
815 }
816
817 #[test]
818 fn test_can_undo_redo() {
819 let mut history = CommandHistory::default();
820 let counter = Arc::new(AtomicI32::new(0));
821
822 assert!(!history.can_undo());
823 assert!(!history.can_redo());
824
825 history.execute(Box::new(IncrementCommand { counter, amount: 1 }));
826
827 assert!(history.can_undo());
828 assert!(!history.can_redo());
829
830 history.undo();
831 assert!(!history.can_undo());
832 assert!(history.can_redo());
833 }
834
835 #[test]
836 fn test_redo_cleared_on_new_execute() {
837 let mut history = CommandHistory::default();
838 let counter = Arc::new(AtomicI32::new(0));
839
840 history.execute(Box::new(IncrementCommand {
841 counter: counter.clone(),
842 amount: 1,
843 }));
844 history.execute(Box::new(IncrementCommand {
845 counter: counter.clone(),
846 amount: 2,
847 }));
848
849 history.undo();
850 assert!(history.can_redo());
851
852 history.execute(Box::new(IncrementCommand { counter, amount: 5 }));
854
855 assert!(!history.can_redo());
856 }
857
858 #[test]
859 fn test_failed_command_not_added() {
860 let mut history = CommandHistory::default();
861
862 let result = history.execute(Box::new(FailingCommand { should_fail: true }));
863 assert!(result.is_failed());
864 assert_eq!(history.undo_count(), 0);
865 }
866
867 #[test]
868 fn test_command_descriptions() {
869 let mut history = CommandHistory::default();
870 let counter = Arc::new(AtomicI32::new(0));
871
872 assert!(history.undo_description().is_none());
873
874 history.execute(Box::new(IncrementCommand { counter, amount: 1 }));
875
876 assert_eq!(history.undo_description(), Some("Increment counter"));
877 }
878
879 #[test]
880 fn test_command_groups() {
881 let mut history = CommandHistory::default();
882 let counter = Arc::new(AtomicI32::new(0));
883
884 let _group = history.begin_group();
885 history.execute(Box::new(IncrementCommand {
886 counter: counter.clone(),
887 amount: 1,
888 }));
889 history.execute(Box::new(IncrementCommand {
890 counter: counter.clone(),
891 amount: 2,
892 }));
893 history.execute(Box::new(IncrementCommand {
894 counter: counter.clone(),
895 amount: 3,
896 }));
897 history.end_group();
898
899 assert_eq!(counter.load(Ordering::SeqCst), 6);
900
901 history.undo();
903 assert_eq!(counter.load(Ordering::SeqCst), 0);
904 }
905
906 #[test]
907 fn test_execute_group() {
908 let mut history = CommandHistory::default();
909 let counter = Arc::new(AtomicI32::new(0));
910
911 let commands: Vec<Box<dyn Command>> = vec![
912 Box::new(IncrementCommand {
913 counter: counter.clone(),
914 amount: 1,
915 }),
916 Box::new(IncrementCommand {
917 counter: counter.clone(),
918 amount: 2,
919 }),
920 ];
921
922 let results = history.execute_group(commands);
923 assert!(results.iter().all(super::CommandResult::is_success));
924 assert_eq!(counter.load(Ordering::SeqCst), 3);
925 }
926
927 #[test]
928 fn test_checkpoints() {
929 let mut history = CommandHistory::default();
930 let counter = Arc::new(AtomicI32::new(0));
931
932 history.execute(Box::new(IncrementCommand {
933 counter: counter.clone(),
934 amount: 5,
935 }));
936
937 let checkpoint = history.create_checkpoint("Initial state");
938
939 history.execute(Box::new(IncrementCommand {
940 counter: counter.clone(),
941 amount: 10,
942 }));
943 history.execute(Box::new(IncrementCommand {
944 counter: counter.clone(),
945 amount: 15,
946 }));
947
948 assert_eq!(counter.load(Ordering::SeqCst), 30);
949
950 let restored = history.restore_checkpoint(checkpoint);
952 assert!(restored);
953 assert_eq!(counter.load(Ordering::SeqCst), 5);
954 }
955
956 #[test]
957 fn test_get_checkpoint() {
958 let mut history = CommandHistory::default();
959
960 let id = history.create_checkpoint("Test checkpoint");
961 let checkpoint = history.get_checkpoint(id);
962
963 assert!(checkpoint.is_some());
964 assert_eq!(checkpoint.unwrap().name, "Test checkpoint");
965 }
966
967 #[test]
968 fn test_list_checkpoints() {
969 let mut history = CommandHistory::default();
970
971 history.create_checkpoint("First");
972 history.create_checkpoint("Second");
973 history.create_checkpoint("Third");
974
975 assert_eq!(history.checkpoints().count(), 3);
976 }
977
978 #[test]
979 fn test_clear() {
980 let mut history = CommandHistory::default();
981 let counter = Arc::new(AtomicI32::new(0));
982
983 history.execute(Box::new(IncrementCommand {
984 counter: counter.clone(),
985 amount: 1,
986 }));
987 history.execute(Box::new(IncrementCommand { counter, amount: 2 }));
988 history.create_checkpoint("Test");
989
990 history.clear();
991
992 assert!(!history.can_undo());
993 assert!(!history.can_redo());
994 assert_eq!(history.checkpoints().count(), 0);
995 }
996
997 #[test]
998 fn test_max_commands_limit() {
999 let config = HistoryConfig {
1000 max_commands: 3,
1001 ..Default::default()
1002 };
1003 let mut history = CommandHistory::new(config);
1004 let counter = Arc::new(AtomicI32::new(0));
1005
1006 for i in 0..5 {
1007 history.execute(Box::new(IncrementCommand {
1008 counter: counter.clone(),
1009 amount: i + 1,
1010 }));
1011 }
1012
1013 assert_eq!(history.undo_count(), 3);
1014 }
1015
1016 #[test]
1017 fn test_pause_resume_recording() {
1018 let mut history = CommandHistory::default();
1019 let counter = Arc::new(AtomicI32::new(0));
1020
1021 history.execute(Box::new(IncrementCommand {
1022 counter: counter.clone(),
1023 amount: 1,
1024 }));
1025
1026 history.pause();
1027 assert!(!history.is_recording());
1028
1029 history.execute(Box::new(IncrementCommand {
1030 counter: counter.clone(),
1031 amount: 2,
1032 }));
1033
1034 assert_eq!(counter.load(Ordering::SeqCst), 3);
1036 assert_eq!(history.undo_count(), 1);
1037
1038 history.resume();
1039 assert!(history.is_recording());
1040
1041 history.execute(Box::new(IncrementCommand { counter, amount: 3 }));
1042 assert_eq!(history.undo_count(), 2);
1043 }
1044
1045 #[test]
1046 fn test_event_callbacks() {
1047 use std::sync::atomic::AtomicUsize;
1048
1049 let mut history = CommandHistory::default();
1050 let counter = Arc::new(AtomicI32::new(0));
1051 let event_count = Arc::new(AtomicUsize::new(0));
1052
1053 let ec = event_count.clone();
1054 history.on_event(Arc::new(move |_event| {
1055 ec.fetch_add(1, Ordering::SeqCst);
1056 }));
1057
1058 history.execute(Box::new(IncrementCommand { counter, amount: 1 }));
1059 history.undo();
1060 history.redo();
1061
1062 assert_eq!(event_count.load(Ordering::SeqCst), 3);
1063 }
1064
1065 #[test]
1066 fn test_memory_tracking() {
1067 let mut history = CommandHistory::default();
1068 let counter = Arc::new(AtomicI32::new(0));
1069
1070 assert_eq!(history.memory_usage(), 0);
1071
1072 history.execute(Box::new(IncrementCommand { counter, amount: 1 }));
1073
1074 assert!(history.memory_usage() > 0);
1075 }
1076
1077 #[test]
1078 fn test_tick() {
1079 let mut history = CommandHistory::default();
1080 history.tick(100);
1081 history.tick(50);
1082 }
1084
1085 #[test]
1086 fn test_command_result_helpers() {
1087 let success = CommandResult::Success;
1088 let failed = CommandResult::Failed("error".into());
1089 let cancelled = CommandResult::Cancelled;
1090
1091 assert!(success.is_success());
1092 assert!(!success.is_failed());
1093
1094 assert!(!failed.is_success());
1095 assert!(failed.is_failed());
1096
1097 assert!(!cancelled.is_success());
1098 assert!(!cancelled.is_failed());
1099 }
1100
1101 #[test]
1102 fn test_command_id() {
1103 let id = CommandId(42);
1104 assert_eq!(id.as_u64(), 42);
1105 }
1106
1107 #[test]
1108 fn test_group_id() {
1109 let id = GroupId(123);
1110 assert_eq!(id.as_u64(), 123);
1111 }
1112
1113 #[test]
1114 fn test_checkpoint_id() {
1115 let id = CheckpointId(456);
1116 assert_eq!(id.as_u64(), 456);
1117 }
1118
1119 #[test]
1120 fn test_composite_command() {
1121 let counter = Arc::new(AtomicI32::new(0));
1122
1123 let composite = CompositeCommand::new("Add 6")
1124 .with_command(Box::new(IncrementCommand {
1125 counter: counter.clone(),
1126 amount: 1,
1127 }))
1128 .with_command(Box::new(IncrementCommand {
1129 counter: counter.clone(),
1130 amount: 2,
1131 }))
1132 .with_command(Box::new(IncrementCommand {
1133 counter: counter.clone(),
1134 amount: 3,
1135 }))
1136 .build();
1137
1138 let mut history = CommandHistory::default();
1139 history.execute(composite);
1140
1141 assert_eq!(counter.load(Ordering::SeqCst), 6);
1142
1143 history.undo();
1144 assert_eq!(counter.load(Ordering::SeqCst), 0);
1145
1146 history.redo();
1147 assert_eq!(counter.load(Ordering::SeqCst), 6);
1148 }
1149
1150 #[test]
1151 fn test_composite_command_rollback_on_failure() {
1152 let counter = Arc::new(AtomicI32::new(0));
1153
1154 let composite = CompositeCommand::new("Should fail")
1155 .with_command(Box::new(IncrementCommand {
1156 counter: counter.clone(),
1157 amount: 5,
1158 }))
1159 .with_command(Box::new(FailingCommand { should_fail: true }))
1160 .build();
1161
1162 let mut history = CommandHistory::default();
1163 let result = history.execute(composite);
1164
1165 assert!(result.is_failed());
1166 assert_eq!(counter.load(Ordering::SeqCst), 0);
1168 }
1169
1170 #[test]
1171 fn test_set_value_command() {
1172 let value = Arc::new(std::sync::RwLock::new(10));
1173
1174 let cmd = Box::new(SetValueCommand::new("Set to 42", value.clone(), 42));
1175
1176 let mut history = CommandHistory::default();
1177 history.execute(cmd);
1178
1179 assert_eq!(*value.read().unwrap(), 42);
1180
1181 history.undo();
1182 assert_eq!(*value.read().unwrap(), 10);
1183
1184 history.redo();
1185 assert_eq!(*value.read().unwrap(), 42);
1186 }
1187
1188 #[test]
1189 fn test_default_config() {
1190 let config = HistoryConfig::default();
1191 assert_eq!(config.max_commands, 1000);
1192 assert_eq!(config.max_memory, 10 * 1024 * 1024);
1193 assert!(config.enable_merging);
1194 assert_eq!(config.auto_group_interval_ms, 500);
1195 }
1196
1197 #[test]
1198 fn test_undo_on_empty_returns_none() {
1199 let mut history = CommandHistory::default();
1200 assert!(history.undo().is_none());
1201 }
1202
1203 #[test]
1204 fn test_redo_on_empty_returns_none() {
1205 let mut history = CommandHistory::default();
1206 assert!(history.redo().is_none());
1207 }
1208
1209 #[test]
1210 fn test_restore_invalid_checkpoint() {
1211 let mut history = CommandHistory::default();
1212 let invalid_id = CheckpointId(999);
1213 assert!(!history.restore_checkpoint(invalid_id));
1214 }
1215
1216 #[test]
1217 fn test_history_event_variants() {
1218 let events = vec![
1219 HistoryEvent::Executed(CommandId(1)),
1220 HistoryEvent::Undone(CommandId(2)),
1221 HistoryEvent::Redone(CommandId(3)),
1222 HistoryEvent::Cleared,
1223 HistoryEvent::CheckpointCreated(CheckpointId(1)),
1224 HistoryEvent::CheckpointRestored(CheckpointId(1)),
1225 HistoryEvent::Trimmed(5),
1226 ];
1227
1228 for event in &events {
1230 assert_eq!(event, event);
1231 }
1232 }
1233
1234 #[test]
1235 fn test_redo_description() {
1236 let mut history = CommandHistory::default();
1237 let counter = Arc::new(AtomicI32::new(0));
1238
1239 assert!(history.redo_description().is_none());
1240
1241 history.execute(Box::new(IncrementCommand { counter, amount: 1 }));
1242 history.undo();
1243
1244 assert_eq!(history.redo_description(), Some("Increment counter"));
1245 }
1246
1247 #[test]
1248 fn test_redo_count() {
1249 let mut history = CommandHistory::default();
1250 let counter = Arc::new(AtomicI32::new(0));
1251
1252 assert_eq!(history.redo_count(), 0);
1253
1254 history.execute(Box::new(IncrementCommand {
1255 counter: counter.clone(),
1256 amount: 1,
1257 }));
1258 history.execute(Box::new(IncrementCommand { counter, amount: 2 }));
1259
1260 history.undo();
1261 assert_eq!(history.redo_count(), 1);
1262
1263 history.undo();
1264 assert_eq!(history.redo_count(), 2);
1265 }
1266
1267 #[test]
1268 fn test_composite_command_description() {
1269 let counter = Arc::new(AtomicI32::new(0));
1270
1271 let composite = CompositeCommand::new("My Composite")
1272 .with_command(Box::new(IncrementCommand { counter, amount: 1 }))
1273 .build();
1274
1275 assert_eq!(composite.description(), "My Composite");
1276 }
1277
1278 #[test]
1283 fn test_command_id_debug() {
1284 let id = CommandId(42);
1285 let debug = format!("{id:?}");
1286 assert!(debug.contains("CommandId"));
1287 assert!(debug.contains("42"));
1288 }
1289
1290 #[test]
1291 fn test_command_id_eq() {
1292 let id1 = CommandId(100);
1293 let id2 = CommandId(100);
1294 let id3 = CommandId(200);
1295 assert_eq!(id1, id2);
1296 assert_ne!(id1, id3);
1297 }
1298
1299 #[test]
1300 fn test_command_id_hash() {
1301 use std::collections::HashSet;
1302 let mut set = HashSet::new();
1303 set.insert(CommandId(1));
1304 set.insert(CommandId(2));
1305 set.insert(CommandId(1)); assert_eq!(set.len(), 2);
1307 }
1308
1309 #[test]
1310 fn test_group_id_debug() {
1311 let id = GroupId(99);
1312 let debug = format!("{id:?}");
1313 assert!(debug.contains("GroupId"));
1314 }
1315
1316 #[test]
1317 fn test_group_id_hash() {
1318 use std::collections::HashSet;
1319 let mut set = HashSet::new();
1320 set.insert(GroupId(1));
1321 set.insert(GroupId(2));
1322 assert_eq!(set.len(), 2);
1323 }
1324
1325 #[test]
1326 fn test_checkpoint_id_debug() {
1327 let id = CheckpointId(77);
1328 let debug = format!("{id:?}");
1329 assert!(debug.contains("CheckpointId"));
1330 }
1331
1332 #[test]
1333 fn test_checkpoint_id_hash() {
1334 use std::collections::HashSet;
1335 let mut set = HashSet::new();
1336 set.insert(CheckpointId(1));
1337 set.insert(CheckpointId(2));
1338 assert_eq!(set.len(), 2);
1339 }
1340
1341 #[test]
1342 fn test_command_result_debug() {
1343 let result = CommandResult::Success;
1344 let debug = format!("{result:?}");
1345 assert!(debug.contains("Success"));
1346
1347 let failed = CommandResult::Failed("error".to_string());
1348 let debug = format!("{failed:?}");
1349 assert!(debug.contains("Failed"));
1350 }
1351
1352 #[test]
1353 fn test_command_result_clone() {
1354 let result = CommandResult::NeedsConfirmation("confirm?".to_string());
1355 let cloned = result.clone();
1356 assert_eq!(result, cloned);
1357 }
1358
1359 #[test]
1360 fn test_command_result_all_variants() {
1361 let variants = vec![
1362 CommandResult::Success,
1363 CommandResult::Failed("error".to_string()),
1364 CommandResult::Cancelled,
1365 CommandResult::NeedsConfirmation("confirm".to_string()),
1366 ];
1367
1368 for variant in &variants {
1369 let _ = format!("{variant:?}");
1370 let cloned = variant.clone();
1371 assert_eq!(variant, &cloned);
1372 }
1373 }
1374
1375 #[test]
1376 fn test_history_config_debug() {
1377 let config = HistoryConfig::default();
1378 let debug = format!("{config:?}");
1379 assert!(debug.contains("HistoryConfig"));
1380 }
1381
1382 #[test]
1383 fn test_history_config_clone() {
1384 let config = HistoryConfig {
1385 max_commands: 500,
1386 max_memory: 5 * 1024 * 1024,
1387 enable_merging: false,
1388 auto_group_interval_ms: 1000,
1389 };
1390 let cloned = config;
1391 assert_eq!(cloned.max_commands, 500);
1392 assert!(!cloned.enable_merging);
1393 }
1394
1395 #[test]
1396 fn test_checkpoint_debug() {
1397 let checkpoint = Checkpoint {
1398 id: CheckpointId(1),
1399 name: "Test".to_string(),
1400 position: 5,
1401 timestamp: 1000,
1402 };
1403 let debug = format!("{checkpoint:?}");
1404 assert!(debug.contains("Checkpoint"));
1405 }
1406
1407 #[test]
1408 fn test_checkpoint_clone() {
1409 let checkpoint = Checkpoint {
1410 id: CheckpointId(1),
1411 name: "Test".to_string(),
1412 position: 5,
1413 timestamp: 1000,
1414 };
1415 let cloned = checkpoint;
1416 assert_eq!(cloned.name, "Test");
1417 assert_eq!(cloned.position, 5);
1418 }
1419
1420 #[test]
1421 fn test_history_event_debug() {
1422 let event = HistoryEvent::Cleared;
1423 let debug = format!("{event:?}");
1424 assert!(debug.contains("Cleared"));
1425 }
1426
1427 #[test]
1428 fn test_history_event_clone() {
1429 let event = HistoryEvent::Trimmed(10);
1430 let cloned = event.clone();
1431 assert_eq!(event, cloned);
1432 }
1433
1434 #[test]
1435 fn test_memory_limit_trimming() {
1436 let config = HistoryConfig {
1437 max_commands: 1000,
1438 max_memory: 200, ..Default::default()
1440 };
1441 let mut history = CommandHistory::new(config);
1442 let counter = Arc::new(AtomicI32::new(0));
1443
1444 for i in 0..10 {
1446 history.execute(Box::new(IncrementCommand {
1447 counter: counter.clone(),
1448 amount: i,
1449 }));
1450 }
1451
1452 assert!(history.undo_count() < 10);
1454 }
1455
1456 #[test]
1457 fn test_default_command_methods() {
1458 let counter = Arc::new(AtomicI32::new(0));
1459 let cmd = IncrementCommand { counter, amount: 1 };
1460
1461 assert!(!cmd.can_merge(&cmd));
1463 assert_eq!(cmd.memory_size(), 64);
1464 assert!(cmd.metadata().is_none());
1465 }
1466
1467 #[test]
1468 fn test_composite_command_memory_size() {
1469 let counter = Arc::new(AtomicI32::new(0));
1470
1471 let composite = CompositeCommand::new("Multi")
1472 .with_command(Box::new(IncrementCommand {
1473 counter: counter.clone(),
1474 amount: 1,
1475 }))
1476 .with_command(Box::new(IncrementCommand { counter, amount: 2 }))
1477 .build();
1478
1479 let memory = composite.memory_size();
1480 assert!(memory > 64); }
1482
1483 #[test]
1484 fn test_group_redo() {
1485 let mut history = CommandHistory::default();
1486 let counter = Arc::new(AtomicI32::new(0));
1487
1488 let _group = history.begin_group();
1489 history.execute(Box::new(IncrementCommand {
1490 counter: counter.clone(),
1491 amount: 1,
1492 }));
1493 history.execute(Box::new(IncrementCommand {
1494 counter: counter.clone(),
1495 amount: 2,
1496 }));
1497 history.end_group();
1498
1499 assert_eq!(counter.load(Ordering::SeqCst), 3);
1500
1501 history.undo();
1502 assert_eq!(counter.load(Ordering::SeqCst), 0);
1503
1504 history.redo();
1505 assert_eq!(counter.load(Ordering::SeqCst), 3);
1506 }
1507
1508 #[test]
1509 fn test_checkpoint_position_updates_on_trim() {
1510 let config = HistoryConfig {
1511 max_commands: 3,
1512 ..Default::default()
1513 };
1514 let mut history = CommandHistory::new(config);
1515 let counter = Arc::new(AtomicI32::new(0));
1516
1517 history.execute(Box::new(IncrementCommand {
1518 counter: counter.clone(),
1519 amount: 1,
1520 }));
1521
1522 let checkpoint_id = history.create_checkpoint("Early");
1523
1524 for i in 2..=5 {
1526 history.execute(Box::new(IncrementCommand {
1527 counter: counter.clone(),
1528 amount: i,
1529 }));
1530 }
1531
1532 let checkpoint = history.get_checkpoint(checkpoint_id);
1534 assert!(checkpoint.is_some());
1535 }
1536
1537 #[test]
1538 fn test_multiple_event_listeners() {
1539 use std::sync::atomic::AtomicUsize;
1540
1541 let mut history = CommandHistory::default();
1542 let counter = Arc::new(AtomicI32::new(0));
1543 let event_count1 = Arc::new(AtomicUsize::new(0));
1544 let event_count2 = Arc::new(AtomicUsize::new(0));
1545
1546 let ec1 = event_count1.clone();
1547 history.on_event(Arc::new(move |_| {
1548 ec1.fetch_add(1, Ordering::SeqCst);
1549 }));
1550
1551 let ec2 = event_count2.clone();
1552 history.on_event(Arc::new(move |_| {
1553 ec2.fetch_add(1, Ordering::SeqCst);
1554 }));
1555
1556 history.execute(Box::new(IncrementCommand { counter, amount: 1 }));
1557
1558 assert_eq!(event_count1.load(Ordering::SeqCst), 1);
1559 assert_eq!(event_count2.load(Ordering::SeqCst), 1);
1560 }
1561
1562 #[test]
1563 fn test_set_value_command_description() {
1564 let value = Arc::new(std::sync::RwLock::new(0));
1565 let cmd = SetValueCommand::new("Set value", value, 100);
1566 assert_eq!(cmd.description(), "Set value");
1567 }
1568
1569 #[test]
1570 fn test_undo_without_old_value() {
1571 let value = Arc::new(std::sync::RwLock::new(10));
1572 let mut cmd = SetValueCommand::new("Set", value, 42);
1573
1574 let result = cmd.undo();
1576 assert!(result.is_failed());
1577 }
1578
1579 #[test]
1580 fn test_empty_composite_command() {
1581 let composite = CompositeCommand::new("Empty").build();
1582
1583 let mut history = CommandHistory::default();
1584 let result = history.execute(composite);
1585
1586 assert!(result.is_success());
1587 }
1588
1589 #[test]
1590 fn test_get_nonexistent_checkpoint() {
1591 let history = CommandHistory::default();
1592 let result = history.get_checkpoint(CheckpointId(999));
1593 assert!(result.is_none());
1594 }
1595
1596 #[test]
1597 fn test_history_clear_resets_memory() {
1598 let mut history = CommandHistory::default();
1599 let counter = Arc::new(AtomicI32::new(0));
1600
1601 history.execute(Box::new(IncrementCommand { counter, amount: 1 }));
1602
1603 assert!(history.memory_usage() > 0);
1604
1605 history.clear();
1606 assert_eq!(history.memory_usage(), 0);
1607 }
1608
1609 struct MergeableIncrement {
1611 counter: Arc<AtomicI32>,
1612 total_amount: i32,
1613 }
1614
1615 impl Command for MergeableIncrement {
1616 fn execute(&mut self) -> CommandResult {
1617 self.counter.fetch_add(self.total_amount, Ordering::SeqCst);
1618 CommandResult::Success
1619 }
1620
1621 fn undo(&mut self) -> CommandResult {
1622 self.counter.fetch_sub(self.total_amount, Ordering::SeqCst);
1623 CommandResult::Success
1624 }
1625
1626 fn description(&self) -> &'static str {
1627 "Mergeable increment"
1628 }
1629
1630 fn can_merge(&self, _other: &dyn Command) -> bool {
1631 true
1632 }
1633
1634 fn merge(&mut self, _other: Box<dyn Command>) -> Option<Box<dyn Command>> {
1635 self.total_amount += 10;
1637 None
1638 }
1639 }
1640
1641 #[test]
1642 fn test_command_merging() {
1643 let config = HistoryConfig {
1644 enable_merging: true,
1645 auto_group_interval_ms: 0, ..Default::default()
1647 };
1648 let mut history = CommandHistory::new(config);
1649 let counter = Arc::new(AtomicI32::new(0));
1650
1651 history.execute(Box::new(MergeableIncrement {
1652 counter: counter.clone(),
1653 total_amount: 5,
1654 }));
1655
1656 history.execute(Box::new(MergeableIncrement {
1657 counter,
1658 total_amount: 3,
1659 }));
1660
1661 assert_eq!(history.undo_count(), 1);
1663 }
1664
1665 #[test]
1666 fn test_redo_clears_on_new_execute() {
1667 let mut history = CommandHistory::default();
1668 let counter = Arc::new(AtomicI32::new(0));
1669
1670 history.execute(Box::new(IncrementCommand {
1671 counter: counter.clone(),
1672 amount: 1,
1673 }));
1674
1675 history.undo();
1676 assert_eq!(history.redo_count(), 1);
1677
1678 history.execute(Box::new(IncrementCommand { counter, amount: 2 }));
1679
1680 assert_eq!(history.redo_count(), 0);
1681 }
1682}