Skip to main content

cell_sheet_core/
engine.rs

1use crate::formula::deps::{mark_dirty, recalculate, set_formula, DepGraph};
2use crate::model::{CellPos, Sheet};
3
4/// Coordinates sheet mutations with dependency graph maintenance.
5///
6/// `Sheet` remains the storage type and `DepGraph` remains the graph type, but
7/// callers that mutate raw cell contents should go through this wrapper so
8/// formula edges, dirty marking, and recalculation stay in sync.
9pub struct SheetEngine<'a> {
10    sheet: &'a mut Sheet,
11    deps: &'a mut DepGraph,
12}
13
14impl<'a> SheetEngine<'a> {
15    pub fn new(sheet: &'a mut Sheet, deps: &'a mut DepGraph) -> Self {
16        Self { sheet, deps }
17    }
18
19    pub fn set_cell_raw(&mut self, pos: CellPos, raw: &str) {
20        if raw.starts_with('=') {
21            set_formula(self.sheet, self.deps, pos, raw);
22        } else {
23            self.sheet.set_cell(pos, raw);
24            self.deps.remove(pos);
25        }
26        mark_dirty(self.sheet, self.deps, pos);
27    }
28
29    pub fn clear_cell(&mut self, pos: CellPos) {
30        self.sheet.clear_cell(pos);
31        self.deps.remove(pos);
32        mark_dirty(self.sheet, self.deps, pos);
33    }
34
35    pub fn write_cell_raw(&mut self, pos: CellPos, raw: &str) {
36        if raw.is_empty() {
37            self.clear_cell(pos);
38        } else {
39            self.set_cell_raw(pos, raw);
40        }
41    }
42
43    pub fn write_cell_raw_and_recalculate(&mut self, pos: CellPos, raw: &str) {
44        self.write_cell_raw(pos, raw);
45        self.recalculate();
46    }
47
48    pub fn set_cell_raw_and_recalculate(&mut self, pos: CellPos, raw: &str) {
49        self.set_cell_raw(pos, raw);
50        self.recalculate();
51    }
52
53    pub fn recalculate(&mut self) {
54        recalculate(self.sheet, self.deps);
55    }
56
57    pub fn sort_by_column_and_recalculate(&mut self, col: usize, ascending: bool) {
58        self.sheet.sort_by_column(col, ascending);
59        self.rebuild_formulas_and_recalculate();
60    }
61
62    pub fn rebuild_formulas_and_recalculate(&mut self) {
63        *self.deps = DepGraph::new();
64        let formula_cells: Vec<_> = self
65            .sheet
66            .cells
67            .iter()
68            .filter(|(_, cell)| cell.raw.starts_with('='))
69            .map(|(pos, cell)| (*pos, cell.raw.clone()))
70            .collect();
71        for (pos, raw) in formula_cells {
72            set_formula(self.sheet, self.deps, pos, &raw);
73        }
74        self.recalculate();
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81    use crate::model::CellValue;
82
83    #[test]
84    fn write_value_updates_dependents() {
85        let mut sheet = Sheet::new();
86        let mut deps = DepGraph::new();
87        SheetEngine::new(&mut sheet, &mut deps).set_cell_raw((0, 0), "10");
88        SheetEngine::new(&mut sheet, &mut deps).write_cell_raw_and_recalculate((0, 1), "=A1+5");
89
90        assert_eq!(
91            sheet.get_cell((0, 1)).unwrap().value,
92            CellValue::Number(15.0)
93        );
94
95        SheetEngine::new(&mut sheet, &mut deps).write_cell_raw_and_recalculate((0, 0), "20");
96
97        assert_eq!(
98            sheet.get_cell((0, 1)).unwrap().value,
99            CellValue::Number(25.0)
100        );
101    }
102
103    #[test]
104    fn rewriting_formula_as_value_drops_edges() {
105        let mut sheet = Sheet::new();
106        let mut deps = DepGraph::new();
107        SheetEngine::new(&mut sheet, &mut deps).set_cell_raw((0, 0), "10");
108        SheetEngine::new(&mut sheet, &mut deps).write_cell_raw_and_recalculate((0, 1), "=A1+5");
109
110        assert!(deps.dependencies.contains_key(&(0, 1)));
111
112        SheetEngine::new(&mut sheet, &mut deps).write_cell_raw_and_recalculate((0, 1), "plain");
113
114        assert!(!deps.dependencies.contains_key(&(0, 1)));
115        assert!(!deps
116            .dependents
117            .get(&(0, 0))
118            .map(|set| set.contains(&(0, 1)))
119            .unwrap_or(false));
120    }
121
122    #[test]
123    fn set_empty_cell_preserves_extent() {
124        let mut sheet = Sheet::new();
125        let mut deps = DepGraph::new();
126
127        SheetEngine::new(&mut sheet, &mut deps).set_cell_raw((1, 1), "");
128
129        assert_eq!(sheet.row_count, 2);
130        assert_eq!(sheet.col_count, 2);
131        assert!(sheet.get_cell((1, 1)).is_some());
132    }
133
134    #[test]
135    fn clear_cell_removes_cell_and_updates_dependents() {
136        let mut sheet = Sheet::new();
137        let mut deps = DepGraph::new();
138        SheetEngine::new(&mut sheet, &mut deps).set_cell_raw((0, 0), "10");
139        SheetEngine::new(&mut sheet, &mut deps).write_cell_raw_and_recalculate((0, 1), "=A1+5");
140
141        SheetEngine::new(&mut sheet, &mut deps).write_cell_raw_and_recalculate((0, 0), "");
142
143        assert!(sheet.get_cell((0, 0)).is_none());
144        assert_eq!(
145            sheet.get_cell((0, 1)).unwrap().value,
146            CellValue::Number(5.0)
147        );
148    }
149
150    #[test]
151    fn sort_rebuilds_formula_graph() {
152        let mut sheet = Sheet::new();
153        let mut deps = DepGraph::new();
154        SheetEngine::new(&mut sheet, &mut deps).set_cell_raw((0, 0), "2");
155        SheetEngine::new(&mut sheet, &mut deps).set_cell_raw((1, 0), "1");
156        SheetEngine::new(&mut sheet, &mut deps).write_cell_raw_and_recalculate((0, 1), "=A1*10");
157
158        SheetEngine::new(&mut sheet, &mut deps).sort_by_column_and_recalculate(0, true);
159        SheetEngine::new(&mut sheet, &mut deps).set_cell_raw_and_recalculate((0, 0), "3");
160
161        assert_eq!(
162            sheet.get_cell((1, 1)).unwrap().value,
163            CellValue::Number(30.0)
164        );
165    }
166
167    #[test]
168    fn rebuilds_formula_graph_for_loaded_sheet() {
169        let mut sheet = Sheet::new();
170        sheet.set_cell((0, 0), "10");
171        sheet.set_cell((0, 1), "=A1+5");
172        let mut deps = DepGraph::new();
173
174        SheetEngine::new(&mut sheet, &mut deps).rebuild_formulas_and_recalculate();
175
176        assert!(deps.dependencies.contains_key(&(0, 1)));
177        assert_eq!(
178            sheet.get_cell((0, 1)).unwrap().value,
179            CellValue::Number(15.0)
180        );
181    }
182}