Recalc: structural edits don't leave old spills behind

Inserting or deleting rows and columns moved formula cells but not the
values they had spilled, and the full recalculation that follows
reconciled spills only at the formulas' new positions. =SEQUENCE(3) in
A5 with row 1 deleted showed #SPILL! in A4 (blocked by its own old
values in A6:A7); deleting the anchor's row left 2 and 3 behind. A full
recalculation now drops all spills and lays them out again from the
formulas.

Fixes #133.
This commit is contained in:
nihilist-narwhal
2026-10-10 04:27:44 -05:00
parent fc2f00bd50
commit dcc5434bc8
2 changed files with 21 additions and 0 deletions
+10
View File
@@ -345,6 +345,16 @@ impl Calc {
/// Recalculates every formula (F9 / Ctrl+Alt+F9, and after loading).
pub fn recalc_all(&mut self, wb: &mut Workbook) {
self.rebuild(wb);
// Spills are laid out again from the formulas where they are now (a structural edit moves
// or deletes the formulas, not the values they spilled).
for i in 0..wb.sheets.len() {
if wb.sheet(i).is_some_and(|s| !s.spill_ranges.is_empty() || !s.spill.is_empty())
&& let Some(sh) = wb.sheet_mut(i)
{
sh.spill.clear();
sh.spill_ranges.clear();
}
}
let all: Vec<Key> = self.graph.nodes.keys().copied().collect();
self.run(wb, all, true);
}
+11
View File
@@ -173,6 +173,17 @@ fn range_ending_in_index_recalculates() {
assert_eq!(v(&s, "B6"), Value::Number(2.0));
}
#[test]
fn structural_edits_move_spills_with_their_formula() {
let mut s = s();
s.execute("cell.set", json!({"cell": "A5", "input": "=SEQUENCE(3)"})).unwrap();
s.execute("home.deleteRows", json!({"rows": "1:1"})).unwrap();
let col = |s: &Session| (4..=7).map(|r| v(s, &format!("A{r}"))).collect::<Vec<_>>();
assert_eq!(col(&s), [Value::Number(1.0), Value::Number(2.0), Value::Number(3.0), Value::Empty]);
s.execute("home.deleteRows", json!({"rows": "4:4"})).unwrap();
assert_eq!(col(&s), [Value::Empty, Value::Empty, Value::Empty, Value::Empty]);
}
#[test]
fn every_command_survives_empty_params() {
let mut s = Session::new();