1use super::change_log::{ChangeEvent, ChangeEventMeta, ChangeLog};
3use super::vertex_editor::VertexEditor;
4use crate::engine::graph::DependencyGraph;
5use crate::engine::graph::editor::vertex_editor::EditorError;
6
7#[derive(Debug, Clone)]
8pub struct UndoBatchItem {
9 pub event: ChangeEvent,
10 pub meta: ChangeEventMeta,
11}
12
13#[derive(Debug, Default)]
14pub struct UndoEngine {
15 undone: Vec<Vec<UndoBatchItem>>, actions_done: Vec<crate::engine::ActionJournal>,
20 actions_undone: Vec<crate::engine::ActionJournal>,
21}
22
23impl UndoEngine {
24 pub fn new() -> Self {
25 Self {
26 undone: Vec::new(),
27 actions_done: Vec::new(),
28 actions_undone: Vec::new(),
29 }
30 }
31
32 pub fn push_action(&mut self, journal: crate::engine::ActionJournal) {
34 self.actions_done.push(journal);
35 self.actions_undone.clear();
36 }
37
38 pub fn pop_undo_action(&mut self) -> Option<crate::engine::ActionJournal> {
39 self.actions_done.pop()
40 }
41
42 pub fn push_redo_action(&mut self, journal: crate::engine::ActionJournal) {
43 self.actions_undone.push(journal);
44 }
45
46 pub fn pop_redo_action(&mut self) -> Option<crate::engine::ActionJournal> {
47 self.actions_undone.pop()
48 }
49
50 pub fn push_done_action(&mut self, journal: crate::engine::ActionJournal) {
51 self.actions_done.push(journal);
52 }
53
54 pub fn undo(
56 &mut self,
57 graph: &mut DependencyGraph,
58 log: &mut ChangeLog,
59 ) -> Result<Vec<UndoBatchItem>, EditorError> {
60 let idxs = log.last_group_indices();
61 if idxs.is_empty() {
62 return Ok(Vec::new());
63 }
64 let batch: Vec<UndoBatchItem> = idxs
65 .iter()
66 .map(|i| UndoBatchItem {
67 event: log.events()[*i].clone(),
68 meta: log.event_meta(*i).cloned().unwrap_or_default(),
69 })
70 .collect();
71 let max_idx = *idxs.iter().max().unwrap();
72 if max_idx + 1 == log.events().len() {
73 let truncate_to = idxs.iter().min().copied().unwrap();
74 log.truncate(truncate_to);
75 } else {
76 return Err(EditorError::TransactionFailed {
77 reason: "Non-tail undo not supported".into(),
78 });
79 }
80 let mut editor = VertexEditor::new(graph);
81 for item in batch.iter().rev() {
82 editor.apply_inverse(item.event.clone())?;
83 }
84
85 self.undone.push(batch.clone());
87 Ok(batch)
88 }
89
90 pub fn redo(
91 &mut self,
92 graph: &mut DependencyGraph,
93 log: &mut ChangeLog,
94 ) -> Result<Vec<UndoBatchItem>, EditorError> {
95 if let Some(batch) = self.undone.pop() {
96 log.begin_compound("redo".to_string());
97 let ret = batch.clone();
100
101 for item in batch {
102 log.record_with_meta(item.event.clone(), item.meta.clone());
104 match item.event {
105 ChangeEvent::SetValue { addr, new, .. } => {
106 let mut editor = VertexEditor::new(graph);
107 editor.set_cell_value(addr, new);
108 }
109 ChangeEvent::SetFormula { addr, new, .. } => {
110 let mut editor = VertexEditor::new(graph);
111 editor.set_cell_formula(addr, new);
112 }
113 ChangeEvent::AddVertex {
114 coord,
115 sheet_id,
116 kind,
117 ..
118 } => {
119 let mut editor = VertexEditor::new(graph);
120 let meta = crate::engine::graph::editor::vertex_editor::VertexMeta::new(
121 coord.row(),
122 coord.col(),
123 sheet_id,
124 kind.unwrap_or(crate::engine::vertex::VertexKind::Cell),
125 );
126 editor.add_vertex(meta);
127 }
128 ChangeEvent::RemoveVertex {
129 coord, sheet_id, ..
130 } => {
131 if let (Some(c), Some(sid)) = (coord, sheet_id) {
132 let mut editor = VertexEditor::new(graph);
133 let cell_ref = crate::reference::CellRef::new(
134 sid,
135 crate::reference::Coord::new(c.row(), c.col(), true, true),
136 );
137 let _ = editor.remove_vertex_at(cell_ref);
138 }
139 }
140 ChangeEvent::VertexMoved { id, new_coord, .. } => {
141 let mut editor = VertexEditor::new(graph);
142 let _ = editor.move_vertex(id, new_coord);
143 }
144 ChangeEvent::FormulaAdjusted { id, new_ast, .. } => {
145 let _ = graph.update_vertex_formula(id, new_ast);
148 graph.mark_vertex_dirty(id);
149 }
150 ChangeEvent::DefineName {
151 name,
152 scope,
153 definition,
154 } => {
155 let mut editor = VertexEditor::new(graph);
156 let _ = editor.define_name(&name, definition, scope);
157 }
158 ChangeEvent::UpdateName {
159 name,
160 scope,
161 new_definition,
162 ..
163 } => {
164 let mut editor = VertexEditor::new(graph);
165 let _ = editor.update_name(&name, new_definition, scope);
166 }
167 ChangeEvent::DeleteName { name, scope, .. } => {
168 let mut editor = VertexEditor::new(graph);
169 let _ = editor.delete_name(&name, scope);
170 }
171 ChangeEvent::NamedRangeAdjusted {
172 name,
173 scope,
174 new_definition,
175 ..
176 } => {
177 let mut editor = VertexEditor::new(graph);
178 let _ = editor.update_name(&name, new_definition, scope);
179 }
180 ChangeEvent::SpillCommitted { anchor, new, .. } => {
181 let _ = graph.commit_spill_region_atomic_with_fault(
182 anchor,
183 new.target_cells,
184 new.values,
185 None,
186 );
187 }
188 ChangeEvent::SpillCleared { anchor, .. } => {
189 graph.clear_spill_region(anchor);
190 }
191 ChangeEvent::SetRowVisibility { .. } => {
192 }
194 _ => {}
195 }
196 }
197 log.end_compound();
198 Ok(ret)
199 } else {
200 Ok(Vec::new())
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use crate::engine::EvalConfig;
209 use crate::engine::graph::editor::change_log::ChangeLog;
210 use crate::reference::{CellRef, Coord};
211 use formualizer_common::LiteralValue;
212
213 fn create_test_graph() -> DependencyGraph {
214 DependencyGraph::new_with_config(EvalConfig::default())
215 }
216
217 #[test]
218 fn test_undo_redo_single_value() {
219 let mut graph = create_test_graph();
220 let mut log = ChangeLog::new();
221 {
222 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
223 let cell = CellRef {
224 sheet_id: 0,
225 coord: Coord::new(1, 1, true, true),
226 };
227 editor.set_cell_value(cell, LiteralValue::Number(10.0));
228 }
229 assert_eq!(log.len(), 1);
230 let mut undo = UndoEngine::new();
231 undo.undo(&mut graph, &mut log).unwrap();
232 assert_eq!(log.len(), 0); undo.redo(&mut graph, &mut log).unwrap();
235 assert!(!log.is_empty());
236 }
237
238 #[test]
239 fn test_undo_redo_row_shift() {
240 let mut graph = create_test_graph();
241 let mut log = ChangeLog::new();
242 {
243 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
244 for r in [5u32, 6u32, 10u32] {
246 let cell = CellRef {
247 sheet_id: 0,
248 coord: Coord::new(r, 1, true, true),
249 };
250 editor.set_cell_value(cell, LiteralValue::Number(r as f64));
251 }
252 }
253 log.clear(); {
255 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
256 editor.insert_rows(0, 6, 2).unwrap(); }
258 assert!(
259 log.events()
260 .iter()
261 .any(|e| matches!(e, ChangeEvent::VertexMoved { .. }))
262 );
263 let moved_count_before = log
264 .events()
265 .iter()
266 .filter(|e| matches!(e, ChangeEvent::VertexMoved { .. }))
267 .count();
268 let mut undo = UndoEngine::new();
269 undo.undo(&mut graph, &mut log).unwrap();
270 assert_eq!(log.events().len(), 0); undo.redo(&mut graph, &mut log).unwrap();
272 let moved_count_after = log
273 .events()
274 .iter()
275 .filter(|e| matches!(e, ChangeEvent::VertexMoved { .. }))
276 .count();
277 assert_eq!(moved_count_before, moved_count_after);
278 }
279
280 #[test]
281 fn test_undo_redo_spill_clear_on_scalar_edit_restores_registry_and_cells() {
282 let mut graph = create_test_graph();
283 let sheet_id = graph.sheet_id_mut("Sheet1");
284
285 let anchor_cell = CellRef::new(sheet_id, Coord::new(0, 0, true, true));
286 let anchor_vid = {
287 let mut editor = VertexEditor::new(&mut graph);
288 editor.set_cell_value(anchor_cell, LiteralValue::Number(0.0))
289 };
290
291 let target_cells = vec![
292 CellRef::new(sheet_id, Coord::new(0, 0, true, true)),
293 CellRef::new(sheet_id, Coord::new(0, 1, true, true)),
294 CellRef::new(sheet_id, Coord::new(1, 0, true, true)),
295 CellRef::new(sheet_id, Coord::new(1, 1, true, true)),
296 ];
297 let values = vec![
298 vec![LiteralValue::Number(1.0), LiteralValue::Number(2.0)],
299 vec![LiteralValue::Number(3.0), LiteralValue::Number(4.0)],
300 ];
301 graph
302 .commit_spill_region_atomic_with_fault(anchor_vid, target_cells.clone(), values, None)
303 .unwrap();
304
305 assert!(graph.spill_registry_has_anchor(anchor_vid));
306
307 let mut log = ChangeLog::new();
308 {
309 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
310 editor.set_cell_value(anchor_cell, LiteralValue::Number(9.0));
312 }
313
314 assert!(!graph.spill_registry_has_anchor(anchor_vid));
315 assert_eq!(graph.spill_registry_counts(), (0, 0));
316
317 let mut undo = UndoEngine::new();
318 undo.undo(&mut graph, &mut log).unwrap();
319
320 assert!(graph.spill_registry_has_anchor(anchor_vid));
321 for cell in &target_cells {
322 assert_eq!(
323 graph.spill_registry_anchor_for_cell(*cell),
324 Some(anchor_vid)
325 );
326 }
327 undo.redo(&mut graph, &mut log).unwrap();
332 assert!(!graph.spill_registry_has_anchor(anchor_vid));
333 assert_eq!(graph.spill_registry_counts(), (0, 0));
334 }
335
336 #[test]
337 fn test_undo_depth_truncates_gracefully_under_changelog_cap() {
338 let mut graph = create_test_graph();
339 let sheet_id = graph.sheet_id_mut("Sheet1");
340 let mut log = ChangeLog::with_max_changelog_events(3);
341
342 for i in 0..5u32 {
344 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
345 let cell = CellRef::new(sheet_id, Coord::new(i, 0, true, true));
346 editor.set_cell_value(cell, LiteralValue::Number(i as f64));
347 }
348 assert_eq!(log.len(), 3);
349
350 let mut undo = UndoEngine::new();
351 undo.undo(&mut graph, &mut log).unwrap();
352 undo.undo(&mut graph, &mut log).unwrap();
353 undo.undo(&mut graph, &mut log).unwrap();
354 undo.undo(&mut graph, &mut log).unwrap();
356 assert_eq!(log.len(), 0);
357 }
358
359 #[test]
360 fn test_undo_redo_column_shift() {
361 let mut graph = create_test_graph();
362 let mut log = ChangeLog::new();
363 {
364 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
365 for c in [3u32, 4u32, 8u32] {
366 let cell = CellRef {
367 sheet_id: 0,
368 coord: Coord::new(1, c, true, true),
369 };
370 editor.set_cell_value(cell, LiteralValue::Number(c as f64));
371 }
372 }
373 log.clear();
374 {
375 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
376 editor.insert_columns(0, 5, 2).unwrap();
377 }
378 assert!(
379 log.events()
380 .iter()
381 .any(|e| matches!(e, ChangeEvent::VertexMoved { .. }))
382 );
383 let mut undo = UndoEngine::new();
384 undo.undo(&mut graph, &mut log).unwrap();
385 assert_eq!(log.events().len(), 0);
386 }
387
388 #[test]
389 fn test_remove_vertex_dependency_roundtrip() {
390 use formualizer_parse::parser::parse;
391 let mut graph = create_test_graph();
392 let mut log = ChangeLog::new();
393 let (a1_cell, a2_cell) = (
394 CellRef {
395 sheet_id: 0,
396 coord: Coord::new(0, 0, true, true), },
398 CellRef {
399 sheet_id: 0,
400 coord: Coord::new(1, 0, true, true), },
402 );
403 let a2_id;
404 {
405 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
406 editor.set_cell_value(a1_cell, LiteralValue::Number(10.0));
407 a2_id = editor.set_cell_formula(a2_cell, parse("=A1").unwrap());
408 }
409 let deps_before = graph.get_dependencies(a2_id);
411 assert!(!deps_before.is_empty());
412 log.clear();
414 {
415 let a1_vid = graph.get_vertex_id_for_address(&a1_cell).copied().unwrap();
417 let mut editor = VertexEditor::with_logger(&mut graph, &mut log);
418 editor.remove_vertex(a1_vid).unwrap();
419 }
420 assert!(
421 log.events()
422 .iter()
423 .any(|e| matches!(e, ChangeEvent::RemoveVertex { .. }))
424 );
425 let deps_after_remove = graph.get_dependencies(a2_id);
427 assert!(deps_after_remove.is_empty());
428 let mut undo = UndoEngine::new();
429 undo.undo(&mut graph, &mut log).unwrap();
430 let deps_after_undo = graph.get_dependencies(a2_id);
432 assert!(!deps_after_undo.is_empty());
433 undo.redo(&mut graph, &mut log).unwrap();
435 let deps_after_redo = graph.get_dependencies(a2_id);
436 assert!(deps_after_redo.is_empty());
437 }
438}