Engine: Merge Across keeps only the upper-left Python code, and unmerging unblocks a Python spill

Merge Across emptied the value and formula of the other cells in each row but
left their Python code, so a hidden cell stayed a Python cell with an empty
value and was saved as a PY formula. It now clears their whole content, as
Merge Cells and Merge & Center already did. In the same command, a merge
change evaluates again the blocked arrays that could reach the area, but only
formulas were picked, so a Python result blocked by a merged cell stayed
#SPILL! after the cells were unmerged; Python cells are picked too.
This commit is contained in:
prxnvt
2026-11-01 14:08:08 -05:00
parent 6c478475e4
commit 0524c04756
2 changed files with 38 additions and 3 deletions
+5 -3
View File
@@ -650,8 +650,7 @@ fn merge(s: &mut Session, p: &Json, how: &str) -> Result<Json> {
let others: Vec<CellRef> = sh.cells.iter_range(m).map(|(c, _)| c).filter(|c| *c != m.start).collect();
for c in others {
if let Some(mut cell) = sh.cells.get(c).cloned() {
cell.value = gridcraft_core::Value::Empty;
cell.formula = None;
cell.clear_content();
sh.set_cell(c, cell);
}
touched.push((sheet, c));
@@ -685,7 +684,10 @@ fn merge(s: &mut Session, p: &Json, how: &str) -> Result<Json> {
let blocked = sh
.cells
.iter_range(reach)
.filter(|(_, x)| x.formula.is_some() && x.value == gridcraft_core::Value::Error(gridcraft_core::CellError::Spill));
// A Python cell's result is blocked and unblocked like a formula's.
.filter(|(_, x)| {
(x.formula.is_some() || x.py.is_some()) && x.value == gridcraft_core::Value::Error(gridcraft_core::CellError::Spill)
});
touched.extend(blocked.map(|(c, _)| (sheet, c)));
}
cx.changed.extend(touched);
+33
View File
@@ -316,3 +316,36 @@ fn a_hidden_python_cell_on_a_protected_sheet_hides_its_code() {
assert!(s.execute("python.set", json!({"cell": "A1", "code": "2"})).is_err());
assert_eq!(code(&s, 0, "A1").as_deref(), Some("secret = 1\nsecret"));
}
#[test]
fn every_merge_keeps_only_the_upper_left_cells_python_code() {
let mut s = s();
for (cmd, row) in [("home.mergeAcross", 1), ("home.mergeCells", 2), ("home.mergeCenter", 3)] {
plant(&mut s, 0, &format!("A{row}"), "'kept'");
plant(&mut s, 0, &format!("B{row}"), "'merged away'");
s.execute(cmd, json!({"range": format!("A{row}:B{row}")})).unwrap();
assert_eq!(code(&s, 0, &format!("A{row}")).as_deref(), Some("'kept'"), "{cmd}");
assert_eq!(code(&s, 0, &format!("B{row}")), None, "{cmd}: the hidden cell is no Python cell");
assert_eq!(super::v(&s, &format!("B{row}")), Value::Empty, "{cmd}");
}
}
#[test]
fn unmerging_lets_a_blocked_python_result_spill_again() {
let mut s = s();
s.execute("python.set", json!({"cell": "A1", "code": "[5, 6]"})).unwrap();
{
let d = s.doc_mut().unwrap();
let wb = Arc::make_mut(&mut d.wb);
let cell = wb.sheet_mut(0).unwrap().cells.get_mut(CellRef::parse("A1").unwrap()).unwrap();
let value = Value::Array(Arc::new(gridcraft_core::Array::new(2, 1, vec![Value::Number(5.0), Value::Number(6.0)]).unwrap()));
Arc::make_mut(cell.py.as_mut().unwrap()).output =
Some(gridcraft_model::PyOutput { value, type_name: "list".into(), preview: String::new(), convertible: true });
}
s.execute("formulas.calculateNow", json!({})).unwrap();
assert_eq!(super::v(&s, "A2"), Value::Number(6.0));
s.execute("home.mergeCells", json!({"range": "A2:B2"})).unwrap();
assert_eq!(super::v(&s, "A1"), Value::Error(gridcraft_core::CellError::Spill), "a merged cell blocks the spill");
s.execute("home.unmergeCells", json!({"range": "A2:B2"})).unwrap();
assert_eq!((super::v(&s, "A1"), super::v(&s, "A2")), (Value::Number(5.0), Value::Number(6.0)), "it spills again");
}