1use std::ops::Range;
4
5use crate::ropetext::change::{Change, Edit};
6use crate::ropetext::history::{DEFAULT_BUDGET_BYTES, Entry, History, Shape};
7use crate::ropetext::position::{Position, Revision, Span};
8use crate::ropetext::text::Text;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12enum Gravity {
13 Forward,
16 Backward,
19}
20
21#[derive(Debug, Clone, Copy)]
24struct Applied {
25 at: usize,
26 removed: usize,
27 inserted: usize,
28 revision_before: Revision,
31}
32
33#[derive(Debug, Clone)]
40pub struct Snapshot {
41 pub text: Text,
42 pub cursor: Position,
43 pub selection: Option<Span>,
44}
45
46#[derive(Debug)]
53pub struct EditBuffer {
54 text: Text,
55 cursor: Position,
56 anchor: Option<Position>,
57 history: History,
58}
59
60impl Default for EditBuffer {
61 fn default() -> Self {
62 Self::new(Text::new())
63 }
64}
65
66impl EditBuffer {
67 pub fn new(text: Text) -> Self {
68 let cursor = text.start();
69 Self {
70 text,
71 cursor,
72 anchor: None,
73 history: History::new(DEFAULT_BUDGET_BYTES),
74 }
75 }
76
77 pub fn text(&self) -> &Text {
78 &self.text
79 }
80
81 pub fn cursor(&self) -> Position {
82 self.cursor
83 }
84
85 pub fn selection(&self) -> Option<Span> {
93 let anchor = self.anchor?;
94 self.text.span(anchor, self.cursor)
95 }
96
97 pub fn snapshot(&self) -> Snapshot {
99 Snapshot {
100 text: self.text.clone(),
101 cursor: self.cursor,
102 selection: self.selection(),
103 }
104 }
105
106 pub fn set_cursor(&mut self, to: Position) -> bool {
115 if self.text.is_stale(to) {
116 return false;
117 }
118 self.cursor = to;
119 self.anchor = None;
120 true
121 }
122
123 pub fn extend_to(&mut self, to: Position) -> bool {
128 if self.text.is_stale(to) {
129 return false;
130 }
131 if self.anchor.is_none() {
132 self.anchor = Some(self.cursor);
133 }
134 self.cursor = to;
135 true
136 }
137
138 pub fn select(&mut self, span: Span) -> bool {
140 if span.revision() != self.text.revision() {
141 return false;
142 }
143 self.anchor = Some(span.start());
144 self.cursor = span.end();
145 true
146 }
147
148 pub fn clear_selection(&mut self) {
149 self.anchor = None;
150 }
151
152 pub fn set_text(&mut self, text: Text) {
158 self.text = text;
159 self.cursor = self.text.start();
160 self.anchor = None;
161 self.history.clear();
162 }
163
164 pub fn set_history_budget(&mut self, bytes: usize) {
166 self.history.set_budget(bytes);
167 }
168
169 pub fn begin(&mut self) -> Txn<'_> {
173 Txn::new(self, false)
174 }
175
176 pub fn begin_extending(&mut self) -> Txn<'_> {
185 Txn::new(self, true)
186 }
187
188 pub fn undo(&mut self) -> Option<Change> {
195 let entry = self.history.undo_candidate()?;
196 let text = entry.before.reidentified();
197 let shape = entry.inverse.clone();
198 let cursor = entry.cursor_before;
199 let anchor = entry.anchor_before;
200 self.history.step_back();
201 Some(self.restore(text, shape, cursor, anchor))
202 }
203
204 pub fn redo(&mut self) -> Option<Change> {
206 let entry = self.history.redo_candidate()?;
207 let text = entry.after.reidentified();
208 let shape = entry.forward.clone();
209 let cursor = entry.cursor_after;
210 let anchor = entry.anchor_after;
211 self.history.step_forward();
212 Some(self.restore(text, shape, cursor, anchor))
213 }
214
215 fn restore(
216 &mut self,
217 text: Text,
218 shape: Shape,
219 cursor: usize,
220 anchor: Option<usize>,
221 ) -> Change {
222 self.text = text;
223 self.cursor = self.text.position_at_derived_byte(cursor);
224 self.anchor = anchor.map(|a| self.text.position_at_derived_byte(a));
225 Change::new(
226 self.text.revision(),
227 shape.edits,
228 shape.rows,
229 shape.line_delta,
230 )
231 }
232}
233
234#[must_use = "a transaction that is dropped without commit() is rolled back"]
242pub struct Txn<'a> {
243 buffer: &'a mut EditBuffer,
244 before: Text,
245 cursor_before: usize,
246 anchor_before: Option<usize>,
247 applied: Vec<Applied>,
248 retained: usize,
250 extending: bool,
251 committed: bool,
252}
253
254impl<'a> Txn<'a> {
255 fn new(buffer: &'a mut EditBuffer, extending: bool) -> Self {
256 let before = buffer.text.clone();
257 let cursor_before = buffer.cursor.byte();
258 let anchor_before = buffer.anchor.map(Position::byte);
259 Self {
260 buffer,
261 before,
262 cursor_before,
263 anchor_before,
264 applied: Vec::new(),
265 retained: 0,
266 extending,
267 committed: false,
268 }
269 }
270
271 pub fn text(&self) -> &Text {
273 &self.buffer.text
274 }
275
276 pub fn cursor(&self) -> Position {
277 self.buffer.cursor
278 }
279
280 pub fn selection(&self) -> Option<Span> {
281 self.buffer.selection()
282 }
283
284 pub fn map(&self, position: Position) -> Option<Position> {
292 let byte = self.remap(position, Gravity::Backward)?;
293 Some(self.buffer.text.position_at_derived_byte(byte))
294 }
295
296 pub fn insert(&mut self, at: Position, text: &str) -> bool {
301 let Some(byte) = self.remap(at, Gravity::Backward) else {
302 return false;
303 };
304 self.splice(byte..byte, text)
305 }
306
307 pub fn delete(&mut self, span: Span) -> bool {
309 self.replace(span, "")
310 }
311
312 pub fn replace(&mut self, span: Span, text: &str) -> bool {
317 let Some(start) = self.remap(span.start(), Gravity::Backward) else {
318 return false;
319 };
320 let Some(end) = self.remap(span.end(), Gravity::Forward) else {
321 return false;
322 };
323 if end < start {
324 debug_assert!(false, "span ends collapsed past its start");
325 return false;
326 }
327 self.splice(start..end, text)
328 }
329
330 pub fn set_cursor(&mut self, to: Position) -> bool {
332 self.buffer.set_cursor(to)
333 }
334
335 pub fn select(&mut self, span: Span) -> bool {
341 self.buffer.select(span)
342 }
343
344 pub fn clear_selection(&mut self) {
345 self.buffer.clear_selection();
346 }
347
348 pub fn commit(mut self) -> Option<Change> {
354 self.committed = true;
355 if self.applied.is_empty() {
356 return None;
357 }
358
359 let forward = self.forward_shape();
360 let inverse = self.inverse_shape();
361 let entry = Entry {
362 before: self.before.clone(),
363 after: self.buffer.text.clone(),
364 forward: forward.clone(),
365 inverse,
366 cursor_before: self.cursor_before,
367 cursor_after: self.buffer.cursor.byte(),
368 anchor_before: self.anchor_before,
369 anchor_after: self.buffer.anchor.map(Position::byte),
370 retained: self.retained,
371 };
372
373 if self.extending {
374 if let Some(entry) = self.buffer.history.extend_newest(entry) {
375 self.buffer.history.push(entry);
376 }
377 } else {
378 self.buffer.history.push(entry);
379 }
380
381 Some(Change::new(
382 self.buffer.text.revision(),
383 forward.edits,
384 forward.rows,
385 forward.line_delta,
386 ))
387 }
388
389 fn splice(&mut self, bytes: Range<usize>, text: &str) -> bool {
392 if bytes.is_empty() && text.is_empty() {
393 return true;
394 }
395 let revision_before = self.buffer.text.revision();
396 let removed = bytes.len();
397 let at = bytes.start;
398 let inserted = self.buffer.text.splice(bytes, text);
399
400 self.applied.push(Applied {
401 at,
402 removed,
403 inserted,
404 revision_before,
405 });
406 self.retained += removed + inserted;
407
408 let edit = Applied {
409 at,
410 removed,
411 inserted,
412 revision_before,
413 };
414 let cursor = shift(self.buffer.cursor.byte(), &edit, Gravity::Forward);
415 self.buffer.cursor = self.buffer.text.position_at_cursor_byte(cursor);
420 self.buffer.anchor = None;
425 true
426 }
427
428 fn remap(&self, position: Position, gravity: Gravity) -> Option<usize> {
431 if position.revision() == self.buffer.text.revision() {
432 return Some(position.byte());
433 }
434 let from = self
435 .applied
436 .iter()
437 .position(|a| a.revision_before == position.revision())?;
438 let mut byte = position.byte();
439 for edit in &self.applied[from..] {
440 byte = shift(byte, edit, gravity);
441 }
442 Some(byte)
443 }
444
445 fn forward_shape(&self) -> Shape {
447 let mut edits = Vec::with_capacity(self.applied.len());
448 let mut rows: Option<Range<usize>> = None;
449 for (i, edit) in self.applied.iter().enumerate() {
450 let later = &self.applied[i + 1..];
451 let start = later
452 .iter()
453 .fold(edit.at, |b, e| shift(b, e, Gravity::Backward));
454 let end = later.iter().fold(edit.at + edit.inserted, |b, e| {
455 shift(b, e, Gravity::Forward)
456 });
457 let end = end.max(start).min(self.buffer.text.len_bytes());
458 let start = start.min(end);
459 edits.push(Edit::new(start..end, edit.removed));
460 rows = Some(union(rows, self.rows_of(&self.buffer.text, start..end)));
461 }
462 Shape {
463 edits: Some(edits),
464 rows: rows.unwrap_or(0..0),
465 line_delta: line_delta(&self.before, &self.buffer.text),
466 }
467 }
468
469 fn inverse_shape(&self) -> Shape {
474 let mut edits = Vec::with_capacity(self.applied.len());
475 let mut rows: Option<Range<usize>> = None;
476 for (i, edit) in self.applied.iter().enumerate() {
477 let earlier = &self.applied[..i];
478 let start = earlier.iter().rev().fold(edit.at, unshift);
481 let end = (start + edit.removed).min(self.before.len_bytes());
482 let start = start.min(end);
483 edits.push(Edit::new(start..end, edit.inserted));
484 rows = Some(union(rows, self.rows_of(&self.before, start..end)));
485 }
486 Shape {
487 edits: Some(edits),
488 rows: rows.unwrap_or(0..0),
489 line_delta: line_delta(&self.buffer.text, &self.before),
490 }
491 }
492
493 fn rows_of(&self, text: &Text, bytes: Range<usize>) -> Range<usize> {
494 let first = text.row_of_byte(bytes.start.min(text.len_bytes()));
495 let last = text.row_of_byte(bytes.end.min(text.len_bytes()));
496 first..last + 1
497 }
498}
499
500impl Drop for Txn<'_> {
501 fn drop(&mut self) {
502 if self.committed {
503 return;
504 }
505 self.buffer.text = self.before.clone();
506 self.buffer.cursor = self
507 .buffer
508 .text
509 .position_at_derived_byte(self.cursor_before);
510 self.buffer.anchor = self
511 .anchor_before
512 .map(|byte| self.buffer.text.position_at_derived_byte(byte));
513 }
514}
515
516fn shift(byte: usize, edit: &Applied, gravity: Gravity) -> usize {
518 let end = edit.at + edit.removed;
519 if byte < edit.at {
520 byte
521 } else if byte > edit.at && byte >= end {
522 byte - edit.removed + edit.inserted
523 } else {
524 match gravity {
528 Gravity::Forward => edit.at + edit.inserted,
529 Gravity::Backward => edit.at,
530 }
531 }
532}
533
534fn unshift(byte: usize, edit: &Applied) -> usize {
536 let end = edit.at + edit.inserted;
537 if byte <= edit.at {
538 byte
539 } else if byte >= end {
540 byte - edit.inserted + edit.removed
541 } else {
542 edit.at
543 }
544}
545
546fn union(rows: Option<Range<usize>>, next: Range<usize>) -> Range<usize> {
547 match rows {
548 Some(rows) => rows.start.min(next.start)..rows.end.max(next.end),
549 None => next,
550 }
551}
552
553fn line_delta(from: &Text, to: &Text) -> isize {
554 to.line_count() as isize - from.line_count() as isize
555}
556
557#[cfg(test)]
558mod tests {
559 use super::*;
560 use crate::ropetext::position::Column;
561
562 fn buffer(text: &str) -> EditBuffer {
563 EditBuffer::new(Text::from(text))
564 }
565
566 fn at(buf: &EditBuffer, row: usize, col: usize) -> Position {
567 buf.text()
568 .position(row, Column::new(col))
569 .expect("addressable in the test fixture")
570 }
571
572 fn span(buf: &EditBuffer, from: (usize, usize), to: (usize, usize)) -> Span {
573 let a = at(buf, from.0, from.1);
574 let b = at(buf, to.0, to.1);
575 buf.text().span(a, b).expect("same text")
576 }
577
578 #[test]
581 fn inserting_at_the_cursor_carries_the_cursor_along() {
582 let mut buf = buffer("hello");
583 let p = at(&buf, 0, 5);
584 buf.set_cursor(p);
585 let mut txn = buf.begin();
586 assert!(txn.insert(p, ", world"));
587 txn.commit();
588 assert_eq!(buf.text().to_string(), "hello, world");
589 assert_eq!(buf.cursor().column().get(), 12);
590 }
591
592 #[test]
593 fn inserting_after_the_cursor_leaves_the_cursor_where_it_was() {
594 let mut buf = buffer("hello");
595 buf.set_cursor(at(&buf, 0, 1));
596 let target = at(&buf, 0, 4);
597 let mut txn = buf.begin();
598 txn.insert(target, "XXX");
599 txn.commit();
600 assert_eq!(buf.cursor().byte(), 1, "an edit elsewhere is not a move");
601 }
602
603 #[test]
604 fn inserting_before_the_cursor_pushes_it_along() {
605 let mut buf = buffer("hello");
606 buf.set_cursor(at(&buf, 0, 4));
607 let target = at(&buf, 0, 0);
608 let mut txn = buf.begin();
609 txn.insert(target, "XX");
610 txn.commit();
611 assert_eq!(buf.cursor().column().get(), 6);
612 }
613
614 #[test]
615 fn deleting_across_the_cursor_collapses_it_to_the_start() {
616 let mut buf = buffer("hello world");
617 buf.set_cursor(at(&buf, 0, 8));
618 let s = span(&buf, (0, 5), (0, 11));
619 let mut txn = buf.begin();
620 txn.delete(s);
621 txn.commit();
622 assert_eq!(buf.text().to_string(), "hello");
623 assert_eq!(buf.cursor().byte(), 5);
624 }
625
626 #[test]
627 fn replacing_leaves_the_cursor_after_the_new_text() {
628 let mut buf = buffer("hello world");
629 let s = span(&buf, (0, 6), (0, 11));
630 buf.set_cursor(s.end());
631 let mut txn = buf.begin();
632 txn.replace(s, "there");
633 txn.commit();
634 assert_eq!(buf.text().to_string(), "hello there");
635 assert_eq!(buf.cursor().column().get(), 11);
636 }
637
638 #[test]
639 fn an_edit_clears_the_selection() {
640 let mut buf = buffer("hello world");
641 let s = span(&buf, (0, 0), (0, 5));
642 buf.select(s);
643 assert!(buf.selection().is_some());
644 let mut txn = buf.begin();
645 txn.replace(s, "bye");
646 txn.commit();
647 assert_eq!(buf.text().to_string(), "bye world");
648 assert!(
649 buf.selection().is_none(),
650 "typing over a selection must not leave the typed text selected"
651 );
652 }
653
654 #[test]
655 fn a_compound_edit_can_leave_a_selection_behind() {
656 let mut buf = buffer("word");
659 let s = span(&buf, (0, 0), (0, 4));
660 let mut txn = buf.begin();
661 txn.insert(s.end(), "]");
662 txn.insert(s.start(), "[");
663 let inner = txn
664 .text()
665 .span(
666 txn.text().position(0, Column::new(1)).unwrap(),
667 txn.text().position(0, Column::new(5)).unwrap(),
668 )
669 .unwrap();
670 txn.select(inner);
671 txn.commit();
672 assert_eq!(buf.text().to_string(), "[word]");
673 assert_eq!(
674 buf.text()
675 .slice(buf.selection().expect("still selected"))
676 .as_deref(),
677 Some("word")
678 );
679 }
680
681 #[test]
682 fn several_primitives_are_one_undo() {
683 let mut buf = buffer("word");
684 let s = span(&buf, (0, 0), (0, 4));
685 let mut txn = buf.begin();
686 txn.insert(s.end(), "]");
687 txn.insert(s.start(), "[");
688 txn.commit();
689 assert_eq!(buf.text().to_string(), "[word]");
690 buf.undo();
691 assert_eq!(buf.text().to_string(), "word", "one action, one undo");
692 }
693
694 #[test]
695 fn a_position_from_before_the_transaction_still_points_at_its_text() {
696 let mut buf = buffer("one two");
697 let two = at(&buf, 0, 4);
698 let head = at(&buf, 0, 0);
699 let mut txn = buf.begin();
700 txn.insert(head, "zero ");
701 let moved = txn.map(two).expect("carried forward");
703 assert_eq!(moved.byte(), 9);
704 txn.commit();
705 }
706
707 #[test]
708 fn a_position_from_another_text_is_refused() {
709 let mut buf = buffer("hello");
710 let other = Text::from("hello");
711 let foreign = other.position(0, Column::new(2)).unwrap();
712 let mut txn = buf.begin();
713 assert!(!txn.insert(foreign, "x"));
714 assert!(txn.map(foreign).is_none());
715 txn.commit();
716 assert_eq!(buf.text().to_string(), "hello", "nothing happened");
717 }
718
719 #[test]
720 fn a_stale_cursor_is_refused_rather_than_approximated() {
721 let mut buf = buffer("hello");
722 let stale = at(&buf, 0, 4);
723 let head = at(&buf, 0, 0);
724 let mut txn = buf.begin();
725 txn.insert(head, "xx");
726 txn.commit();
727 assert!(!buf.set_cursor(stale));
728 }
729
730 #[test]
733 fn a_transaction_that_changes_nothing_reports_nothing() {
734 let mut buf = buffer("hello");
735 let p = at(&buf, 0, 2);
736 let mut txn = buf.begin();
737 txn.insert(p, "");
738 assert!(txn.commit().is_none());
739 assert!(buf.undo().is_none(), "and records no history");
740 }
741
742 #[test]
743 fn a_transaction_that_only_moves_the_cursor_keeps_the_move() {
744 let mut buf = buffer("hello");
745 let p = at(&buf, 0, 3);
746 let mut txn = buf.begin();
747 txn.set_cursor(p);
748 assert!(txn.commit().is_none(), "a cursor move is not history");
749 assert_eq!(buf.cursor().byte(), 3);
750 }
751
752 #[test]
753 fn dropping_a_transaction_rolls_it_back() {
754 let mut buf = buffer("hello");
755 let end = at(&buf, 0, 5);
756 buf.set_cursor(end);
757 {
758 let mut txn = buf.begin();
759 txn.insert(end, " world");
760 assert_eq!(txn.text().to_string(), "hello world");
761 }
763 assert_eq!(buf.text().to_string(), "hello");
764 assert_eq!(buf.cursor().byte(), 5);
765 assert!(buf.undo().is_none(), "a rollback is not an undo step");
766 }
767
768 #[test]
771 fn a_delete_that_removes_nothing_records_no_history() {
772 let mut buf = buffer("hello");
777 let start = at(&buf, 0, 0);
778 let mut txn = buf.begin();
779 txn.insert(start, "x");
780 txn.commit();
781
782 let end = buf.text().end();
785 let empty = buf.text().span(end, end).expect("same text");
786 let mut txn = buf.begin();
787 assert!(txn.delete(empty), "an empty delete is not a failure");
788 assert!(txn.commit().is_none(), "but it is not a change either");
789
790 buf.undo();
791 assert_eq!(
792 buf.text().to_string(),
793 "hello",
794 "the undo took back the insert, not a phantom delete"
795 );
796 assert!(buf.undo().is_none(), "and there was only ever one entry");
797 }
798
799 #[test]
800 fn undo_of_a_forward_delete_returns_the_cursor_to_where_the_user_was() {
801 let mut buf = buffer("hello world");
804 let s = span(&buf, (0, 5), (0, 11));
805 buf.set_cursor(s.start());
806 let mut txn = buf.begin();
807 txn.delete(s);
808 txn.commit();
809 assert_eq!(buf.text().to_string(), "hello");
810 buf.undo();
811 assert_eq!(buf.text().to_string(), "hello world");
812 assert_eq!(buf.cursor().byte(), 5, "not 11");
813 }
814
815 #[test]
816 fn undo_and_redo_walk_the_text_back_and_forward() {
817 let mut buf = buffer("");
818 for word in ["a", "b", "c"] {
819 let end = buf.text().end();
820 let mut txn = buf.begin();
821 txn.insert(end, word);
822 txn.commit();
823 }
824 assert_eq!(buf.text().to_string(), "abc");
825 buf.undo();
826 assert_eq!(buf.text().to_string(), "ab");
827 buf.undo();
828 assert_eq!(buf.text().to_string(), "a");
829 buf.redo();
830 assert_eq!(buf.text().to_string(), "ab");
831 buf.redo();
832 assert_eq!(buf.text().to_string(), "abc");
833 assert!(buf.redo().is_none());
834 }
835
836 #[test]
837 fn undo_restores_the_cursor_the_group_started_from() {
838 let mut buf = buffer("hello");
839 let start = at(&buf, 0, 2);
840 buf.set_cursor(start);
841 let mut txn = buf.begin();
842 txn.insert(start, "XYZ");
843 txn.commit();
844 assert_eq!(buf.cursor().byte(), 5);
845 buf.undo();
846 assert_eq!(buf.cursor().byte(), 2);
847 }
848
849 #[test]
850 fn undo_mints_a_new_revision_rather_than_reusing_the_old_one() {
851 let mut buf = buffer("hello");
852 let before = buf.text().revision();
853 let p = at(&buf, 0, 5);
854 let mut txn = buf.begin();
855 txn.insert(p, "!");
856 txn.commit();
857 buf.undo();
858 assert_eq!(buf.text().to_string(), "hello");
859 assert_ne!(
860 buf.text().revision(),
861 before,
862 "the same content at a later point in the timeline is a later revision"
863 );
864 }
865
866 #[test]
867 fn extending_folds_a_typing_run_into_one_undo() {
868 let mut buf = buffer("");
869 let first = buf.text().end();
870 let mut txn = buf.begin();
871 txn.insert(first, "h");
872 txn.commit();
873 for c in ["e", "l", "l", "o"] {
874 let end = buf.text().end();
875 let mut txn = buf.begin_extending();
876 txn.insert(end, c);
877 txn.commit();
878 }
879 assert_eq!(buf.text().to_string(), "hello");
880 buf.undo();
881 assert_eq!(
882 buf.text().to_string(),
883 "",
884 "the whole run went back at once"
885 );
886 assert!(buf.undo().is_none());
887 }
888
889 #[test]
890 fn extending_with_no_previous_group_records_one() {
891 let mut buf = buffer("");
892 let end = buf.text().end();
893 let mut txn = buf.begin_extending();
894 txn.insert(end, "a");
895 txn.commit();
896 buf.undo();
897 assert_eq!(buf.text().to_string(), "");
898 }
899
900 #[test]
901 fn a_new_group_after_an_undo_drops_the_redo_side() {
902 let mut buf = buffer("");
903 let end = buf.text().end();
904 let mut txn = buf.begin();
905 txn.insert(end, "a");
906 txn.commit();
907 buf.undo();
908 let end = buf.text().end();
909 let mut txn = buf.begin();
910 txn.insert(end, "b");
911 txn.commit();
912 assert!(buf.redo().is_none());
913 assert_eq!(buf.text().to_string(), "b");
914 }
915
916 #[test]
917 fn opening_a_different_note_forgets_the_history() {
918 let mut buf = buffer("first");
919 let end = buf.text().end();
920 let mut txn = buf.begin();
921 txn.insert(end, "!");
922 txn.commit();
923 buf.set_text(Text::from("second"));
924 assert!(
925 buf.undo().is_none(),
926 "an undo must not walk from one note into another"
927 );
928 assert_eq!(buf.text().to_string(), "second");
929 }
930
931 #[test]
934 fn a_change_reports_the_row_it_touched() {
935 let mut buf = buffer("one\ntwo\nthree");
936 let p = at(&buf, 1, 1);
937 let mut txn = buf.begin();
938 txn.insert(p, "X");
939 let change = txn.commit().expect("something changed");
940 assert_eq!(change.rows(), 1..2);
941 assert_eq!(change.line_delta(), 0);
942 assert!(!change.is_bulk());
943 }
944
945 #[test]
946 fn a_change_reports_added_rows() {
947 let mut buf = buffer("one\ntwo");
948 let p = at(&buf, 0, 3);
949 let mut txn = buf.begin();
950 txn.insert(p, "\nmiddle");
951 let change = txn.commit().expect("something changed");
952 assert_eq!(change.line_delta(), 1);
953 assert_eq!(change.rows(), 0..2);
954 assert!(change.is_bulk());
955 }
956
957 #[test]
958 fn a_change_reports_removed_rows() {
959 let mut buf = buffer("one\ntwo\nthree");
960 let s = span(&buf, (0, 3), (2, 0));
961 let mut txn = buf.begin();
962 txn.delete(s);
963 let change = txn.commit().expect("something changed");
964 assert_eq!(change.line_delta(), -2);
965 assert_eq!(buf.text().to_string(), "onethree");
966 }
967
968 #[test]
969 fn a_change_names_the_bytes_it_wrote() {
970 let mut buf = buffer("hello");
971 let p = at(&buf, 0, 5);
972 let mut txn = buf.begin();
973 txn.insert(p, "!!");
974 let change = txn.commit().expect("something changed");
975 let edits = change.edits().expect("a single transaction is precise");
976 assert_eq!(edits.len(), 1);
977 assert_eq!(edits[0].inserted(), 5..7);
978 assert_eq!(edits[0].removed_bytes(), 0);
979 }
980
981 #[test]
982 fn undoing_a_coalesced_group_declines_to_name_bytes() {
983 let mut buf = buffer("");
984 let end = buf.text().end();
985 let mut txn = buf.begin();
986 txn.insert(end, "a");
987 txn.commit();
988 let end = buf.text().end();
989 let mut txn = buf.begin_extending();
990 txn.insert(end, "b");
991 txn.commit();
992 let change = buf.undo().expect("there was a group");
993 assert!(
994 change.edits().is_none(),
995 "two coordinate spaces cannot be reported as one list"
996 );
997 assert!(change.rows().start <= change.rows().end);
998 }
999
1000 #[test]
1001 fn an_undo_reports_the_rows_of_the_text_it_restored() {
1002 let mut buf = buffer("one\ntwo\nthree");
1003 let p = at(&buf, 2, 0);
1004 let mut txn = buf.begin();
1005 txn.insert(p, "new\n");
1006 txn.commit();
1007 assert_eq!(buf.text().line_count(), 4);
1008 let change = buf.undo().expect("there was a group");
1009 assert_eq!(change.line_delta(), -1);
1010 assert!(
1011 change.rows().end <= buf.text().line_count(),
1012 "rows {:?} must address the restored text of {} rows",
1013 change.rows(),
1014 buf.text().line_count()
1015 );
1016 }
1017
1018 #[test]
1021 fn a_snapshot_holds_a_text_its_cursor_cannot_drift_from() {
1022 let mut buf = buffer("hello");
1023 buf.set_cursor(at(&buf, 0, 5));
1024 let snap = buf.snapshot();
1025 let end = buf.text().end();
1026 let mut txn = buf.begin();
1027 txn.insert(end, " world");
1028 txn.commit();
1029 assert_eq!(snap.text.to_string(), "hello");
1030 assert_eq!(snap.cursor.byte(), 5);
1031 assert!(!snap.text.is_stale(snap.cursor));
1032 assert_eq!(
1033 snap.text.slice(snap.text.full_span()).as_deref(),
1034 Some("hello")
1035 );
1036 }
1037}