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::SetRowVisibility { .. } => {
206            // Engine-level sidecar metadata; applied by Engine undo/redo orchestration.
207        }
208        ChangeEvent::AddVertex {
209            coord,
210            sheet_id,
211            kind,
212            ..
213        } => {
214            let mut editor = VertexEditor::new(graph);
215            let meta = VertexMeta::new(
216                coord.row(),
217                coord.col(),
218                *sheet_id,
219                kind.unwrap_or(crate::engine::vertex::VertexKind::Cell),
220            );
221            editor.add_vertex(meta);
222        }
223        ChangeEvent::RemoveVertex {
224            coord, sheet_id, ..
225        } => {
226            if let (Some(c), Some(sid)) = (coord, sheet_id) {
227                let mut editor = VertexEditor::new(graph);
228                let cell_ref = crate::reference::CellRef::new(
229                    *sid,
230                    crate::reference::Coord::new(c.row(), c.col(), true, true),
231                );
232                let _ = editor.remove_vertex_at(cell_ref);
233            }
234        }
235        ChangeEvent::VertexMoved { id, new_coord, .. } => {
236            let mut editor = VertexEditor::new(graph);
237            let _ = editor.move_vertex(*id, *new_coord);
238        }
239        ChangeEvent::FormulaAdjusted { id, new_ast, .. } => {
240            let _ = graph.update_vertex_formula(*id, new_ast.clone());
241            graph.mark_vertex_dirty(*id);
242        }
243        ChangeEvent::DefineName {
244            name,
245            scope,
246            definition,
247        } => {
248            let mut editor = VertexEditor::new(graph);
249            let _ = editor.define_name(name, definition.clone(), *scope);
250        }
251        ChangeEvent::UpdateName {
252            name,
253            scope,
254            new_definition,
255            ..
256        } => {
257            let mut editor = VertexEditor::new(graph);
258            let _ = editor.update_name(name, new_definition.clone(), *scope);
259        }
260        ChangeEvent::DeleteName { name, scope, .. } => {
261            let mut editor = VertexEditor::new(graph);
262            let _ = editor.delete_name(name, *scope);
263        }
264        ChangeEvent::NamedRangeAdjusted {
265            name,
266            scope,
267            new_definition,
268            ..
269        } => {
270            let mut editor = VertexEditor::new(graph);
271            let _ = editor.update_name(name, new_definition.clone(), *scope);
272        }
273        ChangeEvent::SpillCommitted { anchor, new, .. } => {
274            let _ = graph.commit_spill_region_atomic_with_fault(
275                *anchor,
276                new.target_cells.clone(),
277                new.values.clone(),
278                None,
279            );
280        }
281        ChangeEvent::SpillCleared { anchor, .. } => {
282            graph.clear_spill_region(*anchor);
283        }
284        ChangeEvent::EdgeAdded { from, to } => {
285            let mut editor = VertexEditor::new(graph);
286            let _ = editor.add_edge(*from, *to);
287        }
288        ChangeEvent::EdgeRemoved { from, to } => {
289            let mut editor = VertexEditor::new(graph);
290            let _ = editor.remove_edge(*from, *to);
291        }
292        ChangeEvent::CompoundStart { .. }
293        | ChangeEvent::CompoundEnd { .. }
294        | ChangeEvent::StagedFormulaCellChanged { .. } => {}
295    }
296    Ok(())
297}
298
299#[derive(Debug, Clone, Default, PartialEq)]
300pub struct ActionJournal {
301    pub name: String,
302    pub graph: GraphUndoBatch,
303    pub arrow: ArrowUndoBatch,
304    pub affected_cells: usize,
305}