fresh/model/
event.rs

1use crate::model::piece_tree::PieceTree;
2use crate::view::overlay::{OverlayHandle, OverlayNamespace};
3pub use fresh_core::{BufferId, CursorId, SplitDirection, SplitId};
4use serde::{Deserialize, Serialize};
5use std::ops::Range;
6use std::sync::Arc;
7
8/// Core event types representing all possible state changes
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub enum Event {
11    /// Insert text at a position
12    Insert {
13        position: usize,
14        text: String,
15        cursor_id: CursorId,
16    },
17
18    /// Delete a range of text
19    Delete {
20        range: Range<usize>,
21        deleted_text: String,
22        cursor_id: CursorId,
23    },
24
25    /// Move a cursor to a new position
26    MoveCursor {
27        cursor_id: CursorId,
28        old_position: usize,
29        new_position: usize,
30        old_anchor: Option<usize>,
31        new_anchor: Option<usize>,
32        old_sticky_column: usize,
33        new_sticky_column: usize,
34    },
35
36    /// Add a new cursor
37    AddCursor {
38        cursor_id: CursorId,
39        position: usize,
40        anchor: Option<usize>,
41    },
42
43    /// Remove a cursor (stores cursor state for undo)
44    RemoveCursor {
45        cursor_id: CursorId,
46        position: usize,
47        anchor: Option<usize>,
48    },
49
50    /// Scroll the viewport
51    Scroll {
52        line_offset: isize,
53    },
54
55    /// Set viewport to specific position
56    SetViewport {
57        top_line: usize,
58    },
59
60    /// Center the viewport on the cursor
61    Recenter,
62
63    /// Set the anchor (selection start) for a cursor
64    SetAnchor {
65        cursor_id: CursorId,
66        position: usize,
67    },
68
69    /// Clear the anchor and reset deselect_on_move for a cursor
70    /// Used to cancel Emacs mark mode
71    ClearAnchor {
72        cursor_id: CursorId,
73    },
74
75    /// Change mode (if implementing modal editing)
76    ChangeMode {
77        mode: String,
78    },
79
80    /// Add an overlay (for decorations like underlines, highlights)
81    AddOverlay {
82        namespace: Option<OverlayNamespace>,
83        range: Range<usize>,
84        face: OverlayFace,
85        priority: i32,
86        message: Option<String>,
87        /// Whether to extend the overlay's background to the end of the visual line
88        extend_to_line_end: bool,
89    },
90
91    /// Remove overlay by handle
92    RemoveOverlay {
93        handle: OverlayHandle,
94    },
95
96    /// Remove all overlays in a range
97    RemoveOverlaysInRange {
98        range: Range<usize>,
99    },
100
101    /// Clear all overlays in a namespace
102    ClearNamespace {
103        namespace: OverlayNamespace,
104    },
105
106    /// Clear all overlays
107    ClearOverlays,
108
109    /// Show a popup
110    ShowPopup {
111        popup: PopupData,
112    },
113
114    /// Hide the topmost popup
115    HidePopup,
116
117    /// Clear all popups
118    ClearPopups,
119
120    /// Navigate popup selection (for list popups)
121    PopupSelectNext,
122    PopupSelectPrev,
123    PopupPageDown,
124    PopupPageUp,
125
126    /// Margin events
127    /// Add a margin annotation
128    AddMarginAnnotation {
129        line: usize,
130        position: MarginPositionData,
131        content: MarginContentData,
132        annotation_id: Option<String>,
133    },
134
135    /// Remove margin annotation by ID
136    RemoveMarginAnnotation {
137        annotation_id: String,
138    },
139
140    /// Remove all margin annotations at a specific line
141    RemoveMarginAnnotationsAtLine {
142        line: usize,
143        position: MarginPositionData,
144    },
145
146    /// Clear all margin annotations in a position
147    ClearMarginPosition {
148        position: MarginPositionData,
149    },
150
151    /// Clear all margin annotations
152    ClearMargins,
153
154    /// Enable/disable line numbers
155    SetLineNumbers {
156        enabled: bool,
157    },
158
159    /// Split view events
160    /// Split the active pane
161    SplitPane {
162        direction: SplitDirection,
163        new_buffer_id: BufferId,
164        ratio: f32,
165    },
166
167    /// Close a split pane
168    CloseSplit {
169        split_id: SplitId,
170    },
171
172    /// Set the active split pane
173    SetActiveSplit {
174        split_id: SplitId,
175    },
176
177    /// Adjust the split ratio
178    AdjustSplitRatio {
179        split_id: SplitId,
180        delta: f32,
181    },
182
183    /// Navigate to next split
184    NextSplit,
185
186    /// Navigate to previous split
187    PrevSplit,
188
189    /// Batch of events that should be undone/redone atomically
190    /// Used for multi-cursor operations where all cursors perform the same action
191    Batch {
192        events: Vec<Event>,
193        description: String,
194    },
195
196    /// Efficient bulk edit that stores tree snapshots for O(1) undo/redo
197    /// Used for multi-cursor operations, toggle comment, indent/dedent, etc.
198    /// This avoids O(n²) complexity by applying all edits in a single tree pass.
199    ///
200    /// Key insight: PieceTree uses Arc<PieceTreeNode> (persistent data structure),
201    /// so storing trees for undo/redo is O(1) (Arc clone), not O(n) (content copy).
202    BulkEdit {
203        /// Tree state before the edit (for undo)
204        #[serde(skip)]
205        old_tree: Option<Arc<PieceTree>>,
206        /// Tree state after the edit (for redo)
207        #[serde(skip)]
208        new_tree: Option<Arc<PieceTree>>,
209        /// Cursor states before the edit
210        old_cursors: Vec<(CursorId, usize, Option<usize>)>,
211        /// Cursor states after the edit
212        new_cursors: Vec<(CursorId, usize, Option<usize>)>,
213        /// Human-readable description
214        description: String,
215    },
216}
217
218/// Overlay face data for events (must be serializable)
219#[derive(Debug, Clone, Serialize, Deserialize)]
220pub enum OverlayFace {
221    Underline {
222        color: (u8, u8, u8), // RGB color
223        style: UnderlineStyle,
224    },
225    Background {
226        color: (u8, u8, u8),
227    },
228    Foreground {
229        color: (u8, u8, u8),
230    },
231    /// Full style with multiple attributes
232    Style {
233        color: (u8, u8, u8),
234        bg_color: Option<(u8, u8, u8)>,
235        bold: bool,
236        italic: bool,
237        underline: bool,
238    },
239}
240
241/// Underline style for overlays
242#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
243pub enum UnderlineStyle {
244    Straight,
245    Wavy,
246    Dotted,
247    Dashed,
248}
249
250/// Popup data for events (must be serializable)
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct PopupData {
253    pub title: Option<String>,
254    /// Optional description text shown above the content
255    #[serde(default)]
256    pub description: Option<String>,
257    #[serde(default)]
258    pub transient: bool,
259    pub content: PopupContentData,
260    pub position: PopupPositionData,
261    pub width: u16,
262    pub max_height: u16,
263    pub bordered: bool,
264}
265
266/// Popup content for events
267#[derive(Debug, Clone, Serialize, Deserialize)]
268pub enum PopupContentData {
269    Text(Vec<String>),
270    List {
271        items: Vec<PopupListItemData>,
272        selected: usize,
273    },
274}
275
276/// Popup list item for events
277#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct PopupListItemData {
279    pub text: String,
280    pub detail: Option<String>,
281    pub icon: Option<String>,
282    pub data: Option<String>,
283}
284
285/// Popup position for events
286#[derive(Debug, Clone, Serialize, Deserialize)]
287pub enum PopupPositionData {
288    AtCursor,
289    BelowCursor,
290    AboveCursor,
291    Fixed { x: u16, y: u16 },
292    Centered,
293    BottomRight,
294}
295
296/// Margin position for events
297#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
298pub enum MarginPositionData {
299    Left,
300    Right,
301}
302
303/// Margin content for events
304#[derive(Debug, Clone, Serialize, Deserialize)]
305pub enum MarginContentData {
306    Text(String),
307    Symbol {
308        text: String,
309        color: Option<(u8, u8, u8)>, // RGB color
310    },
311    Empty,
312}
313
314impl Event {
315    /// Returns the inverse event for undo functionality
316    /// Uses UNDO_SENTINEL cursor_id to avoid moving the cursor during undo
317    pub fn inverse(&self) -> Option<Self> {
318        match self {
319            Self::Insert { position, text, .. } => {
320                let range = *position..(position + text.len());
321                Some(Self::Delete {
322                    range,
323                    deleted_text: text.clone(),
324                    cursor_id: CursorId::UNDO_SENTINEL,
325                })
326            }
327            Self::Delete {
328                range,
329                deleted_text,
330                ..
331            } => Some(Self::Insert {
332                position: range.start,
333                text: deleted_text.clone(),
334                cursor_id: CursorId::UNDO_SENTINEL,
335            }),
336            Self::Batch {
337                events,
338                description,
339            } => {
340                // Invert all events in the batch in reverse order
341                let inverted: Option<Vec<Self>> =
342                    events.iter().rev().map(|e| e.inverse()).collect();
343
344                inverted.map(|inverted_events| Self::Batch {
345                    events: inverted_events,
346                    description: format!("Undo: {}", description),
347                })
348            }
349            Self::AddCursor {
350                cursor_id,
351                position,
352                anchor,
353            } => {
354                // To undo adding a cursor, we remove it (store its state for redo)
355                Some(Self::RemoveCursor {
356                    cursor_id: *cursor_id,
357                    position: *position,
358                    anchor: *anchor,
359                })
360            }
361            Self::RemoveCursor {
362                cursor_id,
363                position,
364                anchor,
365            } => {
366                // To undo removing a cursor, we add it back
367                Some(Self::AddCursor {
368                    cursor_id: *cursor_id,
369                    position: *position,
370                    anchor: *anchor,
371                })
372            }
373            Self::MoveCursor {
374                cursor_id,
375                old_position,
376                new_position,
377                old_anchor,
378                new_anchor,
379                old_sticky_column,
380                new_sticky_column,
381            } => {
382                // Invert by swapping old and new positions
383                Some(Self::MoveCursor {
384                    cursor_id: *cursor_id,
385                    old_position: *new_position,
386                    new_position: *old_position,
387                    old_anchor: *new_anchor,
388                    new_anchor: *old_anchor,
389                    old_sticky_column: *new_sticky_column,
390                    new_sticky_column: *old_sticky_column,
391                })
392            }
393            Self::AddOverlay { .. } => {
394                // Overlays are ephemeral decorations, not undoable
395                None
396            }
397            Self::RemoveOverlay { .. } => {
398                // Overlays are ephemeral decorations, not undoable
399                None
400            }
401            Self::ClearNamespace { .. } => {
402                // Overlays are ephemeral decorations, not undoable
403                None
404            }
405            Self::Scroll { line_offset } => Some(Self::Scroll {
406                line_offset: -line_offset,
407            }),
408            Self::SetViewport { top_line: _ } => {
409                // Can't invert without knowing old top_line
410                None
411            }
412            Self::ChangeMode { mode: _ } => {
413                // Can't invert without knowing old mode
414                None
415            }
416            Self::BulkEdit {
417                old_tree,
418                new_tree,
419                old_cursors,
420                new_cursors,
421                description,
422            } => {
423                // Inverse swaps both trees and cursor states
424                // For undo: old becomes new, new becomes old
425                Some(Self::BulkEdit {
426                    old_tree: new_tree.clone(),
427                    new_tree: old_tree.clone(),
428                    old_cursors: new_cursors.clone(),
429                    new_cursors: old_cursors.clone(),
430                    description: format!("Undo: {}", description),
431                })
432            }
433            // Other events (popups, margins, splits, etc.) are not automatically invertible
434            _ => None,
435        }
436    }
437
438    /// Returns true if this event modifies the buffer content
439    pub fn modifies_buffer(&self) -> bool {
440        match self {
441            Self::Insert { .. } | Self::Delete { .. } | Self::BulkEdit { .. } => true,
442            Self::Batch { events, .. } => events.iter().any(|e| e.modifies_buffer()),
443            _ => false,
444        }
445    }
446
447    /// Returns true if this event is a write action (modifies state in a way that should be undoable)
448    /// Returns false for readonly actions like cursor movement, scrolling, viewport changes, etc.
449    ///
450    /// Write actions include:
451    /// - Buffer modifications (Insert, Delete)
452    /// - Cursor structure changes (AddCursor, RemoveCursor)
453    /// - Batches containing write actions
454    ///
455    /// Readonly actions include:
456    /// - Cursor movement (MoveCursor)
457    /// - Scrolling and viewport changes (Scroll, SetViewport)
458    /// - UI events (overlays, popups, margins, mode changes, etc.)
459    pub fn is_write_action(&self) -> bool {
460        match self {
461            // Buffer modifications are write actions
462            Self::Insert { .. } | Self::Delete { .. } | Self::BulkEdit { .. } => true,
463
464            // Adding/removing cursors are write actions (structural changes)
465            Self::AddCursor { .. } | Self::RemoveCursor { .. } => true,
466
467            // Batches are write actions if they contain any write actions
468            Self::Batch { events, .. } => events.iter().any(|e| e.is_write_action()),
469
470            // All other events are readonly (movement, scrolling, UI, etc.)
471            _ => false,
472        }
473    }
474
475    /// Returns the cursor ID associated with this event, if any
476    pub fn cursor_id(&self) -> Option<CursorId> {
477        match self {
478            Self::Insert { cursor_id, .. }
479            | Self::Delete { cursor_id, .. }
480            | Self::MoveCursor { cursor_id, .. }
481            | Self::AddCursor { cursor_id, .. }
482            | Self::RemoveCursor { cursor_id, .. } => Some(*cursor_id),
483            _ => None,
484        }
485    }
486}
487
488/// A log entry containing an event and metadata
489#[derive(Debug, Clone, Serialize, Deserialize)]
490pub struct LogEntry {
491    /// The event
492    pub event: Event,
493
494    /// Timestamp when the event occurred (milliseconds since epoch)
495    pub timestamp: u64,
496
497    /// Optional description for debugging
498    pub description: Option<String>,
499}
500
501impl LogEntry {
502    pub fn new(event: Event) -> Self {
503        Self {
504            event,
505            timestamp: std::time::SystemTime::now()
506                .duration_since(std::time::UNIX_EPOCH)
507                .unwrap()
508                .as_millis() as u64,
509            description: None,
510        }
511    }
512
513    pub fn with_description(mut self, description: String) -> Self {
514        self.description = Some(description);
515        self
516    }
517}
518
519/// Snapshot of editor state for fast undo/redo
520#[derive(Debug, Clone)]
521pub struct Snapshot {
522    /// Index in the event log where this snapshot was taken
523    pub log_index: usize,
524
525    /// Buffer content at this point (stored as ChunkTree reference)
526    /// For now we'll use a placeholder - will be filled in when we implement Buffer
527    pub buffer_state: (),
528
529    /// Cursor positions at this point
530    pub cursor_positions: Vec<(CursorId, usize, Option<usize>)>,
531}
532
533/// The event log - append-only log of all events
534pub struct EventLog {
535    /// All logged events
536    entries: Vec<LogEntry>,
537
538    /// Current position in the log (for undo/redo)
539    current_index: usize,
540
541    /// Periodic snapshots for fast seeking
542    snapshots: Vec<Snapshot>,
543
544    /// How often to create snapshots (every N events)
545    snapshot_interval: usize,
546
547    /// Optional file for streaming events to disk
548    stream_file: Option<std::fs::File>,
549
550    /// Index at which the buffer was last saved (for tracking modified status)
551    /// When current_index equals saved_at_index, the buffer is not modified
552    saved_at_index: Option<usize>,
553}
554
555impl EventLog {
556    /// Create a new empty event log
557    pub fn new() -> Self {
558        Self {
559            entries: Vec::new(),
560            current_index: 0,
561            snapshots: Vec::new(),
562            snapshot_interval: 100,
563            stream_file: None,
564            saved_at_index: Some(0), // New buffer starts at "saved" state (index 0)
565        }
566    }
567
568    /// Mark the current position as the saved point
569    /// Call this when the buffer is saved to disk
570    pub fn mark_saved(&mut self) {
571        self.saved_at_index = Some(self.current_index);
572    }
573
574    /// Check if the buffer is at the saved position (not modified)
575    /// Returns true if we're at the saved position OR if all events between
576    /// saved_at_index and current_index are readonly (don't modify buffer content)
577    pub fn is_at_saved_position(&self) -> bool {
578        match self.saved_at_index {
579            None => false,
580            Some(saved_idx) if saved_idx == self.current_index => true,
581            Some(saved_idx) => {
582                // Check if all events between saved position and current position
583                // are readonly (don't modify buffer content)
584                let (start, end) = if saved_idx < self.current_index {
585                    (saved_idx, self.current_index)
586                } else {
587                    (self.current_index, saved_idx)
588                };
589
590                // All events in range [start, end) must be readonly
591                self.entries[start..end]
592                    .iter()
593                    .all(|entry| !entry.event.modifies_buffer())
594            }
595        }
596    }
597
598    /// Enable streaming events to a file
599    pub fn enable_streaming<P: AsRef<std::path::Path>>(&mut self, path: P) -> std::io::Result<()> {
600        use std::io::Write;
601
602        let mut file = std::fs::OpenOptions::new()
603            .create(true)
604            .write(true)
605            .truncate(true)
606            .open(path)?;
607
608        // Write header
609        writeln!(file, "# Event Log Stream")?;
610        writeln!(file, "# Started at: {}", chrono::Local::now())?;
611        writeln!(file, "# Format: JSON Lines (one event per line)")?;
612        writeln!(file, "#")?;
613
614        self.stream_file = Some(file);
615        Ok(())
616    }
617
618    /// Disable streaming
619    pub fn disable_streaming(&mut self) {
620        self.stream_file = None;
621    }
622
623    /// Log rendering state (for debugging)
624    pub fn log_render_state(
625        &mut self,
626        cursor_pos: usize,
627        screen_cursor_x: u16,
628        screen_cursor_y: u16,
629        buffer_len: usize,
630    ) {
631        if let Some(ref mut file) = self.stream_file {
632            use std::io::Write;
633
634            let render_info = serde_json::json!({
635                "type": "render",
636                "timestamp": chrono::Local::now().to_rfc3339(),
637                "cursor_position": cursor_pos,
638                "screen_cursor": {"x": screen_cursor_x, "y": screen_cursor_y},
639                "buffer_length": buffer_len,
640            });
641
642            if let Err(e) = writeln!(file, "{render_info}") {
643                tracing::trace!("Warning: Failed to write render info to stream: {e}");
644            }
645            if let Err(e) = file.flush() {
646                tracing::trace!("Warning: Failed to flush event stream: {e}");
647            }
648        }
649    }
650
651    /// Log keystroke (for debugging)
652    pub fn log_keystroke(&mut self, key_code: &str, modifiers: &str) {
653        if let Some(ref mut file) = self.stream_file {
654            use std::io::Write;
655
656            let keystroke_info = serde_json::json!({
657                "type": "keystroke",
658                "timestamp": chrono::Local::now().to_rfc3339(),
659                "key": key_code,
660                "modifiers": modifiers,
661            });
662
663            if let Err(e) = writeln!(file, "{keystroke_info}") {
664                tracing::trace!("Warning: Failed to write keystroke to stream: {e}");
665            }
666            if let Err(e) = file.flush() {
667                tracing::trace!("Warning: Failed to flush event stream: {e}");
668            }
669        }
670    }
671
672    /// Append an event to the log
673    pub fn append(&mut self, event: Event) -> usize {
674        // If we're not at the end, truncate future events
675        if self.current_index < self.entries.len() {
676            self.entries.truncate(self.current_index);
677
678            // Invalidate saved_at_index if it pointed to a truncated entry
679            if let Some(saved_idx) = self.saved_at_index {
680                if saved_idx > self.current_index {
681                    self.saved_at_index = None;
682                }
683            }
684        }
685
686        // Stream event to file if enabled
687        if let Some(ref mut file) = self.stream_file {
688            use std::io::Write;
689
690            let stream_entry = serde_json::json!({
691                "index": self.entries.len(),
692                "timestamp": chrono::Local::now().to_rfc3339(),
693                "event": event,
694            });
695
696            // Write JSON line and flush immediately for real-time logging
697            if let Err(e) = writeln!(file, "{stream_entry}") {
698                tracing::trace!("Warning: Failed to write to event stream: {e}");
699            }
700            if let Err(e) = file.flush() {
701                tracing::trace!("Warning: Failed to flush event stream: {e}");
702            }
703        }
704
705        let entry = LogEntry::new(event);
706        self.entries.push(entry);
707        self.current_index = self.entries.len();
708
709        // Check if we should create a snapshot
710        if self.entries.len().is_multiple_of(self.snapshot_interval) {
711            // Snapshot creation will be implemented when we have Buffer
712            // For now, just track that we'd create one here
713        }
714
715        self.current_index - 1
716    }
717
718    /// Get the current event index
719    pub fn current_index(&self) -> usize {
720        self.current_index
721    }
722
723    /// Get the number of events in the log
724    pub fn len(&self) -> usize {
725        self.entries.len()
726    }
727
728    /// Check if the event log is empty
729    pub fn is_empty(&self) -> bool {
730        self.entries.is_empty()
731    }
732
733    /// Can we undo?
734    pub fn can_undo(&self) -> bool {
735        self.current_index > 0
736    }
737
738    /// Can we redo?
739    pub fn can_redo(&self) -> bool {
740        self.current_index < self.entries.len()
741    }
742
743    /// Move back through events (for undo)
744    /// Collects all events up to and including the first write action, returns their inverses
745    /// This processes readonly events (like scrolling) and stops at write events (like Insert/Delete)
746    pub fn undo(&mut self) -> Vec<Event> {
747        let mut inverse_events = Vec::new();
748        let mut found_write_action = false;
749
750        // Keep moving backward until we find a write action
751        while self.can_undo() && !found_write_action {
752            self.current_index -= 1;
753            let event = &self.entries[self.current_index].event;
754
755            // Check if this is a write action - we'll stop after processing it
756            if event.is_write_action() {
757                found_write_action = true;
758            }
759
760            // Try to get the inverse of this event
761            if let Some(inverse) = event.inverse() {
762                inverse_events.push(inverse);
763            }
764            // If no inverse exists (like MoveCursor), we just skip it
765        }
766
767        inverse_events
768    }
769
770    /// Move forward through events (for redo)
771    /// Collects the first write action plus all readonly events after it (until next write action)
772    /// This processes readonly events (like scrolling) with write events (like Insert/Delete)
773    pub fn redo(&mut self) -> Vec<Event> {
774        let mut events = Vec::new();
775        let mut found_write_action = false;
776
777        // Keep moving forward to collect write action and subsequent readonly events
778        while self.can_redo() {
779            let event = self.entries[self.current_index].event.clone();
780
781            // If we've already found a write action and this is another write action, stop
782            if found_write_action && event.is_write_action() {
783                // Don't include this event, it's the next write action
784                break;
785            }
786
787            self.current_index += 1;
788
789            // Mark if we found a write action
790            if event.is_write_action() {
791                found_write_action = true;
792            }
793
794            events.push(event);
795        }
796
797        events
798    }
799
800    /// Get all events from the log
801    pub fn entries(&self) -> &[LogEntry] {
802        &self.entries
803    }
804
805    /// Get events in a range
806    pub fn range(&self, range: Range<usize>) -> &[LogEntry] {
807        &self.entries[range]
808    }
809
810    /// Get the most recent event
811    pub fn last_event(&self) -> Option<&Event> {
812        if self.current_index > 0 {
813            Some(&self.entries[self.current_index - 1].event)
814        } else {
815            None
816        }
817    }
818
819    /// Clear all events (for testing or reset)
820    pub fn clear(&mut self) {
821        self.entries.clear();
822        self.current_index = 0;
823        self.snapshots.clear();
824    }
825
826    /// Save event log to JSON Lines format
827    pub fn save_to_file(&self, path: &std::path::Path) -> std::io::Result<()> {
828        use std::io::Write;
829        let file = std::fs::File::create(path)?;
830        let mut writer = std::io::BufWriter::new(file);
831
832        for entry in &self.entries {
833            let json = serde_json::to_string(entry)?;
834            writeln!(writer, "{json}")?;
835        }
836
837        Ok(())
838    }
839
840    /// Load event log from JSON Lines format
841    pub fn load_from_file(path: &std::path::Path) -> std::io::Result<Self> {
842        use std::io::BufRead;
843        let file = std::fs::File::open(path)?;
844        let reader = std::io::BufReader::new(file);
845
846        let mut log = Self::new();
847
848        for line in reader.lines() {
849            let line = line?;
850            if line.trim().is_empty() {
851                continue;
852            }
853            let entry: LogEntry = serde_json::from_str(&line)?;
854            log.entries.push(entry);
855        }
856
857        log.current_index = log.entries.len();
858
859        Ok(log)
860    }
861
862    /// Set snapshot interval
863    pub fn set_snapshot_interval(&mut self, interval: usize) {
864        self.snapshot_interval = interval;
865    }
866}
867
868impl Default for EventLog {
869    fn default() -> Self {
870        Self::new()
871    }
872}
873
874#[cfg(test)]
875mod tests {
876    use super::*;
877
878    // Property-based tests
879    #[cfg(test)]
880    mod property_tests {
881        use super::*;
882        use proptest::prelude::*;
883
884        /// Helper to generate random events
885        fn arb_event() -> impl Strategy<Value = Event> {
886            prop_oneof![
887                // Insert events
888                (0usize..1000, ".{1,50}").prop_map(|(pos, text)| Event::Insert {
889                    position: pos,
890                    text,
891                    cursor_id: CursorId(0),
892                }),
893                // Delete events
894                (0usize..1000, 1usize..50).prop_map(|(pos, len)| Event::Delete {
895                    range: pos..pos + len,
896                    deleted_text: "x".repeat(len),
897                    cursor_id: CursorId(0),
898                }),
899            ]
900        }
901
902        proptest! {
903            /// Event inverse should be truly inverse
904            #[test]
905            fn event_inverse_property(event in arb_event()) {
906                if let Some(inverse) = event.inverse() {
907                    // The inverse of an inverse should be the original
908                    // (for commutative operations)
909                    if let Some(double_inverse) = inverse.inverse() {
910                        match (&event, &double_inverse) {
911                            (Event::Insert { position: p1, text: t1, .. },
912                             Event::Insert { position: p2, text: t2, .. }) => {
913                                assert_eq!(p1, p2);
914                                assert_eq!(t1, t2);
915                            }
916                            (Event::Delete { range: r1, deleted_text: dt1, .. },
917                             Event::Delete { range: r2, deleted_text: dt2, .. }) => {
918                                assert_eq!(r1, r2);
919                                assert_eq!(dt1, dt2);
920                            }
921                            _ => {}
922                        }
923                    }
924                }
925            }
926
927            /// Undo then redo should restore state
928            #[test]
929            fn undo_redo_inverse(events in prop::collection::vec(arb_event(), 1..20)) {
930                let mut log = EventLog::new();
931
932                // Append all events
933                for event in &events {
934                    log.append(event.clone());
935                }
936
937                let after_append = log.current_index();
938
939                // Undo all
940                let mut undo_count = 0;
941                while log.can_undo() {
942                    log.undo();
943                    undo_count += 1;
944                }
945
946                assert_eq!(log.current_index(), 0);
947                assert_eq!(undo_count, events.len());
948
949                // Redo all
950                let mut redo_count = 0;
951                while log.can_redo() {
952                    log.redo();
953                    redo_count += 1;
954                }
955
956                assert_eq!(log.current_index(), after_append);
957                assert_eq!(redo_count, events.len());
958            }
959
960            /// Appending after undo should truncate redo history
961            #[test]
962            fn append_after_undo_truncates(
963                initial_events in prop::collection::vec(arb_event(), 2..10),
964                new_event in arb_event()
965            ) {
966                let mut log = EventLog::new();
967
968                for event in &initial_events {
969                    log.append(event.clone());
970                }
971
972                // Undo at least one
973                log.undo();
974                let index_after_undo = log.current_index();
975
976                // Append new event
977                log.append(new_event);
978
979                // Should not be able to redo past the new event
980                assert_eq!(log.current_index(), index_after_undo + 1);
981                assert!(!log.can_redo());
982            }
983        }
984    }
985
986    #[test]
987    fn test_event_log_append() {
988        let mut log = EventLog::new();
989        let event = Event::Insert {
990            position: 0,
991            text: "hello".to_string(),
992            cursor_id: CursorId(0),
993        };
994
995        let index = log.append(event);
996        assert_eq!(index, 0);
997        assert_eq!(log.current_index(), 1);
998        assert_eq!(log.entries().len(), 1);
999    }
1000
1001    #[test]
1002    fn test_undo_redo() {
1003        let mut log = EventLog::new();
1004
1005        log.append(Event::Insert {
1006            position: 0,
1007            text: "a".to_string(),
1008            cursor_id: CursorId(0),
1009        });
1010
1011        log.append(Event::Insert {
1012            position: 1,
1013            text: "b".to_string(),
1014            cursor_id: CursorId(0),
1015        });
1016
1017        assert_eq!(log.current_index(), 2);
1018        assert!(log.can_undo());
1019        assert!(!log.can_redo());
1020
1021        log.undo();
1022        assert_eq!(log.current_index(), 1);
1023        assert!(log.can_undo());
1024        assert!(log.can_redo());
1025
1026        log.undo();
1027        assert_eq!(log.current_index(), 0);
1028        assert!(!log.can_undo());
1029        assert!(log.can_redo());
1030
1031        log.redo();
1032        assert_eq!(log.current_index(), 1);
1033    }
1034
1035    #[test]
1036    fn test_event_inverse() {
1037        let insert = Event::Insert {
1038            position: 5,
1039            text: "hello".to_string(),
1040            cursor_id: CursorId(0),
1041        };
1042
1043        let inverse = insert.inverse().unwrap();
1044        match inverse {
1045            Event::Delete {
1046                range,
1047                deleted_text,
1048                ..
1049            } => {
1050                assert_eq!(range, 5..10);
1051                assert_eq!(deleted_text, "hello");
1052            }
1053            _ => panic!("Expected Delete event"),
1054        }
1055    }
1056
1057    #[test]
1058    fn test_truncate_on_new_event_after_undo() {
1059        let mut log = EventLog::new();
1060
1061        log.append(Event::Insert {
1062            position: 0,
1063            text: "a".to_string(),
1064            cursor_id: CursorId(0),
1065        });
1066
1067        log.append(Event::Insert {
1068            position: 1,
1069            text: "b".to_string(),
1070            cursor_id: CursorId(0),
1071        });
1072
1073        log.undo();
1074        assert_eq!(log.entries().len(), 2);
1075
1076        // Adding new event should truncate the future
1077        log.append(Event::Insert {
1078            position: 1,
1079            text: "c".to_string(),
1080            cursor_id: CursorId(0),
1081        });
1082
1083        assert_eq!(log.entries().len(), 2);
1084        assert_eq!(log.current_index(), 2);
1085    }
1086
1087    /// Test for v0.1.77 panic: "range end index 148 out of range for slice of length 125"
1088    ///
1089    /// The bug occurs when:
1090    /// 1. Make many changes (entries grows)
1091    /// 2. mark_saved() sets saved_at_index to current position
1092    /// 3. Undo several times
1093    /// 4. Make new changes - this truncates entries but saved_at_index stays
1094    /// 5. is_at_saved_position() panics on out-of-bounds slice access
1095    #[test]
1096    fn test_is_at_saved_position_after_truncate() {
1097        let mut log = EventLog::new();
1098
1099        // Step 1: Make many changes
1100        for i in 0..150 {
1101            log.append(Event::Insert {
1102                position: i,
1103                text: "x".to_string(),
1104                cursor_id: CursorId(0),
1105            });
1106        }
1107
1108        assert_eq!(log.entries().len(), 150);
1109        assert_eq!(log.current_index(), 150);
1110
1111        // Step 2: Save - this sets saved_at_index = 150
1112        log.mark_saved();
1113
1114        // Step 3: Undo 30 times - current_index goes to 120, but entries stay at 150
1115        for _ in 0..30 {
1116            log.undo();
1117        }
1118        assert_eq!(log.current_index(), 120);
1119        assert_eq!(log.entries().len(), 150);
1120
1121        // Step 4: Make new changes - this truncates entries to 120, then adds new
1122        log.append(Event::Insert {
1123            position: 0,
1124            text: "NEW".to_string(),
1125            cursor_id: CursorId(0),
1126        });
1127
1128        // Now entries.len() = 121, but saved_at_index = 150
1129        assert_eq!(log.entries().len(), 121);
1130        assert_eq!(log.current_index(), 121);
1131
1132        // Step 5: Call is_at_saved_position() - THIS PANICS in v0.1.77
1133        // The code does: self.entries[start..end] where end = saved_at_index = 150
1134        // but entries.len() = 121, so 150 is out of bounds
1135        let result = log.is_at_saved_position();
1136
1137        // After fix: should return false (we're not at saved position, we branched off)
1138        assert!(
1139            !result,
1140            "Should not be at saved position after undo + new edit"
1141        );
1142    }
1143}