1use super::offset_types::{Byte, Grapheme, Graphemes, RangeExt};
2use super::operation_types::{InverseOperation, Operation, Replace};
3use super::unicode_segs::UnicodeSegs;
4use super::{diff, unicode_segs};
5use std::ops::Index;
6use unicode_segmentation::UnicodeSegmentation as _;
7use web_time::{Duration, Instant};
8
9#[derive(Default)]
55pub struct Buffer {
56 pub current: Snapshot,
58
59 base: Snapshot,
62
63 ops: Ops,
65
66 external: External,
68}
69
70#[derive(Debug, Default)]
71pub struct Snapshot {
72 pub text: String,
73 pub segs: UnicodeSegs,
74 pub selection: (Grapheme, Grapheme),
75 pub seq: usize,
76}
77
78impl Snapshot {
79 fn apply_select(&mut self, range: (Grapheme, Grapheme)) -> Response {
80 self.selection = range;
81 Response { selection_user_moved: true, ..Response::default() }
82 }
83
84 fn apply_replace(&mut self, replace: &Replace) -> (Response, Graphemes) {
85 let Replace { range, text } = replace;
86 let byte_range = self.segs.range_to_byte(*range);
87
88 let old_segs = self.segs.clone();
94
95 self.text
96 .replace_range(byte_range.start().0..byte_range.end().0, text);
97 self.segs = unicode_segs::calc(&self.text);
98
99 let actual_len = Graphemes::measure_replace(&old_segs, &self.segs, *range);
100
101 adjust_subsequent_range(*range, actual_len, false, &mut self.selection);
102
103 (Response { text_updated: true, ..Default::default() }, actual_len)
104 }
105
106 fn invert_pre(&self, op: &Operation) -> PartialInverse {
110 let mut partial = PartialInverse { select: self.selection, replace: None };
111 if let Operation::Replace(Replace { range, text: _ }) = op {
112 let byte_range = self.segs.range_to_byte(*range);
113 let replaced_text = self[byte_range].into();
114 partial.replace = Some((range.start(), replaced_text));
115 }
116 partial
117 }
118}
119
120struct PartialInverse {
121 select: (Grapheme, Grapheme),
122 replace: Option<(Grapheme, String)>,
126}
127
128impl PartialInverse {
129 fn finalize(self, actual_len: Graphemes) -> InverseOperation {
130 InverseOperation {
131 select: self.select,
132 replace: self.replace.map(|(start, replaced_text)| Replace {
133 range: (start, start + actual_len),
134 text: replaced_text,
135 }),
136 }
137 }
138}
139
140#[derive(Default)]
141struct Ops {
142 all: Vec<Operation>,
144 meta: Vec<OpMeta>,
145
146 processed_seq: usize,
148
149 transformed: Vec<Operation>,
153
154 transformed_inverted: Vec<InverseOperation>,
158
159 transformed_actual_len: Vec<Graphemes>,
167}
168
169impl Ops {
170 fn len(&self) -> usize {
171 self.all.len()
172 }
173
174 fn frame_at(&self, idx: usize) -> (usize, usize) {
178 let base = self.meta[idx].base;
179 let mut start = idx;
180 while start > 0 && self.meta[start - 1].base == base {
181 start -= 1;
182 }
183 let mut end = idx + 1;
184 while end < self.len() && self.meta[end].base == base {
185 end += 1;
186 }
187 (start, end)
188 }
189
190 fn frame_is_typing(&self, start: usize, end: usize) -> bool {
196 let mut replaces = 0usize;
197 let mut text_graphemes = 0usize;
198 let mut selects = 0usize;
199 for op in &self.all[start..end] {
200 match op {
201 Operation::Replace(Replace { text, .. }) => {
202 replaces += 1;
203 text_graphemes += text.graphemes(true).count();
204 }
205 Operation::Select(_) => selects += 1,
206 }
207 }
208 replaces == 1 && text_graphemes <= 1 && selects <= 1
209 }
210
211 fn frame_has_replace(&self, start: usize, end: usize) -> bool {
212 self.all[start..end]
213 .iter()
214 .any(|op| matches!(op, Operation::Replace(_)))
215 }
216
217 fn is_undo_checkpoint(&self, idx: usize) -> bool {
218 if idx == 0 || idx == self.len() {
220 return true;
221 }
222
223 if self.meta[idx].base == self.meta[idx - 1].base {
225 return false;
226 }
227
228 let (curr_start, curr_end) = self.frame_at(idx);
230 if !self.frame_has_replace(curr_start, curr_end) {
231 return false;
233 }
234
235 let mut walk = curr_start;
240 let prev_real = loop {
241 if walk == 0 {
242 break None;
243 }
244 let (s, e) = self.frame_at(walk - 1);
245 if self.frame_has_replace(s, e) {
246 break Some((s, e));
247 }
248 walk = s;
249 };
250
251 if let Some((prev_start, prev_end)) = prev_real {
254 let curr_typing = self.frame_is_typing(curr_start, curr_end);
255 let prev_typing = self.frame_is_typing(prev_start, prev_end);
256 if curr_typing && prev_typing {
257 let gap = self.meta[curr_start].timestamp - self.meta[prev_end - 1].timestamp;
258 if gap <= Duration::from_millis(500) {
259 return false;
260 }
261 }
262 }
263
264 true
265 }
266}
267
268#[derive(Default)]
269struct External {
270 text: String,
273
274 seq: usize,
278}
279
280#[derive(Default)]
281pub struct Response {
282 pub text_updated: bool,
283 pub selection_user_moved: bool,
290 pub open_camera: bool,
291 pub seq_before: usize,
294 pub seq_after: usize,
295}
296
297impl std::ops::BitOrAssign for Response {
298 fn bitor_assign(&mut self, other: Response) {
299 self.text_updated |= other.text_updated;
300 self.selection_user_moved |= other.selection_user_moved;
301 self.open_camera |= other.open_camera;
302 if self.seq_before == self.seq_after {
304 self.seq_before = other.seq_before;
305 }
306 self.seq_after = other.seq_after;
307 }
308}
309
310#[derive(Clone, Debug)]
312struct OpMeta {
313 pub timestamp: Instant,
315
316 pub base: usize,
320}
321
322impl Buffer {
323 pub fn queue(&mut self, mut ops: Vec<Operation>) {
326 let timestamp = Instant::now();
327 let base = self.current.seq;
328
329 let mut combined_ops = Vec::new();
331 ops.sort_by_key(|op| match op {
332 Operation::Select(range) | Operation::Replace(Replace { range, .. }) => range.start(),
333 });
334 for op in ops.into_iter() {
335 match &op {
336 Operation::Replace(Replace { range: op_range, text: op_text }) => {
337 if let Some(Operation::Replace(Replace {
338 range: last_op_range,
339 text: last_op_text,
340 })) = combined_ops.last_mut()
341 {
342 if last_op_range.end() == op_range.start() {
343 last_op_range.1 = op_range.1;
344 last_op_text.push_str(op_text);
345 } else {
346 combined_ops.push(op);
347 }
348 } else {
349 combined_ops.push(op);
350 }
351 }
352 Operation::Select(_) => combined_ops.push(op),
353 }
354 }
355
356 self.ops
357 .meta
358 .extend(combined_ops.iter().map(|_| OpMeta { timestamp, base }));
359 self.ops.all.extend(combined_ops);
360 }
361
362 pub fn reload(&mut self, text: String) {
368 let timestamp = Instant::now();
369 let base = self.external.seq;
370 let ops = diff(&self.external.text, &text);
371
372 self.ops
373 .meta
374 .extend(ops.iter().map(|_| OpMeta { timestamp, base }));
375 self.ops.all.extend(ops.into_iter().map(Operation::Replace));
376
377 self.external.text = text;
378 self.external.seq = self.base.seq + self.ops.all.len();
379 }
380
381 pub fn saved(&mut self, external_seq: usize, external_text: String) {
385 self.external.text = external_text;
386 self.external.seq = external_seq;
387 }
388
389 pub fn merge(mut self, external_text_a: String, external_text_b: String) -> String {
390 let ops_a = diff(&self.external.text, &external_text_a);
391 let ops_b = diff(&self.external.text, &external_text_b);
392
393 let timestamp = Instant::now();
394 let base = self.external.seq;
395 self.ops
396 .meta
397 .extend(ops_a.iter().map(|_| OpMeta { timestamp, base }));
398 self.ops
399 .meta
400 .extend(ops_b.iter().map(|_| OpMeta { timestamp, base }));
401
402 self.ops
403 .all
404 .extend(ops_a.into_iter().map(Operation::Replace));
405 self.ops
406 .all
407 .extend(ops_b.into_iter().map(Operation::Replace));
408
409 self.update();
410 self.current.text
411 }
412
413 pub fn update(&mut self) -> Response {
415 let queue_len = self.base.seq + self.ops.len() - self.ops.processed_seq;
420 if queue_len > 0 {
421 let drain_range = self.current.seq..self.ops.processed_seq;
422 self.ops.all.drain(drain_range.clone());
423 self.ops.meta.drain(drain_range.clone());
424 self.ops.transformed.drain(drain_range.clone());
425 self.ops.transformed_inverted.drain(drain_range.clone());
426 self.ops.transformed_actual_len.drain(drain_range.clone());
427 self.ops.processed_seq = self.current.seq;
428 } else {
429 return Response::default();
430 }
431
432 let mut result = Response { seq_before: self.current.seq, ..Default::default() };
434 for idx in self.current_idx()..self.current_idx() + queue_len {
435 let mut op = self.ops.all[idx].clone();
436 let meta = &self.ops.meta[idx];
437 self.transform(&mut op, meta);
438 let partial_inverse = self.current.invert_pre(&op);
442 self.ops.transformed.push(op.clone());
443 self.ops.transformed_actual_len.push(Graphemes::default());
444 self.ops.processed_seq += 1;
445
446 result |= self.redo();
447
448 let actual_len = *self.ops.transformed_actual_len.last().unwrap();
449 self.ops
450 .transformed_inverted
451 .push(partial_inverse.finalize(actual_len));
452 }
453
454 result.seq_after = self.current.seq;
455 result
456 }
457
458 fn transform(&self, op: &mut Operation, meta: &OpMeta) {
459 let base_idx = meta.base - self.base.seq;
460 for transforming_idx in base_idx..self.ops.processed_seq {
461 let preceding_op = &self.ops.transformed[transforming_idx];
462 let preceding_actual_len = self.ops.transformed_actual_len[transforming_idx];
463 if let Operation::Replace(Replace {
464 range: preceding_replaced_range,
465 text: _preceding_replacement_text,
466 }) = preceding_op
467 {
468 if let Operation::Replace(Replace { range: transformed_range, text }) = op {
469 if preceding_replaced_range.intersects(transformed_range, false)
470 && !(preceding_replaced_range.is_empty() && transformed_range.is_empty())
471 {
472 *text = "".into();
478 transformed_range.1 = transformed_range.0;
479 }
480 }
481
482 match op {
483 Operation::Replace(Replace { range: transformed_range, .. })
484 | Operation::Select(transformed_range) => {
485 adjust_subsequent_range(
486 *preceding_replaced_range,
487 preceding_actual_len,
488 true,
489 transformed_range,
490 );
491 }
492 }
493 }
494 }
495 }
496
497 pub fn can_redo(&self) -> bool {
498 self.current.seq < self.ops.processed_seq
499 }
500
501 pub fn can_undo(&self) -> bool {
502 self.current.seq > self.base.seq
503 }
504
505 pub fn redo(&mut self) -> Response {
506 let mut response = Response::default();
507 while self.can_redo() {
508 let idx = self.current_idx();
509 let op = self.ops.transformed[idx].clone();
512
513 self.current.seq += 1;
514
515 let actual_len = match &op {
516 Operation::Replace(replace) => {
517 let (resp, len) = self.current.apply_replace(replace);
518 response |= resp;
519 len
520 }
521 Operation::Select(range) => {
522 response |= self.current.apply_select(*range);
523 Graphemes::default()
524 }
525 };
526 self.ops.transformed_actual_len[idx] = actual_len;
527
528 if self.ops.is_undo_checkpoint(self.current_idx()) {
529 break;
530 }
531 }
532 response
533 }
534
535 pub fn undo(&mut self) -> Response {
536 let mut response = Response::default();
537 while self.can_undo() {
538 self.current.seq -= 1;
539 let op = self.ops.transformed_inverted[self.current_idx()].clone();
541
542 if let Some(replace) = &op.replace {
543 let (resp, _) = self.current.apply_replace(replace);
544 response |= resp;
545 }
546 response |= self.current.apply_select(op.select);
547
548 if self.ops.is_undo_checkpoint(self.current_idx()) {
549 break;
550 }
551 }
552 response
553 }
554
555 fn current_idx(&self) -> usize {
556 self.current.seq - self.base.seq
557 }
558
559 pub fn transform_range(&self, since_seq: usize, range: &mut (Grapheme, Grapheme)) -> bool {
564 let start = since_seq.saturating_sub(self.base.seq);
565 let end = self.current_idx();
566 for (i, op) in self.ops.transformed[start..end].iter().enumerate() {
567 if let Operation::Replace(replace) = op {
568 if range.intersects(&replace.range, true)
569 && !(range.is_empty() && replace.range.is_empty())
570 {
571 return false;
572 }
573 let replacement_len = self.ops.transformed_actual_len[start + i];
574 adjust_subsequent_range(replace.range, replacement_len, false, range);
575 }
576 }
577 true
578 }
579
580 pub fn is_empty(&self) -> bool {
582 self.current.text.is_empty()
583 }
584
585 pub fn selection_text(&self) -> String {
586 self[self.current.selection].to_string()
587 }
588}
589
590impl From<&str> for Buffer {
591 fn from(value: &str) -> Self {
592 let mut result = Self::default();
593 result.current.text = value.to_string();
594 result.current.segs = unicode_segs::calc(value);
595 result.external.text = value.to_string();
596 result
597 }
598}
599
600pub fn adjust_subsequent_range(
604 replaced_range: (Grapheme, Grapheme), replacement_len: Graphemes, prefer_advance: bool,
605 range: &mut (Grapheme, Grapheme),
606) {
607 for position in [&mut range.0, &mut range.1] {
608 adjust_subsequent_position(replaced_range, replacement_len, prefer_advance, position);
609 }
610}
611
612fn adjust_subsequent_position(
616 replaced_range: (Grapheme, Grapheme), replacement_len: Graphemes, prefer_advance: bool,
617 position: &mut Grapheme,
618) {
619 let replaced_len = replaced_range.len();
620 let replacement_start = replaced_range.start();
621 let replacement_end = replacement_start + replacement_len;
622
623 enum Mode {
624 Insert,
625 Replace,
626 }
627 let mode = if replaced_range.is_empty() { Mode::Insert } else { Mode::Replace };
628
629 let sorted_bounds = {
630 let mut bounds = vec![replaced_range.start(), replaced_range.end(), *position];
631 bounds.sort();
632 bounds
633 };
634 let bind = |start: &Grapheme, end: &Grapheme, pos: &Grapheme| {
635 start == &replaced_range.start() && end == &replaced_range.end() && pos == &*position
636 };
637
638 *position = match (mode, &sorted_bounds[..]) {
639 (Mode::Insert, [start, end, pos]) if bind(start, end, pos) && end == pos => {
649 if prefer_advance {
650 replacement_end
651 } else {
652 replacement_start
653 }
654 }
655
656 (Mode::Replace, [start, pos, end]) if bind(start, end, pos) && start == pos => {
663 if prefer_advance {
664 replacement_end
665 } else {
666 replacement_start
667 }
668 }
669
670 (Mode::Replace, [start, end, pos]) if bind(start, end, pos) && end == pos => {
677 if prefer_advance {
678 replacement_end
679 } else {
680 replacement_start
681 }
682 }
683
684 (_, [pos, start, end]) if bind(start, end, pos) => *position,
691
692 (Mode::Replace, [start, pos, end]) if bind(start, end, pos) => {
702 if prefer_advance {
703 replacement_end
704 } else {
705 replacement_start
706 }
707 }
708
709 (_, [start, end, pos]) if bind(start, end, pos) => {
716 *position + replacement_len - replaced_len
717 }
718 _ => unreachable!(),
719 }
720}
721
722impl Index<(Byte, Byte)> for Snapshot {
723 type Output = str;
724
725 fn index(&self, index: (Byte, Byte)) -> &Self::Output {
726 &self.text[index.start().0..index.end().0]
727 }
728}
729
730impl Index<(Grapheme, Grapheme)> for Snapshot {
731 type Output = str;
732
733 fn index(&self, index: (Grapheme, Grapheme)) -> &Self::Output {
734 let index = self.segs.range_to_byte(index);
735 &self.text[index.start().0..index.end().0]
736 }
737}
738
739impl Index<(Byte, Byte)> for Buffer {
740 type Output = str;
741
742 fn index(&self, index: (Byte, Byte)) -> &Self::Output {
743 &self.current[index]
744 }
745}
746
747impl Index<(Grapheme, Grapheme)> for Buffer {
748 type Output = str;
749
750 fn index(&self, index: (Grapheme, Grapheme)) -> &Self::Output {
751 &self.current[index]
752 }
753}
754
755#[cfg(test)]
756mod test {
757 use super::Buffer;
758 use crate::model::text::offset_types::{Grapheme, RangeExt as _};
759 use crate::model::text::operation_types::{Operation, Replace};
760 use unicode_segmentation::UnicodeSegmentation;
761
762 fn type_into_selection(buffer: &mut Buffer, text: &str) {
763 let range = buffer.current.selection;
764 buffer.queue(vec![
765 Operation::Replace(Replace { range, text: text.into() }),
766 Operation::Select((range.start(), range.start())),
767 ]);
768 buffer.update();
769 }
770
771 #[test]
772 fn type_into_forward_selection() {
773 let mut buffer = Buffer::from("hello");
774 buffer.current.selection = (Grapheme(0), Grapheme(5));
775 type_into_selection(&mut buffer, "X");
776 assert_eq!(buffer.current.text, "X");
777 assert_eq!(buffer.current.selection, (Grapheme(1), Grapheme(1)));
778 }
779
780 #[test]
781 fn type_into_backward_selection() {
782 let mut buffer = Buffer::from("hello");
783 buffer.current.selection = (Grapheme(5), Grapheme(0));
784 type_into_selection(&mut buffer, "X");
785 assert_eq!(buffer.current.text, "X");
786 assert_eq!(buffer.current.selection, (Grapheme(1), Grapheme(1)));
787 }
788
789 #[test]
790 fn buffer_merge_nonintersecting_replace() {
791 let base_content = "base content base";
792 let local_content = "local content base";
793 let remote_content = "base content remote";
794
795 assert_eq!(
796 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
797 "local content remote"
798 );
799 assert_eq!(
800 Buffer::from(base_content).merge(remote_content.into(), local_content.into()),
801 "local content remote"
802 );
803 }
804
805 #[test]
806 fn buffer_merge_prefix_replace() {
807 let base_content = "base content";
808 let local_content = "local content";
809 let remote_content = "remote content";
810
811 assert_eq!(
812 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
813 "local content"
814 );
815 }
816
817 #[test]
818 fn buffer_merge_infix_replace() {
819 let base_content = "con base tent";
820 let local_content = "con local tent";
821 let remote_content = "con remote tent";
822
823 assert_eq!(
824 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
825 "con local tent"
826 );
827 assert_eq!(
828 Buffer::from(base_content).merge(remote_content.into(), local_content.into()),
829 "con remote tent"
830 );
831 }
832
833 #[test]
834 fn buffer_merge_postfix_replace() {
835 let base_content = "content base";
836 let local_content = "content local";
837 let remote_content = "content remote";
838
839 assert_eq!(
840 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
841 "content local"
842 );
843 assert_eq!(
844 Buffer::from(base_content).merge(remote_content.into(), local_content.into()),
845 "content remote"
846 );
847 }
848
849 #[test]
850 fn buffer_merge_insert() {
851 let base_content = "content";
852 let local_content = "content local";
853 let remote_content = "content remote";
854
855 assert_eq!(
856 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
857 "content local remote"
858 );
859 assert_eq!(
860 Buffer::from(base_content).merge(remote_content.into(), local_content.into()),
861 "content remote local"
862 );
863 }
864
865 #[test]
866 fn buffer_merge_insert_replace() {
868 let base_content = "content";
869 let local_content = "content local";
870 let remote_content = "remote";
871
872 assert_eq!(
873 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
874 "remote"
875 );
876 assert_eq!(
877 Buffer::from(base_content).merge(remote_content.into(), local_content.into()),
878 "remote local"
879 );
880 }
881
882 #[test]
883 fn buffer_merge_crash() {
885 let base_content = "con tent";
886 let local_content = "cont tent locallocal";
887 let remote_content = "cont remote tent";
888
889 let _ = Buffer::from(base_content).merge(local_content.into(), remote_content.into());
890 let _ = Buffer::from(base_content).merge(remote_content.into(), local_content.into());
891 }
892
893 use rand::prelude::*;
896
897 const POOL: &[&str] = &[
900 "a",
901 "b",
902 "z",
903 " ",
904 "\n",
905 "\t",
906 "é",
907 "ñ",
908 "ü",
909 "日",
910 "本",
911 "語",
912 "👋",
913 "🎉",
914 "🔥",
915 "❤️",
916 "👨👩👧👦",
917 "🏳️🌈",
918 "👍🏽",
919 "🇺🇸",
920 "🇯🇵",
921 "e\u{0301}",
922 "a\u{0308}", ];
924
925 fn random_edit(rng: &mut StdRng, doc: &str) -> String {
933 let graphemes: Vec<&str> = UnicodeSegmentation::graphemes(doc, true).collect();
934 let len = graphemes.len();
935
936 let mut g: Vec<String> = graphemes.iter().map(|s| s.to_string()).collect();
937
938 match rng.gen_range(0..4) {
939 0 if len > 0 => {
940 let pos = rng.gen_range(0..len);
941 let del = rng.gen_range(1..=(len - pos).min(5));
942 g.drain(pos..pos + del);
943 }
944 1 => {
945 let pos = rng.gen_range(0..=len);
946 let n = rng.gen_range(1..=5);
947 for j in 0..n {
948 g.insert(pos + j, POOL[rng.gen_range(0..POOL.len())].into());
949 }
950 }
951 2 if len > 0 => {
952 let pos = rng.gen_range(0..len);
953 let del = rng.gen_range(1..=(len - pos).min(5));
954 let ins: Vec<String> = (0..rng.gen_range(1..=3))
955 .map(|_| POOL[rng.gen_range(0..POOL.len())].into())
956 .collect();
957 g.splice(pos..pos + del, ins);
958 }
959 3 if len > 0 => {
960 g.clear();
961 }
962 _ => {
963 let n = rng.gen_range(1..=5);
964 for _ in 0..n {
965 g.push(POOL[rng.gen_range(0..POOL.len())].into());
966 }
967 }
968 }
969 g.concat()
970 }
971
972 #[test]
973 fn buffer_merge_fuzz() {
974 let mut rng = StdRng::seed_from_u64(42);
975 let bases = ["hello world", "👨👩👧👦🇺🇸🔥", "café ñoño 日本語", ""];
976 for _ in 0..10_000 {
977 let base = bases[rng.gen_range(0..bases.len())];
978 let a = random_edit(&mut rng, base);
979 let b = random_edit(&mut rng, base);
980
981 let _ = Buffer::from(base).merge(a.clone(), b.clone());
983 let _ = Buffer::from(base).merge(b, a);
984 }
985 }
986
987 struct SyncLink {
995 base: String,
996 }
997
998 impl SyncLink {
999 fn new(base: &str) -> Self {
1000 Self { base: base.into() }
1001 }
1002
1003 fn sync(&mut self, left: &mut String, right: &mut String) {
1004 let merged = Buffer::from(self.base.as_str()).merge(left.clone(), right.clone());
1005 *left = merged.clone();
1006 *right = merged.clone();
1007 self.base = merged;
1008 }
1009 }
1010
1011 fn full_sync(nodes: &mut [String], links: &mut [SyncLink]) {
1014 for _ in 0..nodes.len() * 2 {
1015 for i in 0..links.len() {
1016 let (left, right) = nodes.split_at_mut(i + 1);
1017 links[i].sync(&mut left[i], &mut right[0]);
1018 }
1019 for i in (0..links.len()).rev() {
1020 let (left, right) = nodes.split_at_mut(i + 1);
1021 links[i].sync(&mut left[i], &mut right[0]);
1022 }
1023 }
1024 }
1025
1026 fn partial_sync(nodes: &mut [String], links: &mut [SyncLink], rng: &mut StdRng) {
1027 for _ in 0..3 {
1028 for i in 0..links.len() {
1029 if rng.gen_bool(0.5) {
1030 let (left, right) = nodes.split_at_mut(i + 1);
1031 links[i].sync(&mut left[i], &mut right[0]);
1032 }
1033 }
1034 }
1035 }
1036
1037 fn assert_converged(nodes: &[String]) {
1038 for (i, node) in nodes.iter().enumerate().skip(1) {
1039 assert_eq!(
1040 &nodes[0], node,
1041 "node 0 and node {} diverged: {:?} vs {:?}",
1042 i, nodes[0], node
1043 );
1044 }
1045 }
1046
1047 #[test]
1048 fn buffer_merge_fuzz_chain_2() {
1049 let mut rng = StdRng::seed_from_u64(42);
1050 for _ in 0..10_000 {
1051 let init = if rng.gen_bool(0.5) { "hello 👋🏽" } else { "" };
1052 let mut nodes: Vec<String> = (0..2).map(|_| init.into()).collect();
1053 let mut links: Vec<SyncLink> = (0..1).map(|_| SyncLink::new(init)).collect();
1054
1055 for _ in 0..rng.gen_range(1..=4) {
1056 for _ in 0..rng.gen_range(1..=3) {
1057 let i = rng.gen_range(0..2);
1058 nodes[i] = random_edit(&mut rng, &nodes[i]);
1059 }
1060 if rng.gen_bool(0.5) {
1061 partial_sync(&mut nodes, &mut links, &mut rng);
1062 }
1063 }
1064
1065 full_sync(&mut nodes, &mut links);
1066 assert_converged(&nodes);
1067 }
1068 }
1069
1070 #[test]
1071 fn buffer_merge_fuzz_chain_5() {
1072 let mut rng = StdRng::seed_from_u64(77);
1073 for _ in 0..5_000 {
1074 let init = if rng.gen_bool(0.5) { "café 日本語 🇯🇵" } else { "abc" };
1075 let mut nodes: Vec<String> = (0..5).map(|_| init.into()).collect();
1076 let mut links: Vec<SyncLink> = (0..4).map(|_| SyncLink::new(init)).collect();
1077
1078 for _ in 0..rng.gen_range(1..=3) {
1079 for _ in 0..rng.gen_range(1..=5) {
1080 let i = rng.gen_range(0..5);
1081 nodes[i] = random_edit(&mut rng, &nodes[i]);
1082 }
1083 if rng.gen_bool(0.5) {
1084 partial_sync(&mut nodes, &mut links, &mut rng);
1085 }
1086 }
1087
1088 full_sync(&mut nodes, &mut links);
1089 assert_converged(&nodes);
1090 }
1091 }
1092}
1093
1094#[cfg(test)]
1095mod undo_unit_tests {
1096 use super::Buffer;
1102 use crate::model::text::offset_types::{Grapheme, IntoRangeExt as _, RangeExt as _};
1103 use crate::model::text::operation_types::{Operation, Replace};
1104 use std::thread::sleep;
1105 use std::time::Duration;
1106
1107 fn frame(buf: &mut Buffer, ops: Vec<Operation>) {
1108 buf.queue(ops);
1109 buf.update();
1110 }
1111
1112 fn type_str(buf: &mut Buffer, s: &str) {
1113 let cursor = buf.current.selection.start();
1114 frame(
1117 buf,
1118 vec![
1119 Operation::Replace(Replace { range: cursor.into_range(), text: s.into() }),
1120 Operation::Select(cursor.into_range()),
1121 ],
1122 );
1123 }
1124
1125 fn enter(buf: &mut Buffer) {
1128 type_str(buf, "\n");
1129 }
1130
1131 fn backspace(buf: &mut Buffer) {
1132 let cursor = buf.current.selection.start();
1133 if cursor.0 == 0 {
1134 return;
1135 }
1136 let prev = Grapheme(cursor.0 - 1);
1137 frame(
1138 buf,
1139 vec![
1140 Operation::Replace(Replace { range: (prev, cursor), text: "".into() }),
1141 Operation::Select(prev.into_range()),
1142 ],
1143 );
1144 }
1145
1146 fn click(buf: &mut Buffer, pos: usize) {
1147 frame(buf, vec![Operation::Select(Grapheme(pos).into_range())]);
1148 }
1149
1150 fn cmd_indent_lines(buf: &mut Buffer, line_starts: &[usize]) {
1153 let mut ops: Vec<Operation> = line_starts
1154 .iter()
1155 .map(|&s| {
1156 Operation::Replace(Replace { range: Grapheme(s).into_range(), text: " ".into() })
1157 })
1158 .collect();
1159 ops.push(Operation::Select(Grapheme(line_starts[0] + 2).into_range()));
1160 frame(buf, ops);
1161 }
1162
1163 fn cmd_deindent_lines(buf: &mut Buffer, line_starts: &[usize]) {
1164 let mut ops: Vec<Operation> = line_starts
1165 .iter()
1166 .map(|&s| {
1167 Operation::Replace(Replace {
1168 range: (Grapheme(s), Grapheme(s + 2)),
1169 text: "".into(),
1170 })
1171 })
1172 .collect();
1173 ops.push(Operation::Select(Grapheme(line_starts[0]).into_range()));
1174 frame(buf, ops);
1175 }
1176
1177 #[test]
1179 fn single_command_one_unit() {
1180 let mut buf = Buffer::from("ab\ncd\nef\n");
1181 cmd_indent_lines(&mut buf, &[0, 3, 6]);
1182 assert_eq!(buf.current.text, " ab\n cd\n ef\n");
1183 buf.undo();
1184 assert_eq!(buf.current.text, "ab\ncd\nef\n");
1185 }
1186
1187 #[test]
1189 fn two_commands_two_units() {
1190 let mut buf = Buffer::from("ab\ncd\nef\n");
1191 cmd_indent_lines(&mut buf, &[0, 3, 6]);
1192 cmd_indent_lines(&mut buf, &[2, 7, 12]);
1193 let after_two = buf.current.text.clone();
1194 buf.undo();
1195 assert_ne!(buf.current.text, after_two);
1196 let after_one_undo = buf.current.text.clone();
1197 assert_eq!(after_one_undo, " ab\n cd\n ef\n");
1198 buf.undo();
1199 assert_eq!(buf.current.text, "ab\ncd\nef\n");
1200 }
1201
1202 #[test]
1204 fn click_only_doesnt_create_unit() {
1205 let mut buf = Buffer::from("hello");
1206 click(&mut buf, 3);
1207 type_str(&mut buf, "X");
1212 assert_eq!(buf.current.text, "helXlo");
1213 buf.undo();
1214 assert_eq!(buf.current.text, "hello");
1215 }
1216
1217 #[test]
1219 fn click_between_commands_absorbs() {
1220 let mut buf = Buffer::from("ab\ncd\nef\n");
1221 cmd_indent_lines(&mut buf, &[0, 3, 6]);
1222 click(&mut buf, 0);
1223 cmd_indent_lines(&mut buf, &[2, 7, 12]);
1224 buf.undo();
1225 assert_eq!(buf.current.text, " ab\n cd\n ef\n");
1226 buf.undo();
1227 assert_eq!(buf.current.text, "ab\ncd\nef\n");
1228 }
1229
1230 #[test]
1232 fn rapid_typing_one_unit() {
1233 let mut buf = Buffer::from("");
1234 type_str(&mut buf, "a");
1235 type_str(&mut buf, "b");
1236 type_str(&mut buf, "c");
1237 assert_eq!(buf.current.text, "abc");
1238 buf.undo();
1239 assert_eq!(buf.current.text, "");
1240 }
1241
1242 #[test]
1244 fn typing_long_pause_splits() {
1245 let mut buf = Buffer::from("");
1246 type_str(&mut buf, "a");
1247 type_str(&mut buf, "b");
1248 sleep(Duration::from_millis(600));
1249 type_str(&mut buf, "c");
1250 type_str(&mut buf, "d");
1251 assert_eq!(buf.current.text, "abcd");
1252 buf.undo();
1253 assert_eq!(buf.current.text, "ab");
1254 buf.undo();
1255 assert_eq!(buf.current.text, "");
1256 }
1257
1258 #[test]
1260 fn type_cmd_type_three_units() {
1261 let mut buf = Buffer::from("xx\nyy\n");
1262 buf.current.selection = (Grapheme(6), Grapheme(6));
1264 type_str(&mut buf, "a");
1265 type_str(&mut buf, "b");
1266 cmd_indent_lines(&mut buf, &[0, 3]);
1267 type_str(&mut buf, "c");
1268 type_str(&mut buf, "d");
1269 let final_text = buf.current.text.clone();
1270 buf.undo();
1271 assert_ne!(buf.current.text, final_text); assert_eq!(buf.current.text, " xx\n yy\nab");
1273 buf.undo();
1274 assert_eq!(buf.current.text, "xx\nyy\nab"); buf.undo();
1276 assert_eq!(buf.current.text, "xx\nyy\n"); }
1278
1279 #[test]
1281 fn type_enter_type_one_unit() {
1282 let mut buf = Buffer::from("");
1283 type_str(&mut buf, "a");
1284 type_str(&mut buf, "b");
1285 enter(&mut buf);
1286 type_str(&mut buf, "c");
1287 type_str(&mut buf, "d");
1288 assert_eq!(buf.current.text, "ab\ncd");
1289 buf.undo();
1290 assert_eq!(buf.current.text, "");
1291 }
1292
1293 #[test]
1295 fn backspace_groups_with_typing() {
1296 let mut buf = Buffer::from("");
1297 type_str(&mut buf, "a");
1298 type_str(&mut buf, "b");
1299 backspace(&mut buf);
1300 type_str(&mut buf, "c");
1301 assert_eq!(buf.current.text, "ac");
1302 buf.undo();
1303 assert_eq!(buf.current.text, "");
1304 }
1305
1306 #[test]
1308 fn paste_own_unit() {
1309 let mut buf = Buffer::from("");
1310 type_str(&mut buf, "a");
1311 type_str(&mut buf, "b");
1312 type_str(&mut buf, "PASTED");
1314 type_str(&mut buf, "c");
1315 type_str(&mut buf, "d");
1316 assert_eq!(buf.current.text, "abPASTEDcd");
1317 buf.undo();
1318 assert_eq!(buf.current.text, "abPASTED"); buf.undo();
1320 assert_eq!(buf.current.text, "ab"); buf.undo();
1322 assert_eq!(buf.current.text, ""); }
1324
1325 #[test]
1327 fn select_overwrite_groups() {
1328 let mut buf = Buffer::from("hello");
1329 buf.current.selection = (Grapheme(1), Grapheme(4));
1332 frame(
1333 &mut buf,
1334 vec![
1335 Operation::Replace(Replace { range: (Grapheme(1), Grapheme(4)), text: "X".into() }),
1336 Operation::Select(Grapheme(2).into_range()),
1337 ],
1338 );
1339 type_str(&mut buf, "Y");
1340 assert_eq!(buf.current.text, "hXYo");
1341 buf.undo();
1342 assert_eq!(buf.current.text, "hello");
1343 }
1344
1345 #[test]
1349 fn click_then_edit_undoes_to_post_click_cursor() {
1350 let mut buf = Buffer::from("hello world");
1351 click(&mut buf, 6); type_str(&mut buf, "X");
1353 assert_eq!(buf.current.text, "hello Xworld");
1354 buf.undo();
1355 assert_eq!(buf.current.text, "hello world");
1356 assert_eq!(buf.current.selection, (Grapheme(6), Grapheme(6)));
1359 }
1360
1361 #[test]
1363 fn round_trip_two_units() {
1364 let mut buf = Buffer::from(" foo\n bar\n");
1365 click(&mut buf, 2);
1366 cmd_deindent_lines(&mut buf, &[0, 6]);
1367 let mid = buf.current.text.clone();
1368 assert_eq!(mid, "foo\nbar\n");
1369 cmd_indent_lines(&mut buf, &[0, 4]);
1370 assert_eq!(buf.current.text, " foo\n bar\n");
1371 buf.undo();
1372 assert_eq!(buf.current.text, mid); buf.undo();
1374 assert_eq!(buf.current.text, " foo\n bar\n"); }
1376}