1use std::collections::BTreeMap;
4use std::ops::Range;
5
6use crate::ropetext::change::{Change, Edit};
7use crate::ropetext::history::{DEFAULT_BUDGET_BYTES, Entry, History, Shape};
8use crate::ropetext::position::{Position, Revision, Span};
9use crate::ropetext::text::Text;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13enum Gravity {
14 Forward,
17 Backward,
20}
21
22#[derive(Debug, Clone, Copy)]
25struct Applied {
26 at: usize,
27 removed: usize,
28 inserted: usize,
29 revision_before: Revision,
32}
33
34#[derive(Debug, Clone)]
41pub struct Snapshot {
42 pub text: Text,
43 pub cursor: Position,
44 pub selection: Option<Span>,
45}
46
47#[derive(Debug)]
54pub struct EditBuffer {
55 text: Text,
56 cursor: Position,
57 anchor: Option<Position>,
58 marks: BTreeMap<char, Position>,
62 history: History,
63}
64
65impl Default for EditBuffer {
66 fn default() -> Self {
67 Self::new(Text::new())
68 }
69}
70
71impl EditBuffer {
72 pub fn new(text: Text) -> Self {
73 let cursor = text.start();
74 Self {
75 text,
76 cursor,
77 anchor: None,
78 marks: BTreeMap::new(),
79 history: History::new(DEFAULT_BUDGET_BYTES),
80 }
81 }
82
83 pub fn text(&self) -> &Text {
84 &self.text
85 }
86
87 pub fn cursor(&self) -> Position {
88 self.cursor
89 }
90
91 pub fn selection(&self) -> Option<Span> {
99 let anchor = self.anchor?;
100 self.text.span(anchor, self.cursor)
101 }
102
103 pub fn snapshot(&self) -> Snapshot {
105 Snapshot {
106 text: self.text.clone(),
107 cursor: self.cursor,
108 selection: self.selection(),
109 }
110 }
111
112 pub fn set_cursor(&mut self, to: Position) -> bool {
121 if self.text.is_stale(to) {
122 return false;
123 }
124 self.cursor = to;
125 self.anchor = None;
126 true
127 }
128
129 pub fn extend_to(&mut self, to: Position) -> bool {
134 if self.text.is_stale(to) {
135 return false;
136 }
137 if self.anchor.is_none() {
138 self.anchor = Some(self.cursor);
139 }
140 self.cursor = to;
141 true
142 }
143
144 pub fn select(&mut self, span: Span) -> bool {
146 if span.revision() != self.text.revision() {
147 return false;
148 }
149 self.anchor = Some(span.start());
150 self.cursor = span.end();
151 true
152 }
153
154 pub fn clear_selection(&mut self) {
155 self.anchor = None;
156 }
157
158 pub fn set_text(&mut self, text: Text) {
164 self.text = text;
165 self.cursor = self.text.start();
166 self.anchor = None;
167 self.marks.clear();
168 self.history.clear();
169 }
170
171 pub fn set_mark(&mut self, name: char, at: Position) -> bool {
180 if self.text.is_stale(at) {
181 return false;
182 }
183 self.marks.insert(name, at);
184 true
185 }
186
187 pub fn mark(&self, name: char) -> Option<Position> {
188 self.marks.get(&name).copied()
189 }
190
191 fn mark_offsets(&self) -> Vec<(char, usize)> {
194 self.marks
195 .iter()
196 .map(|(&name, at)| (name, at.byte()))
197 .collect()
198 }
199
200 pub fn set_history_budget(&mut self, bytes: usize) {
202 self.history.set_budget(bytes);
203 }
204
205 pub fn begin(&mut self) -> Txn<'_> {
209 Txn::new(self, false)
210 }
211
212 pub fn begin_extending(&mut self) -> Txn<'_> {
221 Txn::new(self, true)
222 }
223
224 pub fn undo(&mut self) -> Option<Change> {
231 let entry = self.history.undo_candidate()?;
232 let text = entry.before.reidentified();
233 let shape = entry.inverse.clone();
234 let cursor = entry.cursor_before;
235 let anchor = entry.anchor_before;
236 let marks = entry.marks_before.clone();
237 self.history.step_back();
238 Some(self.restore(text, shape, cursor, anchor, &marks))
239 }
240
241 pub fn redo(&mut self) -> Option<Change> {
243 let entry = self.history.redo_candidate()?;
244 let text = entry.after.reidentified();
245 let shape = entry.forward.clone();
246 let cursor = entry.cursor_after;
247 let anchor = entry.anchor_after;
248 let marks = entry.marks_after.clone();
249 self.history.step_forward();
250 Some(self.restore(text, shape, cursor, anchor, &marks))
251 }
252
253 fn restore(
254 &mut self,
255 text: Text,
256 shape: Shape,
257 cursor: usize,
258 anchor: Option<usize>,
259 marks: &[(char, usize)],
260 ) -> Change {
261 let unsaved: Vec<(char, usize, usize)> = self
264 .marks
265 .iter()
266 .filter(|(name, _)| !marks.iter().any(|(saved, _)| saved == *name))
267 .map(|(&name, at)| (name, at.row(), at.column().get()))
268 .collect();
269 self.text = text;
270 self.cursor = self.text.position_at_derived_byte(cursor);
271 self.anchor = anchor.map(|a| self.text.position_at_derived_byte(a));
272 self.marks = marks
273 .iter()
274 .map(|&(name, byte)| (name, self.text.position_at_derived_byte(byte)))
275 .collect();
276 for (name, row, col) in unsaved {
277 let at = self.text.position_near(row, col);
278 self.marks.insert(name, at);
279 }
280 Change::new(
281 self.text.revision(),
282 shape.edits,
283 shape.rows,
284 shape.line_delta,
285 )
286 }
287}
288
289#[must_use = "a transaction that is dropped without commit() is rolled back"]
297pub struct Txn<'a> {
298 buffer: &'a mut EditBuffer,
299 before: Text,
300 cursor_before: usize,
301 anchor_before: Option<usize>,
302 marks_before: Vec<(char, usize)>,
303 applied: Vec<Applied>,
304 retained: usize,
306 extending: bool,
307 committed: bool,
308}
309
310impl<'a> Txn<'a> {
311 fn new(buffer: &'a mut EditBuffer, extending: bool) -> Self {
312 let before = buffer.text.clone();
313 let cursor_before = buffer.cursor.byte();
314 let anchor_before = buffer.anchor.map(Position::byte);
315 let marks_before = buffer.mark_offsets();
316 Self {
317 buffer,
318 before,
319 cursor_before,
320 anchor_before,
321 marks_before,
322 applied: Vec::new(),
323 retained: 0,
324 extending,
325 committed: false,
326 }
327 }
328
329 pub fn text(&self) -> &Text {
331 &self.buffer.text
332 }
333
334 pub fn cursor(&self) -> Position {
335 self.buffer.cursor
336 }
337
338 pub fn selection(&self) -> Option<Span> {
339 self.buffer.selection()
340 }
341
342 pub fn map(&self, position: Position) -> Option<Position> {
356 let byte = self.remap(position, Gravity::Backward)?;
357 Some(self.buffer.text.position_at_cursor_byte(byte))
358 }
359
360 pub fn insert(&mut self, at: Position, text: &str) -> bool {
365 let Some(byte) = self.remap(at, Gravity::Backward) else {
366 return false;
367 };
368 self.splice(byte..byte, text)
369 }
370
371 pub fn delete(&mut self, span: Span) -> bool {
373 self.replace(span, "")
374 }
375
376 pub fn replace(&mut self, span: Span, text: &str) -> bool {
381 let Some(start) = self.remap(span.start(), Gravity::Backward) else {
382 return false;
383 };
384 let Some(end) = self.remap(span.end(), Gravity::Forward) else {
385 return false;
386 };
387 if end < start {
388 debug_assert!(false, "span ends collapsed past its start");
389 return false;
390 }
391 self.splice(start..end, text)
392 }
393
394 pub fn set_cursor(&mut self, to: Position) -> bool {
396 self.buffer.set_cursor(to)
397 }
398
399 pub fn select(&mut self, span: Span) -> bool {
405 self.buffer.select(span)
406 }
407
408 pub fn clear_selection(&mut self) {
409 self.buffer.clear_selection();
410 }
411
412 pub fn commit(mut self) -> Option<Change> {
418 self.committed = true;
419 if self.applied.is_empty() {
420 return None;
421 }
422
423 let forward = self.forward_shape();
424 let inverse = self.inverse_shape();
425 let entry = Entry {
426 before: self.before.clone(),
427 after: self.buffer.text.clone(),
428 forward: forward.clone(),
429 inverse,
430 cursor_before: self.cursor_before,
431 cursor_after: self.buffer.cursor.byte(),
432 anchor_before: self.anchor_before,
433 anchor_after: self.buffer.anchor.map(Position::byte),
434 marks_before: self.marks_before.clone(),
435 marks_after: self.buffer.mark_offsets(),
436 retained: self.retained,
437 };
438
439 if self.extending {
440 if let Some(entry) = self.buffer.history.extend_newest(entry) {
441 self.buffer.history.push(entry);
442 }
443 } else {
444 self.buffer.history.push(entry);
445 }
446
447 Some(Change::new(
448 self.buffer.text.revision(),
449 forward.edits,
450 forward.rows,
451 forward.line_delta,
452 ))
453 }
454
455 fn splice(&mut self, bytes: Range<usize>, text: &str) -> bool {
458 if bytes.is_empty() && text.is_empty() {
459 return true;
460 }
461 let revision_before = self.buffer.text.revision();
462 let removed = bytes.len();
463 let at = bytes.start;
464 let inserted = self.buffer.text.splice(bytes, text);
465
466 self.applied.push(Applied {
467 at,
468 removed,
469 inserted,
470 revision_before,
471 });
472 self.retained += removed + inserted;
473
474 let edit = Applied {
475 at,
476 removed,
477 inserted,
478 revision_before,
479 };
480 let cursor = shift(self.buffer.cursor.byte(), &edit, Gravity::Forward);
481 self.buffer.cursor = self.buffer.text.position_at_cursor_byte(cursor);
486 for mark in self.buffer.marks.values_mut() {
490 let byte = shift(mark.byte(), &edit, Gravity::Backward);
491 *mark = self.buffer.text.position_at_cursor_byte(byte);
492 }
493 self.buffer.anchor = None;
498 true
499 }
500
501 fn remap(&self, position: Position, gravity: Gravity) -> Option<usize> {
504 if position.revision() == self.buffer.text.revision() {
505 return Some(position.byte());
506 }
507 let from = self
508 .applied
509 .iter()
510 .position(|a| a.revision_before == position.revision())?;
511 let mut byte = position.byte();
512 for edit in &self.applied[from..] {
513 byte = shift(byte, edit, gravity);
514 }
515 Some(byte)
516 }
517
518 fn forward_shape(&self) -> Shape {
520 let mut edits = Vec::with_capacity(self.applied.len());
521 let mut rows: Option<Range<usize>> = None;
522 for (i, edit) in self.applied.iter().enumerate() {
523 let later = &self.applied[i + 1..];
524 let start = later
525 .iter()
526 .fold(edit.at, |b, e| shift(b, e, Gravity::Backward));
527 let end = later.iter().fold(edit.at + edit.inserted, |b, e| {
528 shift(b, e, Gravity::Forward)
529 });
530 let end = end.max(start).min(self.buffer.text.len_bytes());
531 let start = start.min(end);
532 edits.push(Edit::new(start..end, edit.removed));
533 rows = Some(union(rows, self.rows_of(&self.buffer.text, start..end)));
534 }
535 Shape {
536 edits: Some(edits),
537 rows: rows.unwrap_or(0..0),
538 line_delta: line_delta(&self.before, &self.buffer.text),
539 }
540 }
541
542 fn inverse_shape(&self) -> Shape {
547 let mut edits = Vec::with_capacity(self.applied.len());
548 let mut rows: Option<Range<usize>> = None;
549 for (i, edit) in self.applied.iter().enumerate() {
550 let earlier = &self.applied[..i];
551 let start = earlier.iter().rev().fold(edit.at, unshift);
554 let end = (start + edit.removed).min(self.before.len_bytes());
555 let start = start.min(end);
556 edits.push(Edit::new(start..end, edit.inserted));
557 rows = Some(union(rows, self.rows_of(&self.before, start..end)));
558 }
559 Shape {
560 edits: Some(edits),
561 rows: rows.unwrap_or(0..0),
562 line_delta: line_delta(&self.buffer.text, &self.before),
563 }
564 }
565
566 fn rows_of(&self, text: &Text, bytes: Range<usize>) -> Range<usize> {
567 let first = text.row_of_byte(bytes.start.min(text.len_bytes()));
568 let last = text.row_of_byte(bytes.end.min(text.len_bytes()));
569 first..last + 1
570 }
571}
572
573impl Drop for Txn<'_> {
574 fn drop(&mut self) {
575 if self.committed {
576 return;
577 }
578 self.buffer.text = self.before.clone();
579 self.buffer.cursor = self
580 .buffer
581 .text
582 .position_at_derived_byte(self.cursor_before);
583 self.buffer.anchor = self
584 .anchor_before
585 .map(|byte| self.buffer.text.position_at_derived_byte(byte));
586 self.buffer.marks = self
587 .marks_before
588 .iter()
589 .map(|&(name, byte)| (name, self.buffer.text.position_at_derived_byte(byte)))
590 .collect();
591 }
592}
593
594fn shift(byte: usize, edit: &Applied, gravity: Gravity) -> usize {
596 let end = edit.at + edit.removed;
597 if byte < edit.at {
598 byte
599 } else if byte > edit.at && byte >= end {
600 byte - edit.removed + edit.inserted
601 } else {
602 match gravity {
606 Gravity::Forward => edit.at + edit.inserted,
607 Gravity::Backward => edit.at,
608 }
609 }
610}
611
612fn unshift(byte: usize, edit: &Applied) -> usize {
614 let end = edit.at + edit.inserted;
615 if byte <= edit.at {
616 byte
617 } else if byte >= end {
618 byte - edit.inserted + edit.removed
619 } else {
620 edit.at
621 }
622}
623
624fn union(rows: Option<Range<usize>>, next: Range<usize>) -> Range<usize> {
625 match rows {
626 Some(rows) => rows.start.min(next.start)..rows.end.max(next.end),
627 None => next,
628 }
629}
630
631fn line_delta(from: &Text, to: &Text) -> isize {
632 to.line_count() as isize - from.line_count() as isize
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638 use crate::ropetext::position::Column;
639
640 fn buffer(text: &str) -> EditBuffer {
641 EditBuffer::new(Text::from(text))
642 }
643
644 fn at(buf: &EditBuffer, row: usize, col: usize) -> Position {
645 buf.text()
646 .position(row, Column::new(col))
647 .expect("addressable in the test fixture")
648 }
649
650 fn span(buf: &EditBuffer, from: (usize, usize), to: (usize, usize)) -> Span {
651 let a = at(buf, from.0, from.1);
652 let b = at(buf, to.0, to.1);
653 buf.text().span(a, b).expect("same text")
654 }
655
656 #[test]
659 fn inserting_at_the_cursor_carries_the_cursor_along() {
660 let mut buf = buffer("hello");
661 let p = at(&buf, 0, 5);
662 buf.set_cursor(p);
663 let mut txn = buf.begin();
664 assert!(txn.insert(p, ", world"));
665 txn.commit();
666 assert_eq!(buf.text().to_string(), "hello, world");
667 assert_eq!(buf.cursor().column().get(), 12);
668 }
669
670 #[test]
671 fn inserting_after_the_cursor_leaves_the_cursor_where_it_was() {
672 let mut buf = buffer("hello");
673 buf.set_cursor(at(&buf, 0, 1));
674 let target = at(&buf, 0, 4);
675 let mut txn = buf.begin();
676 txn.insert(target, "XXX");
677 txn.commit();
678 assert_eq!(buf.cursor().byte(), 1, "an edit elsewhere is not a move");
679 }
680
681 #[test]
682 fn inserting_before_the_cursor_pushes_it_along() {
683 let mut buf = buffer("hello");
684 buf.set_cursor(at(&buf, 0, 4));
685 let target = at(&buf, 0, 0);
686 let mut txn = buf.begin();
687 txn.insert(target, "XX");
688 txn.commit();
689 assert_eq!(buf.cursor().column().get(), 6);
690 }
691
692 #[test]
693 fn deleting_across_the_cursor_collapses_it_to_the_start() {
694 let mut buf = buffer("hello world");
695 buf.set_cursor(at(&buf, 0, 8));
696 let s = span(&buf, (0, 5), (0, 11));
697 let mut txn = buf.begin();
698 txn.delete(s);
699 txn.commit();
700 assert_eq!(buf.text().to_string(), "hello");
701 assert_eq!(buf.cursor().byte(), 5);
702 }
703
704 #[test]
705 fn replacing_leaves_the_cursor_after_the_new_text() {
706 let mut buf = buffer("hello world");
707 let s = span(&buf, (0, 6), (0, 11));
708 buf.set_cursor(s.end());
709 let mut txn = buf.begin();
710 txn.replace(s, "there");
711 txn.commit();
712 assert_eq!(buf.text().to_string(), "hello there");
713 assert_eq!(buf.cursor().column().get(), 11);
714 }
715
716 #[test]
717 fn an_edit_clears_the_selection() {
718 let mut buf = buffer("hello world");
719 let s = span(&buf, (0, 0), (0, 5));
720 buf.select(s);
721 assert!(buf.selection().is_some());
722 let mut txn = buf.begin();
723 txn.replace(s, "bye");
724 txn.commit();
725 assert_eq!(buf.text().to_string(), "bye world");
726 assert!(
727 buf.selection().is_none(),
728 "typing over a selection must not leave the typed text selected"
729 );
730 }
731
732 #[test]
733 fn a_compound_edit_can_leave_a_selection_behind() {
734 let mut buf = buffer("word");
737 let s = span(&buf, (0, 0), (0, 4));
738 let mut txn = buf.begin();
739 txn.insert(s.end(), "]");
740 txn.insert(s.start(), "[");
741 let inner = txn
742 .text()
743 .span(
744 txn.text().position(0, Column::new(1)).unwrap(),
745 txn.text().position(0, Column::new(5)).unwrap(),
746 )
747 .unwrap();
748 txn.select(inner);
749 txn.commit();
750 assert_eq!(buf.text().to_string(), "[word]");
751 assert_eq!(
752 buf.text()
753 .slice(buf.selection().expect("still selected"))
754 .as_deref(),
755 Some("word")
756 );
757 }
758
759 #[test]
760 fn several_primitives_are_one_undo() {
761 let mut buf = buffer("word");
762 let s = span(&buf, (0, 0), (0, 4));
763 let mut txn = buf.begin();
764 txn.insert(s.end(), "]");
765 txn.insert(s.start(), "[");
766 txn.commit();
767 assert_eq!(buf.text().to_string(), "[word]");
768 buf.undo();
769 assert_eq!(buf.text().to_string(), "word", "one action, one undo");
770 }
771
772 #[test]
773 fn a_position_from_before_the_transaction_still_points_at_its_text() {
774 let mut buf = buffer("one two");
775 let two = at(&buf, 0, 4);
776 let head = at(&buf, 0, 0);
777 let mut txn = buf.begin();
778 txn.insert(head, "zero ");
779 let moved = txn.map(two).expect("carried forward");
781 assert_eq!(moved.byte(), 9);
782 txn.commit();
783 }
784
785 #[test]
786 fn a_position_from_another_text_is_refused() {
787 let mut buf = buffer("hello");
788 let other = Text::from("hello");
789 let foreign = other.position(0, Column::new(2)).unwrap();
790 let mut txn = buf.begin();
791 assert!(!txn.insert(foreign, "x"));
792 assert!(txn.map(foreign).is_none());
793 txn.commit();
794 assert_eq!(buf.text().to_string(), "hello", "nothing happened");
795 }
796
797 #[test]
798 fn a_stale_cursor_is_refused_rather_than_approximated() {
799 let mut buf = buffer("hello");
800 let stale = at(&buf, 0, 4);
801 let head = at(&buf, 0, 0);
802 let mut txn = buf.begin();
803 txn.insert(head, "xx");
804 txn.commit();
805 assert!(!buf.set_cursor(stale));
806 }
807
808 #[test]
811 fn a_transaction_that_changes_nothing_reports_nothing() {
812 let mut buf = buffer("hello");
813 let p = at(&buf, 0, 2);
814 let mut txn = buf.begin();
815 txn.insert(p, "");
816 assert!(txn.commit().is_none());
817 assert!(buf.undo().is_none(), "and records no history");
818 }
819
820 #[test]
821 fn a_transaction_that_only_moves_the_cursor_keeps_the_move() {
822 let mut buf = buffer("hello");
823 let p = at(&buf, 0, 3);
824 let mut txn = buf.begin();
825 txn.set_cursor(p);
826 assert!(txn.commit().is_none(), "a cursor move is not history");
827 assert_eq!(buf.cursor().byte(), 3);
828 }
829
830 #[test]
831 fn dropping_a_transaction_rolls_it_back() {
832 let mut buf = buffer("hello");
833 let end = at(&buf, 0, 5);
834 buf.set_cursor(end);
835 {
836 let mut txn = buf.begin();
837 txn.insert(end, " world");
838 assert_eq!(txn.text().to_string(), "hello world");
839 }
841 assert_eq!(buf.text().to_string(), "hello");
842 assert_eq!(buf.cursor().byte(), 5);
843 assert!(buf.undo().is_none(), "a rollback is not an undo step");
844 }
845
846 #[test]
849 fn a_delete_that_removes_nothing_records_no_history() {
850 let mut buf = buffer("hello");
855 let start = at(&buf, 0, 0);
856 let mut txn = buf.begin();
857 txn.insert(start, "x");
858 txn.commit();
859
860 let end = buf.text().end();
863 let empty = buf.text().span(end, end).expect("same text");
864 let mut txn = buf.begin();
865 assert!(txn.delete(empty), "an empty delete is not a failure");
866 assert!(txn.commit().is_none(), "but it is not a change either");
867
868 buf.undo();
869 assert_eq!(
870 buf.text().to_string(),
871 "hello",
872 "the undo took back the insert, not a phantom delete"
873 );
874 assert!(buf.undo().is_none(), "and there was only ever one entry");
875 }
876
877 #[test]
878 fn undo_of_a_forward_delete_returns_the_cursor_to_where_the_user_was() {
879 let mut buf = buffer("hello world");
882 let s = span(&buf, (0, 5), (0, 11));
883 buf.set_cursor(s.start());
884 let mut txn = buf.begin();
885 txn.delete(s);
886 txn.commit();
887 assert_eq!(buf.text().to_string(), "hello");
888 buf.undo();
889 assert_eq!(buf.text().to_string(), "hello world");
890 assert_eq!(buf.cursor().byte(), 5, "not 11");
891 }
892
893 #[test]
894 fn undo_and_redo_walk_the_text_back_and_forward() {
895 let mut buf = buffer("");
896 for word in ["a", "b", "c"] {
897 let end = buf.text().end();
898 let mut txn = buf.begin();
899 txn.insert(end, word);
900 txn.commit();
901 }
902 assert_eq!(buf.text().to_string(), "abc");
903 buf.undo();
904 assert_eq!(buf.text().to_string(), "ab");
905 buf.undo();
906 assert_eq!(buf.text().to_string(), "a");
907 buf.redo();
908 assert_eq!(buf.text().to_string(), "ab");
909 buf.redo();
910 assert_eq!(buf.text().to_string(), "abc");
911 assert!(buf.redo().is_none());
912 }
913
914 #[test]
915 fn undo_restores_the_cursor_the_group_started_from() {
916 let mut buf = buffer("hello");
917 let start = at(&buf, 0, 2);
918 buf.set_cursor(start);
919 let mut txn = buf.begin();
920 txn.insert(start, "XYZ");
921 txn.commit();
922 assert_eq!(buf.cursor().byte(), 5);
923 buf.undo();
924 assert_eq!(buf.cursor().byte(), 2);
925 }
926
927 #[test]
928 fn undo_mints_a_new_revision_rather_than_reusing_the_old_one() {
929 let mut buf = buffer("hello");
930 let before = buf.text().revision();
931 let p = at(&buf, 0, 5);
932 let mut txn = buf.begin();
933 txn.insert(p, "!");
934 txn.commit();
935 buf.undo();
936 assert_eq!(buf.text().to_string(), "hello");
937 assert_ne!(
938 buf.text().revision(),
939 before,
940 "the same content at a later point in the timeline is a later revision"
941 );
942 }
943
944 #[test]
945 fn extending_folds_a_typing_run_into_one_undo() {
946 let mut buf = buffer("");
947 let first = buf.text().end();
948 let mut txn = buf.begin();
949 txn.insert(first, "h");
950 txn.commit();
951 for c in ["e", "l", "l", "o"] {
952 let end = buf.text().end();
953 let mut txn = buf.begin_extending();
954 txn.insert(end, c);
955 txn.commit();
956 }
957 assert_eq!(buf.text().to_string(), "hello");
958 buf.undo();
959 assert_eq!(
960 buf.text().to_string(),
961 "",
962 "the whole run went back at once"
963 );
964 assert!(buf.undo().is_none());
965 }
966
967 #[test]
968 fn extending_with_no_previous_group_records_one() {
969 let mut buf = buffer("");
970 let end = buf.text().end();
971 let mut txn = buf.begin_extending();
972 txn.insert(end, "a");
973 txn.commit();
974 buf.undo();
975 assert_eq!(buf.text().to_string(), "");
976 }
977
978 #[test]
979 fn a_new_group_after_an_undo_drops_the_redo_side() {
980 let mut buf = buffer("");
981 let end = buf.text().end();
982 let mut txn = buf.begin();
983 txn.insert(end, "a");
984 txn.commit();
985 buf.undo();
986 let end = buf.text().end();
987 let mut txn = buf.begin();
988 txn.insert(end, "b");
989 txn.commit();
990 assert!(buf.redo().is_none());
991 assert_eq!(buf.text().to_string(), "b");
992 }
993
994 #[test]
995 fn opening_a_different_note_forgets_the_history() {
996 let mut buf = buffer("first");
997 let end = buf.text().end();
998 let mut txn = buf.begin();
999 txn.insert(end, "!");
1000 txn.commit();
1001 buf.set_text(Text::from("second"));
1002 assert!(
1003 buf.undo().is_none(),
1004 "an undo must not walk from one note into another"
1005 );
1006 assert_eq!(buf.text().to_string(), "second");
1007 }
1008
1009 #[test]
1012 fn a_change_reports_the_row_it_touched() {
1013 let mut buf = buffer("one\ntwo\nthree");
1014 let p = at(&buf, 1, 1);
1015 let mut txn = buf.begin();
1016 txn.insert(p, "X");
1017 let change = txn.commit().expect("something changed");
1018 assert_eq!(change.rows(), 1..2);
1019 assert_eq!(change.line_delta(), 0);
1020 assert!(!change.is_bulk());
1021 }
1022
1023 #[test]
1024 fn a_change_reports_added_rows() {
1025 let mut buf = buffer("one\ntwo");
1026 let p = at(&buf, 0, 3);
1027 let mut txn = buf.begin();
1028 txn.insert(p, "\nmiddle");
1029 let change = txn.commit().expect("something changed");
1030 assert_eq!(change.line_delta(), 1);
1031 assert_eq!(change.rows(), 0..2);
1032 assert!(change.is_bulk());
1033 }
1034
1035 #[test]
1036 fn a_change_reports_removed_rows() {
1037 let mut buf = buffer("one\ntwo\nthree");
1038 let s = span(&buf, (0, 3), (2, 0));
1039 let mut txn = buf.begin();
1040 txn.delete(s);
1041 let change = txn.commit().expect("something changed");
1042 assert_eq!(change.line_delta(), -2);
1043 assert_eq!(buf.text().to_string(), "onethree");
1044 }
1045
1046 #[test]
1047 fn a_change_names_the_bytes_it_wrote() {
1048 let mut buf = buffer("hello");
1049 let p = at(&buf, 0, 5);
1050 let mut txn = buf.begin();
1051 txn.insert(p, "!!");
1052 let change = txn.commit().expect("something changed");
1053 let edits = change.edits().expect("a single transaction is precise");
1054 assert_eq!(edits.len(), 1);
1055 assert_eq!(edits[0].inserted(), 5..7);
1056 assert_eq!(edits[0].removed_bytes(), 0);
1057 }
1058
1059 #[test]
1060 fn undoing_a_coalesced_group_declines_to_name_bytes() {
1061 let mut buf = buffer("");
1062 let end = buf.text().end();
1063 let mut txn = buf.begin();
1064 txn.insert(end, "a");
1065 txn.commit();
1066 let end = buf.text().end();
1067 let mut txn = buf.begin_extending();
1068 txn.insert(end, "b");
1069 txn.commit();
1070 let change = buf.undo().expect("there was a group");
1071 assert!(
1072 change.edits().is_none(),
1073 "two coordinate spaces cannot be reported as one list"
1074 );
1075 assert!(change.rows().start <= change.rows().end);
1076 }
1077
1078 #[test]
1079 fn an_undo_reports_the_rows_of_the_text_it_restored() {
1080 let mut buf = buffer("one\ntwo\nthree");
1081 let p = at(&buf, 2, 0);
1082 let mut txn = buf.begin();
1083 txn.insert(p, "new\n");
1084 txn.commit();
1085 assert_eq!(buf.text().line_count(), 4);
1086 let change = buf.undo().expect("there was a group");
1087 assert_eq!(change.line_delta(), -1);
1088 assert!(
1089 change.rows().end <= buf.text().line_count(),
1090 "rows {:?} must address the restored text of {} rows",
1091 change.rows(),
1092 buf.text().line_count()
1093 );
1094 }
1095
1096 #[test]
1099 fn a_snapshot_holds_a_text_its_cursor_cannot_drift_from() {
1100 let mut buf = buffer("hello");
1101 buf.set_cursor(at(&buf, 0, 5));
1102 let snap = buf.snapshot();
1103 let end = buf.text().end();
1104 let mut txn = buf.begin();
1105 txn.insert(end, " world");
1106 txn.commit();
1107 assert_eq!(snap.text.to_string(), "hello");
1108 assert_eq!(snap.cursor.byte(), 5);
1109 assert!(!snap.text.is_stale(snap.cursor));
1110 assert_eq!(
1111 snap.text.slice(snap.text.full_span()).as_deref(),
1112 Some("hello")
1113 );
1114 }
1115
1116 fn mark_at(buf: &EditBuffer, name: char) -> Option<(usize, usize)> {
1119 buf.mark(name).map(|m| (m.row(), m.column().get()))
1120 }
1121
1122 #[test]
1125 fn undo_and_redo_restore_the_marks_a_change_moved() {
1126 let mut buf = buffer("a\nb\nc\nd");
1127 let (b, d) = (at(&buf, 1, 0), at(&buf, 3, 0));
1128 buf.set_mark('<', b);
1129 buf.set_mark('>', d);
1130 let mut txn = buf.begin();
1131 let span = txn
1132 .text()
1133 .span(at_txn(&txn, 1, 0), at_txn(&txn, 3, 0))
1134 .unwrap();
1135 txn.delete(span);
1136 txn.commit();
1137 assert_eq!(mark_at(&buf, '<'), Some((1, 0)));
1138 assert_eq!(mark_at(&buf, '>'), Some((1, 0)));
1139 buf.undo();
1140 assert_eq!(mark_at(&buf, '<'), Some((1, 0)));
1141 assert_eq!(mark_at(&buf, '>'), Some((3, 0)));
1142 buf.redo();
1143 assert_eq!(mark_at(&buf, '>'), Some((1, 0)));
1144 }
1145
1146 #[test]
1149 fn undo_keeps_a_mark_the_change_never_saw() {
1150 let mut buf = buffer("ab\ncd");
1151 let mut txn = buf.begin();
1152 let start = at_txn(&txn, 0, 0);
1153 txn.insert(start, "x");
1154 txn.commit();
1155 let d = at(&buf, 1, 1);
1156 buf.set_mark('<', d);
1157 buf.undo();
1158 assert_eq!(mark_at(&buf, '<'), Some((1, 1)));
1159 }
1160
1161 #[test]
1164 fn a_rolled_back_transaction_restores_the_marks() {
1165 let mut buf = buffer("abc");
1166 let c = at(&buf, 0, 2);
1167 buf.set_mark('m', c);
1168 {
1169 let mut txn = buf.begin();
1170 let start = at_txn(&txn, 0, 0);
1171 txn.insert(start, "xyz");
1172 }
1173 assert_eq!(buf.text().to_string(), "abc");
1174 assert_eq!(mark_at(&buf, 'm'), Some((0, 2)));
1175 }
1176
1177 fn at_txn(txn: &Txn<'_>, row: usize, col: usize) -> Position {
1178 txn.text()
1179 .position(row, Column::new(col))
1180 .expect("addressable in the test fixture")
1181 }
1182}