1use serde::{Deserialize, Serialize};
10use std::collections::{HashMap, VecDeque};
11use ulid::Ulid;
12
13#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct OperationId(String);
16
17impl OperationId {
18 pub fn new() -> Self {
19 Self(Ulid::new().to_string())
20 }
21}
22
23impl Default for OperationId {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
31pub struct GroupId(String);
32
33impl GroupId {
34 pub fn new() -> Self {
35 Self(Ulid::new().to_string())
36 }
37}
38
39impl Default for GroupId {
40 fn default() -> Self {
41 Self::new()
42 }
43}
44
45#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
47pub enum TextOperation {
48 Insert { position: usize, text: String },
50 Delete { position: usize, deleted: String },
52 Replace {
54 position: usize,
55 deleted: String,
56 inserted: String,
57 },
58}
59
60impl TextOperation {
61 pub fn inverse(&self) -> Self {
63 match self {
64 TextOperation::Insert { position, text } => TextOperation::Delete {
65 position: *position,
66 deleted: text.clone(),
67 },
68 TextOperation::Delete { position, deleted } => TextOperation::Insert {
69 position: *position,
70 text: deleted.clone(),
71 },
72 TextOperation::Replace {
73 position,
74 deleted,
75 inserted,
76 } => TextOperation::Replace {
77 position: *position,
78 deleted: inserted.clone(),
79 inserted: deleted.clone(),
80 },
81 }
82 }
83}
84
85#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
87pub enum FormatOperation {
88 AddMark {
90 mark_id: String,
91 mark_type: String,
92 start: usize,
93 end: usize,
94 },
95 RemoveMark { mark_id: String },
97}
98
99impl FormatOperation {
100 pub fn inverse(&self) -> Self {
102 match self {
103 FormatOperation::AddMark { mark_id, .. } => FormatOperation::RemoveMark {
104 mark_id: mark_id.clone(),
105 },
106 FormatOperation::RemoveMark { mark_id } => {
107 FormatOperation::RemoveMark {
109 mark_id: mark_id.clone(),
110 }
111 }
112 }
113 }
114}
115
116#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
118pub enum JsonOperation {
119 Set {
121 path: String,
122 old_value: Option<serde_json::Value>,
123 new_value: serde_json::Value,
124 },
125 Delete {
127 path: String,
128 old_value: serde_json::Value,
129 },
130 ArrayInsert {
132 array_path: String,
133 index: usize,
134 value: serde_json::Value,
135 },
136 ArrayRemove {
138 array_path: String,
139 index: usize,
140 value: serde_json::Value,
141 },
142}
143
144impl JsonOperation {
145 pub fn inverse(&self) -> Self {
147 match self {
148 JsonOperation::Set {
149 path,
150 old_value,
151 new_value,
152 } => {
153 if let Some(old) = old_value {
154 JsonOperation::Set {
155 path: path.clone(),
156 old_value: Some(new_value.clone()),
157 new_value: old.clone(),
158 }
159 } else {
160 JsonOperation::Delete {
161 path: path.clone(),
162 old_value: new_value.clone(),
163 }
164 }
165 }
166 JsonOperation::Delete { path, old_value } => JsonOperation::Set {
167 path: path.clone(),
168 old_value: None,
169 new_value: old_value.clone(),
170 },
171 JsonOperation::ArrayInsert {
172 array_path,
173 index,
174 value,
175 } => JsonOperation::ArrayRemove {
176 array_path: array_path.clone(),
177 index: *index,
178 value: value.clone(),
179 },
180 JsonOperation::ArrayRemove {
181 array_path,
182 index,
183 value,
184 } => JsonOperation::ArrayInsert {
185 array_path: array_path.clone(),
186 index: *index,
187 value: value.clone(),
188 },
189 }
190 }
191}
192
193#[derive(Clone, Debug, Serialize, Deserialize)]
195pub enum UndoableOperation {
196 Text(TextOperation),
197 Format(FormatOperation),
198 Json(JsonOperation),
199}
200
201impl UndoableOperation {
202 pub fn inverse(&self) -> Self {
204 match self {
205 UndoableOperation::Text(op) => UndoableOperation::Text(op.inverse()),
206 UndoableOperation::Format(op) => UndoableOperation::Format(op.inverse()),
207 UndoableOperation::Json(op) => UndoableOperation::Json(op.inverse()),
208 }
209 }
210}
211
212#[derive(Clone, Debug, Serialize, Deserialize)]
214pub struct Operation {
215 pub id: OperationId,
217 pub document_id: String,
219 pub replica_id: String,
221 pub operation: UndoableOperation,
223 pub timestamp: u64,
225 pub group_id: Option<GroupId>,
227 pub undone: bool,
229}
230
231impl Operation {
232 pub fn new(
233 document_id: impl Into<String>,
234 replica_id: impl Into<String>,
235 operation: UndoableOperation,
236 timestamp: u64,
237 ) -> Self {
238 Self {
239 id: OperationId::new(),
240 document_id: document_id.into(),
241 replica_id: replica_id.into(),
242 operation,
243 timestamp,
244 group_id: None,
245 undone: false,
246 }
247 }
248
249 pub fn with_group(mut self, group_id: GroupId) -> Self {
250 self.group_id = Some(group_id);
251 self
252 }
253}
254
255#[derive(Clone, Debug)]
257pub struct UndoManager {
258 document_id: String,
260 replica_id: String,
262 clock: u64,
264 history: Vec<Operation>,
266 undo_stack: VecDeque<OperationId>,
268 redo_stack: VecDeque<OperationId>,
270 current_group: Option<GroupId>,
272 max_history: usize,
274}
275
276impl UndoManager {
277 pub fn new(document_id: impl Into<String>, replica_id: impl Into<String>) -> Self {
279 Self {
280 document_id: document_id.into(),
281 replica_id: replica_id.into(),
282 clock: 0,
283 history: Vec::new(),
284 undo_stack: VecDeque::new(),
285 redo_stack: VecDeque::new(),
286 current_group: None,
287 max_history: 1000,
288 }
289 }
290
291 pub fn set_max_history(&mut self, max: usize) {
293 self.max_history = max;
294 self.trim_history();
295 }
296
297 pub fn record(&mut self, operation: UndoableOperation) -> &Operation {
299 self.clock += 1;
300
301 let mut op = Operation::new(&self.document_id, &self.replica_id, operation, self.clock);
302
303 if let Some(group_id) = &self.current_group {
304 op.group_id = Some(group_id.clone());
305 }
306
307 let op_id = op.id.clone();
308 self.history.push(op);
309 self.undo_stack.push_back(op_id);
310
311 self.redo_stack.clear();
313
314 self.trim_history();
315
316 self.history.last().unwrap()
317 }
318
319 pub fn record_remote(&mut self, operation: Operation) {
321 self.clock = self.clock.max(operation.timestamp) + 1;
323 self.history.push(operation);
324 self.trim_history();
325 }
326
327 pub fn start_group(&mut self) -> GroupId {
329 let group_id = GroupId::new();
330 self.current_group = Some(group_id.clone());
331 group_id
332 }
333
334 pub fn end_group(&mut self) {
336 self.current_group = None;
337 }
338
339 pub fn can_undo(&self) -> bool {
341 !self.undo_stack.is_empty()
342 }
343
344 pub fn can_redo(&self) -> bool {
346 !self.redo_stack.is_empty()
347 }
348
349 pub fn undo(&mut self) -> Vec<UndoableOperation> {
352 if self.undo_stack.is_empty() {
353 return Vec::new();
354 }
355
356 let op_id = self.undo_stack.pop_back().unwrap();
357 let mut inverses = Vec::new();
358
359 let group_id = self
361 .history
362 .iter()
363 .find(|o| o.id == op_id)
364 .and_then(|o| o.group_id.clone());
365
366 if let Some(group_id) = group_id {
367 for op in self.history.iter_mut() {
369 if op.group_id.as_ref() == Some(&group_id) && !op.undone {
370 inverses.push(op.operation.inverse());
371 op.undone = true;
372 }
373 }
374
375 let group_id_ref = &group_id;
377 self.undo_stack.retain(|id| {
378 self.history
379 .iter()
380 .find(|o| &o.id == id)
381 .map(|o| o.group_id.as_ref() != Some(group_id_ref))
382 .unwrap_or(true)
383 });
384
385 self.redo_stack.push_back(op_id);
386 } else {
387 if let Some(op) = self.history.iter_mut().find(|o| o.id == op_id) {
389 inverses.push(op.operation.inverse());
390 op.undone = true;
391 }
392 self.redo_stack.push_back(op_id);
393 }
394
395 inverses.reverse();
397 inverses
398 }
399
400 pub fn redo(&mut self) -> Vec<UndoableOperation> {
403 if self.redo_stack.is_empty() {
404 return Vec::new();
405 }
406
407 let op_id = self.redo_stack.pop_back().unwrap();
408 let mut operations = Vec::new();
409
410 let group_id = self
412 .history
413 .iter()
414 .find(|o| o.id == op_id)
415 .and_then(|o| o.group_id.clone());
416
417 if let Some(group_id) = group_id {
418 for op in self.history.iter_mut() {
420 if op.group_id.as_ref() == Some(&group_id) && op.undone {
421 operations.push(op.operation.clone());
422 op.undone = false;
423 }
424 }
425 self.undo_stack.push_back(op_id);
426 } else {
427 if let Some(op) = self.history.iter_mut().find(|o| o.id == op_id) {
428 operations.push(op.operation.clone());
429 op.undone = false;
430 }
431 self.undo_stack.push_back(op_id);
432 }
433
434 operations
435 }
436
437 pub fn undo_stack_size(&self) -> usize {
439 self.undo_stack.len()
440 }
441
442 pub fn redo_stack_size(&self) -> usize {
444 self.redo_stack.len()
445 }
446
447 pub fn clear(&mut self) {
449 self.history.clear();
450 self.undo_stack.clear();
451 self.redo_stack.clear();
452 }
453
454 fn trim_history(&mut self) {
456 while self.history.len() > self.max_history {
457 if let Some(removed) = self.history.first() {
458 let removed_id = removed.id.clone();
459 self.undo_stack.retain(|id| *id != removed_id);
460 self.redo_stack.retain(|id| *id != removed_id);
461 }
462 self.history.remove(0);
463 }
464 }
465}
466
467#[derive(Clone, Debug)]
469pub struct CollaborativeUndoManager {
470 managers: HashMap<String, UndoManager>,
472 replica_id: String,
474}
475
476impl CollaborativeUndoManager {
477 pub fn new(replica_id: impl Into<String>) -> Self {
479 Self {
480 managers: HashMap::new(),
481 replica_id: replica_id.into(),
482 }
483 }
484
485 pub fn for_document(&mut self, document_id: &str) -> &mut UndoManager {
487 self.managers
488 .entry(document_id.to_string())
489 .or_insert_with(|| UndoManager::new(document_id, &self.replica_id))
490 }
491
492 pub fn record(&mut self, document_id: &str, operation: UndoableOperation) -> &Operation {
494 self.for_document(document_id).record(operation)
495 }
496
497 pub fn record_remote(&mut self, document_id: &str, operation: Operation) {
499 self.for_document(document_id).record_remote(operation);
500 }
501
502 pub fn start_group(&mut self, document_id: &str) -> GroupId {
504 self.for_document(document_id).start_group()
505 }
506
507 pub fn end_group(&mut self, document_id: &str) {
509 self.for_document(document_id).end_group();
510 }
511
512 pub fn undo(&mut self, document_id: &str) -> Vec<UndoableOperation> {
514 self.for_document(document_id).undo()
515 }
516
517 pub fn redo(&mut self, document_id: &str) -> Vec<UndoableOperation> {
519 self.for_document(document_id).redo()
520 }
521
522 pub fn can_undo(&self, document_id: &str) -> bool {
524 self.managers
525 .get(document_id)
526 .map(|m| m.can_undo())
527 .unwrap_or(false)
528 }
529
530 pub fn can_redo(&self, document_id: &str) -> bool {
532 self.managers
533 .get(document_id)
534 .map(|m| m.can_redo())
535 .unwrap_or(false)
536 }
537
538 pub fn remove_document(&mut self, document_id: &str) {
540 self.managers.remove(document_id);
541 }
542}
543
544#[cfg(test)]
545mod tests {
546 use super::*;
547
548 #[test]
549 fn test_text_operation_inverse() {
550 let insert = TextOperation::Insert {
551 position: 0,
552 text: "Hello".to_string(),
553 };
554 let inverse = insert.inverse();
555
556 assert!(
557 matches!(inverse, TextOperation::Delete { position: 0, deleted } if deleted == "Hello")
558 );
559 }
560
561 #[test]
562 fn test_basic_undo() {
563 let mut manager = UndoManager::new("doc1", "r1");
564
565 manager.record(UndoableOperation::Text(TextOperation::Insert {
567 position: 0,
568 text: "Hello".to_string(),
569 }));
570
571 assert!(manager.can_undo());
572 assert!(!manager.can_redo());
573
574 let inverses = manager.undo();
576 assert_eq!(inverses.len(), 1);
577
578 if let UndoableOperation::Text(TextOperation::Delete { deleted, .. }) = &inverses[0] {
579 assert_eq!(deleted, "Hello");
580 } else {
581 panic!("Expected text delete operation");
582 }
583
584 assert!(!manager.can_undo());
585 assert!(manager.can_redo());
586 }
587
588 #[test]
589 fn test_redo() {
590 let mut manager = UndoManager::new("doc1", "r1");
591
592 manager.record(UndoableOperation::Text(TextOperation::Insert {
593 position: 0,
594 text: "Hello".to_string(),
595 }));
596
597 manager.undo();
598 assert!(manager.can_redo());
599
600 let ops = manager.redo();
601 assert_eq!(ops.len(), 1);
602
603 if let UndoableOperation::Text(TextOperation::Insert { text, .. }) = &ops[0] {
604 assert_eq!(text, "Hello");
605 } else {
606 panic!("Expected text insert operation");
607 }
608 }
609
610 #[test]
611 fn test_operation_group() {
612 let mut manager = UndoManager::new("doc1", "r1");
613
614 manager.start_group();
616
617 manager.record(UndoableOperation::Text(TextOperation::Insert {
619 position: 0,
620 text: "A".to_string(),
621 }));
622 manager.record(UndoableOperation::Text(TextOperation::Insert {
623 position: 1,
624 text: "B".to_string(),
625 }));
626 manager.record(UndoableOperation::Text(TextOperation::Insert {
627 position: 2,
628 text: "C".to_string(),
629 }));
630
631 manager.end_group();
633
634 let inverses = manager.undo();
636 assert!(!inverses.is_empty());
639 }
640
641 #[test]
642 fn test_redo_clears_on_new_operation() {
643 let mut manager = UndoManager::new("doc1", "r1");
644
645 manager.record(UndoableOperation::Text(TextOperation::Insert {
646 position: 0,
647 text: "A".to_string(),
648 }));
649
650 manager.undo();
651 assert!(manager.can_redo());
652
653 manager.record(UndoableOperation::Text(TextOperation::Insert {
655 position: 0,
656 text: "B".to_string(),
657 }));
658
659 assert!(!manager.can_redo());
660 }
661
662 #[test]
663 fn test_collaborative_undo_manager() {
664 let mut manager = CollaborativeUndoManager::new("r1");
665
666 manager.record(
668 "doc1",
669 UndoableOperation::Text(TextOperation::Insert {
670 position: 0,
671 text: "Hello".to_string(),
672 }),
673 );
674
675 manager.record(
676 "doc2",
677 UndoableOperation::Json(JsonOperation::Set {
678 path: "name".to_string(),
679 old_value: None,
680 new_value: serde_json::json!("test"),
681 }),
682 );
683
684 assert!(manager.can_undo("doc1"));
685 assert!(manager.can_undo("doc2"));
686
687 manager.undo("doc1");
689 assert!(!manager.can_undo("doc1"));
690 assert!(manager.can_undo("doc2"));
691 }
692
693 #[test]
694 fn test_json_operation_inverse() {
695 let set = JsonOperation::Set {
696 path: "name".to_string(),
697 old_value: Some(serde_json::json!("old")),
698 new_value: serde_json::json!("new"),
699 };
700
701 let inverse = set.inverse();
702
703 if let JsonOperation::Set {
704 path,
705 old_value,
706 new_value,
707 } = inverse
708 {
709 assert_eq!(path, "name");
710 assert_eq!(new_value, serde_json::json!("old"));
711 assert_eq!(old_value, Some(serde_json::json!("new")));
712 } else {
713 panic!("Expected Set operation");
714 }
715 }
716
717 #[test]
718 fn test_max_history() {
719 let mut manager = UndoManager::new("doc1", "r1");
720 manager.set_max_history(5);
721
722 for i in 0..10 {
723 manager.record(UndoableOperation::Text(TextOperation::Insert {
724 position: i,
725 text: format!("{}", i),
726 }));
727 }
728
729 assert!(manager.undo_stack_size() <= 5);
731 }
732
733 #[test]
734 fn test_remote_operation() {
735 let mut manager = UndoManager::new("doc1", "r1");
736
737 let remote_op = Operation::new(
739 "doc1",
740 "r2",
741 UndoableOperation::Text(TextOperation::Insert {
742 position: 0,
743 text: "Remote".to_string(),
744 }),
745 100,
746 );
747
748 manager.record_remote(remote_op);
749
750 assert!(!manager.can_undo());
752 }
753}