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_replace(&self, start: usize, end: usize) -> Option<&Replace> {
213 let mut replaces = self.all[start..end].iter().filter_map(|op| match op {
214 Operation::Replace(replace) => Some(replace),
215 Operation::Select(_) => None,
216 });
217 let replace = replaces.next()?;
218 replaces.next().is_none().then_some(replace)
219 }
220
221 fn frame_revises(&self, (prev_start, prev_end): (usize, usize), curr_start: usize) -> bool {
226 let curr_end = self.frame_at(curr_start).1;
227 let (Some(prev), Some(curr)) =
228 (self.frame_replace(prev_start, prev_end), self.frame_replace(curr_start, curr_end))
229 else {
230 return false;
231 };
232 let based_after =
233 self.meta[curr_start].base - self.meta[prev_start].base == curr_start - prev_start;
234 let inserted = prev.text.graphemes(true).count();
235 let start = prev.range.start();
236 based_after
237 && inserted > 0
238 && curr.range.start() == start
239 && curr.range.end() == Grapheme(start.0 + inserted)
240 }
241
242 fn frame_has_replace(&self, start: usize, end: usize) -> bool {
243 self.all[start..end]
244 .iter()
245 .any(|op| matches!(op, Operation::Replace(_)))
246 }
247
248 fn is_undo_checkpoint(&self, idx: usize) -> bool {
249 if idx == 0 || idx == self.len() {
251 return true;
252 }
253
254 if self.meta[idx].base == self.meta[idx - 1].base {
256 return false;
257 }
258
259 let (curr_start, curr_end) = self.frame_at(idx);
261 if !self.frame_has_replace(curr_start, curr_end) {
262 return false;
264 }
265
266 let mut walk = curr_start;
271 let prev_real = loop {
272 if walk == 0 {
273 break None;
274 }
275 let (s, e) = self.frame_at(walk - 1);
276 if self.frame_has_replace(s, e) {
277 break Some((s, e));
278 }
279 walk = s;
280 };
281
282 if prev_real.is_some_and(|prev| self.frame_revises(prev, curr_start)) {
284 return false;
285 }
286
287 if let Some((prev_start, prev_end)) = prev_real {
290 let curr_typing = self.frame_is_typing(curr_start, curr_end);
291 let prev_typing = self.frame_is_typing(prev_start, prev_end);
292 if curr_typing && prev_typing {
293 let gap = self.meta[curr_start].timestamp - self.meta[prev_end - 1].timestamp;
294 if gap <= Duration::from_millis(500) {
295 return false;
296 }
297 }
298 }
299
300 true
301 }
302}
303
304#[derive(Default)]
305struct External {
306 text: String,
309
310 seq: usize,
314}
315
316#[derive(Default)]
317pub struct Response {
318 pub text_updated: bool,
319 pub selection_user_moved: bool,
326 pub open_camera: bool,
327 pub seq_before: usize,
330 pub seq_after: usize,
331}
332
333impl std::ops::BitOrAssign for Response {
334 fn bitor_assign(&mut self, other: Response) {
335 self.text_updated |= other.text_updated;
336 self.selection_user_moved |= other.selection_user_moved;
337 self.open_camera |= other.open_camera;
338 if self.seq_before == self.seq_after {
340 self.seq_before = other.seq_before;
341 }
342 self.seq_after = other.seq_after;
343 }
344}
345
346#[derive(Clone, Debug)]
348struct OpMeta {
349 pub timestamp: Instant,
351
352 pub base: usize,
356}
357
358impl Buffer {
359 pub fn queue(&mut self, mut ops: Vec<Operation>) {
362 let timestamp = Instant::now();
363 let base = self.current.seq;
364
365 let mut combined_ops = Vec::new();
367 ops.sort_by_key(|op| match op {
368 Operation::Select(range) | Operation::Replace(Replace { range, .. }) => range.start(),
369 });
370 for op in ops.into_iter() {
371 match &op {
372 Operation::Replace(Replace { range: op_range, text: op_text }) => {
373 if let Some(Operation::Replace(Replace {
374 range: last_op_range,
375 text: last_op_text,
376 })) = combined_ops.last_mut()
377 {
378 if last_op_range.end() == op_range.start() {
379 last_op_range.1 = op_range.1;
380 last_op_text.push_str(op_text);
381 } else {
382 combined_ops.push(op);
383 }
384 } else {
385 combined_ops.push(op);
386 }
387 }
388 Operation::Select(_) => combined_ops.push(op),
389 }
390 }
391
392 self.ops
393 .meta
394 .extend(combined_ops.iter().map(|_| OpMeta { timestamp, base }));
395 self.ops.all.extend(combined_ops);
396 }
397
398 pub fn reload(&mut self, text: String) {
404 let timestamp = Instant::now();
405 let base = self.external.seq;
406 let ops = diff(&self.external.text, &text);
407
408 self.ops
409 .meta
410 .extend(ops.iter().map(|_| OpMeta { timestamp, base }));
411 self.ops.all.extend(ops.into_iter().map(Operation::Replace));
412
413 self.external.text = text;
414 self.external.seq = self.base.seq + self.ops.all.len();
415 }
416
417 pub fn saved(&mut self, external_seq: usize, external_text: String) {
421 self.external.text = external_text;
422 self.external.seq = external_seq;
423 }
424
425 pub fn merge(mut self, external_text_a: String, external_text_b: String) -> String {
426 let ops_a = diff(&self.external.text, &external_text_a);
427 let ops_b = diff(&self.external.text, &external_text_b);
428
429 let timestamp = Instant::now();
430 let base = self.external.seq;
431 self.ops
432 .meta
433 .extend(ops_a.iter().map(|_| OpMeta { timestamp, base }));
434 self.ops
435 .meta
436 .extend(ops_b.iter().map(|_| OpMeta { timestamp, base }));
437
438 self.ops
439 .all
440 .extend(ops_a.into_iter().map(Operation::Replace));
441 self.ops
442 .all
443 .extend(ops_b.into_iter().map(Operation::Replace));
444
445 self.update();
446 self.current.text
447 }
448
449 pub fn update(&mut self) -> Response {
451 let queue_len = self.base.seq + self.ops.len() - self.ops.processed_seq;
456 if queue_len > 0 {
457 let drain_range = self.current.seq..self.ops.processed_seq;
458 self.ops.all.drain(drain_range.clone());
459 self.ops.meta.drain(drain_range.clone());
460 self.ops.transformed.drain(drain_range.clone());
461 self.ops.transformed_inverted.drain(drain_range.clone());
462 self.ops.transformed_actual_len.drain(drain_range.clone());
463 self.ops.processed_seq = self.current.seq;
464 } else {
465 return Response::default();
466 }
467
468 let mut result = Response { seq_before: self.current.seq, ..Default::default() };
470 for idx in self.current_idx()..self.current_idx() + queue_len {
471 let mut op = self.ops.all[idx].clone();
472 let meta = &self.ops.meta[idx];
473 self.transform(&mut op, meta);
474 let partial_inverse = self.current.invert_pre(&op);
478 self.ops.transformed.push(op.clone());
479 self.ops.transformed_actual_len.push(Graphemes::default());
480 self.ops.processed_seq += 1;
481
482 result |= self.redo();
483
484 let actual_len = *self.ops.transformed_actual_len.last().unwrap();
485 self.ops
486 .transformed_inverted
487 .push(partial_inverse.finalize(actual_len));
488 }
489
490 result.seq_after = self.current.seq;
491 result
492 }
493
494 fn transform(&self, op: &mut Operation, meta: &OpMeta) {
495 let base_idx = meta.base - self.base.seq;
496 for transforming_idx in base_idx..self.ops.processed_seq {
497 let preceding_op = &self.ops.transformed[transforming_idx];
498 let preceding_actual_len = self.ops.transformed_actual_len[transforming_idx];
499 if let Operation::Replace(Replace {
500 range: preceding_replaced_range,
501 text: _preceding_replacement_text,
502 }) = preceding_op
503 {
504 if let Operation::Replace(Replace { range: transformed_range, text }) = op {
505 if preceding_replaced_range.intersects(transformed_range, false)
506 && !(preceding_replaced_range.is_empty() && transformed_range.is_empty())
507 {
508 *text = "".into();
514 transformed_range.1 = transformed_range.0;
515 }
516 }
517
518 match op {
519 Operation::Replace(Replace { range: transformed_range, .. })
520 | Operation::Select(transformed_range) => {
521 adjust_subsequent_range(
522 *preceding_replaced_range,
523 preceding_actual_len,
524 true,
525 transformed_range,
526 );
527 }
528 }
529 }
530 }
531 }
532
533 pub fn can_redo(&self) -> bool {
534 self.current.seq < self.ops.processed_seq
535 }
536
537 pub fn can_undo(&self) -> bool {
538 self.current.seq > self.base.seq
539 }
540
541 pub fn redo(&mut self) -> Response {
542 let mut response = Response::default();
543 while self.can_redo() {
544 let idx = self.current_idx();
545 let op = self.ops.transformed[idx].clone();
548
549 self.current.seq += 1;
550
551 let actual_len = match &op {
552 Operation::Replace(replace) => {
553 let (resp, len) = self.current.apply_replace(replace);
554 response |= resp;
555 len
556 }
557 Operation::Select(range) => {
558 response |= self.current.apply_select(*range);
559 Graphemes::default()
560 }
561 };
562 self.ops.transformed_actual_len[idx] = actual_len;
563
564 if self.ops.is_undo_checkpoint(self.current_idx()) {
565 break;
566 }
567 }
568 response
569 }
570
571 pub fn undo(&mut self) -> Response {
572 let mut response = Response::default();
573 while self.can_undo() {
574 self.current.seq -= 1;
575 let op = self.ops.transformed_inverted[self.current_idx()].clone();
577
578 if let Some(replace) = &op.replace {
579 let (resp, _) = self.current.apply_replace(replace);
580 response |= resp;
581 }
582 response |= self.current.apply_select(op.select);
583
584 if self.ops.is_undo_checkpoint(self.current_idx()) {
585 break;
586 }
587 }
588 response
589 }
590
591 fn current_idx(&self) -> usize {
592 self.current.seq - self.base.seq
593 }
594
595 pub fn transform_range(&self, since_seq: usize, range: &mut (Grapheme, Grapheme)) -> bool {
600 let start = since_seq.saturating_sub(self.base.seq);
601 let end = self.current_idx();
602 for (i, op) in self.ops.transformed[start..end].iter().enumerate() {
603 if let Operation::Replace(replace) = op {
604 if range.intersects(&replace.range, true)
605 && !(range.is_empty() && replace.range.is_empty())
606 {
607 return false;
608 }
609 let replacement_len = self.ops.transformed_actual_len[start + i];
610 adjust_subsequent_range(replace.range, replacement_len, false, range);
611 }
612 }
613 true
614 }
615
616 pub fn is_empty(&self) -> bool {
618 self.current.text.is_empty()
619 }
620
621 pub fn selection_text(&self) -> String {
622 self[self.current.selection].to_string()
623 }
624}
625
626impl From<&str> for Buffer {
627 fn from(value: &str) -> Self {
628 let mut result = Self::default();
629 result.current.text = value.to_string();
630 result.current.segs = unicode_segs::calc(value);
631 result.external.text = value.to_string();
632 result
633 }
634}
635
636pub fn adjust_subsequent_range(
640 replaced_range: (Grapheme, Grapheme), replacement_len: Graphemes, prefer_advance: bool,
641 range: &mut (Grapheme, Grapheme),
642) {
643 for position in [&mut range.0, &mut range.1] {
644 adjust_subsequent_position(replaced_range, replacement_len, prefer_advance, position);
645 }
646}
647
648fn adjust_subsequent_position(
652 replaced_range: (Grapheme, Grapheme), replacement_len: Graphemes, prefer_advance: bool,
653 position: &mut Grapheme,
654) {
655 let replaced_len = replaced_range.len();
656 let replacement_start = replaced_range.start();
657 let replacement_end = replacement_start + replacement_len;
658
659 enum Mode {
660 Insert,
661 Replace,
662 }
663 let mode = if replaced_range.is_empty() { Mode::Insert } else { Mode::Replace };
664
665 let sorted_bounds = {
666 let mut bounds = vec![replaced_range.start(), replaced_range.end(), *position];
667 bounds.sort();
668 bounds
669 };
670 let bind = |start: &Grapheme, end: &Grapheme, pos: &Grapheme| {
671 start == &replaced_range.start() && end == &replaced_range.end() && pos == &*position
672 };
673
674 *position = match (mode, &sorted_bounds[..]) {
675 (Mode::Insert, [start, end, pos]) if bind(start, end, pos) && end == pos => {
685 if prefer_advance {
686 replacement_end
687 } else {
688 replacement_start
689 }
690 }
691
692 (Mode::Replace, [start, pos, end]) if bind(start, end, pos) && start == pos => {
699 if prefer_advance {
700 replacement_end
701 } else {
702 replacement_start
703 }
704 }
705
706 (Mode::Replace, [start, end, pos]) if bind(start, end, pos) && end == pos => {
713 if prefer_advance {
714 replacement_end
715 } else {
716 replacement_start
717 }
718 }
719
720 (_, [pos, start, end]) if bind(start, end, pos) => *position,
727
728 (Mode::Replace, [start, pos, end]) if bind(start, end, pos) => {
738 if prefer_advance {
739 replacement_end
740 } else {
741 replacement_start
742 }
743 }
744
745 (_, [start, end, pos]) if bind(start, end, pos) => {
752 *position + replacement_len - replaced_len
753 }
754 _ => unreachable!(),
755 }
756}
757
758impl Index<(Byte, Byte)> for Snapshot {
759 type Output = str;
760
761 fn index(&self, index: (Byte, Byte)) -> &Self::Output {
762 &self.text[index.start().0..index.end().0]
763 }
764}
765
766impl Index<(Grapheme, Grapheme)> for Snapshot {
767 type Output = str;
768
769 fn index(&self, index: (Grapheme, Grapheme)) -> &Self::Output {
770 let index = self.segs.range_to_byte(index);
771 &self.text[index.start().0..index.end().0]
772 }
773}
774
775impl Index<(Byte, Byte)> for Buffer {
776 type Output = str;
777
778 fn index(&self, index: (Byte, Byte)) -> &Self::Output {
779 &self.current[index]
780 }
781}
782
783impl Index<(Grapheme, Grapheme)> for Buffer {
784 type Output = str;
785
786 fn index(&self, index: (Grapheme, Grapheme)) -> &Self::Output {
787 &self.current[index]
788 }
789}
790
791#[cfg(test)]
792mod test {
793 use super::Buffer;
794 use crate::model::text::offset_types::{Grapheme, RangeExt as _};
795 use crate::model::text::operation_types::{Operation, Replace};
796 use unicode_segmentation::UnicodeSegmentation;
797
798 fn type_into_selection(buffer: &mut Buffer, text: &str) {
799 let range = buffer.current.selection;
800 buffer.queue(vec![
801 Operation::Replace(Replace { range, text: text.into() }),
802 Operation::Select((range.start(), range.start())),
803 ]);
804 buffer.update();
805 }
806
807 #[test]
808 fn type_into_forward_selection() {
809 let mut buffer = Buffer::from("hello");
810 buffer.current.selection = (Grapheme(0), Grapheme(5));
811 type_into_selection(&mut buffer, "X");
812 assert_eq!(buffer.current.text, "X");
813 assert_eq!(buffer.current.selection, (Grapheme(1), Grapheme(1)));
814 }
815
816 #[test]
817 fn type_into_backward_selection() {
818 let mut buffer = Buffer::from("hello");
819 buffer.current.selection = (Grapheme(5), Grapheme(0));
820 type_into_selection(&mut buffer, "X");
821 assert_eq!(buffer.current.text, "X");
822 assert_eq!(buffer.current.selection, (Grapheme(1), Grapheme(1)));
823 }
824
825 #[test]
826 fn buffer_merge_nonintersecting_replace() {
827 let base_content = "base content base";
828 let local_content = "local content base";
829 let remote_content = "base content remote";
830
831 assert_eq!(
832 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
833 "local content remote"
834 );
835 assert_eq!(
836 Buffer::from(base_content).merge(remote_content.into(), local_content.into()),
837 "local content remote"
838 );
839 }
840
841 #[test]
842 fn buffer_merge_prefix_replace() {
843 let base_content = "base content";
844 let local_content = "local content";
845 let remote_content = "remote content";
846
847 assert_eq!(
848 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
849 "local content"
850 );
851 }
852
853 #[test]
854 fn buffer_merge_infix_replace() {
855 let base_content = "con base tent";
856 let local_content = "con local tent";
857 let remote_content = "con remote tent";
858
859 assert_eq!(
860 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
861 "con local tent"
862 );
863 assert_eq!(
864 Buffer::from(base_content).merge(remote_content.into(), local_content.into()),
865 "con remote tent"
866 );
867 }
868
869 #[test]
870 fn buffer_merge_postfix_replace() {
871 let base_content = "content base";
872 let local_content = "content local";
873 let remote_content = "content remote";
874
875 assert_eq!(
876 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
877 "content local"
878 );
879 assert_eq!(
880 Buffer::from(base_content).merge(remote_content.into(), local_content.into()),
881 "content remote"
882 );
883 }
884
885 #[test]
886 fn buffer_merge_insert() {
887 let base_content = "content";
888 let local_content = "content local";
889 let remote_content = "content remote";
890
891 assert_eq!(
892 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
893 "content local remote"
894 );
895 assert_eq!(
896 Buffer::from(base_content).merge(remote_content.into(), local_content.into()),
897 "content remote local"
898 );
899 }
900
901 #[test]
902 fn buffer_merge_insert_replace() {
904 let base_content = "content";
905 let local_content = "content local";
906 let remote_content = "remote";
907
908 assert_eq!(
909 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
910 "remote"
911 );
912 assert_eq!(
913 Buffer::from(base_content).merge(remote_content.into(), local_content.into()),
914 "remote local"
915 );
916 }
917
918 #[test]
919 fn buffer_merge_crash() {
921 let base_content = "con tent";
922 let local_content = "cont tent locallocal";
923 let remote_content = "cont remote tent";
924
925 let _ = Buffer::from(base_content).merge(local_content.into(), remote_content.into());
926 let _ = Buffer::from(base_content).merge(remote_content.into(), local_content.into());
927 }
928
929 use rand::prelude::*;
932
933 const POOL: &[&str] = &[
936 "a",
937 "b",
938 "z",
939 " ",
940 "\n",
941 "\t",
942 "é",
943 "ñ",
944 "ü",
945 "日",
946 "本",
947 "語",
948 "👋",
949 "🎉",
950 "🔥",
951 "❤️",
952 "👨👩👧👦",
953 "🏳️🌈",
954 "👍🏽",
955 "🇺🇸",
956 "🇯🇵",
957 "e\u{0301}",
958 "a\u{0308}", ];
960
961 fn random_edit(rng: &mut StdRng, doc: &str) -> String {
969 let graphemes: Vec<&str> = UnicodeSegmentation::graphemes(doc, true).collect();
970 let len = graphemes.len();
971
972 let mut g: Vec<String> = graphemes.iter().map(|s| s.to_string()).collect();
973
974 match rng.gen_range(0..4) {
975 0 if len > 0 => {
976 let pos = rng.gen_range(0..len);
977 let del = rng.gen_range(1..=(len - pos).min(5));
978 g.drain(pos..pos + del);
979 }
980 1 => {
981 let pos = rng.gen_range(0..=len);
982 let n = rng.gen_range(1..=5);
983 for j in 0..n {
984 g.insert(pos + j, POOL[rng.gen_range(0..POOL.len())].into());
985 }
986 }
987 2 if len > 0 => {
988 let pos = rng.gen_range(0..len);
989 let del = rng.gen_range(1..=(len - pos).min(5));
990 let ins: Vec<String> = (0..rng.gen_range(1..=3))
991 .map(|_| POOL[rng.gen_range(0..POOL.len())].into())
992 .collect();
993 g.splice(pos..pos + del, ins);
994 }
995 3 if len > 0 => {
996 g.clear();
997 }
998 _ => {
999 let n = rng.gen_range(1..=5);
1000 for _ in 0..n {
1001 g.push(POOL[rng.gen_range(0..POOL.len())].into());
1002 }
1003 }
1004 }
1005 g.concat()
1006 }
1007
1008 #[test]
1009 fn buffer_merge_fuzz() {
1010 let mut rng = StdRng::seed_from_u64(42);
1011 let bases = ["hello world", "👨👩👧👦🇺🇸🔥", "café ñoño 日本語", ""];
1012 for _ in 0..10_000 {
1013 let base = bases[rng.gen_range(0..bases.len())];
1014 let a = random_edit(&mut rng, base);
1015 let b = random_edit(&mut rng, base);
1016
1017 let _ = Buffer::from(base).merge(a.clone(), b.clone());
1019 let _ = Buffer::from(base).merge(b, a);
1020 }
1021 }
1022
1023 struct SyncLink {
1031 base: String,
1032 }
1033
1034 impl SyncLink {
1035 fn new(base: &str) -> Self {
1036 Self { base: base.into() }
1037 }
1038
1039 fn sync(&mut self, left: &mut String, right: &mut String) {
1040 let merged = Buffer::from(self.base.as_str()).merge(left.clone(), right.clone());
1041 *left = merged.clone();
1042 *right = merged.clone();
1043 self.base = merged;
1044 }
1045 }
1046
1047 fn full_sync(nodes: &mut [String], links: &mut [SyncLink]) {
1050 for _ in 0..nodes.len() * 2 {
1051 for i in 0..links.len() {
1052 let (left, right) = nodes.split_at_mut(i + 1);
1053 links[i].sync(&mut left[i], &mut right[0]);
1054 }
1055 for i in (0..links.len()).rev() {
1056 let (left, right) = nodes.split_at_mut(i + 1);
1057 links[i].sync(&mut left[i], &mut right[0]);
1058 }
1059 }
1060 }
1061
1062 fn partial_sync(nodes: &mut [String], links: &mut [SyncLink], rng: &mut StdRng) {
1063 for _ in 0..3 {
1064 for i in 0..links.len() {
1065 if rng.gen_bool(0.5) {
1066 let (left, right) = nodes.split_at_mut(i + 1);
1067 links[i].sync(&mut left[i], &mut right[0]);
1068 }
1069 }
1070 }
1071 }
1072
1073 fn assert_converged(nodes: &[String]) {
1074 for (i, node) in nodes.iter().enumerate().skip(1) {
1075 assert_eq!(
1076 &nodes[0], node,
1077 "node 0 and node {} diverged: {:?} vs {:?}",
1078 i, nodes[0], node
1079 );
1080 }
1081 }
1082
1083 #[test]
1084 fn buffer_merge_fuzz_chain_2() {
1085 let mut rng = StdRng::seed_from_u64(42);
1086 for _ in 0..10_000 {
1087 let init = if rng.gen_bool(0.5) { "hello 👋🏽" } else { "" };
1088 let mut nodes: Vec<String> = (0..2).map(|_| init.into()).collect();
1089 let mut links: Vec<SyncLink> = (0..1).map(|_| SyncLink::new(init)).collect();
1090
1091 for _ in 0..rng.gen_range(1..=4) {
1092 for _ in 0..rng.gen_range(1..=3) {
1093 let i = rng.gen_range(0..2);
1094 nodes[i] = random_edit(&mut rng, &nodes[i]);
1095 }
1096 if rng.gen_bool(0.5) {
1097 partial_sync(&mut nodes, &mut links, &mut rng);
1098 }
1099 }
1100
1101 full_sync(&mut nodes, &mut links);
1102 assert_converged(&nodes);
1103 }
1104 }
1105
1106 #[test]
1107 fn buffer_merge_fuzz_chain_5() {
1108 let mut rng = StdRng::seed_from_u64(77);
1109 for _ in 0..5_000 {
1110 let init = if rng.gen_bool(0.5) { "café 日本語 🇯🇵" } else { "abc" };
1111 let mut nodes: Vec<String> = (0..5).map(|_| init.into()).collect();
1112 let mut links: Vec<SyncLink> = (0..4).map(|_| SyncLink::new(init)).collect();
1113
1114 for _ in 0..rng.gen_range(1..=3) {
1115 for _ in 0..rng.gen_range(1..=5) {
1116 let i = rng.gen_range(0..5);
1117 nodes[i] = random_edit(&mut rng, &nodes[i]);
1118 }
1119 if rng.gen_bool(0.5) {
1120 partial_sync(&mut nodes, &mut links, &mut rng);
1121 }
1122 }
1123
1124 full_sync(&mut nodes, &mut links);
1125 assert_converged(&nodes);
1126 }
1127 }
1128}
1129
1130#[cfg(test)]
1131mod undo_unit_tests {
1132 use super::Buffer;
1138 use crate::model::text::offset_types::{Grapheme, IntoRangeExt as _, RangeExt as _};
1139 use crate::model::text::operation_types::{Operation, Replace};
1140 use std::thread::sleep;
1141 use std::time::Duration;
1142
1143 fn frame(buf: &mut Buffer, ops: Vec<Operation>) {
1144 buf.queue(ops);
1145 buf.update();
1146 }
1147
1148 fn type_str(buf: &mut Buffer, s: &str) {
1149 let cursor = buf.current.selection.start();
1150 frame(
1153 buf,
1154 vec![
1155 Operation::Replace(Replace { range: cursor.into_range(), text: s.into() }),
1156 Operation::Select(cursor.into_range()),
1157 ],
1158 );
1159 }
1160
1161 fn enter(buf: &mut Buffer) {
1164 type_str(buf, "\n");
1165 }
1166
1167 fn backspace(buf: &mut Buffer) {
1168 let cursor = buf.current.selection.start();
1169 if cursor.0 == 0 {
1170 return;
1171 }
1172 let prev = Grapheme(cursor.0 - 1);
1173 frame(
1174 buf,
1175 vec![
1176 Operation::Replace(Replace { range: (prev, cursor), text: "".into() }),
1177 Operation::Select(prev.into_range()),
1178 ],
1179 );
1180 }
1181
1182 fn click(buf: &mut Buffer, pos: usize) {
1183 frame(buf, vec![Operation::Select(Grapheme(pos).into_range())]);
1184 }
1185
1186 fn cmd_indent_lines(buf: &mut Buffer, line_starts: &[usize]) {
1189 let mut ops: Vec<Operation> = line_starts
1190 .iter()
1191 .map(|&s| {
1192 Operation::Replace(Replace { range: Grapheme(s).into_range(), text: " ".into() })
1193 })
1194 .collect();
1195 ops.push(Operation::Select(Grapheme(line_starts[0] + 2).into_range()));
1196 frame(buf, ops);
1197 }
1198
1199 fn cmd_deindent_lines(buf: &mut Buffer, line_starts: &[usize]) {
1200 let mut ops: Vec<Operation> = line_starts
1201 .iter()
1202 .map(|&s| {
1203 Operation::Replace(Replace {
1204 range: (Grapheme(s), Grapheme(s + 2)),
1205 text: "".into(),
1206 })
1207 })
1208 .collect();
1209 ops.push(Operation::Select(Grapheme(line_starts[0]).into_range()));
1210 frame(buf, ops);
1211 }
1212
1213 #[test]
1215 fn single_command_one_unit() {
1216 let mut buf = Buffer::from("ab\ncd\nef\n");
1217 cmd_indent_lines(&mut buf, &[0, 3, 6]);
1218 assert_eq!(buf.current.text, " ab\n cd\n ef\n");
1219 buf.undo();
1220 assert_eq!(buf.current.text, "ab\ncd\nef\n");
1221 }
1222
1223 #[test]
1225 fn two_commands_two_units() {
1226 let mut buf = Buffer::from("ab\ncd\nef\n");
1227 cmd_indent_lines(&mut buf, &[0, 3, 6]);
1228 cmd_indent_lines(&mut buf, &[2, 7, 12]);
1229 let after_two = buf.current.text.clone();
1230 buf.undo();
1231 assert_ne!(buf.current.text, after_two);
1232 let after_one_undo = buf.current.text.clone();
1233 assert_eq!(after_one_undo, " ab\n cd\n ef\n");
1234 buf.undo();
1235 assert_eq!(buf.current.text, "ab\ncd\nef\n");
1236 }
1237
1238 #[test]
1240 fn click_only_doesnt_create_unit() {
1241 let mut buf = Buffer::from("hello");
1242 click(&mut buf, 3);
1243 type_str(&mut buf, "X");
1248 assert_eq!(buf.current.text, "helXlo");
1249 buf.undo();
1250 assert_eq!(buf.current.text, "hello");
1251 }
1252
1253 #[test]
1255 fn click_between_commands_absorbs() {
1256 let mut buf = Buffer::from("ab\ncd\nef\n");
1257 cmd_indent_lines(&mut buf, &[0, 3, 6]);
1258 click(&mut buf, 0);
1259 cmd_indent_lines(&mut buf, &[2, 7, 12]);
1260 buf.undo();
1261 assert_eq!(buf.current.text, " ab\n cd\n ef\n");
1262 buf.undo();
1263 assert_eq!(buf.current.text, "ab\ncd\nef\n");
1264 }
1265
1266 #[test]
1268 fn rapid_typing_one_unit() {
1269 let mut buf = Buffer::from("");
1270 type_str(&mut buf, "a");
1271 type_str(&mut buf, "b");
1272 type_str(&mut buf, "c");
1273 assert_eq!(buf.current.text, "abc");
1274 buf.undo();
1275 assert_eq!(buf.current.text, "");
1276 }
1277
1278 #[test]
1280 fn typing_long_pause_splits() {
1281 let mut buf = Buffer::from("");
1282 type_str(&mut buf, "a");
1283 type_str(&mut buf, "b");
1284 sleep(Duration::from_millis(600));
1285 type_str(&mut buf, "c");
1286 type_str(&mut buf, "d");
1287 assert_eq!(buf.current.text, "abcd");
1288 buf.undo();
1289 assert_eq!(buf.current.text, "ab");
1290 buf.undo();
1291 assert_eq!(buf.current.text, "");
1292 }
1293
1294 #[test]
1296 fn type_cmd_type_three_units() {
1297 let mut buf = Buffer::from("xx\nyy\n");
1298 buf.current.selection = (Grapheme(6), Grapheme(6));
1300 type_str(&mut buf, "a");
1301 type_str(&mut buf, "b");
1302 cmd_indent_lines(&mut buf, &[0, 3]);
1303 type_str(&mut buf, "c");
1304 type_str(&mut buf, "d");
1305 let final_text = buf.current.text.clone();
1306 buf.undo();
1307 assert_ne!(buf.current.text, final_text); assert_eq!(buf.current.text, " xx\n yy\nab");
1309 buf.undo();
1310 assert_eq!(buf.current.text, "xx\nyy\nab"); buf.undo();
1312 assert_eq!(buf.current.text, "xx\nyy\n"); }
1314
1315 #[test]
1317 fn type_enter_type_one_unit() {
1318 let mut buf = Buffer::from("");
1319 type_str(&mut buf, "a");
1320 type_str(&mut buf, "b");
1321 enter(&mut buf);
1322 type_str(&mut buf, "c");
1323 type_str(&mut buf, "d");
1324 assert_eq!(buf.current.text, "ab\ncd");
1325 buf.undo();
1326 assert_eq!(buf.current.text, "");
1327 }
1328
1329 #[test]
1331 fn backspace_groups_with_typing() {
1332 let mut buf = Buffer::from("");
1333 type_str(&mut buf, "a");
1334 type_str(&mut buf, "b");
1335 backspace(&mut buf);
1336 type_str(&mut buf, "c");
1337 assert_eq!(buf.current.text, "ac");
1338 buf.undo();
1339 assert_eq!(buf.current.text, "");
1340 }
1341
1342 #[test]
1344 fn paste_own_unit() {
1345 let mut buf = Buffer::from("");
1346 type_str(&mut buf, "a");
1347 type_str(&mut buf, "b");
1348 type_str(&mut buf, "PASTED");
1350 type_str(&mut buf, "c");
1351 type_str(&mut buf, "d");
1352 assert_eq!(buf.current.text, "abPASTEDcd");
1353 buf.undo();
1354 assert_eq!(buf.current.text, "abPASTED"); buf.undo();
1356 assert_eq!(buf.current.text, "ab"); buf.undo();
1358 assert_eq!(buf.current.text, ""); }
1360
1361 #[test]
1363 fn select_overwrite_groups() {
1364 let mut buf = Buffer::from("hello");
1365 buf.current.selection = (Grapheme(1), Grapheme(4));
1368 frame(
1369 &mut buf,
1370 vec![
1371 Operation::Replace(Replace { range: (Grapheme(1), Grapheme(4)), text: "X".into() }),
1372 Operation::Select(Grapheme(2).into_range()),
1373 ],
1374 );
1375 type_str(&mut buf, "Y");
1376 assert_eq!(buf.current.text, "hXYo");
1377 buf.undo();
1378 assert_eq!(buf.current.text, "hello");
1379 }
1380
1381 #[test]
1385 fn click_then_edit_undoes_to_post_click_cursor() {
1386 let mut buf = Buffer::from("hello world");
1387 click(&mut buf, 6); type_str(&mut buf, "X");
1389 assert_eq!(buf.current.text, "hello Xworld");
1390 buf.undo();
1391 assert_eq!(buf.current.text, "hello world");
1392 assert_eq!(buf.current.selection, (Grapheme(6), Grapheme(6)));
1395 }
1396
1397 #[test]
1399 fn round_trip_two_units() {
1400 let mut buf = Buffer::from(" foo\n bar\n");
1401 click(&mut buf, 2);
1402 cmd_deindent_lines(&mut buf, &[0, 6]);
1403 let mid = buf.current.text.clone();
1404 assert_eq!(mid, "foo\nbar\n");
1405 cmd_indent_lines(&mut buf, &[0, 4]);
1406 assert_eq!(buf.current.text, " foo\n bar\n");
1407 buf.undo();
1408 assert_eq!(buf.current.text, mid); buf.undo();
1410 assert_eq!(buf.current.text, " foo\n bar\n"); }
1412}