mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-24 18:56:59 -06:00
fix(engine): walk stored cells in transitive precedent selection
Bound the all-level walk to the sheet sparse cell store instead of iterating every position of each precedent area: formulas late in a wide area are no longer skipped, blank positions are never selected, and only formula cells extend the frontier. Adds a regression test (=SUM(A1:Z1) with a formula at Z1) and silences two clippy lints in the same function. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
//! Formulas tab: AutoSum, Insert Function, names, auditing, calculation.
|
||||
|
||||
use gridcraft_core::{CellRef, RangeRef};
|
||||
use gridcraft_core::{CellRef, RangeRef, Value};
|
||||
use gridcraft_model::{CalcMode, DefinedName, Formula};
|
||||
use serde_json::{Value as Json, json};
|
||||
|
||||
@@ -485,7 +485,6 @@ fn trace_dependents(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
/// another sheet, jump there. `all` walks every level (Ctrl+Shift+[ / Ctrl+Shift+]).
|
||||
fn select_trace_cells(s: &mut Session, p: &Json, precedents: bool) -> Result<Json> {
|
||||
use std::collections::BTreeSet;
|
||||
const CAP: usize = 4096;
|
||||
let d = s.doc()?;
|
||||
let at = cell_param(s, p, "cell")?.unwrap_or(d.selection.active);
|
||||
let sheet = d.wb.active_sheet;
|
||||
@@ -495,15 +494,23 @@ fn select_trace_cells(s: &mut Session, p: &Json, precedents: bool) -> Result<Jso
|
||||
let mut seen: BTreeSet<(usize, CellRef)> = BTreeSet::new();
|
||||
let mut frontier = vec![(sheet, at)];
|
||||
seen.insert((sheet, at));
|
||||
loop {
|
||||
let Some((ks, kc)) = frontier.pop() else { break };
|
||||
while let Some((ks, kc)) = frontier.pop() {
|
||||
if precedents {
|
||||
for a in d.calc.graph.precedents_of((ks, kc)) {
|
||||
if all {
|
||||
for c in a.range.iter().take(CAP) {
|
||||
// Walk the sheet's stored cells inside the precedent area: blanks are
|
||||
// never selected and only formula cells extend the frontier, so a
|
||||
// formula late in a large range is not skipped.
|
||||
let Some(sh) = d.wb.sheet(a.sheet) else { continue };
|
||||
for (c, cell) in sh.cells.iter() {
|
||||
if !a.range.contains(c) || matches!(cell.value, Value::Empty) {
|
||||
continue;
|
||||
}
|
||||
if seen.insert((a.sheet, c)) {
|
||||
found.insert((a.sheet, c.a1()));
|
||||
frontier.push((a.sheet, c));
|
||||
if cell.formula.is_some() {
|
||||
frontier.push((a.sheet, c));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -538,7 +545,6 @@ fn select_trace_cells(s: &mut Session, p: &Json, precedents: bool) -> Result<Jso
|
||||
if si != sheet {
|
||||
params["sheet"] = json!(si);
|
||||
}
|
||||
drop(d);
|
||||
s.execute("selection.set", params)
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,23 @@ fn ctrl_shift_bracket_walks_every_level() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transitive_walk_reaches_formulas_late_in_a_wide_area() {
|
||||
// =SUM(A1:Z1) has a wide precedent area; a formula stored late in it must still be
|
||||
// walked, and blank positions must not be selected (#1865 review).
|
||||
let mut s = Session::new();
|
||||
s.new_workbook();
|
||||
s.execute("cell.set", json!({"cell": "Z1", "input": "5"})).unwrap();
|
||||
s.execute("cell.set", json!({"cell": "AB1", "input": "=SUM(A1:Z1)"})).unwrap();
|
||||
s.execute("cell.set", json!({"cell": "AD1", "input": "=AB1*2"})).unwrap();
|
||||
let mut h = harness(s);
|
||||
h.state_mut().session.execute("selection.set", json!({"range": "AD1"})).unwrap();
|
||||
key(&mut h, Key::OpenBracket, Modifiers::CTRL | Modifiers::SHIFT);
|
||||
let r = ranges(&h);
|
||||
assert!(r.contains(&"Z1".to_string()), "the formula late in the area is reached, got {r:?}");
|
||||
assert_eq!(r.len(), 2, "only the stored cells (AB1, Z1) are selected, no blanks, got {r:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_precedents_leaves_selection_untouched() {
|
||||
let mut h = harness(chain());
|
||||
|
||||
Reference in New Issue
Block a user