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