1use ropey::Rope;
6use std::cmp::min;
7
8#[cfg_attr(test, derive(hegel::PrettyPrintable))]
9#[derive(Clone, Debug, Default, Eq, PartialEq)]
10pub struct Edit {
11 ops: Vec<Op>,
12 length_before: usize,
13 length_after: usize,
14}
15
16impl Edit {
17 #[must_use]
18 pub fn new() -> Self {
19 Self::default()
20 }
21
22 #[must_use]
23 pub fn identity(rope: &Rope) -> Self {
24 let mut edit = Self::new();
25 edit.retain_rest(rope).unwrap();
26 edit
27 }
28
29 #[must_use]
30 pub fn ops(&self) -> &[Op] {
31 &self.ops
32 }
33
34 #[must_use]
35 pub fn is_empty(&self) -> bool {
36 self.ops.is_empty()
37 }
38
39 pub fn retain(&mut self, byte_length: usize) {
40 if byte_length == 0 {
41 return;
42 }
43
44 self.length_before += byte_length;
45 self.length_after += byte_length;
46
47 if let Some(Op::Retain(last)) = self.ops.last_mut() {
48 *last += byte_length;
49 } else {
50 self.ops.push(Op::Retain(byte_length));
51 }
52 }
53
54 pub fn retain_rest(&mut self, rope: &Rope) -> Result<(), Error> {
55 if self.length_before > rope.len() {
56 return Err(Error::LengthMismatch {
57 expected: self.length_before,
58 actual: rope.len(),
59 });
60 }
61
62 self.retain(rope.len() - self.length_before);
63
64 Ok(())
65 }
66
67 pub fn delete(&mut self, text: &str) {
68 if text.is_empty() {
69 return;
70 }
71
72 self.length_before += text.len();
73
74 let insert = match self.ops.pop() {
76 Some(Op::Insert(insert)) => Some(insert),
77 Some(op) => {
78 self.ops.push(op);
79 None
80 }
81 None => None,
82 };
83
84 if let Some(Op::Delete(last)) = self.ops.last_mut() {
85 last.push_str(text);
86 } else {
87 self.ops.push(Op::Delete(text.to_owned()));
88 }
89
90 if let Some(insert) = insert {
91 self.ops.push(Op::Insert(insert));
92 }
93 }
94
95 pub fn insert(&mut self, text: &str) {
96 if text.is_empty() {
97 return;
98 }
99
100 self.length_after += text.len();
101
102 if let Some(Op::Insert(last)) = self.ops.last_mut() {
103 last.push_str(text);
104 } else {
105 self.ops.push(Op::Insert(text.to_owned()));
106 }
107 }
108
109 pub fn compose(&self, other: &Self) -> Result<Self, Error> {
110 if self.length_after != other.length_before {
111 return Err(Error::LengthMismatch {
112 expected: self.length_after,
113 actual: other.length_before,
114 });
115 }
116
117 let mut a = Cursor::new(self);
118 let mut b = Cursor::new(other);
119 let mut edit = Self::new();
120
121 loop {
122 match (a.peek(), b.peek()) {
123 (Some(Op::Delete(_)), _) => {
125 let n = a.remaining();
126 edit.delete(a.text(n));
127 a.advance(n);
128 }
129 (_, Some(Op::Insert(_))) => {
131 let n = b.remaining();
132 edit.insert(b.text(n));
133 b.advance(n);
134 }
135 (None, None) => break,
136 (Some(a_op), Some(b_op)) => {
137 let n = min(a.remaining(), b.remaining());
138 match (a_op, b_op) {
139 (Op::Retain(_), Op::Retain(_)) => edit.retain(n),
140 (Op::Retain(_), Op::Delete(_)) => edit.delete(b.text(n)),
141 (Op::Insert(_), Op::Retain(_)) => edit.insert(a.text(n)),
142 (Op::Insert(_), Op::Delete(_)) => debug_assert_eq!(a.text(n), b.text(n)),
144 _ => unreachable!(),
145 }
146 a.advance(n);
147 b.advance(n);
148 }
149 (None, Some(_)) | (Some(_), None) => unreachable!(),
150 }
151 }
152
153 Ok(edit)
154 }
155
156 #[must_use]
157 pub fn invert(&self) -> Self {
158 let mut edit = Self::new();
159 for op in &self.ops {
160 match op {
161 Op::Retain(length) => edit.retain(*length),
162 Op::Delete(text) => edit.insert(text),
163 Op::Insert(text) => edit.delete(text),
164 }
165 }
166 edit
167 }
168
169 pub fn rebase(&self, onto: &Self, bias: Bias) -> Result<Self, Error> {
170 if self.length_before != onto.length_before {
171 return Err(Error::LengthMismatch {
172 expected: self.length_before,
173 actual: onto.length_before,
174 });
175 }
176
177 let mut a = Cursor::new(self);
178 let mut b = Cursor::new(onto);
179 let mut edit = Self::new();
180
181 loop {
182 match (a.peek(), b.peek()) {
183 (Some(Op::Insert(_)), Some(Op::Insert(_))) => match bias {
186 Bias::Backward => {
187 let n = a.remaining();
188 edit.insert(a.text(n));
189 a.advance(n);
190 }
191 Bias::Forward => {
192 let n = b.remaining();
193 edit.retain(n);
194 b.advance(n);
195 }
196 },
197 (Some(Op::Insert(_)), _) => {
199 let n = a.remaining();
200 edit.insert(a.text(n));
201 a.advance(n);
202 }
203 (_, Some(Op::Insert(_))) => {
205 let n = b.remaining();
206 edit.retain(n);
207 b.advance(n);
208 }
209 (None, None) => break,
210 (Some(a_op), Some(b_op)) => {
211 let n = min(a.remaining(), b.remaining());
212 match (a_op, b_op) {
213 (Op::Retain(_), Op::Retain(_)) => edit.retain(n),
214 (Op::Retain(_), Op::Delete(_)) => {}
216 (Op::Delete(_), Op::Retain(_)) => edit.delete(a.text(n)),
217 (Op::Delete(_), Op::Delete(_)) => debug_assert_eq!(a.text(n), b.text(n)),
219 _ => unreachable!(),
220 }
221 a.advance(n);
222 b.advance(n);
223 }
224 (None, Some(_)) | (Some(_), None) => unreachable!(),
225 }
226 }
227
228 Ok(edit)
229 }
230
231 #[must_use]
232 pub fn map_position(&self, byte_index: usize, bias: Bias) -> usize {
233 let mut byte_indexes = [byte_index];
234 self.map_positions(&mut byte_indexes, bias);
235 byte_indexes[0]
236 }
237
238 pub fn map_positions(&self, byte_indexes: &mut [usize], bias: Bias) {
240 let mut indexed: Vec<(usize, usize)> = byte_indexes.iter().copied().enumerate().collect();
241 indexed.sort_by_key(|&(_, index)| index);
242
243 let mut sorted: Vec<usize> = indexed.iter().map(|&(_, index)| index).collect();
244 self.map_positions_sorted(&mut sorted, bias);
245
246 for (sorted_index, &(original_index, _)) in indexed.iter().enumerate() {
247 byte_indexes[original_index] = sorted[sorted_index];
248 }
249 }
250
251 pub fn map_positions_sorted(&self, byte_indexes: &mut [usize], bias: Bias) {
253 debug_assert!(byte_indexes.is_sorted(), "input is not sorted");
254
255 let mut position: usize = 0;
256 let mut delta: isize = 0;
257 let mut i = 0;
258
259 for op in &self.ops {
260 if i >= byte_indexes.len() {
261 break;
262 }
263 match op {
264 Op::Retain(length) => {
265 while i < byte_indexes.len() && byte_indexes[i] < position + length {
266 byte_indexes[i] = byte_indexes[i].checked_add_signed(delta).unwrap();
267 i += 1;
268 }
269 position += length;
270 }
271 Op::Insert(text) => {
272 if let Bias::Backward = bias {
274 while i < byte_indexes.len() && byte_indexes[i] == position {
275 byte_indexes[i] = position.checked_add_signed(delta).unwrap();
276 i += 1;
277 }
278 }
279 delta += text.len().cast_signed();
280 }
281 Op::Delete(text) => {
282 while i < byte_indexes.len() && byte_indexes[i] < position + text.len() {
283 byte_indexes[i] = position.checked_add_signed(delta).unwrap();
284 i += 1;
285 }
286 position += text.len();
287 delta -= text.len().cast_signed();
288 }
289 }
290 }
291
292 while i < byte_indexes.len() {
293 byte_indexes[i] = byte_indexes[i].checked_add_signed(delta).unwrap();
294 i += 1;
295 }
296 }
297
298 pub fn apply(&self, rope: &mut Rope) -> Result<(), Error> {
299 if self.length_before != rope.len() {
300 return Err(Error::LengthMismatch {
301 expected: self.length_before,
302 actual: rope.len(),
303 });
304 }
305
306 let out = rope;
307 let mut rope = out.clone();
308
309 let mut index = 0;
310
311 for op in &self.ops {
312 match op {
313 Op::Retain(length) => index += length,
314 Op::Delete(text) => {
315 debug_assert_eq!(rope.slice(index..index + text.len()), *text);
316 rope.try_remove(index..index + text.len())?;
317 }
318 Op::Insert(text) => {
319 rope.try_insert(index, text)?;
320 index += text.len();
321 }
322 }
323 }
324
325 debug_assert!(
326 self.length_after == rope.len() && index == rope.len(),
327 "edit output length {} != rope length {}",
328 self.length_after,
329 rope.len()
330 );
331
332 *out = rope;
333
334 Ok(())
335 }
336}
337
338#[cfg_attr(test, derive(hegel::PrettyPrintable))]
339#[derive(Clone, Debug, Eq, PartialEq)]
340pub enum Op {
341 Retain(usize),
342 Delete(String),
343 Insert(String),
344}
345
346impl Op {
347 fn len(&self) -> usize {
348 match self {
349 Self::Retain(length) => *length,
350 Self::Delete(text) | Self::Insert(text) => text.len(),
351 }
352 }
353}
354
355#[derive(Clone, Copy)]
357pub enum Bias {
358 Backward,
359 Forward,
360}
361
362struct Cursor<'a> {
363 ops: &'a [Op],
364 op_index: usize,
365 consumed: usize,
366}
367
368impl<'a> Cursor<'a> {
369 fn new(edit: &'a Edit) -> Self {
370 Self {
371 ops: &edit.ops,
372 op_index: 0,
373 consumed: 0,
374 }
375 }
376
377 fn peek(&self) -> Option<&'a Op> {
378 self.ops.get(self.op_index)
379 }
380
381 fn remaining(&self) -> usize {
382 self.ops[self.op_index].len() - self.consumed
383 }
384
385 fn text(&self, n: usize) -> &'a str {
386 match &self.ops[self.op_index] {
387 Op::Retain(_) => unreachable!("text of a retain op"),
388 Op::Delete(text) | Op::Insert(text) => &text[self.consumed..self.consumed + n],
389 }
390 }
391
392 fn advance(&mut self, n: usize) {
393 self.consumed += n;
394 if self.consumed == self.ops[self.op_index].len() {
395 self.op_index += 1;
396 self.consumed = 0;
397 }
398 }
399}
400
401#[derive(Debug)]
402pub enum Error {
403 LengthMismatch { expected: usize, actual: usize },
404 Ropey(ropey::Error),
405}
406
407impl std::fmt::Display for Error {
408 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409 match self {
410 Self::LengthMismatch { expected, actual } => {
411 write!(f, "length mismatch: expected {expected}, got {actual}")
412 }
413 Self::Ropey(ropey) => write!(f, "{ropey}"),
414 }
415 }
416}
417
418impl std::error::Error for Error {
419 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
420 match self {
421 Self::LengthMismatch { .. } => None,
422 Self::Ropey(ropey) => Some(ropey),
423 }
424 }
425}
426
427impl From<ropey::Error> for Error {
428 fn from(ropey: ropey::Error) -> Self {
429 Self::Ropey(ropey)
430 }
431}
432
433#[cfg(test)]
468mod tests {
469 use super::*;
470 use hegel::generators as gs;
471 use std::iter::zip;
472
473 #[test]
474 fn test() -> Result<(), Error> {
475 let mut rope = Rope::from("Hello, world!");
476 let mut edit = Edit::new();
477 edit.retain(7);
478 edit.delete("world");
479 edit.insert("Evan");
480 edit.delete("!");
481 edit.insert("...");
482 edit.insert("?");
483
484 edit.apply(&mut rope)?;
485 assert_eq!(rope, Rope::from("Hello, Evan...?"));
486
487 edit.invert().apply(&mut rope)?;
488 assert_eq!(rope, Rope::from("Hello, world!"));
489
490 edit.apply(&mut rope)?;
491 edit.invert().apply(&mut rope)?;
492 assert_eq!(rope, Rope::from("Hello, world!"));
493
494 let mut rope = Rope::from("Hello, world!");
495 edit.retain(1);
496 assert!(edit.apply(&mut rope).is_err());
497
498 Ok(())
499 }
500
501 #[test]
502 fn test_map_positions() -> Result<(), Error> {
503 let mut rope = Rope::from("Hello, world!");
504
505 let mut edit = Edit::new();
506 edit.delete("Hello, ");
507 edit.retain(5);
508 edit.insert("!!!");
509 edit.retain(1);
510
511 edit.apply(&mut rope)?;
512 assert_eq!(rope, Rope::from("world!!!!"));
513
514 let mut indexes = [0, 5, 9];
516 edit.map_positions(&mut indexes, Bias::Forward);
517 assert_eq!(indexes, [0, 0, 2]);
518
519 let mut edit = Edit::new();
522 edit.retain(5);
523 edit.insert("???");
524 edit.retain_rest(&rope)?;
525
526 let mut indexes = [5];
527 edit.map_positions(&mut indexes, Bias::Backward);
528 assert_eq!(indexes, [5]);
529
530 let mut indexes = [5];
531 edit.map_positions(&mut indexes, Bias::Forward);
532 assert_eq!(indexes, [8]);
533
534 Ok(())
535 }
536
537 #[test]
538 fn test_rebase() -> Result<(), Error> {
539 let rope = Rope::from("Hello, world!");
540
541 let mut a = Edit::new();
542 a.retain(7);
543 a.insert("brave ");
544 a.retain_rest(&rope)?;
545
546 let mut b = Edit::new();
547 b.retain(7);
548 b.insert("new ");
549 b.retain_rest(&rope)?;
550
551 let mut backward = rope.clone();
552 b.apply(&mut backward)?;
553 a.rebase(&b, Bias::Backward)?.apply(&mut backward)?;
554 assert_eq!(backward, Rope::from("Hello, brave new world!"));
555
556 let mut forward = rope.clone();
557 b.apply(&mut forward)?;
558 a.rebase(&b, Bias::Forward)?.apply(&mut forward)?;
559 assert_eq!(forward, Rope::from("Hello, new brave world!"));
560
561 Ok(())
562 }
563
564 fn draw_str_end(tc: &hegel::TestCase, doc: &str, index: usize) -> usize {
566 let length = gs::integers::<usize>()
567 .min_value(1)
568 .max_value(doc.len() - index);
569 let mut end = index + tc.draw(length);
570 while !doc.is_char_boundary(end) {
571 end += 1;
572 }
573 end
574 }
575
576 #[hegel::composite]
577 fn gen_edit_and_text(tc: &hegel::TestCase, doc: String) -> (Edit, String) {
578 let mut edit = Edit::new();
579 let mut expected = String::new();
580 let mut index = 0;
581 while index < doc.len() {
582 match tc.draw(gs::integers::<u8>().max_value(2)) {
583 0 => {
584 let end = draw_str_end(tc, &doc, index);
585 edit.retain(end - index);
586 expected.push_str(&doc[index..end]);
587 index = end;
588 }
589 1 => {
590 let end = draw_str_end(tc, &doc, index);
591 edit.delete(&doc[index..end]);
592 index = end;
593 }
594 _ => {
595 let text: String = tc.draw(gs::text());
596 edit.insert(&text);
597 expected.push_str(&text);
598 }
599 }
600 }
601 if tc.draw(gs::booleans()) {
602 let text: String = tc.draw(gs::text());
603 edit.insert(&text);
604 expected.push_str(&text);
605 }
606 (edit, expected)
607 }
608
609 #[hegel::composite]
610 fn gen_edit(tc: &hegel::TestCase, doc: String) -> Edit {
611 tc.draw(gen_edit_and_text(doc.clone())).0
612 }
613
614 #[hegel::test(test_cases = 5_000)]
615 fn apply_produces_expected_document(tc: hegel::TestCase) {
616 let doc: String = tc.draw(gs::text());
617 let (edit, expected) = tc.draw(gen_edit_and_text(doc.clone()));
618
619 let mut rope = Rope::from(doc.as_str());
620 edit.apply(&mut rope).unwrap();
621
622 assert_eq!(rope, Rope::from(expected.as_str()));
623 }
624
625 #[hegel::test(test_cases = 5_000)]
626 fn invert_roundtrips(tc: hegel::TestCase) {
627 let doc: String = tc.draw(gs::text());
628 let edit = tc.draw(gen_edit(doc.clone()));
629
630 let before = Rope::from(doc.as_str());
631 let mut after = before.clone();
632 edit.apply(&mut after).unwrap();
633
634 let mut inverted = after.clone();
636 edit.invert().apply(&mut inverted).unwrap();
637 assert_eq!(inverted, before);
638
639 assert_eq!(edit.invert().invert(), edit);
641 }
642
643 #[hegel::test(test_cases = 5_000)]
645 fn canonical_form_is_unique(tc: hegel::TestCase) {
646 fn midpoint(s: &str) -> usize {
648 let mut index = s.len() / 2;
649 while !s.is_char_boundary(index) {
650 index += 1;
651 }
652 index
653 }
654
655 let doc: String = tc.draw(gs::text());
656
657 let mut one = Edit::new();
660 let mut two = Edit::new();
661 let mut index = 0;
662 while index < doc.len() {
663 let end = draw_str_end(&tc, &doc, index);
664 if tc.draw(gs::booleans()) {
665 one.retain(end - index);
666 let middle = index + midpoint(&doc[index..end]);
667 two.retain(middle - index);
668 two.retain(end - middle);
669 } else {
670 let deleted = &doc[index..end];
671 let inserted: String = tc.draw(gs::text());
672 one.delete(deleted);
673 one.insert(&inserted);
674 let (delete_head, delete_tail) = deleted.split_at(midpoint(deleted));
675 let (insert_head, insert_tail) = inserted.split_at(midpoint(&inserted));
676 two.insert(insert_head);
677 two.insert(insert_tail);
678 two.delete(delete_head);
679 two.delete(delete_tail);
680 }
681 index = end;
682 }
683
684 assert_eq!(one, two);
685 }
686
687 #[hegel::test(test_cases = 5_000)]
688 fn compose_agrees_with_sequential_application(tc: hegel::TestCase) {
689 let doc: String = tc.draw(gs::text());
690 let a = tc.draw(gen_edit(doc.clone()));
691
692 let mut sequential = Rope::from(doc.as_str());
693 a.apply(&mut sequential).unwrap();
694 let b = tc.draw(gen_edit(sequential.to_string()));
695 b.apply(&mut sequential).unwrap();
696
697 let mut composed = Rope::from(doc.as_str());
698 a.compose(&b).unwrap().apply(&mut composed).unwrap();
699
700 assert_eq!(composed, sequential);
701 }
702
703 #[hegel::test(test_cases = 5_000)]
704 fn compose_is_associative(tc: hegel::TestCase) {
705 let doc: String = tc.draw(gs::text());
706 let mut rope = Rope::from(doc.as_str());
707
708 let a = tc.draw(gen_edit(rope.to_string()));
709 a.apply(&mut rope).unwrap();
710 let b = tc.draw(gen_edit(rope.to_string()));
711 b.apply(&mut rope).unwrap();
712 let c = tc.draw(gen_edit(rope.to_string()));
713
714 assert_eq!(
715 a.compose(&b).unwrap().compose(&c).unwrap(),
716 a.compose(&b.compose(&c).unwrap()).unwrap(),
717 );
718 }
719
720 #[hegel::test(test_cases = 5_000)]
721 fn compose_with_identity_is_identity(tc: hegel::TestCase) {
722 let doc: String = tc.draw(gs::text());
723 let edit = tc.draw(gen_edit(doc.clone()));
724
725 let mut before = Edit::new();
726 before.retain(edit.length_before);
727 assert_eq!(before.compose(&edit).unwrap(), edit);
728
729 let mut after = Edit::new();
730 after.retain(edit.length_after);
731 assert_eq!(edit.compose(&after).unwrap(), edit);
732 }
733
734 #[hegel::test(test_cases = 5_000)]
737 fn rebase_converges(tc: hegel::TestCase) {
738 let doc: String = tc.draw(gs::text());
739 let a = tc.draw(gen_edit(doc.clone()));
740 let b = tc.draw(gen_edit(doc.clone()));
741
742 let mut a_first = Rope::from(doc.as_str());
743 a.apply(&mut a_first).unwrap();
744 b.rebase(&a, Bias::Forward)
745 .unwrap()
746 .apply(&mut a_first)
747 .unwrap();
748
749 let mut b_first = Rope::from(doc.as_str());
750 b.apply(&mut b_first).unwrap();
751 a.rebase(&b, Bias::Backward)
752 .unwrap()
753 .apply(&mut b_first)
754 .unwrap();
755
756 assert_eq!(a_first, b_first);
757 }
758
759 #[hegel::test(test_cases = 5_000)]
760 fn transform_byte_indexes_preserves_order_and_validity(tc: hegel::TestCase) {
761 let doc: String = tc.draw(gs::text());
762 let edit = tc.draw(gen_edit(doc.clone()));
763
764 let mut indexes: Vec<usize> =
765 tc.draw(gs::vecs(gs::integers::<usize>().max_value(doc.len())));
766 for index in &mut indexes {
767 while !doc.is_char_boundary(*index) {
768 *index -= 1;
769 }
770 }
771 indexes.sort_unstable();
772
773 let mut rope = Rope::from(doc.as_str());
774 edit.apply(&mut rope).unwrap();
775 let after = rope.to_string();
776
777 let mut backward = indexes.clone();
778 edit.map_positions(&mut backward, Bias::Backward);
779 let mut forward = indexes;
780 edit.map_positions(&mut forward, Bias::Forward);
781
782 assert!(backward.is_sorted());
785 assert!(forward.is_sorted());
786 for (backward, forward) in zip(&backward, &forward) {
787 assert!(after.is_char_boundary(*backward));
788 assert!(after.is_char_boundary(*forward));
789 assert!(backward <= forward);
790 }
791 }
792}