strop_core/buffer/
seed.rs1use serde::{Deserialize, Serialize};
6
7use super::Buffer;
8use crate::history::History;
9use crate::id::BufferRevision;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct BufferSeed {
16 pub text: String,
17 pub revision: BufferRevision,
18 pub history: History,
19 #[serde(with = "crate::path_serde::option")]
20 pub path: Option<std::path::PathBuf>,
21 pub name: Option<String>,
22 pub dirty: bool,
23 pub readonly: bool,
24 pub disk_stamp: Option<std::time::SystemTime>,
25 #[serde(with = "crate::path_serde::option")]
26 pub file_identity: Option<std::path::PathBuf>,
27}
28
29impl Buffer {
30 pub fn seed(&self) -> BufferSeed {
31 BufferSeed {
32 text: self.rope.to_string(),
33 revision: self.revision(),
34 history: self.history.clone(),
35 path: self.path.clone(),
36 name: self.name.clone(),
37 dirty: self.dirty,
38 readonly: self.readonly,
39 disk_stamp: self.disk_stamp,
40 file_identity: self.file_identity.clone(),
41 }
42 }
43}
44
45#[derive(Serialize)]
50pub struct BufferWitness {
51 pub text_digest: String,
52 pub text_bytes: usize,
53 pub revision: BufferRevision,
54 pub history: History,
55 #[serde(with = "crate::path_serde::option")]
56 pub path: Option<std::path::PathBuf>,
57 pub name: Option<String>,
58 pub dirty: bool,
59 pub readonly: bool,
60 pub disk_stamp: Option<std::time::SystemTime>,
61 #[serde(with = "crate::path_serde::option")]
62 pub file_identity: Option<std::path::PathBuf>,
63}
64
65impl Buffer {
66 pub fn witness(&self) -> BufferWitness {
69 BufferWitness {
70 text_digest: self.text_digest(),
71 text_bytes: self.len_bytes(),
72 revision: self.revision(),
73 history: self.history.clone(),
74 path: self.path.clone(),
75 name: self.name.clone(),
76 dirty: self.dirty,
77 readonly: self.readonly,
78 disk_stamp: self.disk_stamp,
79 file_identity: self.file_identity.clone(),
80 }
81 }
82
83 pub fn text_digest(&self) -> String {
85 use sha2::{Digest, Sha256};
86 use std::fmt::Write as _;
87 let mut hasher = Sha256::new();
88 for chunk in self.rope.chunks() {
89 hasher.update(chunk.as_bytes());
90 }
91 let digest: [u8; 32] = hasher.finalize().into();
92 let mut text = String::with_capacity(64);
93 let _ = digest
94 .iter()
95 .try_for_each(|byte| write!(text, "{byte:02x}"));
96 text
97 }
98}
99
100impl BufferSeed {
101 pub fn into_buffer(self) -> Result<Buffer, crate::history::HistoryError> {
104 let mut buffer = Buffer::from_text(&self.text);
105 buffer.restore_history(self.history)?;
106 buffer.epoch = self.revision.get();
107 buffer.path = self.path;
108 buffer.name = self.name;
109 buffer.dirty = self.dirty;
110 buffer.readonly = self.readonly;
111 buffer.disk_stamp = self.disk_stamp;
112 buffer.file_identity = self.file_identity;
113 Ok(buffer)
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn seed_round_trips_content_history_and_disk_baseline() {
123 let mut buffer = Buffer::from_text("line\nline two\n");
124 buffer.begin_undo_group();
125 buffer
126 .edit()
127 .replace(crate::Range::charwise(5, 8), "XY")
128 .unwrap();
129 buffer.commit_undo_group();
130 buffer.dirty = true;
131 buffer.disk_stamp = Some(std::time::UNIX_EPOCH);
132 let seed = buffer.seed();
133 let mut restored = seed.into_buffer().unwrap();
134 assert_eq!(restored.text().to_string(), "line\nXYe two\n");
135 restored.undo().unwrap();
137 assert_eq!(restored.text().to_string(), "line\nline two\n");
138 }
139
140 #[test]
141 fn history_that_disagrees_with_text_is_rejected() {
142 let mut buffer = Buffer::from_text("abc\n");
143 buffer.begin_undo_group();
144 buffer
145 .edit()
146 .replace(crate::Range::charwise(0, 1), "z")
147 .unwrap();
148 buffer.commit_undo_group();
149 let mut seed = buffer.seed();
150 seed.text = "different bytes\n".into();
151 assert!(seed.into_buffer().is_err());
152 }
153
154 #[test]
155 fn witness_digest_tracks_text_exactly() {
156 let buffer = Buffer::from_text("abc\n");
157 let digest = buffer.text_digest();
158 assert_eq!(Buffer::from_text("abc\n").text_digest(), digest);
160 let mut edited = Buffer::from_text("abc\n");
161 edited
162 .edit()
163 .replace(crate::Range::charwise(0, 1), "z")
164 .unwrap();
165 assert_ne!(edited.text_digest(), digest);
166 let witness = edited.witness();
168 assert_eq!(witness.text_bytes, 4);
169 assert_eq!(witness.text_digest.len(), 64);
170 assert_eq!(witness.revision, edited.revision());
171 }
172}