From e2943455997bb7605bb0e0f61a7b86254e93b094 Mon Sep 17 00:00:00 2001 From: nihilist-narwhal <240798405+nihilist-narwhal@users.noreply.github.com> Date: Fri, 9 Oct 2026 23:25:58 -0500 Subject: [PATCH] Protection: keep hidden formulas hidden On a protected sheet a formula formatted Hidden still showed with Show Formulas on, matched Find in formulas, and showed in the formula bar when a cell of its spill range was selected. --- crates/engine/src/cmd/edit.rs | 6 +++++- crates/engine/src/display.rs | 17 +++++++++++++++++ crates/engine/src/tests.rs | 26 ++++++++++++++++++++++++++ crates/ui-egui/src/formula_bar.rs | 3 +++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/cmd/edit.rs b/crates/engine/src/cmd/edit.rs index cffe15430..cc994f00f 100644 --- a/crates/engine/src/cmd/edit.rs +++ b/crates/engine/src/cmd/edit.rs @@ -1175,7 +1175,11 @@ fn find(s: &mut Session, p: &Json) -> Result { .cells .iter() .filter_map(|(c, cell)| { - let text = if values || cell.formula.is_none() { crate::display::cell_text(&d.wb, sh, c) } else { cell.input_text() }; + let text = if values || cell.formula.is_none() || crate::display::formula_hidden(&d.wb, sh, c) { + crate::display::cell_text(&d.wb, sh, c) + } else { + cell.input_text() + }; matches(&text, &what, case, whole).then_some((c, text)) }) .collect(); diff --git a/crates/engine/src/display.rs b/crates/engine/src/display.rs index 24df503a4..e32422a4f 100644 --- a/crates/engine/src/display.rs +++ b/crates/engine/src/display.rs @@ -31,11 +31,28 @@ pub fn format(v: &Value, code: &str, wb: &Workbook) -> Formatted { format_value(v, &parsed(code), wb.date_system) } +/// Whether `c` has a formula that must not be shown: its cell is formatted Hidden on a +/// protected sheet. A spilled cell follows its anchor. +pub fn formula_hidden(wb: &Workbook, sheet: &Sheet, c: CellRef) -> bool { + if !sheet.is_protected() { + return false; + } + let at = match sheet.cell(c) { + Some(x) if x.formula.is_some() => c, + _ => match sheet.spill_ranges.iter().find(|(_, r)| r.contains(c)) { + Some((anchor, _)) => *anchor, + None => return false, + }, + }; + wb.styles.get(sheet.style_id(at)).protection.hidden +} + /// The text a cell shows (full precision General; the grid narrows General to fit the column). pub fn cell_text(wb: &Workbook, sheet: &Sheet, c: CellRef) -> String { let v = sheet.value(c); if sheet.show_formulas && let Some(f) = sheet.cell(c).and_then(|x| x.formula.as_ref()) + && !formula_hidden(wb, sheet, c) { return format!("={}", f.text); } diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index f59d6fcf0..bf85261dd 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -165,6 +165,32 @@ fn protection_allows_permitted_row_and_column_edits() { assert!(s.execute("home.deleteColumns", json!({"cols": "Z:Z"})).is_err()); } +#[test] +fn hidden_formulas_stay_hidden_on_protected_sheets() { + let mut s = s(); + s.execute("cell.set", json!({"cell": "A1", "input": "=SEQUENCE(3)*7"})).unwrap(); + s.execute("cell.set", json!({"cell": "B1", "input": "=1+1"})).unwrap(); + s.execute("home.formatCells", json!({"range": "A1", "style": {"protection": {"locked": true, "hidden": true}}})).unwrap(); + s.execute("formulas.showFormulas", json!({"on": true})).unwrap(); + let text = |s: &Session, a: &str| { + let d = s.doc().unwrap(); + let sh = d.wb.active().unwrap(); + crate::display::cell_text(&d.wb, sh, CellRef::parse(a).unwrap()) + }; + assert_eq!(text(&s, "A1"), "=SEQUENCE(3)*7"); + assert_eq!(s.execute("edit.find", json!({"what": "SEQUENCE", "all": true})).unwrap()["count"], 1); + s.execute("review.protectSheet", json!({})).unwrap(); + assert_eq!(text(&s, "A1"), "7"); + assert_eq!(text(&s, "B1"), "=1+1"); + let d = s.doc().unwrap(); + let sh = d.wb.active().unwrap(); + assert!(crate::display::formula_hidden(&d.wb, sh, CellRef::parse("A2").unwrap())); + assert!(!crate::display::formula_hidden(&d.wb, sh, CellRef::parse("B1").unwrap())); + assert!(s.execute("edit.find", json!({"what": "SEQUENCE", "all": true})).is_err()); + let r = s.execute("edit.find", json!({"what": "7", "all": true})).unwrap(); + assert_eq!(r["results"][0]["value"], "7"); +} + #[test] fn formatting_and_styles() { let mut s = s(); diff --git a/crates/ui-egui/src/formula_bar.rs b/crates/ui-egui/src/formula_bar.rs index 753585f10..d3d865009 100644 --- a/crates/ui-egui/src/formula_bar.rs +++ b/crates/ui-egui/src/formula_bar.rs @@ -77,6 +77,9 @@ fn active_input(app: &SheetApp) -> String { if r.contains(a) && let Some(f) = sh.cell(*anchor).and_then(|c| c.formula.as_ref()) { + if gridcraft_engine::display::formula_hidden(&d.wb, sh, *anchor) { + return String::new(); + } return format!("={}", f.text); } }