1use alloc::collections::BTreeMap;
2use alloc::string::String;
3use alloc::vec::Vec;
4
5use crate::Crdt;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub struct TextCrdt {
38 actor: String,
39 counter: u64,
40 elements: Vec<Element>,
42 version: BTreeMap<String, u64>,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
50struct Element {
51 id: (String, u64),
53 value: char,
55 deleted: bool,
57}
58
59impl TextCrdt {
60 pub fn new(actor: impl Into<String>) -> Self {
62 Self {
63 actor: actor.into(),
64 counter: 0,
65 elements: Vec::new(),
66 version: BTreeMap::new(),
67 }
68 }
69
70 pub fn fork(&self, new_actor: impl Into<String>) -> Self {
76 Self {
77 actor: new_actor.into(),
78 counter: self.counter,
79 elements: self.elements.clone(),
80 version: self.version.clone(),
81 }
82 }
83
84 pub fn insert(&mut self, index: usize, ch: char) {
90 assert!(
91 index <= self.len(),
92 "index {index} out of bounds for text of length {}",
93 self.len()
94 );
95
96 self.counter += 1;
97 let id = (self.actor.clone(), self.counter);
98 self.version
99 .entry(self.actor.clone())
100 .and_modify(|c| *c = (*c).max(self.counter))
101 .or_insert(self.counter);
102
103 let elem = Element {
104 id,
105 value: ch,
106 deleted: false,
107 };
108
109 let raw_index = self.raw_index_for_insert(index);
110 self.elements.insert(raw_index, elem);
111 }
112
113 pub fn insert_str(&mut self, index: usize, s: &str) {
122 assert!(
123 index <= self.len(),
124 "index {index} out of bounds for text of length {}",
125 self.len()
126 );
127
128 for (i, ch) in s.chars().enumerate() {
129 self.insert(index + i, ch);
130 }
131 }
132
133 pub fn remove(&mut self, index: usize) {
139 assert!(
140 index < self.len(),
141 "index {index} out of bounds for text of length {}",
142 self.len()
143 );
144
145 let raw = self.visible_to_raw(index);
146 self.elements[raw].deleted = true;
147 }
148
149 pub fn remove_range(&mut self, start: usize, len: usize) {
155 assert!(
156 start + len <= self.len(),
157 "range {}..{} out of bounds for text of length {}",
158 start,
159 start + len,
160 self.len()
161 );
162
163 for i in (0..len).rev() {
165 self.remove(start + i);
166 }
167 }
168
169 #[must_use]
171 pub fn len(&self) -> usize {
172 self.elements.iter().filter(|e| !e.deleted).count()
173 }
174
175 #[must_use]
177 pub fn is_empty(&self) -> bool {
178 self.len() == 0
179 }
180
181 #[must_use]
183 pub fn actor(&self) -> &str {
184 &self.actor
185 }
186
187 fn visible_to_raw(&self, visible: usize) -> usize {
193 let mut seen = 0;
194 for (raw, elem) in self.elements.iter().enumerate() {
195 if !elem.deleted {
196 if seen == visible {
197 return raw;
198 }
199 seen += 1;
200 }
201 }
202 panic!(
203 "visible index {} not found (only {} visible elements)",
204 visible, seen
205 );
206 }
207
208 fn raw_index_for_insert(&self, visible_index: usize) -> usize {
214 if visible_index == 0 {
215 return 0;
216 }
217
218 let visible_count = self.len();
219 if visible_index >= visible_count {
220 return self.elements.len();
223 }
224
225 self.visible_to_raw(visible_index)
227 }
228
229 fn find_by_id(&self, id: &(String, u64)) -> Option<usize> {
231 self.elements.iter().position(|e| &e.id == id)
232 }
233
234 fn find_insert_position(&self, elem: &Element, after_raw: Option<usize>) -> usize {
242 let start = match after_raw {
243 Some(idx) => idx + 1,
244 None => 0,
245 };
246
247 let new_key = (elem.id.1, &elem.id.0); for i in start..self.elements.len() {
250 let existing = &self.elements[i];
251 let existing_key = (existing.id.1, &existing.id.0);
252 if existing_key < new_key {
257 return i;
258 }
259 }
260
261 self.elements.len()
262 }
263
264 fn find_predecessor_raw(&self, other: &Self, elem: &Element) -> Option<usize> {
268 let other_pos = other.elements.iter().position(|e| e.id == elem.id)?;
270
271 if other_pos == 0 {
272 return None;
273 }
274
275 for i in (0..other_pos).rev() {
278 let pred_id = &other.elements[i].id;
279 if let Some(raw) = self.find_by_id(pred_id) {
280 return Some(raw);
281 }
282 }
283
284 None
285 }
286}
287
288impl Crdt for TextCrdt {
289 fn merge(&mut self, other: &Self) {
290 for other_elem in &other.elements {
296 if let Some(raw) = self.find_by_id(&other_elem.id) {
297 if other_elem.deleted {
299 self.elements[raw].deleted = true;
300 }
301 } else {
302 let predecessor_raw = self.find_predecessor_raw(other, other_elem);
309 let pos = self.find_insert_position(other_elem, predecessor_raw);
310 self.elements.insert(pos, other_elem.clone());
311 }
312 }
313
314 for (actor, &cnt) in &other.version {
316 let entry = self.version.entry(actor.clone()).or_insert(0);
317 *entry = (*entry).max(cnt);
318 }
319
320 if let Some(&max_cnt) = self.version.values().max() {
322 self.counter = self.counter.max(max_cnt);
323 }
324 }
325}
326
327impl core::fmt::Display for TextCrdt {
328 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
329 for elem in &self.elements {
330 if !elem.deleted {
331 write!(f, "{}", elem.value)?;
332 }
333 }
334 Ok(())
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
343 fn new_text_is_empty() {
344 let t = TextCrdt::new("a");
345 assert!(t.is_empty());
346 assert_eq!(t.len(), 0);
347 assert_eq!(t.to_string(), "");
348 }
349
350 #[test]
351 fn insert_single_char() {
352 let mut t = TextCrdt::new("a");
353 t.insert(0, 'x');
354 assert_eq!(t.to_string(), "x");
355 assert_eq!(t.len(), 1);
356 }
357
358 #[test]
359 fn insert_at_beginning_middle_end() {
360 let mut t = TextCrdt::new("a");
361 t.insert(0, 'a'); t.insert(1, 'c'); t.insert(1, 'b'); t.insert(0, 'z'); assert_eq!(t.to_string(), "zabc");
366 }
367
368 #[test]
369 fn delete_char() {
370 let mut t = TextCrdt::new("a");
371 t.insert_str(0, "hello");
372 assert_eq!(t.to_string(), "hello");
373
374 t.remove(1); assert_eq!(t.to_string(), "hllo");
376 assert_eq!(t.len(), 4);
377 }
378
379 #[test]
380 fn insert_str_basic() {
381 let mut t = TextCrdt::new("a");
382 t.insert_str(0, "hello");
383 assert_eq!(t.to_string(), "hello");
384 assert_eq!(t.len(), 5);
385 }
386
387 #[test]
388 fn insert_str_at_middle() {
389 let mut t = TextCrdt::new("a");
390 t.insert_str(0, "hd");
391 t.insert_str(1, "ello worl");
392 assert_eq!(t.to_string(), "hello world");
393 }
394
395 #[test]
396 fn remove_range_basic() {
397 let mut t = TextCrdt::new("a");
398 t.insert_str(0, "hello world");
399 t.remove_range(5, 6); assert_eq!(t.to_string(), "hello");
401 }
402
403 #[test]
404 fn remove_range_from_start() {
405 let mut t = TextCrdt::new("a");
406 t.insert_str(0, "hello");
407 t.remove_range(0, 3); assert_eq!(t.to_string(), "lo");
409 }
410
411 #[test]
412 fn remove_all() {
413 let mut t = TextCrdt::new("a");
414 t.insert_str(0, "abc");
415 t.remove_range(0, 3);
416 assert!(t.is_empty());
417 assert_eq!(t.to_string(), "");
418 }
419
420 #[test]
421 fn merge_disjoint_inserts() {
422 let mut t1 = TextCrdt::new("alice");
423 t1.insert_str(0, "hello");
424
425 let mut t2 = TextCrdt::new("bob");
426 t2.insert_str(0, "world");
427
428 t1.merge(&t2);
429
430 let result = t1.to_string();
432 assert!(result.contains("hello") || result.contains("world"));
433 assert_eq!(t1.len(), 10);
434 }
435
436 #[test]
437 fn merge_propagates_tombstones() {
438 let mut t1 = TextCrdt::new("alice");
439 t1.insert_str(0, "abc");
440
441 let mut t2 = t1.fork("bob");
445 t2.remove(1); t1.merge(&t2);
448 assert_eq!(t1.to_string(), "ac");
449 }
450
451 #[test]
452 fn merge_commutativity() {
453 let mut t1 = TextCrdt::new("alice");
454 t1.insert_str(0, "hello");
455
456 let mut t2 = TextCrdt::new("bob");
457 t2.insert_str(0, "world");
458
459 let mut left = t1.clone();
460 left.merge(&t2);
461
462 let mut right = t2.clone();
463 right.merge(&t1);
464
465 assert_eq!(left.to_string(), right.to_string());
466 }
467
468 #[test]
469 fn merge_idempotency() {
470 let mut t1 = TextCrdt::new("alice");
471 t1.insert_str(0, "hello");
472
473 let mut t2 = TextCrdt::new("bob");
474 t2.insert_str(0, "world");
475
476 t1.merge(&t2);
477 let after_first = t1.clone();
478 t1.merge(&t2);
479
480 assert_eq!(t1.to_string(), after_first.to_string());
481 assert_eq!(t1.len(), after_first.len());
482 }
483
484 #[test]
485 fn concurrent_inserts_at_same_position() {
486 let mut t1 = TextCrdt::new("alice");
488 t1.insert(0, 'a');
489
490 let mut t2 = TextCrdt::new("bob");
491 t2.insert(0, 'b');
492
493 let mut left = t1.clone();
494 left.merge(&t2);
495
496 let mut right = t2.clone();
497 right.merge(&t1);
498
499 assert_eq!(left.to_string(), right.to_string());
501 assert_eq!(left.len(), 2);
502 let s = left.to_string();
504 assert!(s.contains('a'));
505 assert!(s.contains('b'));
506 }
507
508 #[test]
509 fn concurrent_inserts_at_same_position_in_existing_text() {
510 let mut t1 = TextCrdt::new("alice");
512 t1.insert_str(0, "ac");
513
514 let mut t2 = t1.fork("bob");
516
517 t1.insert(1, 'X');
519 t2.insert(1, 'Y');
520
521 let mut left = t1.clone();
522 left.merge(&t2);
523
524 let mut right = t2.clone();
525 right.merge(&t1);
526
527 assert_eq!(left.to_string(), right.to_string());
528 let s = left.to_string();
529 assert!(s.starts_with('a'));
530 assert!(s.ends_with('c'));
531 assert!(s.contains('X'));
532 assert!(s.contains('Y'));
533 }
534
535 #[test]
536 fn concurrent_insert_and_delete() {
537 let mut t1 = TextCrdt::new("alice");
538 t1.insert_str(0, "abc");
539
540 let mut t2 = t1.fork("bob");
541
542 t1.remove(1);
544 t2.insert(1, 'X');
546
547 let mut left = t1.clone();
548 left.merge(&t2);
549
550 let mut right = t2.clone();
551 right.merge(&t1);
552
553 assert_eq!(left.to_string(), right.to_string());
555 let s = left.to_string();
557 assert!(!s.contains('b'));
558 assert!(s.contains('X'));
559 assert!(s.contains('a'));
560 assert!(s.contains('c'));
561 }
562
563 #[test]
564 fn merge_after_local_edits_on_both_sides() {
565 let mut t1 = TextCrdt::new("alice");
566 t1.insert_str(0, "hello");
567
568 let mut t2 = t1.fork("bob");
569
570 t1.insert_str(5, " world");
572 t2.remove_range(2, 3);
574 t2.insert(2, 'p');
575
576 let mut left = t1.clone();
577 left.merge(&t2);
578
579 let mut right = t2.clone();
580 right.merge(&t1);
581
582 assert_eq!(left.to_string(), right.to_string());
583 }
584
585 #[test]
586 fn display_trait() {
587 let mut t = TextCrdt::new("a");
588 t.insert_str(0, "hello");
589 assert_eq!(format!("{t}"), "hello");
590 }
591
592 #[test]
593 fn actor_getter() {
594 let t = TextCrdt::new("my-node");
595 assert_eq!(t.actor(), "my-node");
596 }
597
598 #[test]
599 #[should_panic(expected = "out of bounds")]
600 fn insert_out_of_bounds_panics() {
601 let mut t = TextCrdt::new("a");
602 t.insert(1, 'x'); }
604
605 #[test]
606 #[should_panic(expected = "out of bounds")]
607 fn remove_out_of_bounds_panics() {
608 let mut t = TextCrdt::new("a");
609 t.remove(0);
610 }
611
612 #[test]
613 #[should_panic(expected = "out of bounds")]
614 fn remove_range_out_of_bounds_panics() {
615 let mut t = TextCrdt::new("a");
616 t.insert_str(0, "abc");
617 t.remove_range(1, 5);
618 }
619
620 #[test]
621 fn triple_merge_convergence() {
622 let mut t1 = TextCrdt::new("alice");
623 t1.insert_str(0, "base");
624
625 let mut t2 = t1.fork("bob");
626 let mut t3 = t1.fork("carol");
627
628 t1.insert(4, '!');
629 t2.insert(0, '>');
630 t3.remove(2); let mut r1 = t1.clone();
634 r1.merge(&t2);
635 r1.merge(&t3);
636
637 let mut r2 = t2.clone();
638 r2.merge(&t3);
639 r2.merge(&t1);
640
641 let mut r3 = t3.clone();
642 r3.merge(&t1);
643 r3.merge(&t2);
644
645 assert_eq!(r1.to_string(), r2.to_string());
646 assert_eq!(r2.to_string(), r3.to_string());
647 }
648}