1use crate::model::piece_tree::PieceTree;
2pub use fresh_core::api::{OverlayColorSpec, OverlayOptions};
3pub use fresh_core::overlay::{OverlayHandle, OverlayNamespace};
4pub use fresh_core::{BufferId, CursorId, SplitDirection, SplitId};
5use serde::{Deserialize, Serialize};
6use std::ops::Range;
7use std::sync::Arc;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub enum Event {
12 Insert {
14 position: usize,
15 text: String,
16 cursor_id: CursorId,
17 },
18
19 Delete {
21 range: Range<usize>,
22 deleted_text: String,
23 cursor_id: CursorId,
24 },
25
26 MoveCursor {
28 cursor_id: CursorId,
29 old_position: usize,
30 new_position: usize,
31 old_anchor: Option<usize>,
32 new_anchor: Option<usize>,
33 old_sticky_column: usize,
34 new_sticky_column: usize,
35 },
36
37 AddCursor {
39 cursor_id: CursorId,
40 position: usize,
41 anchor: Option<usize>,
42 },
43
44 RemoveCursor {
46 cursor_id: CursorId,
47 position: usize,
48 anchor: Option<usize>,
49 },
50
51 Scroll {
53 line_offset: isize,
54 },
55
56 SetViewport {
58 top_line: usize,
59 },
60
61 Recenter,
63
64 SetAnchor {
66 cursor_id: CursorId,
67 position: usize,
68 },
69
70 ClearAnchor {
73 cursor_id: CursorId,
74 },
75
76 ChangeMode {
78 mode: String,
79 },
80
81 AddOverlay {
83 namespace: Option<OverlayNamespace>,
84 range: Range<usize>,
85 face: OverlayFace,
86 priority: i32,
87 message: Option<String>,
88 extend_to_line_end: bool,
90 },
91
92 RemoveOverlay {
94 handle: OverlayHandle,
95 },
96
97 RemoveOverlaysInRange {
99 range: Range<usize>,
100 },
101
102 ClearNamespace {
104 namespace: OverlayNamespace,
105 },
106
107 ClearOverlays,
109
110 ShowPopup {
112 popup: PopupData,
113 },
114
115 HidePopup,
117
118 ClearPopups,
120
121 PopupSelectNext,
123 PopupSelectPrev,
124 PopupPageDown,
125 PopupPageUp,
126
127 AddMarginAnnotation {
130 line: usize,
131 position: MarginPositionData,
132 content: MarginContentData,
133 annotation_id: Option<String>,
134 },
135
136 RemoveMarginAnnotation {
138 annotation_id: String,
139 },
140
141 RemoveMarginAnnotationsAtLine {
143 line: usize,
144 position: MarginPositionData,
145 },
146
147 ClearMarginPosition {
149 position: MarginPositionData,
150 },
151
152 ClearMargins,
154
155 SetLineNumbers {
157 enabled: bool,
158 },
159
160 SplitPane {
163 direction: SplitDirection,
164 new_buffer_id: BufferId,
165 ratio: f32,
166 },
167
168 CloseSplit {
170 split_id: SplitId,
171 },
172
173 SetActiveSplit {
175 split_id: SplitId,
176 },
177
178 AdjustSplitRatio {
180 split_id: SplitId,
181 delta: f32,
182 },
183
184 NextSplit,
186
187 PrevSplit,
189
190 Batch {
193 events: Vec<Event>,
194 description: String,
195 },
196
197 BulkEdit {
204 #[serde(skip)]
206 old_tree: Option<Arc<PieceTree>>,
207 #[serde(skip)]
209 new_tree: Option<Arc<PieceTree>>,
210 old_cursors: Vec<(CursorId, usize, Option<usize>)>,
212 new_cursors: Vec<(CursorId, usize, Option<usize>)>,
214 description: String,
216 },
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
221pub enum OverlayFace {
222 Underline {
223 color: (u8, u8, u8), style: UnderlineStyle,
225 },
226 Background {
227 color: (u8, u8, u8),
228 },
229 Foreground {
230 color: (u8, u8, u8),
231 },
232 Style {
237 options: OverlayOptions,
238 },
239}
240
241impl OverlayFace {
242 pub fn from_options(options: OverlayOptions) -> Self {
244 OverlayFace::Style { options }
245 }
246}
247
248#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
250pub enum UnderlineStyle {
251 Straight,
252 Wavy,
253 Dotted,
254 Dashed,
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct PopupData {
260 pub title: Option<String>,
261 #[serde(default)]
263 pub description: Option<String>,
264 #[serde(default)]
265 pub transient: bool,
266 pub content: PopupContentData,
267 pub position: PopupPositionData,
268 pub width: u16,
269 pub max_height: u16,
270 pub bordered: bool,
271}
272
273#[derive(Debug, Clone, Serialize, Deserialize)]
275pub enum PopupContentData {
276 Text(Vec<String>),
277 List {
278 items: Vec<PopupListItemData>,
279 selected: usize,
280 },
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct PopupListItemData {
286 pub text: String,
287 pub detail: Option<String>,
288 pub icon: Option<String>,
289 pub data: Option<String>,
290}
291
292#[derive(Debug, Clone, Serialize, Deserialize)]
294pub enum PopupPositionData {
295 AtCursor,
296 BelowCursor,
297 AboveCursor,
298 Fixed { x: u16, y: u16 },
299 Centered,
300 BottomRight,
301}
302
303#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
305pub enum MarginPositionData {
306 Left,
307 Right,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
312pub enum MarginContentData {
313 Text(String),
314 Symbol {
315 text: String,
316 color: Option<(u8, u8, u8)>, },
318 Empty,
319}
320
321impl Event {
322 pub fn inverse(&self) -> Option<Self> {
325 match self {
326 Self::Insert { position, text, .. } => {
327 let range = *position..(position + text.len());
328 Some(Self::Delete {
329 range,
330 deleted_text: text.clone(),
331 cursor_id: CursorId::UNDO_SENTINEL,
332 })
333 }
334 Self::Delete {
335 range,
336 deleted_text,
337 ..
338 } => Some(Self::Insert {
339 position: range.start,
340 text: deleted_text.clone(),
341 cursor_id: CursorId::UNDO_SENTINEL,
342 }),
343 Self::Batch {
344 events,
345 description,
346 } => {
347 let inverted: Option<Vec<Self>> =
349 events.iter().rev().map(|e| e.inverse()).collect();
350
351 inverted.map(|inverted_events| Self::Batch {
352 events: inverted_events,
353 description: format!("Undo: {}", description),
354 })
355 }
356 Self::AddCursor {
357 cursor_id,
358 position,
359 anchor,
360 } => {
361 Some(Self::RemoveCursor {
363 cursor_id: *cursor_id,
364 position: *position,
365 anchor: *anchor,
366 })
367 }
368 Self::RemoveCursor {
369 cursor_id,
370 position,
371 anchor,
372 } => {
373 Some(Self::AddCursor {
375 cursor_id: *cursor_id,
376 position: *position,
377 anchor: *anchor,
378 })
379 }
380 Self::MoveCursor {
381 cursor_id,
382 old_position,
383 new_position,
384 old_anchor,
385 new_anchor,
386 old_sticky_column,
387 new_sticky_column,
388 } => {
389 Some(Self::MoveCursor {
391 cursor_id: *cursor_id,
392 old_position: *new_position,
393 new_position: *old_position,
394 old_anchor: *new_anchor,
395 new_anchor: *old_anchor,
396 old_sticky_column: *new_sticky_column,
397 new_sticky_column: *old_sticky_column,
398 })
399 }
400 Self::AddOverlay { .. } => {
401 None
403 }
404 Self::RemoveOverlay { .. } => {
405 None
407 }
408 Self::ClearNamespace { .. } => {
409 None
411 }
412 Self::Scroll { line_offset } => Some(Self::Scroll {
413 line_offset: -line_offset,
414 }),
415 Self::SetViewport { top_line: _ } => {
416 None
418 }
419 Self::ChangeMode { mode: _ } => {
420 None
422 }
423 Self::BulkEdit {
424 old_tree,
425 new_tree,
426 old_cursors,
427 new_cursors,
428 description,
429 } => {
430 Some(Self::BulkEdit {
433 old_tree: new_tree.clone(),
434 new_tree: old_tree.clone(),
435 old_cursors: new_cursors.clone(),
436 new_cursors: old_cursors.clone(),
437 description: format!("Undo: {}", description),
438 })
439 }
440 _ => None,
442 }
443 }
444
445 pub fn modifies_buffer(&self) -> bool {
447 match self {
448 Self::Insert { .. } | Self::Delete { .. } | Self::BulkEdit { .. } => true,
449 Self::Batch { events, .. } => events.iter().any(|e| e.modifies_buffer()),
450 _ => false,
451 }
452 }
453
454 pub fn is_write_action(&self) -> bool {
467 match self {
468 Self::Insert { .. } | Self::Delete { .. } | Self::BulkEdit { .. } => true,
470
471 Self::AddCursor { .. } | Self::RemoveCursor { .. } => true,
473
474 Self::Batch { events, .. } => events.iter().any(|e| e.is_write_action()),
476
477 _ => false,
479 }
480 }
481
482 pub fn cursor_id(&self) -> Option<CursorId> {
484 match self {
485 Self::Insert { cursor_id, .. }
486 | Self::Delete { cursor_id, .. }
487 | Self::MoveCursor { cursor_id, .. }
488 | Self::AddCursor { cursor_id, .. }
489 | Self::RemoveCursor { cursor_id, .. } => Some(*cursor_id),
490 _ => None,
491 }
492 }
493}
494
495#[derive(Debug, Clone, Serialize, Deserialize)]
497pub struct LogEntry {
498 pub event: Event,
500
501 pub timestamp: u64,
503
504 pub description: Option<String>,
506}
507
508impl LogEntry {
509 pub fn new(event: Event) -> Self {
510 Self {
511 event,
512 timestamp: std::time::SystemTime::now()
513 .duration_since(std::time::UNIX_EPOCH)
514 .unwrap()
515 .as_millis() as u64,
516 description: None,
517 }
518 }
519
520 pub fn with_description(mut self, description: String) -> Self {
521 self.description = Some(description);
522 self
523 }
524}
525
526#[derive(Debug, Clone)]
528pub struct Snapshot {
529 pub log_index: usize,
531
532 pub buffer_state: (),
535
536 pub cursor_positions: Vec<(CursorId, usize, Option<usize>)>,
538}
539
540pub struct EventLog {
542 entries: Vec<LogEntry>,
544
545 current_index: usize,
547
548 snapshots: Vec<Snapshot>,
550
551 snapshot_interval: usize,
553
554 #[cfg(feature = "runtime")]
556 stream_file: Option<std::fs::File>,
557
558 saved_at_index: Option<usize>,
561}
562
563impl EventLog {
564 pub fn new() -> Self {
566 Self {
567 entries: Vec::new(),
568 current_index: 0,
569 snapshots: Vec::new(),
570 snapshot_interval: 100,
571 #[cfg(feature = "runtime")]
572 stream_file: None,
573 saved_at_index: Some(0), }
575 }
576
577 pub fn mark_saved(&mut self) {
580 self.saved_at_index = Some(self.current_index);
581 }
582
583 pub fn is_at_saved_position(&self) -> bool {
587 match self.saved_at_index {
588 None => false,
589 Some(saved_idx) if saved_idx == self.current_index => true,
590 Some(saved_idx) => {
591 let (start, end) = if saved_idx < self.current_index {
594 (saved_idx, self.current_index)
595 } else {
596 (self.current_index, saved_idx)
597 };
598
599 self.entries[start..end]
601 .iter()
602 .all(|entry| !entry.event.modifies_buffer())
603 }
604 }
605 }
606
607 #[cfg(feature = "runtime")]
609 pub fn enable_streaming<P: AsRef<std::path::Path>>(&mut self, path: P) -> std::io::Result<()> {
610 use std::io::Write;
611
612 let mut file = std::fs::OpenOptions::new()
613 .create(true)
614 .write(true)
615 .truncate(true)
616 .open(path)?;
617
618 writeln!(file, "# Event Log Stream")?;
620 writeln!(file, "# Started at: {}", chrono::Local::now())?;
621 writeln!(file, "# Format: JSON Lines (one event per line)")?;
622 writeln!(file, "#")?;
623
624 self.stream_file = Some(file);
625 Ok(())
626 }
627
628 #[cfg(feature = "runtime")]
630 pub fn disable_streaming(&mut self) {
631 self.stream_file = None;
632 }
633
634 #[cfg(feature = "runtime")]
636 pub fn log_render_state(
637 &mut self,
638 cursor_pos: usize,
639 screen_cursor_x: u16,
640 screen_cursor_y: u16,
641 buffer_len: usize,
642 ) {
643 if let Some(ref mut file) = self.stream_file {
644 use std::io::Write;
645
646 let render_info = serde_json::json!({
647 "type": "render",
648 "timestamp": chrono::Local::now().to_rfc3339(),
649 "cursor_position": cursor_pos,
650 "screen_cursor": {"x": screen_cursor_x, "y": screen_cursor_y},
651 "buffer_length": buffer_len,
652 });
653
654 if let Err(e) = writeln!(file, "{render_info}") {
655 tracing::trace!("Warning: Failed to write render info to stream: {e}");
656 }
657 if let Err(e) = file.flush() {
658 tracing::trace!("Warning: Failed to flush event stream: {e}");
659 }
660 }
661 }
662
663 #[cfg(feature = "runtime")]
665 pub fn log_keystroke(&mut self, key_code: &str, modifiers: &str) {
666 if let Some(ref mut file) = self.stream_file {
667 use std::io::Write;
668
669 let keystroke_info = serde_json::json!({
670 "type": "keystroke",
671 "timestamp": chrono::Local::now().to_rfc3339(),
672 "key": key_code,
673 "modifiers": modifiers,
674 });
675
676 if let Err(e) = writeln!(file, "{keystroke_info}") {
677 tracing::trace!("Warning: Failed to write keystroke to stream: {e}");
678 }
679 if let Err(e) = file.flush() {
680 tracing::trace!("Warning: Failed to flush event stream: {e}");
681 }
682 }
683 }
684
685 pub fn append(&mut self, event: Event) -> usize {
687 if self.current_index < self.entries.len() {
689 self.entries.truncate(self.current_index);
690
691 if let Some(saved_idx) = self.saved_at_index {
693 if saved_idx > self.current_index {
694 self.saved_at_index = None;
695 }
696 }
697 }
698
699 #[cfg(feature = "runtime")]
701 if let Some(ref mut file) = self.stream_file {
702 use std::io::Write;
703
704 let stream_entry = serde_json::json!({
705 "index": self.entries.len(),
706 "timestamp": chrono::Local::now().to_rfc3339(),
707 "event": event,
708 });
709
710 if let Err(e) = writeln!(file, "{stream_entry}") {
712 tracing::trace!("Warning: Failed to write to event stream: {e}");
713 }
714 if let Err(e) = file.flush() {
715 tracing::trace!("Warning: Failed to flush event stream: {e}");
716 }
717 }
718
719 let entry = LogEntry::new(event);
720 self.entries.push(entry);
721 self.current_index = self.entries.len();
722
723 if self.entries.len().is_multiple_of(self.snapshot_interval) {
725 }
728
729 self.current_index - 1
730 }
731
732 pub fn current_index(&self) -> usize {
734 self.current_index
735 }
736
737 pub fn len(&self) -> usize {
739 self.entries.len()
740 }
741
742 pub fn is_empty(&self) -> bool {
744 self.entries.is_empty()
745 }
746
747 pub fn can_undo(&self) -> bool {
749 self.current_index > 0
750 }
751
752 pub fn can_redo(&self) -> bool {
754 self.current_index < self.entries.len()
755 }
756
757 pub fn undo(&mut self) -> Vec<Event> {
761 let mut inverse_events = Vec::new();
762 let mut found_write_action = false;
763
764 while self.can_undo() && !found_write_action {
766 self.current_index -= 1;
767 let event = &self.entries[self.current_index].event;
768
769 if event.is_write_action() {
771 found_write_action = true;
772 }
773
774 if let Some(inverse) = event.inverse() {
776 inverse_events.push(inverse);
777 }
778 }
780
781 inverse_events
782 }
783
784 pub fn redo(&mut self) -> Vec<Event> {
788 let mut events = Vec::new();
789 let mut found_write_action = false;
790
791 while self.can_redo() {
793 let event = self.entries[self.current_index].event.clone();
794
795 if found_write_action && event.is_write_action() {
797 break;
799 }
800
801 self.current_index += 1;
802
803 if event.is_write_action() {
805 found_write_action = true;
806 }
807
808 events.push(event);
809 }
810
811 events
812 }
813
814 pub fn entries(&self) -> &[LogEntry] {
816 &self.entries
817 }
818
819 pub fn range(&self, range: Range<usize>) -> &[LogEntry] {
821 &self.entries[range]
822 }
823
824 pub fn last_event(&self) -> Option<&Event> {
826 if self.current_index > 0 {
827 Some(&self.entries[self.current_index - 1].event)
828 } else {
829 None
830 }
831 }
832
833 pub fn clear(&mut self) {
835 self.entries.clear();
836 self.current_index = 0;
837 self.snapshots.clear();
838 }
839
840 pub fn save_to_file(&self, path: &std::path::Path) -> std::io::Result<()> {
842 use std::io::Write;
843 let file = std::fs::File::create(path)?;
844 let mut writer = std::io::BufWriter::new(file);
845
846 for entry in &self.entries {
847 let json = serde_json::to_string(entry)?;
848 writeln!(writer, "{json}")?;
849 }
850
851 Ok(())
852 }
853
854 pub fn load_from_file(path: &std::path::Path) -> std::io::Result<Self> {
856 use std::io::BufRead;
857 let file = std::fs::File::open(path)?;
858 let reader = std::io::BufReader::new(file);
859
860 let mut log = Self::new();
861
862 for line in reader.lines() {
863 let line = line?;
864 if line.trim().is_empty() {
865 continue;
866 }
867 let entry: LogEntry = serde_json::from_str(&line)?;
868 log.entries.push(entry);
869 }
870
871 log.current_index = log.entries.len();
872
873 Ok(log)
874 }
875
876 pub fn set_snapshot_interval(&mut self, interval: usize) {
878 self.snapshot_interval = interval;
879 }
880}
881
882impl Default for EventLog {
883 fn default() -> Self {
884 Self::new()
885 }
886}
887
888#[cfg(test)]
889mod tests {
890 use super::*;
891
892 #[cfg(test)]
894 mod property_tests {
895 use super::*;
896 use proptest::prelude::*;
897
898 fn arb_event() -> impl Strategy<Value = Event> {
900 prop_oneof![
901 (0usize..1000, ".{1,50}").prop_map(|(pos, text)| Event::Insert {
903 position: pos,
904 text,
905 cursor_id: CursorId(0),
906 }),
907 (0usize..1000, 1usize..50).prop_map(|(pos, len)| Event::Delete {
909 range: pos..pos + len,
910 deleted_text: "x".repeat(len),
911 cursor_id: CursorId(0),
912 }),
913 ]
914 }
915
916 proptest! {
917 #[test]
919 fn event_inverse_property(event in arb_event()) {
920 if let Some(inverse) = event.inverse() {
921 if let Some(double_inverse) = inverse.inverse() {
924 match (&event, &double_inverse) {
925 (Event::Insert { position: p1, text: t1, .. },
926 Event::Insert { position: p2, text: t2, .. }) => {
927 assert_eq!(p1, p2);
928 assert_eq!(t1, t2);
929 }
930 (Event::Delete { range: r1, deleted_text: dt1, .. },
931 Event::Delete { range: r2, deleted_text: dt2, .. }) => {
932 assert_eq!(r1, r2);
933 assert_eq!(dt1, dt2);
934 }
935 _ => {}
936 }
937 }
938 }
939 }
940
941 #[test]
943 fn undo_redo_inverse(events in prop::collection::vec(arb_event(), 1..20)) {
944 let mut log = EventLog::new();
945
946 for event in &events {
948 log.append(event.clone());
949 }
950
951 let after_append = log.current_index();
952
953 let mut undo_count = 0;
955 while log.can_undo() {
956 log.undo();
957 undo_count += 1;
958 }
959
960 assert_eq!(log.current_index(), 0);
961 assert_eq!(undo_count, events.len());
962
963 let mut redo_count = 0;
965 while log.can_redo() {
966 log.redo();
967 redo_count += 1;
968 }
969
970 assert_eq!(log.current_index(), after_append);
971 assert_eq!(redo_count, events.len());
972 }
973
974 #[test]
976 fn append_after_undo_truncates(
977 initial_events in prop::collection::vec(arb_event(), 2..10),
978 new_event in arb_event()
979 ) {
980 let mut log = EventLog::new();
981
982 for event in &initial_events {
983 log.append(event.clone());
984 }
985
986 log.undo();
988 let index_after_undo = log.current_index();
989
990 log.append(new_event);
992
993 assert_eq!(log.current_index(), index_after_undo + 1);
995 assert!(!log.can_redo());
996 }
997 }
998 }
999
1000 #[test]
1001 fn test_event_log_append() {
1002 let mut log = EventLog::new();
1003 let event = Event::Insert {
1004 position: 0,
1005 text: "hello".to_string(),
1006 cursor_id: CursorId(0),
1007 };
1008
1009 let index = log.append(event);
1010 assert_eq!(index, 0);
1011 assert_eq!(log.current_index(), 1);
1012 assert_eq!(log.entries().len(), 1);
1013 }
1014
1015 #[test]
1016 fn test_undo_redo() {
1017 let mut log = EventLog::new();
1018
1019 log.append(Event::Insert {
1020 position: 0,
1021 text: "a".to_string(),
1022 cursor_id: CursorId(0),
1023 });
1024
1025 log.append(Event::Insert {
1026 position: 1,
1027 text: "b".to_string(),
1028 cursor_id: CursorId(0),
1029 });
1030
1031 assert_eq!(log.current_index(), 2);
1032 assert!(log.can_undo());
1033 assert!(!log.can_redo());
1034
1035 log.undo();
1036 assert_eq!(log.current_index(), 1);
1037 assert!(log.can_undo());
1038 assert!(log.can_redo());
1039
1040 log.undo();
1041 assert_eq!(log.current_index(), 0);
1042 assert!(!log.can_undo());
1043 assert!(log.can_redo());
1044
1045 log.redo();
1046 assert_eq!(log.current_index(), 1);
1047 }
1048
1049 #[test]
1050 fn test_event_inverse() {
1051 let insert = Event::Insert {
1052 position: 5,
1053 text: "hello".to_string(),
1054 cursor_id: CursorId(0),
1055 };
1056
1057 let inverse = insert.inverse().unwrap();
1058 match inverse {
1059 Event::Delete {
1060 range,
1061 deleted_text,
1062 ..
1063 } => {
1064 assert_eq!(range, 5..10);
1065 assert_eq!(deleted_text, "hello");
1066 }
1067 _ => panic!("Expected Delete event"),
1068 }
1069 }
1070
1071 #[test]
1072 fn test_truncate_on_new_event_after_undo() {
1073 let mut log = EventLog::new();
1074
1075 log.append(Event::Insert {
1076 position: 0,
1077 text: "a".to_string(),
1078 cursor_id: CursorId(0),
1079 });
1080
1081 log.append(Event::Insert {
1082 position: 1,
1083 text: "b".to_string(),
1084 cursor_id: CursorId(0),
1085 });
1086
1087 log.undo();
1088 assert_eq!(log.entries().len(), 2);
1089
1090 log.append(Event::Insert {
1092 position: 1,
1093 text: "c".to_string(),
1094 cursor_id: CursorId(0),
1095 });
1096
1097 assert_eq!(log.entries().len(), 2);
1098 assert_eq!(log.current_index(), 2);
1099 }
1100
1101 #[test]
1110 fn test_is_at_saved_position_after_truncate() {
1111 let mut log = EventLog::new();
1112
1113 for i in 0..150 {
1115 log.append(Event::Insert {
1116 position: i,
1117 text: "x".to_string(),
1118 cursor_id: CursorId(0),
1119 });
1120 }
1121
1122 assert_eq!(log.entries().len(), 150);
1123 assert_eq!(log.current_index(), 150);
1124
1125 log.mark_saved();
1127
1128 for _ in 0..30 {
1130 log.undo();
1131 }
1132 assert_eq!(log.current_index(), 120);
1133 assert_eq!(log.entries().len(), 150);
1134
1135 log.append(Event::Insert {
1137 position: 0,
1138 text: "NEW".to_string(),
1139 cursor_id: CursorId(0),
1140 });
1141
1142 assert_eq!(log.entries().len(), 121);
1144 assert_eq!(log.current_index(), 121);
1145
1146 let result = log.is_at_saved_position();
1150
1151 assert!(
1153 !result,
1154 "Should not be at saved position after undo + new edit"
1155 );
1156 }
1157}