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        graph.authority_set_replay(crate::engine::authority::history::Replay::Undo);
166        let replayed = self.undo_events(graph);
167        graph.authority_set_replay(crate::engine::authority::history::Replay::Forward);
168        replayed
169    }
170
171    fn undo_events(&self, graph: &mut DependencyGraph) -> Result<(), EditorError> {
172        let mut editor = VertexEditor::new(graph);
173        let mut compound_stack: Vec<usize> = Vec::new();
174        for (i, ev) in self.events.iter().enumerate().rev() {
175            match ev {
176                ChangeEvent::CompoundEnd { depth } => {
177                    compound_stack.push(*depth);
178                    if let Some(description) =
179                        crate::engine::graph::editor::change_log::compound_start_description(
180                            i,
181                            |j| &self.events[j],
182                        )
183                    {
184                        editor.inverse_compound_end(description);
185                    }
186                }
187                ChangeEvent::CompoundStart { depth, .. } => {
188                    if compound_stack.last() == Some(depth) {
189                        compound_stack.pop();
190                    }
191                    editor.apply_inverse(ev.clone())?;
192                }
193                _ => {
194                    editor.apply_inverse(ev.clone())?;
195                }
196            }
197        }
198        Ok(())
199    }
200
201    pub fn redo(&self, graph: &mut DependencyGraph) -> Result<(), EditorError> {
202        graph.authority_set_replay(crate::engine::authority::history::Replay::Redo);
203        let replayed = self
204            .events
205            .iter()
206            .try_for_each(|ev| apply_forward_change_event(graph, ev));
207        graph.authority_set_replay(crate::engine::authority::history::Replay::Forward);
208        replayed
209    }
210}
211
212fn apply_forward_change_event(
213    graph: &mut DependencyGraph,
214    ev: &ChangeEvent,
215) -> Result<(), EditorError> {
216    use crate::engine::graph::editor::vertex_editor::VertexMeta;
217    match ev {
218        ChangeEvent::SetValue { addr, new, .. } => {
219            let mut editor = VertexEditor::new(graph);
220            editor.set_cell_value(*addr, new.clone());
221        }
222        ChangeEvent::SetFormula { addr, new, .. } => {
223            let mut editor = VertexEditor::new(graph);
224            editor.set_cell_formula(*addr, new.clone());
225        }
226        ChangeEvent::SetRowVisibility { .. } => {
227            // Engine-level sidecar metadata; applied by Engine undo/redo orchestration.
228        }
229        ChangeEvent::AddVertex {
230            coord,
231            sheet_id,
232            kind,
233            ..
234        } => {
235            let mut editor = VertexEditor::new(graph);
236            let meta = VertexMeta::new(
237                coord.row(),
238                coord.col(),
239                *sheet_id,
240                kind.unwrap_or(crate::engine::vertex::VertexKind::Cell),
241            );
242            editor.try_add_vertex(meta)?;
243        }
244        ChangeEvent::RemoveVertex {
245            coord, sheet_id, ..
246        } => {
247            if let (Some(c), Some(sid)) = (coord, sheet_id) {
248                let mut editor = VertexEditor::new(graph);
249                let cell_ref = crate::reference::CellRef::new(
250                    *sid,
251                    crate::reference::Coord::new(c.row(), c.col(), true, true),
252                );
253                let _ = editor.remove_vertex_at(cell_ref);
254            }
255        }
256        ChangeEvent::VertexMoved { id, new_coord, .. } => {
257            let mut editor = VertexEditor::new(graph);
258            let _ = editor.move_vertex(*id, *new_coord);
259        }
260        ChangeEvent::FormulaAdjusted { id, new_ast, .. } => {
261            let _ = graph.update_vertex_formula(*id, new_ast.clone());
262            graph.mark_vertex_dirty(*id);
263        }
264        ChangeEvent::DefineName {
265            name,
266            scope,
267            definition,
268        } => {
269            let mut editor = VertexEditor::new(graph);
270            let _ = editor.define_name(name, definition.clone(), *scope);
271        }
272        ChangeEvent::UpdateName {
273            name,
274            scope,
275            new_definition,
276            ..
277        } => {
278            let mut editor = VertexEditor::new(graph);
279            let _ = editor.update_name(name, new_definition.clone(), *scope);
280        }
281        ChangeEvent::DeleteName { name, scope, .. } => {
282            let mut editor = VertexEditor::new(graph);
283            let _ = editor.delete_name(name, *scope);
284        }
285        ChangeEvent::NamedRangeAdjusted {
286            name,
287            scope,
288            new_definition,
289            ..
290        } => {
291            let mut editor = VertexEditor::new(graph);
292            let _ = editor.update_name(name, new_definition.clone(), *scope);
293        }
294        ChangeEvent::SpillCommitted { anchor, new, .. } => {
295            let _ = graph.commit_spill_region_atomic_with_fault(
296                *anchor,
297                new.target_cells.clone(),
298                new.values.clone(),
299                None,
300            );
301        }
302        ChangeEvent::SpillCleared { anchor, .. } => {
303            graph.clear_spill_region(*anchor);
304        }
305        ChangeEvent::EdgeAdded { from, to } => {
306            let mut editor = VertexEditor::new(graph);
307            let _ = editor.add_edge(*from, *to);
308        }
309        ChangeEvent::EdgeRemoved { from, to } => {
310            let mut editor = VertexEditor::new(graph);
311            let _ = editor.remove_edge(*from, *to);
312        }
313        ChangeEvent::CompoundStart { description, .. } => {
314            graph.replay_structural_marker(description, true);
315        }
316        ChangeEvent::CompoundEnd { .. } | ChangeEvent::StagedFormulaCellChanged { .. } => {}
317    }
318    Ok(())
319}
320
321#[derive(Debug, Clone, Default, PartialEq)]
322pub struct ActionJournal {
323    pub name: String,
324    pub graph: GraphUndoBatch,
325    pub arrow: ArrowUndoBatch,
326    pub affected_cells: usize,
327}