Skip to main content

formualizer_eval/engine/
journal.rs

1//! Internal action journal types used for atomicity and undo/redo.
2//!
3//! This module intentionally does not depend on the external `ChangeLog` as a correctness
4//! mechanism. It uses graph `ChangeEvent` as a structural delta representation and records
5//! explicit Arrow overlay mutations for value truth rollback.
6
7use crate::SheetId;
8use crate::engine::DependencyGraph;
9use crate::engine::graph::editor::change_log::ChangeEvent;
10use crate::engine::graph::editor::vertex_editor::{EditorError, VertexEditor};
11use formualizer_common::LiteralValue;
12
13#[derive(Debug, Clone, PartialEq)]
14pub enum ArrowOp {
15    SetDeltaCell {
16        sheet_id: SheetId,
17        row0: u32,
18        col0: u32,
19        old: Option<LiteralValue>,
20        new: Option<LiteralValue>,
21    },
22    SetComputedCell {
23        sheet_id: SheetId,
24        row0: u32,
25        col0: u32,
26        old: Option<LiteralValue>,
27        new: Option<LiteralValue>,
28    },
29    RestoreComputedRect {
30        sheet_id: SheetId,
31        sr0: u32,
32        sc0: u32,
33        er0: u32,
34        ec0: u32,
35        old: Vec<Vec<LiteralValue>>,
36        new: Vec<Vec<LiteralValue>>,
37    },
38    InsertRows {
39        sheet_id: SheetId,
40        before0: u32,
41        count: u32,
42    },
43    InsertCols {
44        sheet_id: SheetId,
45        before0: u32,
46        count: u32,
47    },
48}
49
50#[derive(Debug, Clone, Default, PartialEq)]
51pub struct ArrowUndoBatch {
52    pub ops: Vec<ArrowOp>,
53}
54
55impl ArrowUndoBatch {
56    #[inline]
57    pub fn is_empty(&self) -> bool {
58        self.ops.is_empty()
59    }
60
61    #[inline]
62    pub fn record_delta_cell(
63        &mut self,
64        sheet_id: SheetId,
65        row0: u32,
66        col0: u32,
67        old: Option<LiteralValue>,
68        new: Option<LiteralValue>,
69    ) {
70        if old == new {
71            return;
72        }
73        self.ops.push(ArrowOp::SetDeltaCell {
74            sheet_id,
75            row0,
76            col0,
77            old,
78            new,
79        });
80    }
81
82    #[inline]
83    pub fn record_computed_cell(
84        &mut self,
85        sheet_id: SheetId,
86        row0: u32,
87        col0: u32,
88        old: Option<LiteralValue>,
89        new: Option<LiteralValue>,
90    ) {
91        if old == new {
92            return;
93        }
94        self.ops.push(ArrowOp::SetComputedCell {
95            sheet_id,
96            row0,
97            col0,
98            old,
99            new,
100        });
101    }
102
103    #[inline]
104    pub fn record_restore_computed_rect(
105        &mut self,
106        sheet_id: SheetId,
107        sr0: u32,
108        sc0: u32,
109        er0: u32,
110        ec0: u32,
111        old: Vec<Vec<LiteralValue>>,
112        new: Vec<Vec<LiteralValue>>,
113    ) {
114        if old == new {
115            return;
116        }
117        self.ops.push(ArrowOp::RestoreComputedRect {
118            sheet_id,
119            sr0,
120            sc0,
121            er0,
122            ec0,
123            old,
124            new,
125        });
126    }
127
128    #[inline]
129    pub fn record_insert_rows(&mut self, sheet_id: SheetId, before0: u32, count: u32) {
130        if count == 0 {
131            return;
132        }
133        self.ops.push(ArrowOp::InsertRows {
134            sheet_id,
135            before0,
136            count,
137        });
138    }
139
140    #[inline]
141    pub fn record_insert_cols(&mut self, sheet_id: SheetId, before0: u32, count: u32) {
142        if count == 0 {
143            return;
144        }
145        self.ops.push(ArrowOp::InsertCols {
146            sheet_id,
147            before0,
148            count,
149        });
150    }
151}
152
153#[derive(Debug, Clone, Default, PartialEq)]
154pub struct GraphUndoBatch {
155    pub events: Vec<ChangeEvent>,
156}
157
158impl GraphUndoBatch {
159    #[inline]
160    pub fn is_empty(&self) -> bool {
161        self.events.is_empty()
162    }
163
164    pub fn undo(&self, graph: &mut DependencyGraph) -> Result<(), EditorError> {
165        let mut editor = VertexEditor::new(graph);
166        let mut compound_stack: Vec<usize> = Vec::new();
167        for ev in self.events.iter().rev() {
168            match ev {
169                ChangeEvent::CompoundEnd { depth } => compound_stack.push(*depth),
170                ChangeEvent::CompoundStart { depth, .. } => {
171                    if compound_stack.last() == Some(depth) {
172                        compound_stack.pop();
173                    }
174                }
175                _ => {
176                    editor.apply_inverse(ev.clone())?;
177                }
178            }
179        }
180        Ok(())
181    }
182
183    pub fn redo(&self, graph: &mut DependencyGraph) -> Result<(), EditorError> {
184        for ev in &self.events {
185            apply_forward_change_event(graph, ev)?;
186        }
187        Ok(())
188    }
189}
190
191fn apply_forward_change_event(
192    graph: &mut DependencyGraph,
193    ev: &ChangeEvent,
194) -> Result<(), EditorError> {
195    use crate::engine::graph::editor::vertex_editor::VertexMeta;
196    match ev {
197        ChangeEvent::SetValue { addr, new, .. } => {
198            let mut editor = VertexEditor::new(graph);
199            editor.set_cell_value(*addr, new.clone());
200        }
201        ChangeEvent::SetFormula { addr, new, .. } => {
202            let mut editor = VertexEditor::new(graph);
203            editor.set_cell_formula(*addr, new.clone());
204        }
205        ChangeEvent::AddVertex {
206            coord,
207            sheet_id,
208            kind,
209            ..
210        } => {
211            let mut editor = VertexEditor::new(graph);
212            let meta = VertexMeta::new(
213                coord.row(),
214                coord.col(),
215                *sheet_id,
216                kind.unwrap_or(crate::engine::vertex::VertexKind::Cell),
217            );
218            editor.add_vertex(meta);
219        }
220        ChangeEvent::RemoveVertex {
221            coord, sheet_id, ..
222        } => {
223            if let (Some(c), Some(sid)) = (coord, sheet_id) {
224                let mut editor = VertexEditor::new(graph);
225                let cell_ref = crate::reference::CellRef::new(
226                    *sid,
227                    crate::reference::Coord::new(c.row(), c.col(), true, true),
228                );
229                let _ = editor.remove_vertex_at(cell_ref);
230            }
231        }
232        ChangeEvent::VertexMoved { id, new_coord, .. } => {
233            let mut editor = VertexEditor::new(graph);
234            let _ = editor.move_vertex(*id, *new_coord);
235        }
236        ChangeEvent::FormulaAdjusted { id, new_ast, .. } => {
237            let _ = graph.update_vertex_formula(*id, new_ast.clone());
238            graph.mark_vertex_dirty(*id);
239        }
240        ChangeEvent::DefineName {
241            name,
242            scope,
243            definition,
244        } => {
245            let mut editor = VertexEditor::new(graph);
246            let _ = editor.define_name(name, definition.clone(), *scope);
247        }
248        ChangeEvent::UpdateName {
249            name,
250            scope,
251            new_definition,
252            ..
253        } => {
254            let mut editor = VertexEditor::new(graph);
255            let _ = editor.update_name(name, new_definition.clone(), *scope);
256        }
257        ChangeEvent::DeleteName { name, scope, .. } => {
258            let mut editor = VertexEditor::new(graph);
259            let _ = editor.delete_name(name, *scope);
260        }
261        ChangeEvent::NamedRangeAdjusted {
262            name,
263            scope,
264            new_definition,
265            ..
266        } => {
267            let mut editor = VertexEditor::new(graph);
268            let _ = editor.update_name(name, new_definition.clone(), *scope);
269        }
270        ChangeEvent::SpillCommitted { anchor, new, .. } => {
271            let _ = graph.commit_spill_region_atomic_with_fault(
272                *anchor,
273                new.target_cells.clone(),
274                new.values.clone(),
275                None,
276            );
277        }
278        ChangeEvent::SpillCleared { anchor, .. } => {
279            graph.clear_spill_region(*anchor);
280        }
281        ChangeEvent::EdgeAdded { from, to } => {
282            let mut editor = VertexEditor::new(graph);
283            let _ = editor.add_edge(*from, *to);
284        }
285        ChangeEvent::EdgeRemoved { from, to } => {
286            let mut editor = VertexEditor::new(graph);
287            let _ = editor.remove_edge(*from, *to);
288        }
289        ChangeEvent::CompoundStart { .. } | ChangeEvent::CompoundEnd { .. } => {}
290    }
291    Ok(())
292}
293
294#[derive(Debug, Clone, Default, PartialEq)]
295pub struct ActionJournal {
296    pub name: String,
297    pub graph: GraphUndoBatch,
298    pub arrow: ArrowUndoBatch,
299    pub affected_cells: usize,
300}