mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-26 03:36:54 -06:00
Merge pull request #74 from DWAA1660/fix/70-inherit-cell-formatting
Preserve whole-sheet formatting when entering empty cells (#70)
This commit is contained in:
@@ -662,8 +662,7 @@ fn text_to_columns(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
}
|
||||
}
|
||||
for (c, t) in out {
|
||||
let old = cx.wb.sheet(sheet).and_then(|sh| sh.cell(c)).cloned();
|
||||
let cell = super::edit::input_to_cell(&t, old.as_ref(), &mut cx.wb).unwrap_or(None);
|
||||
let cell = super::edit::input_to_cell(&t, sheet, c, &mut cx.wb).unwrap_or(None);
|
||||
let shm = cx.sheet_mut(sheet)?;
|
||||
match cell {
|
||||
Some(x) => shm.cells.set(c, x),
|
||||
|
||||
@@ -119,9 +119,10 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
// ---------------------------------------------------------------- entry
|
||||
|
||||
/// What typing `input` into a cell produces (constant or formula, plus an automatic number
|
||||
/// format). Errors when the formula can't be parsed.
|
||||
pub(crate) fn input_to_cell(input: &str, old: Option<&Cell>, wb: &mut gridcraft_model::Workbook) -> Result<Option<Cell>> {
|
||||
let style = old.map(|c| c.style).unwrap_or_default();
|
||||
/// format). Inherits the destination's cell, row or column style, including its number
|
||||
/// format, before parsing. Errors when the formula can't be parsed.
|
||||
pub(crate) fn input_to_cell(input: &str, sheet: usize, at: CellRef, wb: &mut gridcraft_model::Workbook) -> Result<Option<Cell>> {
|
||||
let style = wb.sheet(sheet).map(|sh| sh.style_id(at)).unwrap_or_default();
|
||||
if input.is_empty() {
|
||||
let c = Cell { value: Value::Empty, formula: None, style };
|
||||
return Ok(if c.is_blank() { None } else { Some(c) });
|
||||
@@ -207,8 +208,7 @@ fn cell_set(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
if array {
|
||||
// Legacy Ctrl+Shift+Enter array formula over the selection.
|
||||
let range = cx.sel.current();
|
||||
let old = cx.wb.sheet(sheet).and_then(|sh| sh.cell(range.start)).cloned();
|
||||
let mut cell = input_to_cell(&input, old.as_ref(), &mut cx.wb)?.unwrap_or_default();
|
||||
let mut cell = input_to_cell(&input, sheet, range.start, &mut cx.wb)?.unwrap_or_default();
|
||||
if let Some(f) = cell.formula.as_mut() {
|
||||
std::sync::Arc::make_mut(f).array = Some(range);
|
||||
}
|
||||
@@ -217,8 +217,7 @@ fn cell_set(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
cx.touch(sheet, range.start);
|
||||
return Ok(Json::Null);
|
||||
}
|
||||
let old = cx.wb.sheet(sheet).and_then(|sh| sh.cell(at)).cloned();
|
||||
let cell = input_to_cell(&input, old.as_ref(), &mut cx.wb)?;
|
||||
let cell = input_to_cell(&input, sheet, at, &mut cx.wb)?;
|
||||
let sh = cx.sheet_mut(sheet)?;
|
||||
match cell {
|
||||
Some(c) => sh.cells.set(at, c),
|
||||
@@ -266,15 +265,16 @@ fn range_set_values(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
for (ci, v) in cols.iter().enumerate() {
|
||||
let Some(at) = start.offset(ri as i64, ci as i64) else { continue };
|
||||
let input = json_to_input(v);
|
||||
let old = cx.wb.sheet(sheet).and_then(|sh| sh.cell(at)).cloned();
|
||||
let cell = match v {
|
||||
Json::Number(n) => Some(Cell {
|
||||
value: Value::number(n.as_f64().unwrap_or(0.0)),
|
||||
formula: None,
|
||||
style: old.as_ref().map(|c| c.style).unwrap_or_default(),
|
||||
style: cx.wb.sheet(sheet).map(|sh| sh.style_id(at)).unwrap_or_default(),
|
||||
}),
|
||||
Json::Bool(b) => Some(Cell { value: Value::Bool(*b), formula: None, style: old.as_ref().map(|c| c.style).unwrap_or_default() }),
|
||||
_ => input_to_cell(&input, old.as_ref(), &mut cx.wb)?,
|
||||
Json::Bool(b) => {
|
||||
Some(Cell { value: Value::Bool(*b), formula: None, style: cx.wb.sheet(sheet).map(|sh| sh.style_id(at)).unwrap_or_default() })
|
||||
}
|
||||
_ => input_to_cell(&input, sheet, at, &mut cx.wb)?,
|
||||
};
|
||||
let sh = cx.sheet_mut(sheet)?;
|
||||
match cell {
|
||||
@@ -298,8 +298,7 @@ fn range_fill(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let input = str_param(p, "input").unwrap_or("").to_string();
|
||||
let origin = s.doc()?.selection.active;
|
||||
edit(s, |cx| {
|
||||
let old = cx.wb.sheet(sheet).and_then(|sh| sh.cell(origin)).cloned();
|
||||
let base = input_to_cell(&input, old.as_ref(), &mut cx.wb)?;
|
||||
let base = input_to_cell(&input, sheet, origin, &mut cx.wb)?;
|
||||
for r in &ranges {
|
||||
if r.count() > 2_000_000 {
|
||||
return Err(bad("range.fill", "range too large"));
|
||||
@@ -666,8 +665,7 @@ fn paste_text(s: &mut Session, p: &Json, text: &str) -> Result<Json> {
|
||||
for (ri, row) in rows.iter().enumerate() {
|
||||
for (ci, v) in row.iter().enumerate() {
|
||||
let Some(c) = at.offset(ri as i64, ci as i64) else { continue };
|
||||
let old = cx.wb.sheet(sheet).and_then(|sh| sh.cell(c)).cloned();
|
||||
let cell = input_to_cell(v, old.as_ref(), &mut cx.wb).unwrap_or_else(|_| Some(Cell::value(Value::text(v.as_str()))));
|
||||
let cell = input_to_cell(v, sheet, c, &mut cx.wb).unwrap_or_else(|_| Some(Cell::value(Value::text(v.as_str()))));
|
||||
let sh = cx.sheet_mut(sheet)?;
|
||||
match cell {
|
||||
Some(cell) => sh.cells.set(c, cell),
|
||||
@@ -1239,8 +1237,7 @@ fn replace(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
} else {
|
||||
replace_ci(&text, &what, &with)
|
||||
};
|
||||
let old = cx.wb.sheet(si).and_then(|sh| sh.cell(c)).cloned();
|
||||
let cell = input_to_cell(&new, old.as_ref(), &mut cx.wb).unwrap_or_else(|_| Some(Cell::value(Value::text(new.as_str()))));
|
||||
let cell = input_to_cell(&new, si, c, &mut cx.wb).unwrap_or_else(|_| Some(Cell::value(Value::text(new.as_str()))));
|
||||
let sh = cx.sheet_mut(si)?;
|
||||
match cell {
|
||||
Some(cell) => sh.cells.set(c, cell),
|
||||
|
||||
@@ -489,8 +489,7 @@ fn scenario_show(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
edit(s, |cx| {
|
||||
for (c, v) in cells.iter().zip(sc.values.iter()) {
|
||||
let input = super::edit::json_to_input(v);
|
||||
let old = cx.wb.sheet(si).and_then(|sh| sh.cell(*c)).cloned();
|
||||
let cell = super::edit::input_to_cell(&input, old.as_ref(), &mut cx.wb)?;
|
||||
let cell = super::edit::input_to_cell(&input, si, *c, &mut cx.wb)?;
|
||||
let sh = cx.sheet_mut(si)?;
|
||||
match cell {
|
||||
Some(cell) => sh.cells.set(*c, cell),
|
||||
|
||||
@@ -13,6 +13,77 @@ fn v(s: &Session, a: &str) -> Value {
|
||||
s.doc().unwrap().wb.active().unwrap().value(CellRef::parse(a).unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entering_empty_cells_preserves_whole_sheet_formatting() {
|
||||
let mut s = s();
|
||||
s.execute("cell.set", json!({"cell": "A1", "input": "existing"})).unwrap();
|
||||
s.execute("selection.set", json!({"range": "A1:XFD1048576"})).unwrap();
|
||||
s.execute("home.alignCenter", json!({})).unwrap();
|
||||
let border = gridcraft_model::BorderLine { style: gridcraft_model::BorderStyle::Thin, color: gridcraft_model::Color::Auto };
|
||||
s.execute("home.formatCells", json!({"style": {"borders": {"top": border, "bottom": border, "left": border, "right": border}}})).unwrap();
|
||||
let expected = {
|
||||
let d = s.doc().unwrap();
|
||||
d.wb.styles.get(d.wb.active().unwrap().style_id(CellRef::parse("B1").unwrap())).clone()
|
||||
};
|
||||
assert_eq!(expected.align.h, gridcraft_model::HAlign::Center);
|
||||
for (cell, input) in [("A1", "edited"), ("B1", "new"), ("C1", "=1+2"), ("XFD1048576", "edge")] {
|
||||
s.execute("cell.set", json!({"cell": cell, "input": input})).unwrap();
|
||||
let d = s.doc().unwrap();
|
||||
assert_eq!(d.wb.styles.get(d.wb.active().unwrap().style_id(CellRef::parse(cell).unwrap())), &expected, "{cell}");
|
||||
}
|
||||
s.execute("range.setValues", json!({"range": "D1", "values": [["text", 7, true]]})).unwrap();
|
||||
for cell in ["D1", "E1", "F1"] {
|
||||
let d = s.doc().unwrap();
|
||||
assert_eq!(d.wb.styles.get(d.wb.active().unwrap().style_id(CellRef::parse(cell).unwrap())), &expected, "{cell}");
|
||||
}
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
assert!(v(&s, "D1").is_empty());
|
||||
s.execute("edit.redo", json!({})).unwrap();
|
||||
let d = s.doc().unwrap();
|
||||
assert_eq!(d.wb.styles.get(d.wb.active().unwrap().style_id(CellRef::parse("D1").unwrap())), &expected);
|
||||
assert_eq!(d.wb.active().unwrap().cells.len(), 7);
|
||||
let (reopened, _) = gridcraft_xlsx::read_xlsx(&gridcraft_xlsx::write_xlsx(&d.wb).unwrap()).unwrap();
|
||||
for cell in ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "XFD1048576"] {
|
||||
assert_eq!(
|
||||
reopened.styles.get(reopened.active().unwrap().style_id(CellRef::parse(cell).unwrap())),
|
||||
&expected,
|
||||
"{cell} after XLSX round trip"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entry_and_text_paste_use_inherited_formats_and_cell_overrides() {
|
||||
let mut s = s();
|
||||
s.execute("home.bold", json!({"range": "B:B", "on": true})).unwrap();
|
||||
s.execute("home.formatCells", json!({"range": "3:3", "style": {"num_fmt": "@", "font": {"italic": true}}})).unwrap();
|
||||
s.execute("home.fontColor", json!({"range": "B3", "color": "#FF0000"})).unwrap();
|
||||
let expected = {
|
||||
let d = s.doc().unwrap();
|
||||
d.wb.styles.get(d.wb.active().unwrap().style_id(CellRef::parse("B3").unwrap())).clone()
|
||||
};
|
||||
s.execute("cell.set", json!({"cell": "B3", "input": "=1+2"})).unwrap();
|
||||
s.execute("cell.set", json!({"cell": "C3", "input": "00123"})).unwrap();
|
||||
s.execute("edit.paste", json!({"at": "D3", "text": "=2+3\t00456"})).unwrap();
|
||||
assert_eq!(v(&s, "B3"), Value::text("=1+2"));
|
||||
assert_eq!(v(&s, "C3"), Value::text("00123"));
|
||||
assert_eq!(v(&s, "D3"), Value::text("=2+3"));
|
||||
assert_eq!(v(&s, "E3"), Value::text("00456"));
|
||||
s.execute("cell.set", json!({"cell": "B4", "input": "12%"})).unwrap();
|
||||
let d = s.doc().unwrap();
|
||||
let sh = d.wb.active().unwrap();
|
||||
assert_eq!(d.wb.styles.get(sh.style_id(CellRef::parse("B3").unwrap())), &expected);
|
||||
for cell in ["C3", "D3", "E3"] {
|
||||
let st = d.wb.styles.get(sh.style_id(CellRef::parse(cell).unwrap()));
|
||||
assert!(st.font.italic);
|
||||
assert!(!st.font.bold, "row style takes precedence over column style");
|
||||
assert_eq!(st.num_fmt.as_str(), "@");
|
||||
}
|
||||
let percent = d.wb.styles.get(sh.style_id(CellRef::parse("B4").unwrap()));
|
||||
assert!(percent.font.bold);
|
||||
assert_ne!(percent.num_fmt.as_str(), "General");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enter_values_and_formulas() {
|
||||
let mut s = s();
|
||||
|
||||
Reference in New Issue
Block a user