1use std::path::PathBuf;
2
3use crate::edit::{Caret, Edit};
4use crate::error::DocError;
5use crate::files::Files;
6
7pub const DEPTH: usize = 256;
8pub const GAP: f64 = 1.0;
9
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct Changed {
12 pub label: String,
13 pub files: Vec<PathBuf>,
14 pub caret: Option<Caret>,
15}
16
17impl Changed {
18 pub fn is_empty(&self) -> bool {
19 self.files.is_empty()
20 }
21}
22
23#[derive(Debug)]
24pub struct History {
25 undo: Vec<Box<dyn Edit>>,
26 redo: Vec<Box<dyn Edit>>,
27 depth: usize,
28 typed_at: Option<f64>,
29}
30
31impl Default for History {
32 fn default() -> History {
33 History::new(DEPTH)
34 }
35}
36
37pub fn run(files: &mut Files, edit: &mut dyn Edit) -> Result<Changed, DocError> {
38 let touched = edit.files();
39 let before: Vec<Option<u64>> = touched.iter().map(|p| files.generation_of(p)).collect();
40 files.attempt(&touched, |files| edit.apply(files))?;
41 let changed = touched
42 .into_iter()
43 .zip(before)
44 .filter(|(path, generation)| files.generation_of(path) != *generation)
45 .map(|(path, _)| path)
46 .collect();
47 Ok(Changed {
48 label: edit.label().to_string(),
49 files: changed,
50 caret: edit.caret(),
51 })
52}
53
54fn keep(entries: &mut Vec<Box<dyn Edit>>, files: &Files) -> usize {
55 let mut scratch = files.clone();
56 let before = entries.len();
57 let mut kept: Vec<Box<dyn Edit>> = entries
58 .drain(..)
59 .rev()
60 .filter(|entry| run(&mut scratch, entry.inverse().as_mut()).is_ok())
61 .collect();
62 kept.reverse();
63 *entries = kept;
64 before - entries.len()
65}
66
67impl History {
68 pub fn new(depth: usize) -> History {
69 History {
70 undo: Vec::new(),
71 redo: Vec::new(),
72 depth: depth.max(1),
73 typed_at: None,
74 }
75 }
76
77 pub fn depth(&self) -> usize {
78 self.depth
79 }
80
81 pub fn set_depth(&mut self, depth: usize) {
82 self.depth = depth.max(1);
83 self.trim();
84 }
85
86 fn trim(&mut self) {
87 if self.undo.len() > self.depth {
88 let extra = self.undo.len() - self.depth;
89 self.undo.drain(..extra);
90 }
91 }
92
93 pub fn apply(&mut self, files: &mut Files, edit: impl Edit) -> Result<Changed, DocError> {
94 self.record(files, Box::new(edit), None)
95 }
96
97 pub fn apply_boxed(
98 &mut self,
99 files: &mut Files,
100 edit: Box<dyn Edit>,
101 ) -> Result<Changed, DocError> {
102 self.record(files, edit, None)
103 }
104
105 pub fn typed(
106 &mut self,
107 files: &mut Files,
108 edit: impl Edit,
109 now: f64,
110 gap: f64,
111 ) -> Result<Changed, DocError> {
112 self.record(files, Box::new(edit), Some((now, gap)))
113 }
114
115 fn record(
116 &mut self,
117 files: &mut Files,
118 mut edit: Box<dyn Edit>,
119 typed: Option<(f64, f64)>,
120 ) -> Result<Changed, DocError> {
121 let changed = run(files, edit.as_mut())?;
122 if changed.is_empty() {
123 return Ok(changed);
124 }
125 self.redo.clear();
126 let merged = match (typed, self.typed_at, self.undo.last()) {
127 (Some((now, gap)), Some(at), Some(top)) if now >= at && now - at <= gap => {
128 top.merge(edit.as_ref())
129 }
130 _ => None,
131 };
132 match merged {
133 Some(merged) => {
134 let top = self.undo.len() - 1;
135 self.undo[top] = merged;
136 }
137 None => {
138 self.undo.push(edit);
139 self.trim();
140 }
141 }
142 self.typed_at = typed.map(|(now, _)| now);
143 Ok(changed)
144 }
145
146 pub fn undo(&mut self, files: &mut Files) -> Result<Option<Changed>, DocError> {
147 self.step(files, true)
148 }
149
150 pub fn redo(&mut self, files: &mut Files) -> Result<Option<Changed>, DocError> {
151 self.step(files, false)
152 }
153
154 fn step(&mut self, files: &mut Files, undo: bool) -> Result<Option<Changed>, DocError> {
155 let (from, to) = if undo {
156 (&mut self.undo, &mut self.redo)
157 } else {
158 (&mut self.redo, &mut self.undo)
159 };
160 let Some(top) = from.last() else {
161 return Ok(None);
162 };
163 let mut back = top.inverse();
164 let changed = run(files, back.as_mut())?;
165 from.pop();
166 to.push(back);
167 self.typed_at = None;
168 self.trim();
169 Ok(Some(changed))
170 }
171
172 pub fn seal(&mut self) {
173 self.typed_at = None;
174 }
175
176 pub fn clear(&mut self) {
177 self.undo.clear();
178 self.redo.clear();
179 self.typed_at = None;
180 }
181
182 pub fn can_undo(&self) -> bool {
183 !self.undo.is_empty()
184 }
185
186 pub fn can_redo(&self) -> bool {
187 !self.redo.is_empty()
188 }
189
190 pub fn undo_len(&self) -> usize {
191 self.undo.len()
192 }
193
194 pub fn redo_len(&self) -> usize {
195 self.redo.len()
196 }
197
198 pub fn undo_label(&self) -> Option<&str> {
199 self.undo.last().map(|edit| edit.label())
200 }
201
202 pub fn redo_label(&self) -> Option<&str> {
203 self.redo.last().map(|edit| edit.label())
204 }
205
206 pub fn undo_labels(&self) -> Vec<&str> {
207 self.undo.iter().map(|edit| edit.label()).collect()
208 }
209
210 pub fn redo_labels(&self) -> Vec<&str> {
211 self.redo.iter().map(|edit| edit.label()).collect()
212 }
213
214 pub fn prune(&mut self, files: &Files) -> usize {
215 self.typed_at = None;
216 keep(&mut self.undo, files) + keep(&mut self.redo, files)
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use std::path::Path;
223
224 use super::*;
225 use crate::patch::{TomlPatch, parse_path};
226 use crate::splice::TextSplice;
227 use toml_edit::value;
228
229 const FILE: &str = "a.toml";
230
231 fn files(text: &str) -> Files {
232 let mut files = Files::new();
233 files.open(FILE, text);
234 files
235 }
236
237 fn text(files: &Files) -> &str {
238 files.text(Path::new(FILE)).unwrap()
239 }
240
241 fn set(key: &str, n: i64) -> TomlPatch {
242 TomlPatch::set(FILE, parse_path(key).unwrap(), value(n)).labelled(key)
243 }
244
245 fn typed(at: usize, what: &str) -> TextSplice {
246 TextSplice::new(FILE, at..at, "", what)
247 .with_carets(at..at, at + what.len()..at + what.len())
248 }
249
250 #[test]
251 fn a_new_step_clears_the_redo_side() {
252 let mut f = files("a = 0\n");
253 let mut history = History::default();
254 history.apply(&mut f, set("one", 1)).unwrap();
255 history.apply(&mut f, set("two", 2)).unwrap();
256 history.undo(&mut f).unwrap().unwrap();
257 assert_eq!(
258 (history.undo_labels(), history.redo_labels()),
259 (vec!["one"], vec!["two"])
260 );
261 assert_eq!(history.redo_label(), Some("two"));
262 history.apply(&mut f, set("three", 3)).unwrap();
263 assert_eq!(
264 (history.undo_labels(), history.redo_labels()),
265 (vec!["one", "three"], vec![])
266 );
267 }
268
269 #[test]
270 fn undo_and_redo_say_what_changed() {
271 let mut f = files("a = 0\n");
272 let mut history = History::default();
273 history.apply(&mut f, set("a", 1)).unwrap();
274 let changed = history.undo(&mut f).unwrap().unwrap();
275 assert_eq!(changed.label, "a");
276 assert_eq!(changed.files, vec![PathBuf::from(FILE)]);
277 assert_eq!(text(&f), "a = 0\n");
278 assert!(history.undo(&mut f).unwrap().is_none());
279 history.redo(&mut f).unwrap().unwrap();
280 assert_eq!(text(&f), "a = 1\n");
281 assert!(history.redo(&mut f).unwrap().is_none());
282 }
283
284 #[test]
285 fn the_stack_keeps_its_last_steps() {
286 let mut f = files("a = 0\n");
287 let mut history = History::new(8);
288 for step in 1..=11 {
289 history
290 .apply(&mut f, set("a", step).labelled(step.to_string()))
291 .unwrap();
292 }
293 assert_eq!(history.undo_len(), 8);
294 assert_eq!(history.undo_labels()[0], "4");
295 history.set_depth(2);
296 assert_eq!(history.undo_labels(), vec!["10", "11"]);
297 }
298
299 #[test]
300 fn a_refused_edit_records_nothing_and_a_no_op_is_not_a_step() {
301 let mut f = files("a = 0\n");
302 let mut history = History::default();
303 let generation = f.generation();
304 assert!(
305 history
306 .apply(&mut f, TomlPatch::unset(FILE, parse_path("b").unwrap()))
307 .is_err()
308 );
309 assert!(history.apply(&mut f, set("a", 0)).unwrap().is_empty());
310 assert_eq!(history.undo_len(), 0);
311 assert_eq!(f.generation(), generation);
312 }
313
314 #[test]
315 fn typing_merges_within_the_gap_and_splits_across_it_or_a_jump() {
316 let mut f = files("a = 0\n");
317 let mut history = History::default();
318 history.typed(&mut f, typed(0, "#"), 0.0, GAP).unwrap();
319 history.typed(&mut f, typed(1, " "), 0.5, GAP).unwrap();
320 history.typed(&mut f, typed(2, "x"), 1.4, GAP).unwrap();
321 assert_eq!(history.undo_len(), 1);
322 history.typed(&mut f, typed(3, "y"), 3.0, GAP).unwrap();
323 assert_eq!(history.undo_len(), 2);
324 history.typed(&mut f, typed(0, "z"), 3.1, GAP).unwrap();
325 assert_eq!(history.undo_len(), 3);
326 assert_eq!(text(&f), "z# xya = 0\n");
327 let changed = history.undo(&mut f).unwrap().unwrap();
328 assert_eq!(changed.caret.unwrap().selection, 0..0);
329 history.undo(&mut f).unwrap();
330 assert_eq!(text(&f), "# xa = 0\n");
331 let changed = history.undo(&mut f).unwrap().unwrap();
332 assert_eq!(changed.caret.unwrap().selection, 0..0);
333 assert_eq!(text(&f), "a = 0\n");
334 }
335
336 #[test]
337 fn a_patch_or_an_undo_ends_the_typing_run() {
338 let mut f = files("a = 0\n");
339 let mut history = History::default();
340 history.typed(&mut f, typed(0, "#"), 0.0, GAP).unwrap();
341 history.apply(&mut f, set("b", 1)).unwrap();
342 history.typed(&mut f, typed(1, " "), 0.1, GAP).unwrap();
343 assert_eq!(history.undo_len(), 3);
344 history.undo(&mut f).unwrap();
345 history.typed(&mut f, typed(1, "!"), 0.2, GAP).unwrap();
346 assert_eq!(history.undo_len(), 3);
347 history.seal();
348 history.typed(&mut f, typed(2, "!"), 0.3, GAP).unwrap();
349 assert_eq!(history.undo_len(), 4);
350 }
351
352 #[test]
353 fn pruning_drops_only_the_steps_that_no_longer_apply() {
354 let mut f = files("a = 0\n[t]\nx = 0\n");
355 let mut history = History::default();
356 history.apply(&mut f, set("t.x", 1)).unwrap();
357 history.typed(&mut f, typed(0, "# x\n"), 0.0, GAP).unwrap();
358 history.apply(&mut f, set("a", 1)).unwrap();
359 f.reload(Path::new(FILE), "a = 1\n").unwrap();
360 assert_eq!(history.prune(&f), 2);
361 assert_eq!(history.undo_labels(), vec!["a"]);
362 history.undo(&mut f).unwrap();
363 assert_eq!(text(&f), "a = 0\n");
364 }
365}