From 4fbd1dc337551f1d34f6f731ad2e017b1eb333c1 Mon Sep 17 00:00:00 2001 From: prxnvt <53838599+prxnvt@users.noreply.github.com> Date: Sun, 1 Nov 2026 12:16:05 -0500 Subject: [PATCH] Engine: a Hidden cell on a protected sheet hides its Python code A cell formatted Hidden on a protected sheet hid its formula but not its Python code: display::formula_hidden only looked for a formula at the cell (a Python cell was covered only while its result spilled), and the formula bar had its own check for formulas alone. Both now treat Python code as they treat a formula, so the formula bar shows nothing for a hidden Python cell, as it does for a hidden formula. Editing the cell is refused as before, because it is locked. --- crates/engine/src/display.rs | 6 +++--- crates/engine/src/tests/python_cells.rs | 20 ++++++++++++++++++++ crates/ui-egui/src/formula_bar.rs | 20 +++++++++++++++++++- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/display.rs b/crates/engine/src/display.rs index aa8bdb22d..2189d6c64 100644 --- a/crates/engine/src/display.rs +++ b/crates/engine/src/display.rs @@ -90,14 +90,14 @@ pub fn edit_text(wb: &Workbook, sheet: &Sheet, c: CellRef) -> String { if reads_back(&iso) { iso } else { raw } } -/// 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. +/// Whether `c` has a formula or Python code 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, + Some(x) if x.formula.is_some() || x.py.is_some() => c, _ => match sheet.spill_ranges.iter().find(|(_, r)| r.contains(c)) { Some((anchor, _)) => *anchor, None => return false, diff --git a/crates/engine/src/tests/python_cells.rs b/crates/engine/src/tests/python_cells.rs index ef1df605a..7971f7811 100644 --- a/crates/engine/src/tests/python_cells.rs +++ b/crates/engine/src/tests/python_cells.rs @@ -296,3 +296,23 @@ fn replace_leaves_python_code_that_would_no_longer_fit() { assert_eq!(r["skipped"], 1); assert_eq!(code(&s, 0, "A1").as_deref(), Some(long.as_str())); } + +#[test] +fn a_hidden_python_cell_on_a_protected_sheet_hides_its_code() { + let mut s = s(); + s.execute("python.set", json!({"cell": "A1", "code": "secret = 1\nsecret"})).unwrap(); + s.execute("python.set", json!({"cell": "B1", "code": "'shown'"})).unwrap(); + s.execute("home.formatCells", json!({"range": "A1", "style": {"protection": {"locked": true, "hidden": true}}})).unwrap(); + let hidden = |s: &Session, a: &str| { + let d = s.doc().unwrap(); + let sh = d.wb.active().unwrap(); + crate::display::formula_hidden(&d.wb, sh, CellRef::parse(a).unwrap()) + }; + assert!(!hidden(&s, "A1"), "an unprotected sheet hides nothing"); + s.execute("review.protectSheet", json!({})).unwrap(); + assert!(hidden(&s, "A1"), "Hidden on a protected sheet hides the code, as it hides a formula"); + assert!(!hidden(&s, "B1")); + // Editing it is refused where a formula's would be: the cell is locked. + assert!(s.execute("python.set", json!({"cell": "A1", "code": "2"})).is_err()); + assert_eq!(code(&s, 0, "A1").as_deref(), Some("secret = 1\nsecret")); +} diff --git a/crates/ui-egui/src/formula_bar.rs b/crates/ui-egui/src/formula_bar.rs index 3db0c3da4..557e3dac0 100644 --- a/crates/ui-egui/src/formula_bar.rs +++ b/crates/ui-egui/src/formula_bar.rs @@ -173,7 +173,8 @@ fn active_input(app: &SheetApp) -> String { .unwrap_or_default(); } if let Some(c) = sh.cell(a) { - if c.formula.is_some() && d.wb.styles.get(c.style).protection.hidden && sh.is_protected() { + // A Hidden cell on a protected sheet shows neither its formula nor its Python code. + if (c.formula.is_some() || c.py.is_some()) && d.wb.styles.get(c.style).protection.hidden && sh.is_protected() { return String::new(); } return if let Some(f) = &c.formula { @@ -753,6 +754,23 @@ mod spill_tests { app.run("sheet.activate", json!({"sheet": "Sheet1"})).unwrap(); check(&mut app, "A2", "=FILTER(D1:D3,D1:D3>0)", true); } + + #[test] + fn a_hidden_python_cell_on_a_protected_sheet_shows_no_code() { + let mut app = app(); + app.run("python.set", json!({"cell": "A1", "code": "secret = 1\nsecret"})).unwrap(); + app.run("python.set", json!({"cell": "B1", "code": "'shown'"})).unwrap(); + set(&mut app, "C1", "=1+1"); + app.run("home.formatCells", json!({"range": "A1:C1", "style": {"protection": {"locked": true, "hidden": true}}})).unwrap(); + app.run("home.formatCells", json!({"range": "B1", "style": {"protection": {"locked": true, "hidden": false}}})).unwrap(); + select(&mut app, "A1"); + assert_eq!(active_input(&app), "secret = 1\nsecret", "an unprotected sheet hides nothing"); + app.run("review.protectSheet", json!({})).unwrap(); + for (cell, text) in [("A1", ""), ("B1", "'shown'"), ("C1", "")] { + select(&mut app, cell); + assert_eq!(active_input(&app), text, "{cell}"); + } + } #[test] /// A disabled spill formula paints disabled and does not edit; clicking an active formula starts formula-bar editing in both light and dark themes, with accessibility checks. fn formula_bar_click_and_theme_presentation() {