1use super::offset_types::{DocByteOffset, DocCharOffset, RangeExt, RelCharOffset};
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;
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: (DocCharOffset, DocCharOffset),
75 pub seq: usize,
76}
77
78impl Snapshot {
79 fn apply_select(&mut self, range: (DocCharOffset, DocCharOffset)) -> Response {
80 self.selection = range;
81 Response::default()
82 }
83
84 fn apply_replace(&mut self, replace: &Replace) -> Response {
85 let Replace { range, text } = replace;
86 let byte_range = self.segs.range_to_byte(*range);
87
88 self.text
89 .replace_range(byte_range.start().0..byte_range.end().0, text);
90 self.segs = unicode_segs::calc(&self.text);
91 adjust_subsequent_range(
92 *range,
93 text.graphemes(true).count().into(),
94 false,
95 &mut self.selection,
96 );
97
98 Response { text_updated: true, ..Default::default() }
99 }
100
101 fn invert(&self, op: &Operation) -> InverseOperation {
102 let mut inverse = InverseOperation { replace: None, select: self.selection };
103 if let Operation::Replace(replace) = op {
104 inverse.replace = Some(self.invert_replace(replace));
105 }
106 inverse
107 }
108
109 fn invert_replace(&self, replace: &Replace) -> Replace {
110 let Replace { range, text } = replace;
111 let byte_range = self.segs.range_to_byte(*range);
112 let replaced_text = self[byte_range].into();
113 let replacement_range = (range.start(), range.start() + text.graphemes(true).count());
114 Replace { range: replacement_range, text: replaced_text }
115 }
116}
117
118#[derive(Default)]
119struct Ops {
120 all: Vec<Operation>,
122 meta: Vec<OpMeta>,
123
124 processed_seq: usize,
126
127 transformed: Vec<Operation>,
131
132 transformed_inverted: Vec<InverseOperation>,
136}
137
138impl Ops {
139 fn len(&self) -> usize {
140 self.all.len()
141 }
142
143 fn is_undo_checkpoint(&self, idx: usize) -> bool {
144 if idx == 0 {
146 return true;
147 }
148 if idx == self.len() {
149 return true;
150 }
151
152 let meta = &self.meta[idx];
154 let prev_meta = &self.meta[idx - 1];
155 if meta.timestamp - prev_meta.timestamp > Duration::from_millis(500) {
156 return true;
157 }
158
159 let mut prev_op_standalone = meta.base != prev_meta.base;
161 if idx > 1 {
162 let prev_prev_meta = &self.meta[idx - 2];
163 prev_op_standalone &= prev_meta.base != prev_prev_meta.base;
164 }
165 let prev_op_selection = matches!(&self.all[idx - 1], Operation::Select(..));
166 if prev_op_standalone && prev_op_selection {
167 return true;
168 }
169
170 false
171 }
172}
173
174#[derive(Default)]
175struct External {
176 text: String,
179
180 seq: usize,
184}
185
186#[derive(Default)]
187pub struct Response {
188 pub text_updated: bool,
189 pub open_camera: bool,
190 pub seq_before: usize,
193 pub seq_after: usize,
194}
195
196impl std::ops::BitOrAssign for Response {
197 fn bitor_assign(&mut self, other: Response) {
198 self.text_updated |= other.text_updated;
199 self.open_camera |= other.open_camera;
200 if self.seq_before == self.seq_after {
202 self.seq_before = other.seq_before;
203 }
204 self.seq_after = other.seq_after;
205 }
206}
207
208#[derive(Clone, Debug)]
210struct OpMeta {
211 pub timestamp: Instant,
213
214 pub base: usize,
218}
219
220impl Buffer {
221 pub fn queue(&mut self, mut ops: Vec<Operation>) {
224 let timestamp = Instant::now();
225 let base = self.current.seq;
226
227 let mut combined_ops = Vec::new();
229 ops.sort_by_key(|op| match op {
230 Operation::Select(range) | Operation::Replace(Replace { range, .. }) => range.start(),
231 });
232 for op in ops.into_iter() {
233 match &op {
234 Operation::Replace(Replace { range: op_range, text: op_text }) => {
235 if let Some(Operation::Replace(Replace {
236 range: last_op_range,
237 text: last_op_text,
238 })) = combined_ops.last_mut()
239 {
240 if last_op_range.end() == op_range.start() {
241 last_op_range.1 = op_range.1;
242 last_op_text.push_str(op_text);
243 } else {
244 combined_ops.push(op);
245 }
246 } else {
247 combined_ops.push(op);
248 }
249 }
250 Operation::Select(_) => combined_ops.push(op),
251 }
252 }
253
254 self.ops
255 .meta
256 .extend(combined_ops.iter().map(|_| OpMeta { timestamp, base }));
257 self.ops.all.extend(combined_ops);
258 }
259
260 pub fn reload(&mut self, text: String) {
266 let timestamp = Instant::now();
267 let base = self.external.seq;
268 let ops = diff(&self.external.text, &text);
269
270 self.ops
271 .meta
272 .extend(ops.iter().map(|_| OpMeta { timestamp, base }));
273 self.ops.all.extend(ops.into_iter().map(Operation::Replace));
274
275 self.external.text = text;
276 self.external.seq = self.base.seq + self.ops.all.len();
277 }
278
279 pub fn saved(&mut self, external_seq: usize, external_text: String) {
283 self.external.text = external_text;
284 self.external.seq = external_seq;
285 }
286
287 pub fn merge(mut self, external_text_a: String, external_text_b: String) -> String {
288 let ops_a = diff(&self.external.text, &external_text_a);
289 let ops_b = diff(&self.external.text, &external_text_b);
290
291 let timestamp = Instant::now();
292 let base = self.external.seq;
293 self.ops
294 .meta
295 .extend(ops_a.iter().map(|_| OpMeta { timestamp, base }));
296 self.ops
297 .meta
298 .extend(ops_b.iter().map(|_| OpMeta { timestamp, base }));
299
300 self.ops
301 .all
302 .extend(ops_a.into_iter().map(Operation::Replace));
303 self.ops
304 .all
305 .extend(ops_b.into_iter().map(Operation::Replace));
306
307 self.update();
308 self.current.text
309 }
310
311 pub fn update(&mut self) -> Response {
313 let queue_len = self.base.seq + self.ops.len() - self.ops.processed_seq;
318 if queue_len > 0 {
319 let drain_range = self.current.seq..self.ops.processed_seq;
320 self.ops.all.drain(drain_range.clone());
321 self.ops.meta.drain(drain_range.clone());
322 self.ops.transformed.drain(drain_range.clone());
323 self.ops.transformed_inverted.drain(drain_range.clone());
324 self.ops.processed_seq = self.current.seq;
325 } else {
326 return Response::default();
327 }
328
329 let mut result = Response { seq_before: self.current.seq, ..Default::default() };
331 for idx in self.current_idx()..self.current_idx() + queue_len {
332 let mut op = self.ops.all[idx].clone();
333 let meta = &self.ops.meta[idx];
334 self.transform(&mut op, meta);
335 self.ops.transformed_inverted.push(self.current.invert(&op));
336 self.ops.transformed.push(op.clone());
337 self.ops.processed_seq += 1;
338
339 result |= self.redo();
340 }
341
342 result.seq_after = self.current.seq;
343 result
344 }
345
346 fn transform(&self, op: &mut Operation, meta: &OpMeta) {
347 let base_idx = meta.base - self.base.seq;
348 for transforming_idx in base_idx..self.ops.processed_seq {
349 let preceding_op = &self.ops.transformed[transforming_idx];
350 if let Operation::Replace(Replace {
351 range: preceding_replaced_range,
352 text: preceding_replacement_text,
353 }) = preceding_op
354 {
355 if let Operation::Replace(Replace { range: transformed_range, text }) = op {
356 if preceding_replaced_range.intersects(transformed_range, false)
357 && !(preceding_replaced_range.is_empty() && transformed_range.is_empty())
358 {
359 *text = "".into();
365 transformed_range.1 = transformed_range.0;
366 }
367 }
368
369 match op {
370 Operation::Replace(Replace { range: transformed_range, .. })
371 | Operation::Select(transformed_range) => {
372 adjust_subsequent_range(
373 *preceding_replaced_range,
374 preceding_replacement_text.graphemes(true).count().into(),
375 true,
376 transformed_range,
377 );
378 }
379 }
380 }
381 }
382 }
383
384 pub fn can_redo(&self) -> bool {
385 self.current.seq < self.ops.processed_seq
386 }
387
388 pub fn can_undo(&self) -> bool {
389 self.current.seq > self.base.seq
390 }
391
392 pub fn redo(&mut self) -> Response {
393 let mut response = Response::default();
394 while self.can_redo() {
395 let op = &self.ops.transformed[self.current_idx()];
396
397 self.current.seq += 1;
398
399 response |= match op {
400 Operation::Replace(replace) => self.current.apply_replace(replace),
401 Operation::Select(range) => self.current.apply_select(*range),
402 };
403
404 if self.ops.is_undo_checkpoint(self.current_idx()) {
405 break;
406 }
407 }
408 response
409 }
410
411 pub fn undo(&mut self) -> Response {
412 let mut response = Response::default();
413 while self.can_undo() {
414 self.current.seq -= 1;
415 let op = &self.ops.transformed_inverted[self.current_idx()];
416
417 if let Some(replace) = &op.replace {
418 response |= self.current.apply_replace(replace);
419 }
420 response |= self.current.apply_select(op.select);
421
422 if self.ops.is_undo_checkpoint(self.current_idx()) {
423 break;
424 }
425 }
426 response
427 }
428
429 fn current_idx(&self) -> usize {
430 self.current.seq - self.base.seq
431 }
432
433 pub fn transform_range(
438 &self, since_seq: usize, range: &mut (DocCharOffset, DocCharOffset),
439 ) -> bool {
440 let start = since_seq.saturating_sub(self.base.seq);
441 let end = self.current_idx();
442 for op in &self.ops.transformed[start..end] {
443 if let Operation::Replace(replace) = op {
444 if range.intersects(&replace.range, true)
445 && !(range.is_empty() && replace.range.is_empty())
446 {
447 return false;
448 }
449 let replacement_len = replace.text.graphemes(true).count().into();
450 adjust_subsequent_range(replace.range, replacement_len, false, range);
451 }
452 }
453 true
454 }
455
456 pub fn is_empty(&self) -> bool {
458 self.current.text.is_empty()
459 }
460
461 pub fn selection_text(&self) -> String {
462 self[self.current.selection].to_string()
463 }
464}
465
466impl From<&str> for Buffer {
467 fn from(value: &str) -> Self {
468 let mut result = Self::default();
469 result.current.text = value.to_string();
470 result.current.segs = unicode_segs::calc(value);
471 result.external.text = value.to_string();
472 result
473 }
474}
475
476pub fn adjust_subsequent_range(
480 replaced_range: (DocCharOffset, DocCharOffset), replacement_len: RelCharOffset,
481 prefer_advance: bool, range: &mut (DocCharOffset, DocCharOffset),
482) {
483 for position in [&mut range.0, &mut range.1] {
484 adjust_subsequent_position(replaced_range, replacement_len, prefer_advance, position);
485 }
486}
487
488fn adjust_subsequent_position(
492 replaced_range: (DocCharOffset, DocCharOffset), replacement_len: RelCharOffset,
493 prefer_advance: bool, position: &mut DocCharOffset,
494) {
495 let replaced_len = replaced_range.len();
496 let replacement_start = replaced_range.start();
497 let replacement_end = replacement_start + replacement_len;
498
499 enum Mode {
500 Insert,
501 Replace,
502 }
503 let mode = if replaced_range.is_empty() { Mode::Insert } else { Mode::Replace };
504
505 let sorted_bounds = {
506 let mut bounds = vec![replaced_range.start(), replaced_range.end(), *position];
507 bounds.sort();
508 bounds
509 };
510 let bind = |start: &DocCharOffset, end: &DocCharOffset, pos: &DocCharOffset| {
511 start == &replaced_range.start() && end == &replaced_range.end() && pos == &*position
512 };
513
514 *position = match (mode, &sorted_bounds[..]) {
515 (Mode::Insert, [start, end, pos]) if bind(start, end, pos) && end == pos => {
525 if prefer_advance {
526 replacement_end
527 } else {
528 replacement_start
529 }
530 }
531
532 (Mode::Replace, [start, pos, end]) if bind(start, end, pos) && start == pos => {
539 if prefer_advance {
540 replacement_end
541 } else {
542 replacement_start
543 }
544 }
545
546 (Mode::Replace, [start, end, pos]) if bind(start, end, pos) && end == pos => {
553 if prefer_advance {
554 replacement_end
555 } else {
556 replacement_start
557 }
558 }
559
560 (_, [pos, start, end]) if bind(start, end, pos) => *position,
567
568 (Mode::Replace, [start, pos, end]) if bind(start, end, pos) => {
578 if prefer_advance {
579 replacement_end
580 } else {
581 replacement_start
582 }
583 }
584
585 (_, [start, end, pos]) if bind(start, end, pos) => {
592 *position + replacement_len - replaced_len
593 }
594 _ => unreachable!(),
595 }
596}
597
598impl Index<(DocByteOffset, DocByteOffset)> for Snapshot {
599 type Output = str;
600
601 fn index(&self, index: (DocByteOffset, DocByteOffset)) -> &Self::Output {
602 &self.text[index.start().0..index.end().0]
603 }
604}
605
606impl Index<(DocCharOffset, DocCharOffset)> for Snapshot {
607 type Output = str;
608
609 fn index(&self, index: (DocCharOffset, DocCharOffset)) -> &Self::Output {
610 let index = self.segs.range_to_byte(index);
611 &self.text[index.start().0..index.end().0]
612 }
613}
614
615impl Index<(DocByteOffset, DocByteOffset)> for Buffer {
616 type Output = str;
617
618 fn index(&self, index: (DocByteOffset, DocByteOffset)) -> &Self::Output {
619 &self.current[index]
620 }
621}
622
623impl Index<(DocCharOffset, DocCharOffset)> for Buffer {
624 type Output = str;
625
626 fn index(&self, index: (DocCharOffset, DocCharOffset)) -> &Self::Output {
627 &self.current[index]
628 }
629}
630
631#[cfg(test)]
632mod test {
633 use super::Buffer;
634 use unicode_segmentation::UnicodeSegmentation;
635
636 #[test]
637 fn buffer_merge_nonintersecting_replace() {
638 let base_content = "base content base";
639 let local_content = "local content base";
640 let remote_content = "base content remote";
641
642 assert_eq!(
643 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
644 "local content remote"
645 );
646 assert_eq!(
647 Buffer::from(base_content).merge(remote_content.into(), local_content.into()),
648 "local content remote"
649 );
650 }
651
652 #[test]
653 fn buffer_merge_prefix_replace() {
654 let base_content = "base content";
655 let local_content = "local content";
656 let remote_content = "remote content";
657
658 assert_eq!(
659 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
660 "local content"
661 );
662 }
663
664 #[test]
665 fn buffer_merge_infix_replace() {
666 let base_content = "con base tent";
667 let local_content = "con local tent";
668 let remote_content = "con remote tent";
669
670 assert_eq!(
671 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
672 "con local tent"
673 );
674 assert_eq!(
675 Buffer::from(base_content).merge(remote_content.into(), local_content.into()),
676 "con remote tent"
677 );
678 }
679
680 #[test]
681 fn buffer_merge_postfix_replace() {
682 let base_content = "content base";
683 let local_content = "content local";
684 let remote_content = "content remote";
685
686 assert_eq!(
687 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
688 "content local"
689 );
690 assert_eq!(
691 Buffer::from(base_content).merge(remote_content.into(), local_content.into()),
692 "content remote"
693 );
694 }
695
696 #[test]
697 fn buffer_merge_insert() {
698 let base_content = "content";
699 let local_content = "content local";
700 let remote_content = "content remote";
701
702 assert_eq!(
703 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
704 "content local remote"
705 );
706 assert_eq!(
707 Buffer::from(base_content).merge(remote_content.into(), local_content.into()),
708 "content remote local"
709 );
710 }
711
712 #[test]
713 fn buffer_merge_insert_replace() {
715 let base_content = "content";
716 let local_content = "content local";
717 let remote_content = "remote";
718
719 assert_eq!(
720 Buffer::from(base_content).merge(local_content.into(), remote_content.into()),
721 "remote"
722 );
723 assert_eq!(
724 Buffer::from(base_content).merge(remote_content.into(), local_content.into()),
725 "remote local"
726 );
727 }
728
729 #[test]
730 fn buffer_merge_crash() {
732 let base_content = "con tent";
733 let local_content = "cont tent locallocal";
734 let remote_content = "cont remote tent";
735
736 let _ = Buffer::from(base_content).merge(local_content.into(), remote_content.into());
737 let _ = Buffer::from(base_content).merge(remote_content.into(), local_content.into());
738 }
739
740 use rand::prelude::*;
743
744 const POOL: &[&str] = &[
747 "a",
748 "b",
749 "z",
750 " ",
751 "\n",
752 "\t",
753 "é",
754 "ñ",
755 "ü",
756 "日",
757 "本",
758 "語",
759 "👋",
760 "🎉",
761 "🔥",
762 "❤️",
763 "👨👩👧👦",
764 "🏳️🌈",
765 "👍🏽",
766 "🇺🇸",
767 "🇯🇵",
768 "e\u{0301}",
769 "a\u{0308}", ];
771
772 fn random_edit(rng: &mut StdRng, doc: &str) -> String {
780 let graphemes: Vec<&str> = UnicodeSegmentation::graphemes(doc, true).collect();
781 let len = graphemes.len();
782
783 let mut g: Vec<String> = graphemes.iter().map(|s| s.to_string()).collect();
784
785 match rng.gen_range(0..4) {
786 0 if len > 0 => {
787 let pos = rng.gen_range(0..len);
788 let del = rng.gen_range(1..=(len - pos).min(5));
789 g.drain(pos..pos + del);
790 }
791 1 => {
792 let pos = rng.gen_range(0..=len);
793 let n = rng.gen_range(1..=5);
794 for j in 0..n {
795 g.insert(pos + j, POOL[rng.gen_range(0..POOL.len())].into());
796 }
797 }
798 2 if len > 0 => {
799 let pos = rng.gen_range(0..len);
800 let del = rng.gen_range(1..=(len - pos).min(5));
801 let ins: Vec<String> = (0..rng.gen_range(1..=3))
802 .map(|_| POOL[rng.gen_range(0..POOL.len())].into())
803 .collect();
804 g.splice(pos..pos + del, ins);
805 }
806 3 if len > 0 => {
807 g.clear();
808 }
809 _ => {
810 let n = rng.gen_range(1..=5);
811 for _ in 0..n {
812 g.push(POOL[rng.gen_range(0..POOL.len())].into());
813 }
814 }
815 }
816 g.concat()
817 }
818
819 #[test]
820 fn buffer_merge_fuzz() {
821 let mut rng = StdRng::seed_from_u64(42);
822 let bases = ["hello world", "👨👩👧👦🇺🇸🔥", "café ñoño 日本語", ""];
823 for _ in 0..10_000 {
824 let base = bases[rng.gen_range(0..bases.len())];
825 let a = random_edit(&mut rng, base);
826 let b = random_edit(&mut rng, base);
827
828 let _ = Buffer::from(base).merge(a.clone(), b.clone());
830 let _ = Buffer::from(base).merge(b, a);
831 }
832 }
833
834 struct SyncLink {
842 base: String,
843 }
844
845 impl SyncLink {
846 fn new(base: &str) -> Self {
847 Self { base: base.into() }
848 }
849
850 fn sync(&mut self, left: &mut String, right: &mut String) {
851 let merged = Buffer::from(self.base.as_str()).merge(left.clone(), right.clone());
852 *left = merged.clone();
853 *right = merged.clone();
854 self.base = merged;
855 }
856 }
857
858 fn full_sync(nodes: &mut [String], links: &mut [SyncLink]) {
861 for _ in 0..nodes.len() * 2 {
862 for i in 0..links.len() {
863 let (left, right) = nodes.split_at_mut(i + 1);
864 links[i].sync(&mut left[i], &mut right[0]);
865 }
866 for i in (0..links.len()).rev() {
867 let (left, right) = nodes.split_at_mut(i + 1);
868 links[i].sync(&mut left[i], &mut right[0]);
869 }
870 }
871 }
872
873 fn partial_sync(nodes: &mut [String], links: &mut [SyncLink], rng: &mut StdRng) {
874 for _ in 0..3 {
875 for i in 0..links.len() {
876 if rng.gen_bool(0.5) {
877 let (left, right) = nodes.split_at_mut(i + 1);
878 links[i].sync(&mut left[i], &mut right[0]);
879 }
880 }
881 }
882 }
883
884 fn assert_converged(nodes: &[String]) {
885 for (i, node) in nodes.iter().enumerate().skip(1) {
886 assert_eq!(
887 &nodes[0], node,
888 "node 0 and node {} diverged: {:?} vs {:?}",
889 i, nodes[0], node
890 );
891 }
892 }
893
894 #[test]
895 fn buffer_merge_fuzz_chain_2() {
896 let mut rng = StdRng::seed_from_u64(42);
897 for _ in 0..10_000 {
898 let init = if rng.gen_bool(0.5) { "hello 👋🏽" } else { "" };
899 let mut nodes: Vec<String> = (0..2).map(|_| init.into()).collect();
900 let mut links: Vec<SyncLink> = (0..1).map(|_| SyncLink::new(init)).collect();
901
902 for _ in 0..rng.gen_range(1..=4) {
903 for _ in 0..rng.gen_range(1..=3) {
904 let i = rng.gen_range(0..2);
905 nodes[i] = random_edit(&mut rng, &nodes[i]);
906 }
907 if rng.gen_bool(0.5) {
908 partial_sync(&mut nodes, &mut links, &mut rng);
909 }
910 }
911
912 full_sync(&mut nodes, &mut links);
913 assert_converged(&nodes);
914 }
915 }
916
917 #[test]
918 fn buffer_merge_fuzz_chain_5() {
919 let mut rng = StdRng::seed_from_u64(77);
920 for _ in 0..5_000 {
921 let init = if rng.gen_bool(0.5) { "café 日本語 🇯🇵" } else { "abc" };
922 let mut nodes: Vec<String> = (0..5).map(|_| init.into()).collect();
923 let mut links: Vec<SyncLink> = (0..4).map(|_| SyncLink::new(init)).collect();
924
925 for _ in 0..rng.gen_range(1..=3) {
926 for _ in 0..rng.gen_range(1..=5) {
927 let i = rng.gen_range(0..5);
928 nodes[i] = random_edit(&mut rng, &nodes[i]);
929 }
930 if rng.gen_bool(0.5) {
931 partial_sync(&mut nodes, &mut links, &mut rng);
932 }
933 }
934
935 full_sync(&mut nodes, &mut links);
936 assert_converged(&nodes);
937 }
938 }
939}