UI: a draft typed over a hidden Python cell stays open

An ordinary draft typed over an unlocked, hidden Python cell on a protected sheet holds only what
was typed. follow_open_draft dropped it after any command, and ui.inspect hid its text, because
both checked whether the cell was a Python cell. They now check whether the draft holds code: a
draft in Python mode, or one that began on a Python cell's code (py_cell).

Test: a_draft_typed_over_a_hidden_python_cell_stays_open_through_commands (fails without the fix:
the draft was dropped).
This commit is contained in:
prxnvt
2026-11-02 02:52:48 -05:00
parent 9a094696fe
commit b5acb39356
3 changed files with 42 additions and 15 deletions
+6 -6
View File
@@ -220,18 +220,18 @@ impl SheetApp {
/// drawn, pointed from and committed on its sheet, and the point-mode anchor, held by sheet
/// index, is let go. A draft is dropped at once with a message when its sheet is gone, or when
/// it holds Python code that sheet protection now hides (a draft in Python mode, or one that
/// began on a Python cell): neither editor shows hidden code, as the Python editor window
/// closes then too.
/// began on a Python cell's code): neither editor shows hidden code, as the Python editor
/// window closes then too. A draft typed over a Python cell holds only what was typed, so it
/// stays open.
pub(crate) fn follow_open_draft(&mut self) {
let Some(ed) = self.editor.as_ref() else { return };
let Some(d) = self.session.documents().iter().find(|d| d.uid == ed.doc) else { return };
let order = editor::sheet_order(&d.wb);
let moved = ed.sheet_uid != 0 && order != ed.sheet_order;
let found = if moved { ed.sheet_in(&d.wb) } else { Some(ed.sheet) };
let hidden = found.and_then(|i| d.wb.sheet(i)).is_some_and(|sh| {
(ed.python || ed.py_cell || sh.cell(ed.cell).is_some_and(|c| c.py.is_some()))
&& gridcraft_engine::display::formula_hidden(&d.wb, sh, ed.cell)
});
let hidden = found
.and_then(|i| d.wb.sheet(i))
.is_some_and(|sh| (ed.python || ed.py_cell) && gridcraft_engine::display::formula_hidden(&d.wb, sh, ed.cell));
let why = match found {
None => Some(DRAFT_SHEET_GONE),
Some(_) if hidden => Some(DRAFT_CODE_HIDDEN),
+11 -9
View File
@@ -161,16 +161,18 @@ pub fn inspect(app: &SheetApp, ctx: &egui::Context) -> Json {
/// The open edit, for `ui.inspect`. The draft is left out (`text` is null, `hidden` true) while
/// its cell's code is hidden on a protected sheet, checked now: a protection command earlier in
/// the same batch runs before the next frame. That holds for a draft in Python mode, and for any
/// draft of a Python cell, since leaving Python mode keeps the code as an ordinary draft. The
/// sheet is the draft's own, found by identity: a sheet inserted, deleted or moved in the same
/// batch puts another at its index. With its sheet gone, such a draft is left out too.
/// the same batch runs before the next frame. That holds for a draft in Python mode, and for a
/// draft that began on a Python cell's code, since leaving Python mode keeps the code as an
/// ordinary draft; a draft typed over a Python cell holds only what was typed. The sheet is the
/// draft's own, found by identity: a sheet inserted, deleted or moved in the same batch puts
/// another at its index. With its sheet gone, such a draft is left out too.
fn editor_json(app: &SheetApp, e: &crate::editor::EditState) -> Json {
let code = |sh: &gridcraft_engine::model::Sheet| e.python || sh.cell(e.cell).is_some_and(|c| c.py.is_some());
let hidden = app.session.documents().iter().find(|d| d.uid == e.doc).is_some_and(|d| match e.sheet_in(&d.wb).and_then(|i| d.wb.sheet(i)) {
Some(sh) => code(sh) && gridcraft_engine::display::formula_hidden(&d.wb, sh, e.cell),
None => e.python || e.py_cell,
});
let code = e.python || e.py_cell;
let hidden = code
&& app.session.documents().iter().find(|d| d.uid == e.doc).is_some_and(|d| match e.sheet_in(&d.wb).and_then(|i| d.wb.sheet(i)) {
Some(sh) => gridcraft_engine::display::formula_hidden(&d.wb, sh, e.cell),
None => true,
});
let text = if hidden { Json::Null } else { json!(e.text) };
json!({"cell": e.cell.a1(), "text": text, "hidden": hidden, "formulaBar": e.from_formula_bar, "point": e.can_point(), "python": e.python})
}
+25
View File
@@ -1208,3 +1208,28 @@ fn a_draft_of_python_code_that_protection_hides_is_dropped_with_a_message() {
execute(&mut app, &ctx, "review.protectSheet", json!({}));
assert!(app.editor.as_ref().is_some_and(|e| e.text == "=2+2"), "a formula draft is unchanged");
}
#[test]
fn a_draft_typed_over_a_hidden_python_cell_stays_open_through_commands() {
// An unlocked, hidden Python cell on a protected sheet: typing replaces it, as in Excel. The
// draft holds only what was typed, so a command neither drops it nor hides it from ui.inspect.
let mut session = gridcraft_engine::Session::new();
session.new_workbook();
session.execute("python.set", json!({"cell": "B2", "code": "secret = 41"})).unwrap();
session.execute("home.formatCells", json!({"range": "B2", "style": {"protection": {"hidden": true, "locked": false}}})).unwrap();
session.execute("review.protectSheet", json!({})).unwrap();
session.execute("selection.set", json!({"cell": "B2"})).unwrap();
let mut app = SheetApp::new(session, crate::Services::default());
let ctx = egui::Context::default();
call(&mut app, &ctx, "ui.edit.begin", json!({"text": "7"}));
execute(&mut app, &ctx, "home.insertSheet", json!({"name": "Other", "before": 1}));
assert!(app.editor.as_ref().is_some_and(|e| e.text == "7"), "the typed entry stays open");
assert!(app.message.is_none(), "{:?}", app.message);
let after = inspected(&mut app, &ctx);
assert_eq!(after["editor"]["text"], "7");
assert_eq!(after["editor"]["hidden"], false);
call(&mut app, &ctx, "ui.edit.commit", json!({"move": "none"}));
let b2 = app.session.active().and_then(|d| d.wb.sheet(0)).and_then(|sh| sh.cell(gridcraft_engine::core::CellRef::new(1, 1)).cloned());
assert_eq!(b2.map(|c| (c.input_text(), c.py.is_some())), Some(("7".to_string(), false)), "the typed entry replaces the Python cell");
}