1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct TextRange {
9 pub start: usize,
11 pub end: usize,
13}
14
15impl TextRange {
16 pub fn new(start: usize, end: usize) -> Self {
18 Self { start, end }
19 }
20
21 pub fn len(&self) -> usize {
23 self.end.saturating_sub(self.start)
24 }
25
26 pub fn is_empty(&self) -> bool {
28 self.len() == 0
29 }
30}
31
32#[derive(Debug, Clone)]
37pub struct DocumentChange {
38 pub range: TextRange,
40 pub new_text: String,
42 pub old_text: String,
44}
45
46impl DocumentChange {
47 pub fn new(range: TextRange, new_text: impl Into<String>, old_text: impl Into<String>) -> Self {
49 Self {
50 range,
51 new_text: new_text.into(),
52 old_text: old_text.into(),
53 }
54 }
55
56 pub fn apply(&self, source: &str) -> String {
61 let mut result =
62 String::with_capacity(source.len() - self.range.len() + self.new_text.len());
63 result.push_str(&source[..self.range.start]);
64 result.push_str(&self.new_text);
65 result.push_str(&source[self.range.end..]);
66 result
67 }
68
69 pub fn inverse(&self) -> Self {
74 Self {
75 range: TextRange::new(self.range.start, self.range.start + self.new_text.len()),
76 new_text: self.old_text.clone(),
77 old_text: self.new_text.clone(),
78 }
79 }
80}
81
82#[derive(Debug, Default)]
88pub struct DocumentHistory {
89 past: Vec<DocumentChange>,
90 future: Vec<DocumentChange>,
91}
92
93impl DocumentHistory {
94 pub fn new() -> Self {
96 Self::default()
97 }
98
99 pub fn push(&mut self, change: DocumentChange) {
101 self.past.push(change);
102 self.future.clear();
103 }
104
105 pub fn undo(&mut self) -> Option<&DocumentChange> {
110 let change = self.past.pop()?;
111 self.future.push(change.inverse());
112 self.future.last()
115 }
116
117 pub fn redo(&mut self) -> Option<&DocumentChange> {
120 let change = self.future.pop()?;
121 self.past.push(change.inverse());
124 self.past.last()
125 }
126
127 pub fn can_undo(&self) -> bool {
129 !self.past.is_empty()
130 }
131
132 pub fn can_redo(&self) -> bool {
134 !self.future.is_empty()
135 }
136}
137
138#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
149 fn test_text_range_len() {
150 assert_eq!(TextRange::new(2, 7).len(), 5);
151 assert_eq!(TextRange::new(3, 3).len(), 0);
152 }
153
154 #[test]
155 fn test_text_range_is_empty() {
156 assert!(TextRange::new(5, 5).is_empty());
157 assert!(!TextRange::new(5, 6).is_empty());
158 }
159
160 #[test]
163 fn test_apply_replaces_range() {
164 let source = "Hello, world!";
165 let change = DocumentChange::new(TextRange::new(7, 12), "Rust", "world");
166 assert_eq!(change.apply(source), "Hello, Rust!");
167 }
168
169 #[test]
170 fn test_apply_insert_at_start() {
171 let source = "world";
172 let change = DocumentChange::new(TextRange::new(0, 0), "Hello ", "");
173 assert_eq!(change.apply(source), "Hello world");
174 }
175
176 #[test]
177 fn test_apply_delete_range() {
178 let source = "Hello, world!";
179 let change = DocumentChange::new(TextRange::new(5, 12), "", ", world");
180 assert_eq!(change.apply(source), "Hello!");
181 }
182
183 #[test]
186 fn test_inverse_undoes_change() {
187 let source = "Hello, world!";
188 let change = DocumentChange::new(TextRange::new(7, 12), "Rust", "world");
189 let modified = change.apply(source);
190 assert_eq!(modified, "Hello, Rust!");
191
192 let undo = change.inverse();
193 let restored = undo.apply(&modified);
194 assert_eq!(restored, source);
195 }
196
197 #[test]
198 fn test_inverse_of_inverse_is_original() {
199 let c = DocumentChange::new(TextRange::new(0, 5), "new", "old_t");
200 let inv = c.inverse();
201 let inv_inv = inv.inverse();
202 assert_eq!(inv_inv.range.start, c.range.start);
203 assert_eq!(inv_inv.new_text, c.new_text);
204 assert_eq!(inv_inv.old_text, c.old_text);
205 }
206
207 #[test]
210 fn test_history_can_undo_after_push() {
211 let mut h = DocumentHistory::new();
212 assert!(!h.can_undo());
213 h.push(DocumentChange::new(TextRange::new(0, 0), "x", ""));
214 assert!(h.can_undo());
215 }
216
217 #[test]
218 fn test_history_undo_returns_inverse() {
219 let source = "Hello, world!";
220 let change = DocumentChange::new(TextRange::new(7, 12), "Rust", "world");
221 let modified = change.apply(source);
222
223 let mut h = DocumentHistory::new();
224 h.push(change);
225
226 let undo_change = h.undo().expect("should have undo");
227 let restored = undo_change.apply(&modified);
228 assert_eq!(restored, source);
229 }
230
231 #[test]
232 fn test_history_undo_enables_redo() {
233 let mut h = DocumentHistory::new();
234 h.push(DocumentChange::new(TextRange::new(0, 1), "b", "a"));
235 assert!(!h.can_redo());
236 h.undo();
237 assert!(h.can_redo());
238 assert!(!h.can_undo());
239 }
240
241 #[test]
242 fn test_history_redo_re_applies() {
243 let source = "a";
244 let change = DocumentChange::new(TextRange::new(0, 1), "b", "a");
245 let modified = change.apply(source); let mut h = DocumentHistory::new();
248 h.push(change);
249
250 let undo = h.undo().expect("undo").clone();
251 let after_undo = undo.apply(&modified); assert_eq!(after_undo, "a");
253
254 let redo = h.redo().expect("redo").clone();
255 let after_redo = redo.apply(&after_undo);
256 assert_eq!(after_redo, "b");
257 }
258
259 #[test]
260 fn test_push_clears_redo_stack() {
261 let mut h = DocumentHistory::new();
262 h.push(DocumentChange::new(TextRange::new(0, 1), "b", "a"));
263 h.undo();
264 assert!(h.can_redo());
265
266 h.push(DocumentChange::new(TextRange::new(0, 1), "c", "a"));
268 assert!(!h.can_redo());
269 }
270
271 #[test]
272 fn test_multi_step_undo_redo() {
273 let mut source = String::from("a");
274 let mut h = DocumentHistory::new();
275
276 let c1 = DocumentChange::new(TextRange::new(1, 1), "b", "");
277 source = c1.apply(&source); h.push(c1);
279
280 let c2 = DocumentChange::new(TextRange::new(2, 2), "c", "");
281 source = c2.apply(&source); h.push(c2);
283
284 let u2 = h.undo().expect("undo c2").clone();
286 source = u2.apply(&source); assert_eq!(source, "ab");
288
289 let u1 = h.undo().expect("undo c1").clone();
291 source = u1.apply(&source); assert_eq!(source, "a");
293
294 let r1 = h.redo().expect("redo c1").clone();
296 source = r1.apply(&source); assert_eq!(source, "ab");
298
299 let r2 = h.redo().expect("redo c2").clone();
301 source = r2.apply(&source); assert_eq!(source, "abc");
303 }
304}