Skip to main content

reovim_kernel/block/
snapshot.rs

1//! Buffer state capture and restore.
2//!
3//! Snapshots capture the complete state of a buffer at a point in time,
4//! enabling restore operations for recovery or checkpointing.
5//!
6//! # Cursor Isolation (#471)
7//!
8//! Cursor position is passed explicitly to `capture()` and returned from
9//! `restore()`. This is because cursor is now per-client state in Window,
10//! not Buffer. The caller (session layer) is responsible for getting cursor
11//! from Window before capture and setting it back to Window after restore.
12//!
13//! # Structural Sharing (#711)
14//!
15//! Snapshots store a rope clone, which is O(1) via `Arc` sharing.
16//! The snapshot shares unchanged text nodes with the original buffer.
17//!
18//! # Design Principle
19//!
20//! Following the kernel purity principle, this module provides pure Rust
21//! data structures and accessors. File serialization (JSON, etc.) is
22//! handled by the driver layer (`server/lib/drivers/vfs/`).
23
24use {
25    crate::mm::{Buffer, BufferId, Position, Rope},
26    std::time::SystemTime,
27};
28
29/// A captured buffer state.
30///
31/// Snapshots are used for:
32/// - Safety checkpoints before complex operations
33/// - Crash recovery
34/// - Cursor restoration when reopening files
35///
36/// # Cursor Isolation (#471)
37///
38/// Cursor is passed explicitly to `capture()` and returned from `restore()`.
39/// The caller manages cursor via Window, not Buffer.
40///
41/// # File Storage
42///
43/// This struct provides accessors for all data needed by the driver
44/// layer to serialize/deserialize to files. The kernel does not handle
45/// serialization directly.
46///
47/// # Example
48///
49/// ```
50/// use reovim_kernel::api::v1::*;
51///
52/// // Create a buffer with some content
53/// let mut buffer = Buffer::from_string("Hello, World!");
54/// let cursor = Position::new(0, 0); // Get from Window in real usage
55///
56/// // Capture state with explicit cursor
57/// let snapshot = Snapshot::capture(&buffer, cursor);
58///
59/// // Later, restore the state
60/// let restored_cursor = snapshot.restore(&mut buffer);
61/// // Caller sets restored_cursor to Window
62/// ```
63#[derive(Debug, Clone)]
64pub struct Snapshot {
65    /// Text content at time of capture (rope clone, O(1)).
66    text: Rope,
67    /// Cursor position at time of capture.
68    cursor: Position,
69    /// Buffer ID (for validation on restore).
70    buffer_id: BufferId,
71    /// When snapshot was taken.
72    timestamp: SystemTime,
73}
74
75impl Snapshot {
76    /// Capture the current state of a buffer.
77    ///
78    /// Cursor position must be passed explicitly - get it from Window.
79    /// This is O(1) — the rope is cloned via `Arc` sharing.
80    #[must_use]
81    pub fn capture(buffer: &Buffer, cursor: Position) -> Self {
82        Self {
83            text: buffer.clone_rope(),
84            cursor,
85            buffer_id: buffer.id(),
86            timestamp: SystemTime::now(),
87        }
88    }
89
90    /// Create a snapshot from components.
91    ///
92    /// This is useful for restoring from serialized data.
93    #[must_use]
94    pub fn from_parts(
95        lines: &[String],
96        cursor: Position,
97        buffer_id: BufferId,
98        timestamp: SystemTime,
99    ) -> Self {
100        let content = lines.join("\n");
101        let text = if content.is_empty() {
102            Rope::new()
103        } else {
104            Rope::from_str(&content)
105        };
106        Self {
107            text,
108            cursor,
109            buffer_id,
110            timestamp,
111        }
112    }
113
114    /// Restore a buffer to this snapshot's state.
115    ///
116    /// Returns the cursor position that should be set on Window.
117    /// The buffer ID is not changed.
118    pub fn restore(&self, buffer: &mut Buffer) -> Position {
119        // O(1) restore via rope clone (Arc sharing)
120        buffer.set_rope(self.text.clone());
121
122        // Return cursor position for caller to set on Window
123        self.cursor
124    }
125
126    /// Get the cursor position from this snapshot.
127    ///
128    /// Use this when reopening a file that already has its content
129    /// loaded - just get the cursor position to set on Window.
130    #[must_use]
131    pub const fn cursor(&self) -> Position {
132        self.cursor
133    }
134
135    // === Accessors for driver-layer serialization ===
136
137    /// Collect the captured lines as a `Vec<String>`.
138    ///
139    /// This allocates — prefer `line(idx)` for individual access.
140    #[must_use]
141    pub fn lines(&self) -> Vec<String> {
142        (0..self.text.line_count())
143            .filter_map(|i| self.text.line(i).map(String::from))
144            .collect()
145    }
146
147    // NOTE: cursor() method defined above (single accessor, no duplicate)
148
149    /// Get the buffer ID.
150    #[must_use]
151    pub const fn buffer_id(&self) -> BufferId {
152        self.buffer_id
153    }
154
155    /// Get the timestamp when snapshot was taken.
156    #[must_use]
157    pub const fn timestamp(&self) -> SystemTime {
158        self.timestamp
159    }
160
161    // === Utility Methods ===
162
163    /// Check if this snapshot matches a buffer's ID.
164    #[must_use]
165    pub fn matches_buffer(&self, buffer: &Buffer) -> bool {
166        self.buffer_id == buffer.id()
167    }
168
169    /// Get the total number of characters in the snapshot.
170    #[must_use]
171    pub fn char_count(&self) -> usize {
172        self.text.char_len()
173    }
174
175    /// Get the number of lines in the snapshot.
176    #[must_use]
177    pub fn line_count(&self) -> usize {
178        self.text.line_count()
179    }
180
181    /// Check if the snapshot is empty.
182    #[must_use]
183    pub fn is_empty(&self) -> bool {
184        self.text.is_empty()
185    }
186}