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}
48
49impl Editing {
50 pub fn reset(&mut self) {
52 self.undo.clear();
53 self.redo.clear();
54 self.saved_at = Some(0);
55 }
56
57 pub fn record(&mut self, content: &Element) {
63 self.record_snapshot(content.clone(), None);
64 }
65
66 pub fn record_snapshot(&mut self, content: Element, caret: Option<Caret>) {
70 if self.saved_at.is_some_and(|depth| depth > self.undo.len()) {
71 self.saved_at = None;
73 }
74 self.redo.clear();
75 self.undo.push(Snapshot { content, caret });
76 if self.undo.len() > DEPTH {
77 self.undo.remove(0);
78 self.saved_at = self.saved_at.and_then(|depth| depth.checked_sub(1));
79 }
80 }
81
82 pub fn undo(
86 &mut self,
87 current: &Element,
88 caret: Option<Caret>,
89 ) -> Option<(Element, Option<Caret>)> {
90 let previous = self.undo.pop()?;
91 self.redo.push(Snapshot {
92 content: current.clone(),
93 caret,
94 });
95 Some((previous.content, previous.caret))
96 }
97
98 pub fn redo(
100 &mut self,
101 current: &Element,
102 caret: Option<Caret>,
103 ) -> Option<(Element, Option<Caret>)> {
104 let next = self.redo.pop()?;
105 self.undo.push(Snapshot {
106 content: current.clone(),
107 caret,
108 });
109 Some((next.content, next.caret))
110 }
111
112 pub fn can_undo(&self) -> bool {
114 !self.undo.is_empty()
115 }
116
117 pub fn can_redo(&self) -> bool {
119 !self.redo.is_empty()
120 }
121
122 pub fn modified(&self) -> bool {
125 self.saved_at != Some(self.undo.len())
126 }
127
128 pub fn mark_saved(&mut self) {
130 self.saved_at = Some(self.undo.len());
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::Editing;
137 use odox_core::{Element, Ns};
138
139 fn tree(text: &str) -> Element {
140 let mut e = Element::new("text", "p", Ns::Text);
141 e.children.push(odox_core::Node::Text(text.to_owned()));
142 e
143 }
144
145 #[test]
146 fn undo_returns_to_the_saved_state_and_redo_leaves_it() {
147 let mut editing = Editing::default();
148 editing.reset();
149 assert!(!editing.modified());
150
151 let mut current = tree("one");
152 editing.record(¤t);
153 current = tree("two");
154 assert!(editing.modified());
155
156 current = editing
157 .undo(¤t, None)
158 .map(|(tree, _)| tree)
159 .expect("something to undo");
160 assert_eq!(current, tree("one"));
161 assert!(!editing.modified(), "undone to what the file holds");
162
163 current = editing
164 .redo(¤t, None)
165 .map(|(tree, _)| tree)
166 .expect("something to redo");
167 assert_eq!(current, tree("two"));
168 assert!(editing.modified());
169 }
170
171 #[test]
172 fn a_new_edit_below_the_saved_state_makes_it_unreachable() {
173 let mut editing = Editing::default();
174 editing.reset();
175 let mut current = tree("one");
176 editing.record(¤t);
177 current = tree("two");
178 editing.mark_saved();
179 assert!(!editing.modified());
180
181 current = editing
184 .undo(¤t, None)
185 .map(|(tree, _)| tree)
186 .expect("something to undo");
187 assert!(editing.modified());
188 editing.record(¤t);
189 assert!(
190 editing.modified(),
191 "the same depth is not the same document"
192 );
193 assert!(!editing.can_redo(), "the branch the save was on is gone");
194 }
195
196 #[test]
197 fn undo_and_redo_answer_where_the_caret_was() {
198 let mut editing = Editing::default();
199 editing.reset();
200 let before = super::Caret::new(vec![0], 1);
201 let after = super::Caret::new(vec![0], 4);
202 editing.record_snapshot(tree("one"), Some(before.clone()));
203 let (_, caret) = editing
204 .undo(&tree("one more"), Some(after.clone()))
205 .expect("something to undo");
206 assert_eq!(caret, Some(before), "where the edit began");
207 let (_, caret) = editing.redo(&tree("one"), None).expect("something to redo");
208 assert_eq!(caret, Some(after), "where the caret was when it was undone");
209 }
210
211 #[test]
212 fn the_stack_is_bounded() {
213 let mut editing = Editing::default();
214 editing.reset();
215 for i in 0..(super::DEPTH + 10) {
216 editing.record(&tree(&i.to_string()));
217 }
218 assert!(editing.modified());
219 let mut undone = 0;
220 let mut current = tree("last");
221 while let Some(previous) = editing.undo(¤t, None).map(|(tree, _)| tree) {
222 current = previous;
223 undone += 1;
224 }
225 assert_eq!(undone, super::DEPTH);
226 assert!(
227 editing.modified(),
228 "the saved state was forgotten with the oldest snapshots"
229 );
230 }
231}