Merge pull request #151 from nihilist-narwhal/fix-current-region

Current region: grow past 10,000 rows, in linear time
This commit is contained in:
Brandon Thomas 2026-10-10 15:35:42 -04:00
commit c4e4aeb293
1 changed files with 30 additions and 27 deletions

View File

@ -175,38 +175,34 @@ pub fn jump(sheet: &Sheet, from: CellRef, dr: i64, dc: i64) -> CellRef {
/// The contiguous data region around `c` (Ctrl+A / Ctrl+Shift+8, AutoSum, sort, tables).
pub fn current_region(sheet: &Sheet, c: CellRef) -> RangeRef {
let filled = |c: CellRef| sheet.value_ref(c).is_some_and(|v| !v.is_empty());
let mut r = RangeRef::cell(c);
for _ in 0..10_000 {
let mut grown = r;
let r0 = r.start.row.saturating_sub(1);
let r1 = (r.end.row + 1).min(MAX_ROWS - 1);
let c0 = r.start.col.saturating_sub(1);
let c1 = (r.end.col + 1).min(MAX_COLS - 1);
// Check the ring around r.
let ring = RangeRef::new(CellRef::new(r0, c0), CellRef::new(r1, c1));
let mut any = false;
for (cc, cell) in sheet.cells.iter_range(ring) {
if !r.contains(cc) && (!cell.value.is_empty() || cell.formula.is_some()) {
grown = grown.union(&RangeRef::cell(cc));
any = true;
}
let filled = |c: CellRef| sheet.cell(c).is_some_and(|x| !x.value.is_empty() || x.formula.is_some()) || sheet.spill.contains_key(&c);
// Whether a row (or column) has a filled cell between two columns (rows), inclusive.
let row_has = |row: u32, a: u32, b: u32| (a..=b).any(|col| filled(CellRef::new(row, col)));
let col_has = |col: u32, a: u32, b: u32| (a..=b).any(|row| filled(CellRef::new(row, col)));
let (mut r0, mut r1, mut c0, mut c1) = (c.row, c.row, c.col, c.col);
// Grow one side at a time while the line next to it (diagonals included) has data. Each
// pass scans only the lines next to the region, so tall regions take linear time.
loop {
let before = (r0, r1, c0, c1);
let (lo_c, hi_c) = (c0.saturating_sub(1), (c1 + 1).min(MAX_COLS - 1));
while r0 > 0 && row_has(r0 - 1, lo_c, hi_c) {
r0 -= 1;
}
for (cc, _) in sheet.spill.range(CellRef::new(r0, 0)..=CellRef::new(r1, MAX_COLS - 1)) {
if ring.contains(*cc) && !r.contains(*cc) {
grown = grown.union(&RangeRef::cell(*cc));
any = true;
}
while r1 + 1 < MAX_ROWS && row_has(r1 + 1, lo_c, hi_c) {
r1 += 1;
}
if !any || grown == r {
let (lo_r, hi_r) = (r0.saturating_sub(1), (r1 + 1).min(MAX_ROWS - 1));
while c0 > 0 && col_has(c0 - 1, lo_r, hi_r) {
c0 -= 1;
}
while c1 + 1 < MAX_COLS && col_has(c1 + 1, lo_r, hi_r) {
c1 += 1;
}
if (r0, r1, c0, c1) == before {
break;
}
r = grown;
}
if r.is_single() && !filled(c) {
return RangeRef::cell(c);
}
r
RangeRef::new(CellRef::new(r0, c0), CellRef::new(r1, c1))
}
#[cfg(test)]
@ -239,6 +235,13 @@ mod tests {
assert_eq!(current_region(&s, CellRef::new(3, 2)).a1(), "B3:D5");
assert_eq!(current_region(&s, CellRef::new(5, 4)).a1(), "B3:E6");
assert_eq!(current_region(&s, CellRef::new(20, 20)).a1(), "U21");
// Taller than 10,000 rows: it used to stop growing after 10,000 steps.
let mut s = Sheet::new("S");
for r in 0..20_000 {
s.set_value(CellRef::new(r, 0), Value::Number(r as f64));
s.set_value(CellRef::new(r, 1), Value::Number(1.0));
}
assert_eq!(current_region(&s, CellRef::new(5, 0)).a1(), "A1:B20000");
}
#[test]