1#![forbid(unsafe_code)]
2
3use std::collections::VecDeque;
46use std::fmt;
47
48use super::command::{MergeConfig, UndoableCmd};
49
50#[derive(Debug, Clone)]
52pub struct HistoryConfig {
53 pub max_depth: usize,
55 pub max_bytes: usize,
57 pub merge_config: MergeConfig,
59}
60
61impl Default for HistoryConfig {
62 fn default() -> Self {
63 Self {
64 max_depth: 100,
65 max_bytes: 10 * 1024 * 1024, merge_config: MergeConfig::default(),
67 }
68 }
69}
70
71impl HistoryConfig {
72 #[must_use]
74 pub fn new(max_depth: usize, max_bytes: usize) -> Self {
75 Self {
76 max_depth,
77 max_bytes,
78 merge_config: MergeConfig::default(),
79 }
80 }
81
82 #[must_use]
84 pub fn with_merge_config(mut self, config: MergeConfig) -> Self {
85 self.merge_config = config;
86 self
87 }
88
89 #[must_use]
91 pub fn unlimited() -> Self {
92 Self {
93 max_depth: usize::MAX,
94 max_bytes: 0,
95 merge_config: MergeConfig::default(),
96 }
97 }
98}
99
100pub struct HistoryManager {
105 undo_stack: VecDeque<Box<dyn UndoableCmd>>,
107 redo_stack: VecDeque<Box<dyn UndoableCmd>>,
109 config: HistoryConfig,
111 total_bytes: usize,
113}
114
115impl fmt::Debug for HistoryManager {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 f.debug_struct("HistoryManager")
118 .field("undo_depth", &self.undo_stack.len())
119 .field("redo_depth", &self.redo_stack.len())
120 .field("total_bytes", &self.total_bytes)
121 .field("config", &self.config)
122 .finish()
123 }
124}
125
126impl Default for HistoryManager {
127 fn default() -> Self {
128 Self::new(HistoryConfig::default())
129 }
130}
131
132impl HistoryManager {
133 #[must_use]
135 pub fn new(config: HistoryConfig) -> Self {
136 Self {
137 undo_stack: VecDeque::new(),
138 redo_stack: VecDeque::new(),
139 config,
140 total_bytes: 0,
141 }
142 }
143
144 pub fn push(&mut self, cmd: Box<dyn UndoableCmd>) {
153 self.clear_redo();
155
156 let cmd = match self.try_merge(cmd) {
158 Ok(()) => {
159 self.enforce_limits();
161 return;
162 }
163 Err(cmd) => cmd,
164 };
165
166 self.total_bytes += cmd.size_bytes();
168 self.undo_stack.push_back(cmd);
169
170 self.enforce_limits();
172 }
173
174 pub fn undo(&mut self) -> Option<Result<String, super::command::CommandError>> {
184 let mut cmd = self.undo_stack.pop_back()?;
185 let description = cmd.description().to_string();
186
187 match cmd.undo() {
188 Ok(()) => {
189 self.redo_stack.push_back(cmd);
191 Some(Ok(description))
192 }
193 Err(e) => {
194 self.undo_stack.push_back(cmd);
196 Some(Err(e))
197 }
198 }
199 }
200
201 pub fn redo(&mut self) -> Option<Result<String, super::command::CommandError>> {
211 let mut cmd = self.redo_stack.pop_back()?;
212 let description = cmd.description().to_string();
213
214 match cmd.redo() {
215 Ok(()) => {
216 self.undo_stack.push_back(cmd);
218 Some(Ok(description))
219 }
220 Err(e) => {
221 self.redo_stack.push_back(cmd);
223 Some(Err(e))
224 }
225 }
226 }
227
228 #[must_use]
230 pub fn can_undo(&self) -> bool {
231 !self.undo_stack.is_empty()
232 }
233
234 #[must_use]
236 pub fn can_redo(&self) -> bool {
237 !self.redo_stack.is_empty()
238 }
239
240 #[must_use]
246 pub fn undo_depth(&self) -> usize {
247 self.undo_stack.len()
248 }
249
250 #[must_use]
252 pub fn redo_depth(&self) -> usize {
253 self.redo_stack.len()
254 }
255
256 pub fn undo_descriptions(&self, limit: usize) -> Vec<&str> {
258 self.undo_stack
259 .iter()
260 .rev()
261 .take(limit)
262 .map(|c| c.description())
263 .collect()
264 }
265
266 pub fn redo_descriptions(&self, limit: usize) -> Vec<&str> {
268 self.redo_stack
269 .iter()
270 .rev()
271 .take(limit)
272 .map(|c| c.description())
273 .collect()
274 }
275
276 #[must_use]
278 pub fn next_undo_description(&self) -> Option<&str> {
279 self.undo_stack.back().map(|c| c.description())
280 }
281
282 #[must_use]
284 pub fn next_redo_description(&self) -> Option<&str> {
285 self.redo_stack.back().map(|c| c.description())
286 }
287
288 #[must_use]
290 pub fn memory_usage(&self) -> usize {
291 self.total_bytes
292 }
293
294 #[must_use]
296 pub fn config(&self) -> &HistoryConfig {
297 &self.config
298 }
299
300 pub fn clear(&mut self) {
306 self.undo_stack.clear();
307 self.redo_stack.clear();
308 self.total_bytes = 0;
309 }
310
311 fn clear_redo(&mut self) {
313 for cmd in self.redo_stack.drain(..) {
314 self.total_bytes = self.total_bytes.saturating_sub(cmd.size_bytes());
315 }
316 }
317
318 fn enforce_limits(&mut self) {
320 while self.undo_stack.len() > self.config.max_depth {
322 if let Some(cmd) = self.undo_stack.pop_front() {
323 self.total_bytes = self.total_bytes.saturating_sub(cmd.size_bytes());
324 }
325 }
326
327 if self.config.max_bytes > 0 {
329 while self.total_bytes > self.config.max_bytes {
330 if let Some(cmd) = self.redo_stack.pop_front() {
332 self.total_bytes = self.total_bytes.saturating_sub(cmd.size_bytes());
333 continue;
334 }
335
336 if let Some(cmd) = self.undo_stack.pop_front() {
338 self.total_bytes = self.total_bytes.saturating_sub(cmd.size_bytes());
339 } else {
340 break;
342 }
343 }
344 }
345 }
346
347 fn try_merge(&mut self, cmd: Box<dyn UndoableCmd>) -> Result<(), Box<dyn UndoableCmd>> {
351 let Some(last) = self.undo_stack.back_mut() else {
352 return Err(cmd);
353 };
354
355 if !last.can_merge(cmd.as_ref(), &self.config.merge_config) {
357 return Err(cmd);
358 }
359
360 if cmd.merge_text().is_none() {
362 return Err(cmd);
363 }
364
365 let old_size = last.size_bytes();
367
368 if !last.accept_merge(cmd.as_ref()) {
370 return Err(cmd);
371 }
372
373 let new_size = last.size_bytes();
375 self.total_bytes = self.total_bytes.saturating_sub(old_size) + new_size;
376
377 Ok(())
379 }
380}
381
382#[cfg(test)]
387mod tests {
388 use super::*;
389 use crate::undo::command::{TextInsertCmd, WidgetId};
390 use std::sync::Arc;
391 use std::sync::Mutex;
392
393 fn make_insert_cmd(text: &str) -> Box<dyn UndoableCmd> {
396 let buffer = Arc::new(Mutex::new(String::new()));
397 let b1 = buffer.clone();
398 let b2 = buffer.clone();
399
400 let mut cmd = TextInsertCmd::new(WidgetId::new(1), 0, text)
401 .with_apply(move |_, pos, txt| {
402 let mut buf = b1.lock().unwrap();
403 buf.insert_str(pos, txt);
404 Ok(())
405 })
406 .with_remove(move |_, pos, len| {
407 let mut buf = b2.lock().unwrap();
408 buf.drain(pos..pos + len);
409 Ok(())
410 });
411
412 cmd.execute().expect("test command should execute");
414
415 Box::new(cmd)
416 }
417
418 #[test]
419 fn test_new_manager() {
420 let mgr = HistoryManager::default();
421 assert!(!mgr.can_undo());
422 assert!(!mgr.can_redo());
423 assert_eq!(mgr.undo_depth(), 0);
424 assert_eq!(mgr.redo_depth(), 0);
425 }
426
427 #[test]
428 fn test_push_enables_undo() {
429 let mut mgr = HistoryManager::default();
430 mgr.push(make_insert_cmd("hello"));
431
432 assert!(mgr.can_undo());
433 assert!(!mgr.can_redo());
434 assert_eq!(mgr.undo_depth(), 1);
435 }
436
437 #[test]
438 fn test_undo_enables_redo() {
439 let mut mgr = HistoryManager::default();
440 mgr.push(make_insert_cmd("hello"));
441
442 let result = mgr.undo();
443 assert!(result.is_some());
444 assert!(result.unwrap().is_ok());
445
446 assert!(!mgr.can_undo());
447 assert!(mgr.can_redo());
448 assert_eq!(mgr.redo_depth(), 1);
449 }
450
451 #[test]
452 fn test_redo_moves_back_to_undo() {
453 let mut mgr = HistoryManager::default();
454 mgr.push(make_insert_cmd("hello"));
455 mgr.undo();
456
457 let result = mgr.redo();
458 assert!(result.is_some());
459 assert!(result.unwrap().is_ok());
460
461 assert!(mgr.can_undo());
462 assert!(!mgr.can_redo());
463 }
464
465 #[test]
466 fn test_push_clears_redo() {
467 let mut mgr = HistoryManager::default();
468 mgr.push(make_insert_cmd("hello"));
469 mgr.undo();
470
471 assert!(mgr.can_redo());
472
473 mgr.push(make_insert_cmd("world"));
475
476 assert!(!mgr.can_redo());
478 assert_eq!(mgr.redo_depth(), 0);
479 }
480
481 #[test]
482 fn test_max_depth_enforced() {
483 let config = HistoryConfig::new(3, 0);
484 let mut mgr = HistoryManager::new(config);
485
486 for i in 0..5 {
487 mgr.push(make_insert_cmd(&format!("cmd{}", i)));
488 }
489
490 assert_eq!(mgr.undo_depth(), 3);
492 }
493
494 #[test]
495 fn test_descriptions() {
496 let mut mgr = HistoryManager::default();
497 mgr.push(make_insert_cmd("a"));
498 mgr.push(make_insert_cmd("b"));
499 mgr.push(make_insert_cmd("c"));
500
501 let descs = mgr.undo_descriptions(5);
502 assert_eq!(descs.len(), 3);
503 assert!(descs.iter().all(|d| *d == "Insert text"));
505 }
506
507 #[test]
508 fn test_next_descriptions() {
509 let mut mgr = HistoryManager::default();
510 mgr.push(make_insert_cmd("hello"));
511
512 assert_eq!(mgr.next_undo_description(), Some("Insert text"));
513 assert_eq!(mgr.next_redo_description(), None);
514
515 mgr.undo();
516
517 assert_eq!(mgr.next_undo_description(), None);
518 assert_eq!(mgr.next_redo_description(), Some("Insert text"));
519 }
520
521 #[test]
522 fn test_clear() {
523 let mut mgr = HistoryManager::default();
524 mgr.push(make_insert_cmd("a"));
525 mgr.push(make_insert_cmd("b"));
526 mgr.undo();
527
528 assert!(mgr.can_undo());
529 assert!(mgr.can_redo());
530
531 mgr.clear();
532
533 assert!(!mgr.can_undo());
534 assert!(!mgr.can_redo());
535 assert_eq!(mgr.memory_usage(), 0);
536 }
537
538 #[test]
539 fn test_memory_tracking() {
540 let mut mgr = HistoryManager::new(HistoryConfig::unlimited());
541
542 let initial = mgr.memory_usage();
543 assert_eq!(initial, 0);
544
545 mgr.push(make_insert_cmd("hello"));
546 let after_push = mgr.memory_usage();
547 assert!(after_push > 0);
548
549 mgr.push(make_insert_cmd("world"));
550 let after_second = mgr.memory_usage();
551 assert!(after_second > after_push);
552 }
553
554 #[test]
555 fn test_undo_without_commands() {
556 let mut mgr = HistoryManager::default();
557 assert!(mgr.undo().is_none());
558 }
559
560 #[test]
561 fn test_redo_without_commands() {
562 let mut mgr = HistoryManager::default();
563 assert!(mgr.redo().is_none());
564 }
565
566 #[test]
567 fn test_multiple_undo_redo_cycle() {
568 let mut mgr = HistoryManager::default();
569
570 mgr.push(make_insert_cmd("a"));
572 mgr.push(make_insert_cmd("b"));
573 mgr.push(make_insert_cmd("c"));
574
575 assert_eq!(mgr.undo_depth(), 3);
576 assert_eq!(mgr.redo_depth(), 0);
577
578 mgr.undo();
580 mgr.undo();
581 mgr.undo();
582
583 assert_eq!(mgr.undo_depth(), 0);
584 assert_eq!(mgr.redo_depth(), 3);
585
586 mgr.redo();
588 mgr.redo();
589 mgr.redo();
590
591 assert_eq!(mgr.undo_depth(), 3);
592 assert_eq!(mgr.redo_depth(), 0);
593 }
594
595 #[test]
596 fn test_config_default() {
597 let config = HistoryConfig::default();
598 assert_eq!(config.max_depth, 100);
599 assert_eq!(config.max_bytes, 10 * 1024 * 1024);
600 }
601
602 #[test]
603 fn test_config_unlimited() {
604 let config = HistoryConfig::unlimited();
605 assert_eq!(config.max_depth, usize::MAX);
606 assert_eq!(config.max_bytes, 0);
607 }
608
609 #[test]
610 fn test_debug_impl() {
611 let mgr = HistoryManager::default();
612 let debug_str = format!("{:?}", mgr);
613 assert!(debug_str.contains("HistoryManager"));
614 assert!(debug_str.contains("undo_depth"));
615 }
616
617 #[test]
618 fn test_config_new_custom_limits() {
619 let config = HistoryConfig::new(50, 4096);
620 assert_eq!(config.max_depth, 50);
621 assert_eq!(config.max_bytes, 4096);
622 }
623
624 #[test]
625 fn test_config_with_merge_config() {
626 let mc = MergeConfig::default();
627 let config = HistoryConfig::new(10, 0).with_merge_config(mc);
628 assert_eq!(config.max_depth, 10);
630 }
631
632 #[test]
633 fn test_config_accessor() {
634 let config = HistoryConfig::new(42, 1024);
635 let mgr = HistoryManager::new(config);
636 assert_eq!(mgr.config().max_depth, 42);
637 assert_eq!(mgr.config().max_bytes, 1024);
638 }
639
640 #[test]
641 fn test_undo_descriptions_limited() {
642 let mut mgr = HistoryManager::default();
643 mgr.push(make_insert_cmd("a"));
644 mgr.push(make_insert_cmd("b"));
645 mgr.push(make_insert_cmd("c"));
646
647 let descs = mgr.undo_descriptions(2);
648 assert_eq!(descs.len(), 2, "should limit to 2 descriptions");
649 }
650
651 #[test]
652 fn test_redo_descriptions() {
653 let mut mgr = HistoryManager::default();
654 mgr.push(make_insert_cmd("a"));
655 mgr.push(make_insert_cmd("b"));
656 mgr.undo();
657 mgr.undo();
658
659 let descs = mgr.redo_descriptions(5);
660 assert_eq!(descs.len(), 2);
661
662 let descs_limited = mgr.redo_descriptions(1);
663 assert_eq!(descs_limited.len(), 1);
664 }
665
666 #[test]
667 fn test_memory_byte_limit_evicts_old_commands() {
668 let config = HistoryConfig::new(100, 1); let mut mgr = HistoryManager::new(config);
671
672 for i in 0..5 {
674 mgr.push(make_insert_cmd(&format!("cmd{i}")));
675 }
676
677 assert!(
679 mgr.undo_depth() < 5,
680 "byte limit should evict old commands, depth={}",
681 mgr.undo_depth()
682 );
683 }
684
685 #[test]
686 fn test_memory_tracking_after_undo_redo() {
687 let mut mgr = HistoryManager::new(HistoryConfig::unlimited());
688 mgr.push(make_insert_cmd("a"));
689 let after_push = mgr.memory_usage();
690
691 mgr.undo();
692 let after_undo = mgr.memory_usage();
693 assert_eq!(after_push, after_undo);
695
696 mgr.redo();
697 let after_redo = mgr.memory_usage();
698 assert_eq!(after_push, after_redo);
699 }
700
701 fn make_failing_undo_cmd() -> Box<dyn UndoableCmd> {
707 use crate::undo::command::CommandError;
708 let buffer = Arc::new(Mutex::new(String::new()));
709 let b1 = buffer.clone();
710
711 Box::new(
712 TextInsertCmd::new(WidgetId::new(1), 0, "x")
713 .with_apply(move |_, _, txt| {
714 let mut buf = b1.lock().unwrap();
715 buf.push_str(txt);
716 Ok(())
717 })
718 .with_remove(move |_, _, _| Err(CommandError::Other("undo fail".to_string()))),
719 )
720 }
721
722 fn make_failing_redo_cmd() -> (Box<dyn UndoableCmd>, Arc<Mutex<String>>) {
725 use crate::undo::command::CommandError;
726 let buffer = Arc::new(Mutex::new(String::new()));
727 let b1 = buffer.clone();
728 let b2 = buffer.clone();
729 let call_count = Arc::new(Mutex::new(0u32));
730 let cc = call_count.clone();
731
732 let cmd = TextInsertCmd::new(WidgetId::new(1), 0, "y")
733 .with_apply(move |_, _, txt| {
734 let mut count = cc.lock().unwrap();
735 *count += 1;
736 if *count > 1 {
737 return Err(CommandError::Other("redo fail".to_string()));
738 }
739 let mut buf = b1.lock().unwrap();
740 buf.push_str(txt);
741 Ok(())
742 })
743 .with_remove(move |_, _, len| {
744 let mut buf = b2.lock().unwrap();
745 buf.drain(..len);
746 Ok(())
747 });
748
749 (Box::new(cmd), buffer)
750 }
751
752 #[test]
753 fn test_undo_failure_keeps_command_on_stack() {
754 let mut mgr = HistoryManager::new(HistoryConfig::unlimited());
755
756 let mut cmd = make_failing_undo_cmd();
758 cmd.execute().unwrap();
759 mgr.push(cmd);
760
761 assert_eq!(mgr.undo_depth(), 1);
762
763 let result = mgr.undo();
765 assert!(result.is_some());
766 assert!(result.unwrap().is_err());
767
768 assert_eq!(mgr.undo_depth(), 1);
770 assert_eq!(mgr.redo_depth(), 0);
771 }
772
773 #[test]
774 fn test_redo_failure_keeps_command_on_redo_stack() {
775 let mut mgr = HistoryManager::new(HistoryConfig::unlimited());
776
777 let (mut cmd, _buffer) = make_failing_redo_cmd();
778 cmd.execute().unwrap(); mgr.push(cmd);
780
781 let result = mgr.undo();
783 assert!(result.unwrap().is_ok());
784 assert_eq!(mgr.redo_depth(), 1);
785
786 let result = mgr.redo();
788 assert!(result.is_some());
789 assert!(result.unwrap().is_err());
790
791 assert_eq!(mgr.redo_depth(), 1);
793 assert_eq!(mgr.undo_depth(), 0);
794 }
795
796 #[test]
801 fn test_push_merges_consecutive_inserts() {
802 let buffer = Arc::new(Mutex::new(String::new()));
803 let b1 = buffer.clone();
804 let b2 = buffer.clone();
805 let b3 = buffer.clone();
806 let b4 = buffer.clone();
807
808 let mut mgr = HistoryManager::new(HistoryConfig::default());
809
810 let mut cmd1 = TextInsertCmd::new(WidgetId::new(1), 0, "a")
812 .with_apply(move |_, pos, txt| {
813 let mut buf = b1.lock().unwrap();
814 buf.insert_str(pos, txt);
815 Ok(())
816 })
817 .with_remove(move |_, pos, len| {
818 let mut buf = b2.lock().unwrap();
819 buf.drain(pos..pos + len);
820 Ok(())
821 });
822 cmd1.execute().unwrap();
823 mgr.push(Box::new(cmd1));
824
825 let mut cmd2 = TextInsertCmd::new(WidgetId::new(1), 1, "b")
827 .with_apply(move |_, pos, txt| {
828 let mut buf = b3.lock().unwrap();
829 buf.insert_str(pos, txt);
830 Ok(())
831 })
832 .with_remove(move |_, pos, len| {
833 let mut buf = b4.lock().unwrap();
834 buf.drain(pos..pos + len);
835 Ok(())
836 });
837 cmd2.metadata.timestamp = mgr.undo_stack.back().unwrap().metadata().timestamp;
838 cmd2.execute().unwrap();
839 mgr.push(Box::new(cmd2));
840
841 assert_eq!(
843 mgr.undo_depth(),
844 1,
845 "consecutive inserts should merge into 1 command"
846 );
847 }
848
849 #[test]
854 fn test_push_clears_redo_memory_accounting() {
855 let mut mgr = HistoryManager::new(HistoryConfig::unlimited());
856
857 mgr.push(make_insert_cmd("redo_me"));
858 let mem_after_push = mgr.memory_usage();
859
860 mgr.undo();
861 assert_eq!(mgr.memory_usage(), mem_after_push);
863
864 mgr.push(make_insert_cmd("new"));
866 assert_eq!(mgr.redo_depth(), 0);
867 assert!(mgr.memory_usage() > 0);
869 }
870
871 #[test]
876 fn test_descriptions_empty_stacks() {
877 let mgr = HistoryManager::default();
878 assert!(mgr.undo_descriptions(10).is_empty());
879 assert!(mgr.redo_descriptions(10).is_empty());
880 assert_eq!(mgr.next_undo_description(), None);
881 assert_eq!(mgr.next_redo_description(), None);
882 }
883
884 #[test]
889 fn test_memory_decreases_when_push_clears_redo() {
890 let mut mgr = HistoryManager::new(HistoryConfig::unlimited());
891
892 mgr.push(make_insert_cmd("aaa"));
893 mgr.push(make_insert_cmd("bbb"));
894 mgr.undo(); let mem_with_redo = mgr.memory_usage();
896 assert!(mem_with_redo > 0);
897
898 mgr.push(make_insert_cmd("ccc"));
900
901 assert_eq!(mgr.redo_depth(), 0);
903 let mem_after = mgr.memory_usage();
905 assert!(mem_after > 0);
906 }
907
908 #[test]
913 fn test_depth_limit_evicts_oldest() {
914 let config = HistoryConfig::new(2, 0);
915 let mut mgr = HistoryManager::new(config);
916
917 mgr.push(make_insert_cmd("first"));
918 mgr.push(make_insert_cmd("second"));
919 mgr.push(make_insert_cmd("third"));
920
921 assert_eq!(mgr.undo_depth(), 2);
922 let descs = mgr.undo_descriptions(2);
924 assert_eq!(descs.len(), 2);
925 }
926
927 #[test]
932 fn test_default_impl() {
933 let mgr = HistoryManager::default();
934 assert_eq!(mgr.config().max_depth, 100);
935 assert_eq!(mgr.config().max_bytes, 10 * 1024 * 1024);
936 }
937
938 struct MergeableNoText {
945 metadata: crate::undo::command::CommandMetadata,
946 }
947
948 impl MergeableNoText {
949 fn new() -> Self {
950 Self {
951 metadata: crate::undo::command::CommandMetadata::new("MergeableNoText"),
952 }
953 }
954 }
955
956 impl crate::undo::command::UndoableCmd for MergeableNoText {
957 fn execute(&mut self) -> crate::undo::command::CommandResult {
958 Ok(())
959 }
960 fn undo(&mut self) -> crate::undo::command::CommandResult {
961 Ok(())
962 }
963 fn description(&self) -> &str {
964 &self.metadata.description
965 }
966 fn size_bytes(&self) -> usize {
967 std::mem::size_of::<Self>()
968 }
969 fn can_merge(
970 &self,
971 _other: &dyn crate::undo::command::UndoableCmd,
972 _config: &MergeConfig,
973 ) -> bool {
974 true }
976 fn merge_text(&self) -> Option<&str> {
977 None }
979 fn accept_merge(&mut self, _other: &dyn crate::undo::command::UndoableCmd) -> bool {
980 false
981 }
982 fn metadata(&self) -> &crate::undo::command::CommandMetadata {
983 &self.metadata
984 }
985 fn as_any(&self) -> &dyn std::any::Any {
986 self
987 }
988 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
989 self
990 }
991 }
992
993 #[test]
994 fn test_try_merge_exits_early_when_merge_text_none() {
995 let mut mgr = HistoryManager::new(HistoryConfig::unlimited());
996
997 mgr.push(Box::new(MergeableNoText::new()));
998 assert_eq!(mgr.undo_depth(), 1);
999
1000 mgr.push(Box::new(MergeableNoText::new()));
1002 assert_eq!(mgr.undo_depth(), 2);
1003 }
1004
1005 struct MergeableRejectsAccept {
1011 metadata: crate::undo::command::CommandMetadata,
1012 }
1013
1014 impl MergeableRejectsAccept {
1015 fn new() -> Self {
1016 Self {
1017 metadata: crate::undo::command::CommandMetadata::new("MergeableRejectsAccept"),
1018 }
1019 }
1020 }
1021
1022 impl crate::undo::command::UndoableCmd for MergeableRejectsAccept {
1023 fn execute(&mut self) -> crate::undo::command::CommandResult {
1024 Ok(())
1025 }
1026 fn undo(&mut self) -> crate::undo::command::CommandResult {
1027 Ok(())
1028 }
1029 fn description(&self) -> &str {
1030 &self.metadata.description
1031 }
1032 fn size_bytes(&self) -> usize {
1033 std::mem::size_of::<Self>()
1034 }
1035 fn can_merge(
1036 &self,
1037 _other: &dyn crate::undo::command::UndoableCmd,
1038 _config: &MergeConfig,
1039 ) -> bool {
1040 true
1041 }
1042 fn merge_text(&self) -> Option<&str> {
1043 Some("text")
1044 }
1045 fn accept_merge(&mut self, _other: &dyn crate::undo::command::UndoableCmd) -> bool {
1046 false }
1048 fn metadata(&self) -> &crate::undo::command::CommandMetadata {
1049 &self.metadata
1050 }
1051 fn as_any(&self) -> &dyn std::any::Any {
1052 self
1053 }
1054 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
1055 self
1056 }
1057 }
1058
1059 #[test]
1060 fn test_try_merge_not_merged_when_accept_merge_false() {
1061 let mut mgr = HistoryManager::new(HistoryConfig::unlimited());
1062
1063 mgr.push(Box::new(MergeableRejectsAccept::new()));
1064 assert_eq!(mgr.undo_depth(), 1);
1065
1066 mgr.push(Box::new(MergeableRejectsAccept::new()));
1067 assert_eq!(mgr.undo_depth(), 2);
1068 }
1069
1070 #[test]
1075 fn test_push_always_clears_redo_before_enforce() {
1076 let mut mgr = HistoryManager::new(HistoryConfig::unlimited());
1080
1081 mgr.push(make_insert_cmd("redo_item"));
1082 mgr.undo();
1083 assert_eq!(mgr.redo_depth(), 1);
1084
1085 mgr.push(make_insert_cmd("new_item"));
1087 assert_eq!(mgr.redo_depth(), 0);
1088 }
1089
1090 #[test]
1095 fn test_max_bytes_zero_means_unlimited() {
1096 let config = HistoryConfig::new(100, 0); let mut mgr = HistoryManager::new(config);
1098
1099 for i in 0..50 {
1100 mgr.push(make_insert_cmd(&format!("cmd{i}")));
1101 }
1102
1103 assert_eq!(mgr.undo_depth(), 50);
1105 assert!(mgr.memory_usage() > 0);
1106 }
1107
1108 #[test]
1113 fn test_memory_accounting_after_merge() {
1114 let buffer = Arc::new(Mutex::new(String::new()));
1115 let b1 = buffer.clone();
1116 let b2 = buffer.clone();
1117 let b3 = buffer.clone();
1118 let b4 = buffer.clone();
1119
1120 let mut mgr = HistoryManager::new(HistoryConfig::default());
1121
1122 let mut cmd1 = TextInsertCmd::new(WidgetId::new(1), 0, "a")
1124 .with_apply(move |_, pos, txt| {
1125 let mut buf = b1.lock().unwrap();
1126 buf.insert_str(pos, txt);
1127 Ok(())
1128 })
1129 .with_remove(move |_, pos, len| {
1130 let mut buf = b2.lock().unwrap();
1131 buf.drain(pos..pos + len);
1132 Ok(())
1133 });
1134 cmd1.execute().unwrap();
1135 mgr.push(Box::new(cmd1));
1136 let mem_after_first = mgr.memory_usage();
1137
1138 let mut cmd2 = TextInsertCmd::new(WidgetId::new(1), 1, "b")
1140 .with_apply(move |_, pos, txt| {
1141 let mut buf = b3.lock().unwrap();
1142 buf.insert_str(pos, txt);
1143 Ok(())
1144 })
1145 .with_remove(move |_, pos, len| {
1146 let mut buf = b4.lock().unwrap();
1147 buf.drain(pos..pos + len);
1148 Ok(())
1149 });
1150 cmd2.metadata.timestamp = mgr.undo_stack.back().unwrap().metadata().timestamp;
1151 cmd2.execute().unwrap();
1152 mgr.push(Box::new(cmd2));
1153
1154 assert_eq!(mgr.undo_depth(), 1);
1156
1157 let mem_after_merge = mgr.memory_usage();
1159 assert!(
1160 mem_after_merge > mem_after_first,
1161 "memory should increase after merge: {} vs {}",
1162 mem_after_merge,
1163 mem_after_first
1164 );
1165 }
1166
1167 #[test]
1172 fn test_history_config_debug() {
1173 let config = HistoryConfig::new(50, 4096);
1174 let debug_str = format!("{:?}", config);
1175 assert!(debug_str.contains("HistoryConfig"));
1176 assert!(debug_str.contains("50"));
1177 assert!(debug_str.contains("4096"));
1178 }
1179
1180 #[test]
1181 fn test_history_config_clone() {
1182 let config = HistoryConfig::new(50, 4096);
1183 let cloned = config.clone();
1184 assert_eq!(cloned.max_depth, 50);
1185 assert_eq!(cloned.max_bytes, 4096);
1186 }
1187
1188 #[test]
1193 fn test_depth_and_byte_limits_both_enforced() {
1194 let config = HistoryConfig::new(3, 1);
1196 let mut mgr = HistoryManager::new(config);
1197
1198 for i in 0..10 {
1199 mgr.push(make_insert_cmd(&format!("cmd{i}")));
1200 }
1201
1202 assert!(mgr.undo_depth() <= 3);
1204 }
1205
1206 #[test]
1211 fn test_try_merge_returns_err_on_empty_stack() {
1212 let mut mgr = HistoryManager::new(HistoryConfig::default());
1213
1214 mgr.push(make_insert_cmd("first"));
1216 assert_eq!(mgr.undo_depth(), 1);
1217 }
1218
1219 #[test]
1224 fn test_undo_returns_description_string() {
1225 let mut mgr = HistoryManager::new(HistoryConfig::unlimited());
1226 mgr.push(make_insert_cmd("hello"));
1227
1228 let result = mgr.undo().unwrap();
1229 assert_eq!(result.unwrap(), "Insert text");
1230 }
1231
1232 #[test]
1233 fn test_redo_returns_description_string() {
1234 let mut mgr = HistoryManager::new(HistoryConfig::unlimited());
1235 mgr.push(make_insert_cmd("hello"));
1236 mgr.undo();
1237
1238 let result = mgr.redo().unwrap();
1239 assert_eq!(result.unwrap(), "Insert text");
1240 }
1241
1242 #[test]
1247 fn test_clear_resets_memory_with_both_stacks() {
1248 let mut mgr = HistoryManager::new(HistoryConfig::unlimited());
1249 mgr.push(make_insert_cmd("a"));
1250 mgr.push(make_insert_cmd("b"));
1251 mgr.push(make_insert_cmd("c"));
1252 mgr.undo(); mgr.undo(); assert_eq!(mgr.undo_depth(), 1);
1256 assert_eq!(mgr.redo_depth(), 2);
1257 assert!(mgr.memory_usage() > 0);
1258
1259 mgr.clear();
1260
1261 assert_eq!(mgr.undo_depth(), 0);
1262 assert_eq!(mgr.redo_depth(), 0);
1263 assert_eq!(mgr.memory_usage(), 0);
1264 }
1265
1266 #[test]
1271 fn test_depth_limit_one() {
1272 let config = HistoryConfig::new(1, 0);
1273 let mut mgr = HistoryManager::new(config);
1274
1275 mgr.push(make_insert_cmd("first"));
1276 mgr.push(make_insert_cmd("second"));
1277 mgr.push(make_insert_cmd("third"));
1278
1279 assert_eq!(mgr.undo_depth(), 1);
1280 }
1281
1282 #[test]
1287 fn test_depth_limit_zero_evicts_everything() {
1288 let config = HistoryConfig::new(0, 0);
1289 let mut mgr = HistoryManager::new(config);
1290
1291 mgr.push(make_insert_cmd("will_be_evicted"));
1292
1293 assert_eq!(mgr.undo_depth(), 0);
1295 }
1296}