diff --git a/crates/engine/src/cmd/data.rs b/crates/engine/src/cmd/data.rs index 383624b70..3a2e476ac 100644 --- a/crates/engine/src/cmd/data.rs +++ b/crates/engine/src/cmd/data.rs @@ -1127,10 +1127,10 @@ fn circle_invalid(s: &mut Session, _: &Json) -> Result { // ---------------------------------------------------------------- outline fn outline_lines(s: &Session, p: &Json) -> Result<(bool, u32, u32)> { - if let Some(r) = str_param(p, "rows").and_then(RangeRef::parse) { + if let Some(r) = str_param(p, "rows").and_then(parse_lines) { return Ok((true, r.start.row, r.end.row)); } - if let Some(r) = str_param(p, "cols").and_then(RangeRef::parse) { + if let Some(r) = str_param(p, "cols").and_then(parse_lines) { return Ok((false, r.start.col, r.end.col)); } let r = s.doc()?.selection.current(); @@ -1493,6 +1493,20 @@ mod tests { } } + #[test] + fn single_row_and_column_parameters() { + let mut s = Session::new(); + s.new_workbook(); + s.execute("range.setValues", json!({"range": "A1", "values": [[1, 2], [3, 4]]})).unwrap(); + s.execute("home.insertRows", json!({"rows": "2"})).unwrap(); + s.execute("home.insertColumns", json!({"cols": "B"})).unwrap(); + let v = |s: &mut Session, a: &str| s.execute("cell.get", json!({"cell": a})).unwrap()["value"].clone(); + assert_eq!(v(&mut s, "C3"), json!(4.0)); + s.execute("home.hideRows", json!({"rows": "1"})).unwrap(); + s.execute("data.group", json!({"cols": "C"})).unwrap(); + assert!(s.execute("home.deleteRows", json!({"rows": "0"})).is_err()); + } + #[test] fn split() { assert_eq!(split_delimited("a,b,,c", &[','], false, '"'), ["a", "b", "", "c"]); diff --git a/crates/engine/src/cmd/format.rs b/crates/engine/src/cmd/format.rs index 4712351ca..b7f2e13f8 100644 --- a/crates/engine/src/cmd/format.rs +++ b/crates/engine/src/cmd/format.rs @@ -940,7 +940,7 @@ fn line_targets(s: &Session, p: &Json, rows: bool) -> Result> { return t .split(',') .map(|part| { - let r = RangeRef::parse(part).ok_or_else(|| bad(key, format!("not a range: {part}")))?; + let r = parse_lines(part).ok_or_else(|| bad(key, format!("not a range: {part}")))?; Ok(if rows { (r.start.row, r.end.row) } else { (r.start.col, r.end.col) }) }) .collect(); diff --git a/crates/engine/src/cmd/mod.rs b/crates/engine/src/cmd/mod.rs index 50865a2b7..2be06318c 100644 --- a/crates/engine/src/cmd/mod.rs +++ b/crates/engine/src/cmd/mod.rs @@ -246,6 +246,11 @@ pub(crate) fn target_ranges(s: &Session, p: &Json) -> Result> { Ok(s.doc()?.selection.ranges.clone()) } +/// A `rows`/`cols` parameter: `3:5` and `B:C`, or a single `3` or `B`. +pub(crate) fn parse_lines(t: &str) -> Option { + RangeRef::parse(t).or_else(|| if t.contains(':') { None } else { RangeRef::parse(&format!("{t}:{t}")) }) +} + pub(crate) fn target_range(s: &Session, p: &Json) -> Result { Ok(target_ranges(s, p)?.first().copied().unwrap_or_default()) } diff --git a/crates/engine/src/cmd/sheet.rs b/crates/engine/src/cmd/sheet.rs index 7870dee4e..a81634e9a 100644 --- a/crates/engine/src/cmd/sheet.rs +++ b/crates/engine/src/cmd/sheet.rs @@ -63,7 +63,7 @@ pub fn specs() -> Vec { fn lines(s: &Session, p: &Json, axis: Axis) -> Result<(u32, u32)> { let key = if axis == Axis::Rows { "rows" } else { "cols" }; let r = match str_param(p, key).or_else(|| str_param(p, "range")) { - Some(t) => RangeRef::parse(t).ok_or_else(|| bad(key, format!("not a range: {t}")))?, + Some(t) => parse_lines(t).ok_or_else(|| bad(key, format!("not a range: {t}")))?, None => s.doc()?.selection.current(), }; let (a, b) = if axis == Axis::Rows { (r.start.row, r.end.row) } else { (r.start.col, r.end.col) };