1use egui_richedit::Position;
17use odox_core::Element;
18
19const DEPTH: usize = 100;
21
22pub type Caret = Position<Vec<usize>>;
25
26struct Snapshot {
28 content: Element,
29 caret: Option<Caret>,
30}
31
32#[derive(Default)]
34pub struct Editing {
35 pub on: bool,
38 pub asking: bool,
41 undo: Vec<Snapshot>,
42 redo: Vec<Snapshot>,
43 saved_at: Option<usize>,
47 revision: u64,
50}
51
52impl Editing {
53 pub fn reset(&mut self) {
55 self.undo.clear();
56 self.redo.clear();
57 self.saved_at = Some(0);
58 self.touch();
59 }
60
61 pub fn touch(&mut self) {
63 self.revision += 1;
64 }
65
66 pub fn revision(&self) -> u64 {
68 self.revision
69 }
70
71 pub fn record(&mut self, content: &Element) {
77 self.record_snapshot(content.clone(), None);
78 }
79
80 pub fn record_snapshot(&mut self, content: Element, caret: Option<Caret>) {
84 if self.saved_at.is_some_and(|depth| depth > self.undo.len()) {
85 self.saved_at = None;
87 }
88 self.redo.clear();
89 self.touch();
90 self.undo.push(Snapshot { content, caret });
91 if self.undo.len() > DEPTH {
92 self.undo.remove(0);
93 self.saved_at = self.saved_at.and_then(|depth| depth.checked_sub(1));
94 }
95 }
96
97 pub fn undo(
101 &mut self,
102 current: &Element,
103 caret: Option<Caret>,
104 ) -> Option<(Element, Option<Caret>)> {
105 let previous = self.undo.pop()?;
106 self.touch();
107 self.redo.push(Snapshot {
108 content: current.clone(),
109 caret,
110 });
111 Some((previous.content, previous.caret))
112 }
113
114 pub fn redo(
116 &mut self,
117 current: &Element,
118 caret: Option<Caret>,
119 ) -> Option<(Element, Option<Caret>)> {
120 let next = self.redo.pop()?;
121 self.touch();
122 self.undo.push(Snapshot {
123 content: current.clone(),
124 caret,
125 });
126 Some((next.content, next.caret))
127 }
128
129 pub fn can_undo(&self) -> bool {
131 !self.undo.is_empty()
132 }
133
134 pub fn can_redo(&self) -> bool {
136 !self.redo.is_empty()
137 }
138
139 pub fn modified(&self) -> bool {
142 self.saved_at != Some(self.undo.len())
143 }
144
145 pub fn mark_saved(&mut self) {
147 self.saved_at = Some(self.undo.len());
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::Editing;
154 use odox_core::{Element, Ns};
155
156 fn tree(text: &str) -> Element {
157 let mut e = Element::new("text", "p", Ns::Text);
158 e.children.push(odox_core::Node::Text(text.to_owned()));
159 e
160 }
161
162 #[test]
163 fn undo_returns_to_the_saved_state_and_redo_leaves_it() {
164 let mut editing = Editing::default();
165 editing.reset();
166 assert!(!editing.modified());
167
168 let mut current = tree("one");
169 editing.record(¤t);
170 current = tree("two");
171 assert!(editing.modified());
172
173 current = editing
174 .undo(¤t, None)
175 .map(|(tree, _)| tree)
176 .expect("something to undo");
177 assert_eq!(current, tree("one"));
178 assert!(!editing.modified(), "undone to what the file holds");
179
180 current = editing
181 .redo(¤t, None)
182 .map(|(tree, _)| tree)
183 .expect("something to redo");
184 assert_eq!(current, tree("two"));
185 assert!(editing.modified());
186 }
187
188 #[test]
189 fn a_new_edit_below_the_saved_state_makes_it_unreachable() {
190 let mut editing = Editing::default();
191 editing.reset();
192 let mut current = tree("one");
193 editing.record(¤t);
194 current = tree("two");
195 editing.mark_saved();
196 assert!(!editing.modified());
197
198 current = editing
201 .undo(¤t, None)
202 .map(|(tree, _)| tree)
203 .expect("something to undo");
204 assert!(editing.modified());
205 editing.record(¤t);
206 assert!(
207 editing.modified(),
208 "the same depth is not the same document"
209 );
210 assert!(!editing.can_redo(), "the branch the save was on is gone");
211 }
212
213 #[test]
214 fn undo_and_redo_answer_where_the_caret_was() {
215 let mut editing = Editing::default();
216 editing.reset();
217 let before = super::Caret::new(vec![0], 1);
218 let after = super::Caret::new(vec![0], 4);
219 editing.record_snapshot(tree("one"), Some(before.clone()));
220 let (_, caret) = editing
221 .undo(&tree("one more"), Some(after.clone()))
222 .expect("something to undo");
223 assert_eq!(caret, Some(before), "where the edit began");
224 let (_, caret) = editing.redo(&tree("one"), None).expect("something to redo");
225 assert_eq!(caret, Some(after), "where the caret was when it was undone");
226 }
227
228 #[test]
229 fn the_stack_is_bounded() {
230 let mut editing = Editing::default();
231 editing.reset();
232 for i in 0..(super::DEPTH + 10) {
233 editing.record(&tree(&i.to_string()));
234 }
235 assert!(editing.modified());
236 let mut undone = 0;
237 let mut current = tree("last");
238 while let Some(previous) = editing.undo(¤t, None).map(|(tree, _)| tree) {
239 current = previous;
240 undone += 1;
241 }
242 assert_eq!(undone, super::DEPTH);
243 assert!(
244 editing.modified(),
245 "the saved state was forgotten with the oldest snapshots"
246 );
247 }
248}