1use odox_core::Element;
13
14const DEPTH: usize = 100;
16
17#[derive(Default)]
19pub struct Editing {
20 pub on: bool,
23 pub asking: bool,
26 undo: Vec<Element>,
27 redo: Vec<Element>,
28 saved_at: Option<usize>,
32}
33
34impl Editing {
35 pub fn reset(&mut self) {
37 self.undo.clear();
38 self.redo.clear();
39 self.saved_at = Some(0);
40 }
41
42 pub fn record(&mut self, content: &Element) {
48 if self.saved_at.is_some_and(|depth| depth > self.undo.len()) {
49 self.saved_at = None;
51 }
52 self.redo.clear();
53 self.undo.push(content.clone());
54 if self.undo.len() > DEPTH {
55 self.undo.remove(0);
56 self.saved_at = self.saved_at.and_then(|depth| depth.checked_sub(1));
57 }
58 }
59
60 pub fn undo(&mut self, current: &Element) -> Option<Element> {
63 let previous = self.undo.pop()?;
64 self.redo.push(current.clone());
65 Some(previous)
66 }
67
68 pub fn redo(&mut self, current: &Element) -> Option<Element> {
70 let next = self.redo.pop()?;
71 self.undo.push(current.clone());
72 Some(next)
73 }
74
75 pub fn can_undo(&self) -> bool {
77 !self.undo.is_empty()
78 }
79
80 pub fn can_redo(&self) -> bool {
82 !self.redo.is_empty()
83 }
84
85 pub fn modified(&self) -> bool {
88 self.saved_at != Some(self.undo.len())
89 }
90
91 pub fn mark_saved(&mut self) {
93 self.saved_at = Some(self.undo.len());
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::Editing;
100 use odox_core::{Element, Ns};
101
102 fn tree(text: &str) -> Element {
103 let mut e = Element::new("text", "p", Ns::Text);
104 e.children.push(odox_core::Node::Text(text.to_owned()));
105 e
106 }
107
108 #[test]
109 fn undo_returns_to_the_saved_state_and_redo_leaves_it() {
110 let mut editing = Editing::default();
111 editing.reset();
112 assert!(!editing.modified());
113
114 let mut current = tree("one");
115 editing.record(¤t);
116 current = tree("two");
117 assert!(editing.modified());
118
119 current = editing.undo(¤t).expect("something to undo");
120 assert_eq!(current, tree("one"));
121 assert!(!editing.modified(), "undone to what the file holds");
122
123 current = editing.redo(¤t).expect("something to redo");
124 assert_eq!(current, tree("two"));
125 assert!(editing.modified());
126 }
127
128 #[test]
129 fn a_new_edit_below_the_saved_state_makes_it_unreachable() {
130 let mut editing = Editing::default();
131 editing.reset();
132 let mut current = tree("one");
133 editing.record(¤t);
134 current = tree("two");
135 editing.mark_saved();
136 assert!(!editing.modified());
137
138 current = editing.undo(¤t).expect("something to undo");
141 assert!(editing.modified());
142 editing.record(¤t);
143 assert!(
144 editing.modified(),
145 "the same depth is not the same document"
146 );
147 assert!(!editing.can_redo(), "the branch the save was on is gone");
148 }
149
150 #[test]
151 fn the_stack_is_bounded() {
152 let mut editing = Editing::default();
153 editing.reset();
154 for i in 0..(super::DEPTH + 10) {
155 editing.record(&tree(&i.to_string()));
156 }
157 assert!(editing.modified());
158 let mut undone = 0;
159 let mut current = tree("last");
160 while let Some(previous) = editing.undo(¤t) {
161 current = previous;
162 undone += 1;
163 }
164 assert_eq!(undone, super::DEPTH);
165 assert!(
166 editing.modified(),
167 "the saved state was forgotten with the oldest snapshots"
168 );
169 }
170}