1mod validate;
9pub use validate::HistoryError;
10#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
13pub struct Edit {
14 pub at: usize,
15 pub text: String,
17 pub kind: EditKind,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
23pub struct EditRef {
24 pub revision: usize,
25 pub index: usize,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
29pub enum EditKind {
30 Insert,
31 Delete,
32}
33
34#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
35struct Revision {
36 parent: usize,
37 last_child: Option<usize>,
38 undo: Vec<Edit>,
40 redo: Vec<Edit>,
42}
43
44#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
45pub struct History {
46 revisions: Vec<Revision>,
48 current: usize,
49 pending: Option<(Vec<Edit>, Vec<Edit>)>,
51}
52
53impl Default for History {
54 fn default() -> Self {
57 Self {
58 revisions: vec![Revision {
59 parent: 0,
60 last_child: None,
61 undo: vec![],
62 redo: vec![],
63 }],
64 current: 0,
65 pending: None,
66 }
67 }
68}
69
70#[derive(Debug, Clone)]
72pub struct RevisionRow {
73 pub index: usize,
74 pub parent: usize,
75 pub depth: usize,
77 pub summary: String,
79 pub is_current: bool,
80 pub branches: bool,
83}
84
85#[derive(Clone, Copy)]
86pub(crate) enum HistoryAction {
87 Undo,
88 Redo,
89 Jump(usize),
90}
91
92impl History {
93 pub(crate) fn movement_cost(&self, action: HistoryAction) -> Option<usize> {
96 let pending = self.pending.as_ref().map_or(0, |(undo, _)| undo.len());
97 match action {
98 HistoryAction::Undo if pending > 0 => Some(pending),
99 HistoryAction::Undo => {
100 (self.current != 0).then(|| self.revisions[self.current].undo.len())
101 }
102 HistoryAction::Redo if pending > 0 => None,
103 HistoryAction::Redo => self.revisions[self.current]
104 .last_child
105 .map(|child| self.revisions[child].redo.len()),
106 HistoryAction::Jump(target) => {
107 if target >= self.revisions.len() {
108 return None;
109 }
110 let depth = |mut at: usize| {
111 let mut depth = 0;
112 while at != 0 {
113 depth += 1;
114 at = self.revisions[at].parent;
115 }
116 depth
117 };
118 let (mut from, mut to) = (self.current, target);
119 let (mut left, mut right) = (depth(from), depth(to));
120 let mut count = pending;
121 while from != to {
122 if left >= right {
123 count += self.revisions[from].undo.len();
124 from = self.revisions[from].parent;
125 left -= 1;
126 } else {
127 count += self.revisions[to].redo.len();
128 to = self.revisions[to].parent;
129 right -= 1;
130 }
131 }
132 Some(count)
133 }
134 }
135 }
136
137 pub(crate) fn navigate(&mut self, action: HistoryAction) -> Option<Vec<Edit>> {
138 self.commit();
139 match action {
140 HistoryAction::Undo => self.undo_ops(),
141 HistoryAction::Redo => self.redo_ops(),
142 HistoryAction::Jump(target) => self.ops_to(target),
143 }
144 }
145}
146
147impl History {
148 pub fn tree_rows(&self) -> Vec<RevisionRow> {
150 let mut depths = vec![0usize; self.revisions.len()];
151 for i in 1..self.revisions.len() {
152 depths[i] = depths[self.revisions[i].parent] + 1;
153 }
154 let mut out: Vec<RevisionRow> = (1..self.revisions.len())
155 .rev()
156 .map(|i| {
157 let rev = &self.revisions[i];
158 let first = rev.redo.first();
159 let summary = match first {
160 Some(e) => {
161 let sign = match e.kind {
162 EditKind::Insert => "+",
163 EditKind::Delete => "-",
164 };
165 let text: String = e
166 .text
167 .chars()
168 .take(24)
169 .map(|c| if c == '\n' { '↵' } else { c })
170 .collect();
171 let more = if e.text.chars().count() > 24 {
172 "…"
173 } else {
174 ""
175 };
176 format!("{sign} \"{text}{more}\"")
177 }
178 None => "(empty)".into(),
179 };
180 RevisionRow {
181 index: i,
182 parent: rev.parent,
183 depth: depths[i],
184 summary,
185 is_current: i == self.current,
186 branches: self.revisions[..i].iter().any(|r| r.parent == rev.parent),
189 }
190 })
191 .collect();
192 out.sort_by_key(|r| std::cmp::Reverse(r.index));
193 out
194 }
195
196 pub fn ops_to(&mut self, target: usize) -> Option<Vec<Edit>> {
200 if target >= self.revisions.len() {
201 return None;
202 }
203 let mut anc_cur = Vec::new();
205 let mut at = self.current;
206 loop {
207 anc_cur.push(at);
208 if at == 0 {
209 break;
210 }
211 at = self.revisions[at].parent;
212 }
213 let mut up_path = Vec::new(); let mut t = target;
216 while !anc_cur.contains(&t) {
217 up_path.push(t);
218 t = self.revisions[t].parent;
219 }
220 let fork = t;
221 let mut ops = Vec::new();
222 let mut c = self.current;
224 while c != fork {
225 let mut rev_undo = self.revisions[c].undo.clone();
226 rev_undo.reverse();
227 ops.extend(rev_undo);
228 c = self.revisions[c].parent;
229 }
230 for &r in up_path.iter().rev() {
232 ops.extend(self.revisions[r].redo.clone());
233 }
234 let mut c = self.current;
236 while c != fork {
237 let p = self.revisions[c].parent;
238 self.revisions[p].last_child = Some(c);
239 c = p;
240 }
241 let mut p = fork;
242 for &r in up_path.iter().rev() {
243 self.revisions[p].last_child = Some(r);
244 p = r;
245 }
246 self.current = target;
247 Some(ops)
248 }
249}
250
251impl History {
252 pub fn begin(&mut self) {
253 if self.pending.is_none() {
254 self.pending = Some((Vec::new(), Vec::new()));
255 }
256 }
257
258 pub fn commit(&mut self) {
259 let Some((undo, redo)) = self.pending.take() else {
260 return;
261 };
262 if undo.is_empty() {
263 return;
264 }
265 let rev = Revision {
266 parent: self.current,
267 last_child: None,
268 undo,
269 redo,
270 };
271 self.revisions.push(rev);
272 let idx = self.revisions.len() - 1;
273 self.revisions[self.current].last_child = Some(idx);
274 self.current = idx;
275 }
276
277 pub fn record(&mut self, undo: Edit, redo: Edit) -> EditRef {
279 let revision = self.revisions.len();
280 let (undo_edits, redo_edits) = self.pending.get_or_insert_with(Default::default);
281 let index = redo_edits.len();
282 undo_edits.push(undo);
283 redo_edits.push(redo);
284 EditRef { revision, index }
285 }
286
287 pub(crate) fn inserted_text(&self, reference: EditRef) -> Option<&str> {
288 let edits = if reference.revision == self.revisions.len() {
289 &self.pending.as_ref()?.1
290 } else {
291 &self.revisions.get(reference.revision)?.redo
292 };
293 let edit = edits.get(reference.index)?;
294 (edit.kind == EditKind::Insert).then_some(edit.text.as_str())
295 }
296
297 pub fn can_undo(&self) -> bool {
298 self.current > 0
299 }
300
301 pub fn can_redo(&self) -> bool {
302 self.revisions
303 .get(self.current)
304 .and_then(|r| r.last_child)
305 .is_some()
306 }
307
308 pub fn undo_ops(&mut self) -> Option<Vec<Edit>> {
310 if self.current == 0 {
311 return None;
312 }
313 let rev = &self.revisions[self.current];
314 let parent = rev.parent;
315 let mut ops = rev.undo.clone();
316 ops.reverse();
317 self.revisions[parent].last_child = Some(self.current);
318 self.current = parent;
319 Some(ops)
320 }
321
322 pub fn redo_ops(&mut self) -> Option<Vec<Edit>> {
323 let child = self.revisions.get(self.current)?.last_child?;
324 let ops = self.revisions[child].redo.clone();
325 self.current = child;
326 Some(ops)
327 }
328
329 pub fn last_committed_ops(&self) -> Option<Vec<Edit>> {
332 if self.current == 0 {
333 return None;
334 }
335 Some(self.revisions[self.current].redo.clone())
336 }
337
338 pub fn change_positions(&self) -> Vec<usize> {
342 let mut out = Vec::new();
343 let mut i = self.current;
344 while i != 0 {
345 let rev = &self.revisions[i];
346 if let Some(first) = rev.redo.first() {
349 out.push(first.at);
350 }
351 i = rev.parent;
352 }
353 out
354 }
355
356 pub fn depth(&self) -> usize {
357 self.revisions.len()
358 }
359
360 pub fn committed_position(&self) -> Option<usize> {
364 if self
365 .pending
366 .as_ref()
367 .is_some_and(|(undo, _)| !undo.is_empty())
368 {
369 None
370 } else {
371 Some(self.current)
372 }
373 }
374
375 pub fn snapshot(&self, revisions: usize, text_bytes: usize) -> Self {
379 let mut chain = Vec::new();
380 let mut bytes = 0usize;
381 let mut retain = |undo: &Vec<Edit>, redo: &Vec<Edit>| {
382 let size = undo
383 .iter()
384 .chain(redo)
385 .try_fold(0usize, |n, e| n.checked_add(e.text.len()));
386 let Some(next) = size.and_then(|size| bytes.checked_add(size)) else {
387 return false;
388 };
389 if chain.len() >= revisions || next > text_bytes {
390 return false;
391 }
392 bytes = next;
393 chain.push((undo.clone(), redo.clone()));
394 true
395 };
396 if let Some((undo, redo)) = &self.pending {
397 if !undo.is_empty() && !retain(undo, redo) {
398 return Self::default();
399 }
400 }
401 let mut at = self.current;
402 while at != 0 {
403 let revision = &self.revisions[at];
404 if !retain(&revision.undo, &revision.redo) {
405 break;
406 }
407 at = revision.parent;
408 }
409 let mut snapshot = Self::default();
410 for (undo, redo) in chain.into_iter().rev() {
411 let parent = snapshot.current;
412 let index = snapshot.revisions.len();
413 snapshot.revisions[parent].last_child = Some(index);
414 snapshot.revisions.push(Revision {
415 parent,
416 last_child: None,
417 undo,
418 redo,
419 });
420 snapshot.current = index;
421 }
422 snapshot
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429
430 fn edit(at: usize, text: &str, kind: EditKind) -> Edit {
431 Edit {
432 at,
433 text: text.into(),
434 kind,
435 }
436 }
437
438 #[test]
439 fn linear_undo_redo() {
440 let mut h = History::default();
441 h.begin();
442 h.record(
443 edit(0, "", EditKind::Delete),
444 edit(0, "x", EditKind::Insert),
445 );
446 h.commit();
447 assert!(h.can_undo());
448 let ops = h.undo_ops().unwrap();
449 assert_eq!(ops, vec![edit(0, "", EditKind::Delete)]);
450 assert!(h.can_redo());
451 let ops = h.redo_ops().unwrap();
452 assert_eq!(ops, vec![edit(0, "x", EditKind::Insert)]);
453 assert!(h.can_undo());
455 assert!(!h.can_redo());
456 }
457
458 #[test]
459 fn edit_after_undo_forks_a_branch() {
460 let mut h = History::default();
461 h.begin();
462 h.record(
463 edit(0, "", EditKind::Delete),
464 edit(0, "a", EditKind::Insert),
465 );
466 h.commit();
467 h.undo_ops();
468 h.begin();
469 h.record(
470 edit(0, "", EditKind::Delete),
471 edit(0, "b", EditKind::Insert),
472 );
473 h.commit();
474 assert!(h.depth() >= 2);
477 }
478}