1use super::position::{Position, Range};
7use crate::commands::CommandResult;
8
9#[cfg(feature = "stream")]
10use ass_core::parser::ScriptDeltaOwned;
11
12#[cfg(feature = "arena")]
13use bumpalo::Bump;
14
15#[cfg(not(feature = "std"))]
16use alloc::{string::String, vec::Vec};
17
18#[cfg(feature = "std")]
19use std::collections::VecDeque;
20
21#[cfg(not(feature = "std"))]
22use alloc::collections::VecDeque;
23
24#[cfg(feature = "stream")]
26#[derive(Debug, Clone)]
27pub struct DeltaUndoData {
28 pub removed_sections: Vec<(usize, String)>,
30 pub modified_sections: Vec<(usize, String)>,
32}
33
34#[derive(Debug, Clone)]
36pub enum Operation {
37 Insert { position: Position, text: String },
39 Delete { range: Range, deleted_text: String },
41 Replace {
43 range: Range,
44 old_text: String,
45 new_text: String,
46 },
47 #[cfg(feature = "stream")]
49 Delta {
50 forward: ScriptDeltaOwned,
52 undo_data: DeltaUndoData,
54 },
55}
56
57impl Operation {
58 pub fn memory_usage(&self) -> usize {
60 match self {
61 Self::Insert { text, .. } => core::mem::size_of::<Self>() + text.len(),
62 Self::Delete { deleted_text, .. } => core::mem::size_of::<Self>() + deleted_text.len(),
63 Self::Replace {
64 old_text, new_text, ..
65 } => core::mem::size_of::<Self>() + old_text.len() + new_text.len(),
66 #[cfg(feature = "stream")]
67 Self::Delta { forward, undo_data } => {
68 core::mem::size_of::<Self>()
69 + forward.added.iter().map(|s| s.len()).sum::<usize>()
70 + forward.modified.iter().map(|(_, s)| s.len()).sum::<usize>()
71 + undo_data
72 .removed_sections
73 .iter()
74 .map(|(_, s)| s.len())
75 .sum::<usize>()
76 + undo_data
77 .modified_sections
78 .iter()
79 .map(|(_, s)| s.len())
80 .sum::<usize>()
81 }
82 }
83 }
84}
85
86#[derive(Debug)]
91pub struct HistoryEntry {
92 pub operation: Operation,
94
95 pub description: String,
97
98 pub modified_range: Option<Range>,
100
101 pub cursor_before: Option<Position>,
103
104 pub cursor_after: Option<Position>,
106
107 #[cfg(feature = "stream")]
109 pub script_delta: Option<ScriptDeltaOwned>,
110
111 #[cfg(feature = "std")]
113 pub timestamp: std::time::Instant,
114
115 pub memory_usage: usize,
117}
118
119impl HistoryEntry {
120 pub fn new(
122 operation: Operation,
123 description: String,
124 result: &CommandResult,
125 cursor_before: Option<Position>,
126 ) -> Self {
127 let memory_usage = operation.memory_usage() + description.len();
128
129 Self {
130 operation,
131 description,
132 modified_range: result.modified_range,
133 cursor_before,
134 cursor_after: result.new_cursor,
135 #[cfg(feature = "stream")]
136 script_delta: None,
137 #[cfg(feature = "std")]
138 timestamp: std::time::Instant::now(),
139 memory_usage,
140 }
141 }
142
143 #[cfg(feature = "stream")]
145 pub fn with_delta(
146 operation: Operation,
147 description: String,
148 result: &CommandResult,
149 cursor_before: Option<Position>,
150 script_delta: ScriptDeltaOwned,
151 ) -> Self {
152 let delta_memory = script_delta.added.iter().map(|s| s.len()).sum::<usize>()
153 + script_delta
154 .modified
155 .iter()
156 .map(|(_, s)| s.len())
157 .sum::<usize>();
158 let memory_usage = operation.memory_usage() + description.len() + delta_memory;
159
160 Self {
161 operation,
162 description,
163 modified_range: result.modified_range,
164 cursor_before,
165 cursor_after: result.new_cursor,
166 script_delta: Some(script_delta),
167 #[cfg(feature = "std")]
168 timestamp: std::time::Instant::now(),
169 memory_usage,
170 }
171 }
172}
173
174#[derive(Debug, Clone)]
176pub struct UndoStackConfig {
177 pub max_entries: usize,
179
180 pub max_memory: usize,
182
183 pub enable_compression: bool,
185
186 pub arena_reset_interval: usize,
188}
189
190impl Default for UndoStackConfig {
191 fn default() -> Self {
192 Self {
193 max_entries: 50, max_memory: 10 * 1024 * 1024, enable_compression: true,
196 arena_reset_interval: 100, }
198 }
199}
200
201#[derive(Debug)]
206pub struct UndoStack {
207 config: UndoStackConfig,
209
210 undo_stack: VecDeque<HistoryEntry>,
212
213 redo_stack: VecDeque<HistoryEntry>,
215
216 current_memory: usize,
218
219 #[cfg(feature = "arena")]
221 arena: Bump,
222
223 #[cfg(feature = "arena")]
225 ops_since_reset: usize,
226}
227
228impl UndoStack {
229 pub fn new() -> Self {
231 Self::with_config(UndoStackConfig::default())
232 }
233
234 pub fn with_config(config: UndoStackConfig) -> Self {
236 Self {
237 config,
238 undo_stack: VecDeque::new(),
239 redo_stack: VecDeque::new(),
240 current_memory: 0,
241 #[cfg(feature = "arena")]
242 arena: Bump::new(),
243 #[cfg(feature = "arena")]
244 ops_since_reset: 0,
245 }
246 }
247
248 pub fn push(&mut self, entry: HistoryEntry) {
253 self.clear_redo_stack();
255
256 self.current_memory += entry.memory_usage;
258
259 self.undo_stack.push_front(entry);
261
262 self.enforce_limits();
264
265 #[cfg(feature = "arena")]
267 {
268 self.ops_since_reset += 1;
269 if self.config.arena_reset_interval > 0
270 && self.ops_since_reset >= self.config.arena_reset_interval
271 {
272 self.reset_arena();
273 }
274 }
275 }
276
277 pub fn pop_undo(&mut self) -> Option<HistoryEntry> {
279 if let Some(entry) = self.undo_stack.pop_front() {
280 self.current_memory -= entry.memory_usage;
281 Some(entry)
282 } else {
283 None
284 }
285 }
286
287 pub fn push_redo(&mut self, entry: HistoryEntry) {
289 self.current_memory += entry.memory_usage;
290 self.redo_stack.push_front(entry);
291 }
292
293 pub fn pop_redo(&mut self) -> Option<HistoryEntry> {
295 if let Some(entry) = self.redo_stack.pop_front() {
296 self.current_memory -= entry.memory_usage;
297 Some(entry)
298 } else {
299 None
300 }
301 }
302
303 #[must_use]
305 pub fn can_undo(&self) -> bool {
306 !self.undo_stack.is_empty()
307 }
308
309 #[must_use]
311 pub fn can_redo(&self) -> bool {
312 !self.redo_stack.is_empty()
313 }
314
315 #[must_use]
317 pub fn undo_count(&self) -> usize {
318 self.undo_stack.len()
319 }
320
321 #[must_use]
323 pub fn redo_count(&self) -> usize {
324 self.redo_stack.len()
325 }
326
327 #[must_use]
329 pub fn memory_usage(&self) -> usize {
330 self.current_memory
331 }
332
333 #[must_use]
335 pub fn next_undo_description(&self) -> Option<&str> {
336 self.undo_stack
337 .front()
338 .map(|entry| entry.description.as_str())
339 }
340
341 #[must_use]
343 pub fn next_redo_description(&self) -> Option<&str> {
344 self.redo_stack
345 .front()
346 .map(|entry| entry.description.as_str())
347 }
348
349 pub fn clear(&mut self) {
351 self.undo_stack.clear();
352 self.redo_stack.clear();
353 self.current_memory = 0;
354
355 #[cfg(feature = "arena")]
356 {
357 self.reset_arena();
358 }
359 }
360
361 fn clear_redo_stack(&mut self) {
363 for entry in self.redo_stack.drain(..) {
364 self.current_memory -= entry.memory_usage;
365 }
366 }
367
368 fn enforce_limits(&mut self) {
370 while self.undo_stack.len() > self.config.max_entries {
372 if let Some(entry) = self.undo_stack.pop_back() {
373 self.current_memory -= entry.memory_usage;
374 }
375 }
376
377 while self.config.max_memory > 0
379 && self.current_memory > self.config.max_memory
380 && !self.undo_stack.is_empty()
381 {
382 if let Some(entry) = self.undo_stack.pop_back() {
383 self.current_memory -= entry.memory_usage;
384 }
385 }
386 }
387
388 #[cfg(feature = "arena")]
390 fn reset_arena(&mut self) {
391 self.arena.reset();
392 self.ops_since_reset = 0;
393 }
394
395 #[cfg(feature = "arena")]
397 #[must_use]
398 pub fn arena(&self) -> &Bump {
399 &self.arena
400 }
401
402 #[cfg(feature = "arena")]
404 pub fn arena_mut(&mut self) -> &mut Bump {
405 &mut self.arena
406 }
407}
408
409impl Default for UndoStack {
410 fn default() -> Self {
411 Self::new()
412 }
413}
414
415#[derive(Debug)]
420pub struct UndoManager {
421 stack: UndoStack,
423
424 current_cursor: Option<Position>,
426}
427
428impl UndoManager {
429 pub fn new() -> Self {
431 Self {
432 stack: UndoStack::new(),
433 current_cursor: None,
434 }
435 }
436
437 pub fn with_config(config: UndoStackConfig) -> Self {
439 Self {
440 stack: UndoStack::with_config(config),
441 current_cursor: None,
442 }
443 }
444
445 pub fn set_config(&mut self, config: UndoStackConfig) {
447 self.stack = UndoStack::with_config(config);
448 }
449
450 pub fn set_cursor(&mut self, cursor: Option<Position>) {
452 self.current_cursor = cursor;
453 }
454
455 #[must_use]
457 pub fn cursor_position(&self) -> Option<Position> {
458 self.current_cursor
459 }
460
461 pub fn record_operation(
465 &mut self,
466 operation: Operation,
467 description: String,
468 result: &CommandResult,
469 ) {
470 if result.success && result.content_changed {
471 #[cfg(feature = "stream")]
472 let entry = if let Some(ref delta) = result.script_delta {
473 HistoryEntry::with_delta(
474 operation,
475 description,
476 result,
477 self.current_cursor,
478 delta.clone(),
479 )
480 } else {
481 HistoryEntry::new(operation, description, result, self.current_cursor)
482 };
483
484 #[cfg(not(feature = "stream"))]
485 let entry = HistoryEntry::new(operation, description, result, self.current_cursor);
486
487 self.stack.push(entry);
488
489 if let Some(new_cursor) = result.new_cursor {
491 self.current_cursor = Some(new_cursor);
492 }
493 }
494 }
495
496 #[must_use]
498 pub fn can_undo(&self) -> bool {
499 self.stack.can_undo()
500 }
501
502 #[must_use]
504 pub fn can_redo(&self) -> bool {
505 self.stack.can_redo()
506 }
507
508 #[must_use]
510 pub fn next_undo_description(&self) -> Option<&str> {
511 self.stack.next_undo_description()
512 }
513
514 #[must_use]
516 pub fn next_redo_description(&self) -> Option<&str> {
517 self.stack.next_redo_description()
518 }
519
520 #[must_use]
522 pub fn stats(&self) -> HistoryStats {
523 HistoryStats {
524 undo_count: self.stack.undo_count(),
525 redo_count: self.stack.redo_count(),
526 memory_usage: self.stack.memory_usage(),
527 }
528 }
529
530 pub fn clear(&mut self) {
532 self.stack.clear();
533 }
534
535 #[must_use]
537 pub fn stack(&self) -> &UndoStack {
538 &self.stack
539 }
540
541 pub fn stack_mut(&mut self) -> &mut UndoStack {
543 &mut self.stack
544 }
545
546 pub fn pop_undo_entry(&mut self) -> Option<HistoryEntry> {
548 self.stack.pop_undo()
549 }
550
551 pub fn pop_redo_entry(&mut self) -> Option<HistoryEntry> {
553 self.stack.pop_redo()
554 }
555
556 pub fn push_redo_entry(&mut self, entry: HistoryEntry) {
558 self.stack.push_redo(entry);
559 }
560}
561
562impl Default for UndoManager {
563 fn default() -> Self {
564 Self::new()
565 }
566}
567
568#[derive(Debug, Clone, PartialEq, Eq)]
570pub struct HistoryStats {
571 pub undo_count: usize,
573 pub redo_count: usize,
575 pub memory_usage: usize,
577}
578
579#[cfg(test)]
580mod tests {
581 use super::*;
582 #[cfg(not(feature = "std"))]
583 use alloc::format;
584 #[cfg(not(feature = "std"))]
585 use alloc::string::ToString;
586
587 #[test]
588 fn undo_stack_basic_operations() {
589 let mut stack = UndoStack::new();
590 assert!(!stack.can_undo());
591 assert!(!stack.can_redo());
592
593 let operation = Operation::Insert {
595 position: Position::new(0),
596 text: "test".to_string(),
597 };
598 let result = CommandResult::success();
599 let entry = HistoryEntry::new(operation, "Test".to_string(), &result, None);
600
601 stack.push(entry);
602 assert!(stack.can_undo());
603 assert!(!stack.can_redo());
604
605 let popped = stack.pop_undo().unwrap();
606 assert_eq!(popped.description, "Test");
607 assert!(!stack.can_undo());
608 }
609
610 #[test]
611 fn undo_stack_memory_limits() {
612 let config = UndoStackConfig {
613 max_entries: 2,
614 max_memory: 1000,
615 enable_compression: false,
616 arena_reset_interval: 0,
617 };
618
619 let mut stack = UndoStack::with_config(config);
620
621 for i in 0..5 {
623 let operation = Operation::Insert {
624 position: Position::new(0),
625 text: format!("test{i}"),
626 };
627 let result = CommandResult::success();
628 let entry = HistoryEntry::new(operation, format!("Test {i}"), &result, None);
629 stack.push(entry);
630 }
631
632 assert_eq!(stack.undo_count(), 2);
634 }
635
636 #[test]
637 fn undo_manager_operations() {
638 let mut manager = UndoManager::new();
639
640 let operation = Operation::Insert {
642 position: Position::new(0),
643 text: "Hello".to_string(),
644 };
645 let mut result = CommandResult::success();
646 result.content_changed = true;
647
648 manager.record_operation(operation, "Insert text".to_string(), &result);
650
651 assert!(manager.can_undo());
652 assert_eq!(manager.next_undo_description(), Some("Insert text"));
653 }
654
655 #[test]
656 fn history_stats() {
657 let manager = UndoManager::new();
658 let stats = manager.stats();
659
660 assert_eq!(stats.undo_count, 0);
661 assert_eq!(stats.redo_count, 0);
662 assert_eq!(stats.memory_usage, 0);
663 }
664
665 #[test]
666 fn operation_memory_usage() {
667 let insert_op = Operation::Insert {
668 position: Position::new(0),
669 text: "Hello".to_string(),
670 };
671 assert!(insert_op.memory_usage() >= 5);
672
673 let delete_op = Operation::Delete {
674 range: Range::new(Position::new(0), Position::new(5)),
675 deleted_text: "Hello".to_string(),
676 };
677 assert!(delete_op.memory_usage() >= 5);
678
679 let replace_op = Operation::Replace {
680 range: Range::new(Position::new(0), Position::new(5)),
681 old_text: "Hello".to_string(),
682 new_text: "World".to_string(),
683 };
684 assert!(replace_op.memory_usage() >= 10);
685 }
686
687 #[test]
688 fn programmatic_undo_limit_configuration() {
689 let custom_config = UndoStackConfig {
691 max_entries: 3,
692 max_memory: 1000,
693 enable_compression: false,
694 arena_reset_interval: 0,
695 };
696
697 let mut manager = UndoManager::with_config(custom_config);
698
699 for i in 0..5 {
701 let operation = Operation::Insert {
702 position: Position::new(i * 10),
703 text: format!("test{i}"),
704 };
705 let mut result = CommandResult::success();
706 result.content_changed = true;
707 manager.record_operation(operation, format!("Insert {i}"), &result);
708 }
709
710 let stats = manager.stats();
712 assert_eq!(stats.undo_count, 3);
713
714 assert_eq!(manager.next_undo_description(), Some("Insert 4"));
716
717 manager.pop_undo_entry();
719 assert_eq!(manager.next_undo_description(), Some("Insert 3"));
720
721 manager.pop_undo_entry();
722 assert_eq!(manager.next_undo_description(), Some("Insert 2"));
723
724 manager.pop_undo_entry();
725 assert_eq!(manager.next_undo_description(), None);
726 }
727
728 #[test]
729 fn undo_stack_config_default() {
730 let default_config = UndoStackConfig::default();
732 assert_eq!(default_config.max_entries, 50);
733 assert_eq!(default_config.max_memory, 10 * 1024 * 1024); assert!(default_config.enable_compression);
735 assert_eq!(default_config.arena_reset_interval, 100);
736 }
737
738 #[test]
739 fn undo_manager_config_update() {
740 let mut manager = UndoManager::new();
742
743 let operation = Operation::Insert {
745 position: Position::new(0),
746 text: "test".to_string(),
747 };
748 let mut result = CommandResult::success();
749 result.content_changed = true;
750 manager.record_operation(operation, "Initial".to_string(), &result);
751
752 assert_eq!(manager.stats().undo_count, 1);
753
754 let restrictive_config = UndoStackConfig {
756 max_entries: 0, max_memory: 0,
758 enable_compression: false,
759 arena_reset_interval: 0,
760 };
761
762 manager.set_config(restrictive_config);
763
764 assert_eq!(manager.stats().undo_count, 0);
766
767 let operation = Operation::Insert {
769 position: Position::new(5),
770 text: "test2".to_string(),
771 };
772 manager.record_operation(operation, "Should not record".to_string(), &result);
773 assert_eq!(manager.stats().undo_count, 0);
774 }
775
776 #[test]
777 fn memory_limit_enforcement() {
778 let memory_limited_config = UndoStackConfig {
780 max_entries: 100, max_memory: 50, enable_compression: false,
783 arena_reset_interval: 0,
784 };
785
786 let mut manager = UndoManager::with_config(memory_limited_config);
787
788 for i in 0..5 {
790 let operation = Operation::Insert {
791 position: Position::new(i * 10),
792 text: format!(
793 "This is a long text string for operation {i} that should consume memory"
794 ),
795 };
796 let mut result = CommandResult::success();
797 result.content_changed = true;
798 manager.record_operation(operation, format!("Long operation {i}"), &result);
799 }
800
801 let stats = manager.stats();
803 assert!(stats.undo_count < 5);
804 assert!(stats.memory_usage <= 50 || stats.undo_count == 0);
805 }
806}