From 79a4cbac57e4a708e2a8e339ec0ec7ecb7788116 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 3 Nov 2026 19:01:13 +0000 Subject: [PATCH] FORMULATEXT and the cell editor keep a hidden formula's source hidden --- crates/calc/src/eval/call.rs | 9 ++++++--- crates/engine/src/tests.rs | 13 +++++++++++++ crates/ui-egui/src/app/editing.rs | 5 +++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/crates/calc/src/eval/call.rs b/crates/calc/src/eval/call.rs index 08d563d43..64300188f 100644 --- a/crates/calc/src/eval/call.rs +++ b/crates/calc/src/eval/call.rs @@ -52,13 +52,16 @@ impl<'h> Evaluator<'h> { "ISFORMULA" | "FORMULATEXT" => { let Some(Ev::R(a)) = args.first().map(|a| self.eval(a)) else { return err(CellError::NA) }; let Some(a) = a.first() else { return err(CellError::NA) }; - let f = self.host.workbook().sheet(a.sheet).and_then(|s| s.cell(a.range.start)).and_then(|c| c.formula.clone()); + let wb = self.host.workbook(); + let f = wb.sheet(a.sheet).and_then(|s| s.cell(a.range.start)).and_then(|c| c.formula.clone()); + // A formula formatted Hidden on a protected sheet stays hidden from FORMULATEXT too. + let hidden = wb.sheet(a.sheet).is_some_and(|s| s.is_protected() && wb.styles.get(s.style_id(a.range.start)).protection.hidden); if name == "ISFORMULA" { Ev::V(Value::Bool(f.is_some())) } else { match f { - Some(f) => Ev::V(Value::text(format!("={}", f.text))), - None => err(CellError::NA), + Some(f) if !hidden => Ev::V(Value::text(format!("={}", f.text))), + _ => err(CellError::NA), } } } diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index fe42b6977..a811b01b9 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -5572,3 +5572,16 @@ fn clearing_links_and_notes_keeps_allow_edit_ranges() { let sh = s.doc().unwrap().wb.active().unwrap().clone(); assert!(sh.hyperlinks.is_empty() && sh.comments.is_empty()); } + +#[test] +fn formulatext_does_not_reveal_a_hidden_formula() { + let mut s = s(); + s.execute("cell.set", json!({"cell": "A1", "input": "=1+1"})).unwrap(); + s.execute("home.formatCells", json!({"range": "A1", "style": {"protection": {"locked": false, "hidden": true}}})).unwrap(); + s.execute("home.formatCells", json!({"range": "B1", "style": {"protection": {"locked": false}}})).unwrap(); + s.execute("cell.set", json!({"cell": "B1", "input": "=FORMULATEXT(A1)"})).unwrap(); + assert_eq!(v(&s, "B1"), Value::from("=1+1"), "not protected yet"); + s.execute("review.protectSheet", json!({})).unwrap(); + s.execute("cell.set", json!({"cell": "B1", "input": "=FORMULATEXT(A1)"})).unwrap(); + assert_eq!(v(&s, "B1"), Value::Error(gridcraft_core::CellError::NA)); +} diff --git a/crates/ui-egui/src/app/editing.rs b/crates/ui-egui/src/app/editing.rs index 8682a435c..a5ac8a176 100644 --- a/crates/ui-egui/src/app/editing.rs +++ b/crates/ui-egui/src/app/editing.rs @@ -50,6 +50,11 @@ impl SheetApp { self.session.mode = gridcraft_engine::Mode::Edit; return; } + // A hidden formula's source isn't loaded into an edit (a blank edit would also write over + // it on commit): no edit starts. Typing still replaces the cell. + if text.is_none() && gridcraft_engine::display::formula_hidden(&d.wb, sh, at) { + return; + } let locale = self.ui.language.formula_locale(); let current = if sh.cell_pictures.contains_key(&at) { String::new()