Merge pull request #206 from mikl0s/
Cell references: an invalid reference is an error instead of the active cell
This commit is contained in:
commit
ff516650da
|
|
@ -759,7 +759,7 @@ fn text_to_columns(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
p.get("fixedWidths").and_then(Json::as_array).map(|a| a.iter().filter_map(|x| x.as_u64().map(|v| v as usize)).collect());
|
||||
let consecutive = bool_param(p, "treatConsecutive").unwrap_or(false);
|
||||
let quote = str_param(p, "textQualifier").and_then(|q| q.chars().next()).unwrap_or('"');
|
||||
let dest = cell_param(p, "destination").unwrap_or(r.start);
|
||||
let dest = cell_param_on(s, p, "destination", sheet)?.unwrap_or(r.start);
|
||||
edit(s, |cx| {
|
||||
let Some(sh) = cx.wb.sheet(sheet) else { return Ok(()) };
|
||||
let mut out: Vec<(CellRef, String)> = Vec::new();
|
||||
|
|
@ -998,7 +998,7 @@ fn compare_op(op: CfOperator, n: f64, a: Option<f64>, b: Option<f64>) -> bool {
|
|||
|
||||
fn validate_cmd(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let d = s.doc()?;
|
||||
let at = cell_param(p, "cell").unwrap_or(d.selection.active);
|
||||
let at = cell_param(s, p, "cell")?.unwrap_or(d.selection.active);
|
||||
let input = str_param(p, "input").unwrap_or("");
|
||||
match check_validation(&d.wb, d.wb.active_sheet, at, input) {
|
||||
None => Ok(json!({"ok": true})),
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ pub(crate) fn input_to_cell(input: &str, sheet: usize, at: CellRef, wb: &mut gri
|
|||
|
||||
fn cell_set(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let sheet = target_sheet(s, p)?;
|
||||
let at = match cell_param(p, "cell") {
|
||||
let at = match cell_param_on(s, p, "cell", sheet)? {
|
||||
Some(c) => c,
|
||||
None => s.doc()?.selection.active,
|
||||
};
|
||||
|
|
@ -346,8 +346,9 @@ fn selection_set(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
s.doc_mut()?.wb_switch_sheet(i);
|
||||
}
|
||||
let ranges = target_ranges(s, p)?;
|
||||
let active = cell_param(s, p, "active")?;
|
||||
let d = s.doc_mut()?;
|
||||
let active = cell_param(p, "active").or_else(|| ranges.last().map(|r| r.start)).unwrap_or_default();
|
||||
let active = active.or_else(|| ranges.last().map(|r| r.start)).unwrap_or_default();
|
||||
let mut sel = crate::Selection { active, anchor: ranges.last().map(|r| r.start).unwrap_or(active), ranges };
|
||||
if let Some(sh) = d.wb.active() {
|
||||
sel.expand_merges(sh);
|
||||
|
|
@ -766,7 +767,7 @@ fn paste(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
|
||||
fn paste_text(s: &mut Session, p: &Json, text: &str) -> Result<Json> {
|
||||
let sheet = target_sheet(s, p)?;
|
||||
let at = cell_param(p, "at").unwrap_or(s.doc()?.selection.current().start);
|
||||
let at = cell_param_on(s, p, "at", sheet)?.unwrap_or(s.doc()?.selection.current().start);
|
||||
let rows: Vec<Vec<String>> = parse_tsv(text);
|
||||
// The HTML flavour, when the host passed it: the number behind each cell's shown text.
|
||||
let html = str_param(p, "html").map(crate::clip_html::table_cells).unwrap_or_default();
|
||||
|
|
@ -894,14 +895,14 @@ fn paste_special(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
}
|
||||
let dest_sheet = target_sheet(s, p)?;
|
||||
let sel = s.doc()?.selection.current();
|
||||
let at = cell_param(p, "at").unwrap_or(sel.start);
|
||||
let at = cell_param_on(s, p, "at", dest_sheet)?.unwrap_or(sel.start);
|
||||
let src = clip.range;
|
||||
let (h, w) = if transpose { (src.width(), src.height()) } else { (src.height(), src.width()) };
|
||||
// Tile the copy over a larger destination that is a multiple of its size.
|
||||
let tile = sel.height() % h == 0
|
||||
&& sel.width() % w == 0
|
||||
&& !sel.is_single()
|
||||
&& cell_param(p, "at").is_none()
|
||||
&& cell_param_on(s, p, "at", dest_sheet)?.is_none()
|
||||
&& !sel.is_full_cols()
|
||||
&& !sel.is_full_rows()
|
||||
&& sel.count() <= 4_000_000;
|
||||
|
|
|
|||
|
|
@ -444,7 +444,7 @@ fn move_chart(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
Some(n) => Some(s.doc()?.wb.sheet_index(n).ok_or_else(|| bad("chart.move", "no such sheet"))?),
|
||||
None => None,
|
||||
};
|
||||
let at = cell_param(p, "at");
|
||||
let at = cell_param_on(s, p, "at", target.unwrap_or(si))?;
|
||||
edit(s, |cx| {
|
||||
let mut chart = cx.sheet_mut(si)?.charts.remove(ci);
|
||||
if let Some(a) = at {
|
||||
|
|
@ -951,7 +951,7 @@ fn checkbox(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
fn toggle_checkbox(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let si = target_sheet(s, p)?;
|
||||
let d = s.doc()?;
|
||||
let cells: Vec<CellRef> = match cell_param(p, "cell") {
|
||||
let cells: Vec<CellRef> = match cell_param_on(s, p, "cell", si)? {
|
||||
Some(c) => vec![c],
|
||||
None => d.selection.ranges.iter().flat_map(|r| r.iter().take(100_000).collect::<Vec<_>>()).collect(),
|
||||
};
|
||||
|
|
@ -1023,7 +1023,7 @@ fn new_cell_style(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
let name = str_param(p, "name").ok_or_else(|| bad("home.newCellStyle", "missing `name`"))?.to_string();
|
||||
let d = s.doc()?;
|
||||
let sh = d.wb.active().ok_or(EngineError::NoDocument)?;
|
||||
let c = cell_param(p, "fromCell").unwrap_or(d.selection.active);
|
||||
let c = cell_param(s, p, "fromCell")?.unwrap_or(d.selection.active);
|
||||
let style = d.wb.styles.get(sh.style_id(c)).clone();
|
||||
edit(s, |cx| {
|
||||
cx.wb.cell_styles.retain(|(n, _)| !n.eq_ignore_ascii_case(&name));
|
||||
|
|
|
|||
|
|
@ -444,7 +444,7 @@ fn create_from_selection(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
|
||||
fn trace_precedents(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let d = s.doc()?;
|
||||
let at = cell_param(p, "cell").unwrap_or(d.selection.active);
|
||||
let at = cell_param(s, p, "cell")?.unwrap_or(d.selection.active);
|
||||
let areas = d.calc.graph.precedents_of((d.wb.active_sheet, at));
|
||||
let v: Vec<Json> = areas.iter().map(|a| json!({"sheet": d.wb.sheet(a.sheet).map(|s| s.name.clone()), "range": a.range.a1()})).collect();
|
||||
Ok(Json::Array(v))
|
||||
|
|
@ -452,7 +452,7 @@ fn trace_precedents(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
|
||||
fn trace_dependents(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let d = s.doc()?;
|
||||
let at = cell_param(p, "cell").unwrap_or(d.selection.active);
|
||||
let at = cell_param(s, p, "cell")?.unwrap_or(d.selection.active);
|
||||
let mut deps = Vec::new();
|
||||
d.calc.graph.dependents(d.wb.active_sheet, at, &mut deps);
|
||||
deps.sort();
|
||||
|
|
@ -493,7 +493,7 @@ fn error_checking(s: &mut Session, _: &Json) -> Result<Json> {
|
|||
|
||||
fn evaluate_formula(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let d = s.doc()?;
|
||||
let at = cell_param(p, "cell").unwrap_or(d.selection.active);
|
||||
let at = cell_param(s, p, "cell")?.unwrap_or(d.selection.active);
|
||||
let sheet = d.wb.active_sheet;
|
||||
let text = match str_param(p, "formula") {
|
||||
Some(f) => f.trim_start_matches('=').to_string(),
|
||||
|
|
@ -525,7 +525,7 @@ fn evaluate_formula(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
fn evaluate(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let d = s.doc()?;
|
||||
let f = str_param(p, "formula").ok_or_else(|| bad("formulas.evaluate", "missing `formula`"))?;
|
||||
let at = cell_param(p, "cell").unwrap_or(d.selection.active);
|
||||
let at = cell_param(s, p, "cell")?.unwrap_or(d.selection.active);
|
||||
let v = gridcraft_calc::evaluate(&d.wb, d.wb.active_sheet, at, f);
|
||||
Ok(crate::cmd::inspect::value_json(&v))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -666,7 +666,7 @@ pub fn build_chart(s: &Session, p: &Json) -> Result<(usize, Chart)> {
|
|||
return Err(EngineError::Other("Select data for the chart first.".into()));
|
||||
}
|
||||
let id = d.wb.next_object_id();
|
||||
let at = cell_param(p, "at").unwrap_or_else(|| CellRef::new(r.start.row, r.end.col + 2));
|
||||
let at = cell_param_on(s, p, "at", sheet)?.unwrap_or_else(|| CellRef::new(r.start.row, r.end.col + 2));
|
||||
let title = str_param(p, "title").map(str::to_string).or_else(|| {
|
||||
if series.len() == 1 {
|
||||
series[0].name.as_ref().map(|n| gridcraft_calc::evaluate(&d.wb, sheet, at, n).display())
|
||||
|
|
@ -722,6 +722,7 @@ fn find_chart(s: &Session, p: &Json) -> Result<(usize, usize)> {
|
|||
|
||||
fn chart_set(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let (si, ci) = find_chart(s, p)?;
|
||||
let at = cell_param_on(s, p, "at", si)?;
|
||||
edit(s, |cx| {
|
||||
let sh = cx.sheet_mut(si)?;
|
||||
let Some(c) = sh.charts.get_mut(ci) else { return Ok(Json::Null) };
|
||||
|
|
@ -755,7 +756,7 @@ fn chart_set(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
if let Some(t) = p.get("yTitle") {
|
||||
c.y_title = t.as_str().map(str::to_string);
|
||||
}
|
||||
if let Some(at) = cell_param(p, "at") {
|
||||
if let Some(at) = at {
|
||||
c.anchor.cell = at;
|
||||
}
|
||||
if let Some(w) = f64_param(p, "width") {
|
||||
|
|
@ -838,7 +839,7 @@ fn chart_delete(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
|
||||
fn insert_sparkline(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let src = str_param(p, "range").ok_or_else(|| bad("insert.sparkline", "missing `range`"))?.to_string();
|
||||
let loc = cell_param(p, "location").ok_or_else(|| bad("insert.sparkline", "missing `location`"))?;
|
||||
let loc = cell_param(s, p, "location")?.ok_or_else(|| bad("insert.sparkline", "missing `location`"))?;
|
||||
let kind = match str_param(p, "type") {
|
||||
Some("column") => SparklineKind::Column,
|
||||
Some("winLoss") => SparklineKind::WinLoss,
|
||||
|
|
@ -897,7 +898,7 @@ fn insert_picture(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
let (w, h) = crate::io::image_size(&data).unwrap_or((320, 240));
|
||||
let d = s.doc()?;
|
||||
let id = d.wb.next_object_id();
|
||||
let at = cell_param(p, "at").unwrap_or(d.selection.active);
|
||||
let at = cell_param(s, p, "at")?.unwrap_or(d.selection.active);
|
||||
let scale = (640.0 / w.max(1) as f32).min(1.0);
|
||||
let img = Image {
|
||||
id,
|
||||
|
|
@ -960,7 +961,7 @@ fn insert_shape(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
};
|
||||
let d = s.doc()?;
|
||||
let id = d.wb.next_object_id();
|
||||
let at = cell_param(p, "at").unwrap_or(d.selection.active);
|
||||
let at = cell_param(s, p, "at")?.unwrap_or(d.selection.active);
|
||||
let fill =
|
||||
super::format::color_param(p.get("fill")).unwrap_or(if kind == ShapeKind::TextBox { Color::rgb(255, 255, 255) } else { Color::Theme(4, 0) });
|
||||
let line = super::format::color_param(p.get("line")).unwrap_or(if kind == ShapeKind::TextBox {
|
||||
|
|
@ -1040,6 +1041,7 @@ fn move_object(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
let id = u32_param(p, "id").ok_or_else(|| bad("object.move", "missing `id`"))?;
|
||||
let kind = str_param(p, "kind").unwrap_or("").to_string();
|
||||
let sheet = s.doc()?.wb.active_sheet;
|
||||
let at = cell_param(s, p, "at")?;
|
||||
edit(s, |cx| {
|
||||
let sh = cx.sheet_mut(sheet)?;
|
||||
let anchor = match kind.as_str() {
|
||||
|
|
@ -1049,7 +1051,7 @@ fn move_object(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
_ => None,
|
||||
};
|
||||
let Some(a) = anchor else { return Err(bad("object.move", "no such object")) };
|
||||
if let Some(at) = cell_param(p, "at") {
|
||||
if let Some(at) = at {
|
||||
a.cell = at;
|
||||
}
|
||||
if let Some(v) = f64_param(p, "dx") {
|
||||
|
|
@ -1096,7 +1098,7 @@ fn set_anchor_mode(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
|
||||
fn insert_link(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let sheet = target_sheet(s, p)?;
|
||||
let at = cell_param(p, "cell").unwrap_or(s.doc()?.selection.active);
|
||||
let at = cell_param_on(s, p, "cell", sheet)?.unwrap_or(s.doc()?.selection.active);
|
||||
if bool_param(p, "remove").unwrap_or(false) {
|
||||
return edit(s, |cx| {
|
||||
cx.sheet_mut(sheet)?.hyperlinks.remove(&at);
|
||||
|
|
@ -1128,7 +1130,7 @@ fn insert_link(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
|
||||
fn comment(s: &mut Session, p: &Json, threaded: bool) -> Result<Json> {
|
||||
let sheet = target_sheet(s, p)?;
|
||||
let at = cell_param(p, "cell").unwrap_or(s.doc()?.selection.active);
|
||||
let at = cell_param_on(s, p, "cell", sheet)?.unwrap_or(s.doc()?.selection.active);
|
||||
let text = str_param(p, "text").unwrap_or("").to_string();
|
||||
let author = str_param(p, "author").map(str::to_string).unwrap_or_else(|| s.prefs.user_name.clone());
|
||||
edit(s, |cx| {
|
||||
|
|
@ -1142,7 +1144,7 @@ fn comment(s: &mut Session, p: &Json, threaded: bool) -> Result<Json> {
|
|||
|
||||
fn reply_comment(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let sheet = target_sheet(s, p)?;
|
||||
let at = cell_param(p, "cell").unwrap_or(s.doc()?.selection.active);
|
||||
let at = cell_param_on(s, p, "cell", sheet)?.unwrap_or(s.doc()?.selection.active);
|
||||
let text = str_param(p, "text").unwrap_or("").to_string();
|
||||
let author = str_param(p, "author").map(str::to_string).unwrap_or_else(|| s.prefs.user_name.clone());
|
||||
edit(s, |cx| {
|
||||
|
|
@ -1154,7 +1156,7 @@ fn reply_comment(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
|
||||
fn resolve_comment(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let sheet = target_sheet(s, p)?;
|
||||
let at = cell_param(p, "cell").unwrap_or(s.doc()?.selection.active);
|
||||
let at = cell_param_on(s, p, "cell", sheet)?.unwrap_or(s.doc()?.selection.active);
|
||||
let v = bool_param(p, "resolved").unwrap_or(true);
|
||||
edit(s, |cx| {
|
||||
if let Some(c) = cx.sheet_mut(sheet)?.comments.get_mut(&at) {
|
||||
|
|
@ -1175,7 +1177,7 @@ fn delete_comment(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
|
||||
fn show_note(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let sheet = target_sheet(s, p)?;
|
||||
let at = cell_param(p, "cell").unwrap_or(s.doc()?.selection.active);
|
||||
let at = cell_param_on(s, p, "cell", sheet)?.unwrap_or(s.doc()?.selection.active);
|
||||
edit(s, |cx| {
|
||||
if let Some(c) = cx.sheet_mut(sheet)?.comments.get_mut(&at) {
|
||||
c.visible = !c.visible;
|
||||
|
|
@ -1402,7 +1404,7 @@ fn insert_icon(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
let size = f64_param(p, "size").unwrap_or(48.0).clamp(8.0, 1000.0) as f32;
|
||||
let d = s.doc()?;
|
||||
let id = d.wb.next_object_id();
|
||||
let at = cell_param(p, "at").unwrap_or(d.selection.active);
|
||||
let at = cell_param(s, p, "at")?.unwrap_or(d.selection.active);
|
||||
let color = super::format::color_param(p.get("color")).unwrap_or(Color::Theme(1, 0));
|
||||
let shape = Shape {
|
||||
id,
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ fn cell_get(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
let sheet = target_sheet(s, p)?;
|
||||
let d = s.doc()?;
|
||||
let sh = d.wb.sheet(sheet).ok_or(EngineError::NoDocument)?;
|
||||
let c = cell_param(p, "cell").unwrap_or(d.selection.active);
|
||||
let c = cell_param_on(s, p, "cell", sheet)?.unwrap_or(d.selection.active);
|
||||
let cell = sh.cell(c);
|
||||
let style = d.wb.styles.get(sh.style_id(c));
|
||||
Ok(json!({
|
||||
|
|
|
|||
|
|
@ -240,8 +240,28 @@ pub(crate) fn split_sheet(r: &str) -> (Option<String>, &str) {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn cell_param(p: &Json, key: &str) -> Option<CellRef> {
|
||||
str_param(p, key).and_then(|t| CellRef::parse(split_sheet(t).1))
|
||||
/// A single-cell parameter. Absent or null is `Ok(None)` (callers default it); a value that is not
|
||||
/// a cell reference, or that names a sheet other than the active one, is an error rather than a
|
||||
/// silent fallback to some other cell.
|
||||
pub(crate) fn cell_param(s: &Session, p: &Json, key: &str) -> Result<Option<CellRef>> {
|
||||
cell_param_on(s, p, key, s.doc()?.wb.active_sheet)
|
||||
}
|
||||
|
||||
/// Like [`cell_param`], for a command that operates on `sheet` (a `Sheet!` prefix must name it).
|
||||
pub(crate) fn cell_param_on(s: &Session, p: &Json, key: &str, sheet: usize) -> Result<Option<CellRef>> {
|
||||
let t = match p.get(key) {
|
||||
None | Some(Json::Null) => return Ok(None),
|
||||
Some(Json::String(t)) => t,
|
||||
Some(v) => return Err(bad("cell", format!("invalid cell reference `{v}` for `{key}`"))),
|
||||
};
|
||||
let (sh, body) = split_sheet(t);
|
||||
let c = CellRef::parse(body).ok_or_else(|| bad("cell", format!("invalid cell reference `{t}` for `{key}`")))?;
|
||||
if let Some(name) = sh
|
||||
&& s.doc()?.wb.sheet_index(&name) != Some(sheet)
|
||||
{
|
||||
return Err(bad("cell", format!("`{t}` for `{key}` names a sheet other than the one this command works on")));
|
||||
}
|
||||
Ok(Some(c))
|
||||
}
|
||||
|
||||
/// Applies an edit to a copy of the active workbook, then recalculates and commits it.
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ fn change(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
if bool_param(p, "all").unwrap_or(false) {
|
||||
return s.execute("edit.replace", json!({"what": word, "with": to, "matchCase": true, "all": true}));
|
||||
}
|
||||
let c = cell_param(p, "cell").ok_or_else(|| bad("review.changeSpelling", "missing `cell`"))?;
|
||||
let c = cell_param(s, p, "cell")?.ok_or_else(|| bad("review.changeSpelling", "missing `cell`"))?;
|
||||
let text = s.doc()?.wb.active().and_then(|sh| sh.cell(c)).map(|x| x.input_text()).unwrap_or_default();
|
||||
s.execute("cell.set", json!({"cell": c.a1(), "input": text.replacen(&word, &to, 1)}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ fn freeze_panes(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
if sh.freeze.is_some() && p.get("cell").is_none() {
|
||||
return set_freeze(s, None);
|
||||
}
|
||||
let at = cell_param(p, "cell").unwrap_or(d.selection.active);
|
||||
let at = cell_param(s, p, "cell")?.unwrap_or(d.selection.active);
|
||||
let top = sh.view_top_left;
|
||||
let rows = at.row.saturating_sub(top.row);
|
||||
let cols = at.col.saturating_sub(top.col);
|
||||
|
|
@ -259,7 +259,7 @@ fn scale_fit(s: &mut Session, p: &Json) -> Result<Json> {
|
|||
}
|
||||
|
||||
fn breaks(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let at = cell_param(p, "cell").unwrap_or(s.doc()?.selection.active);
|
||||
let at = cell_param(s, p, "cell")?.unwrap_or(s.doc()?.selection.active);
|
||||
let insert = bool_param(p, "insert").unwrap_or(false);
|
||||
let remove = bool_param(p, "remove").unwrap_or(false);
|
||||
let reset = bool_param(p, "reset").unwrap_or(false);
|
||||
|
|
|
|||
|
|
@ -404,3 +404,116 @@ fn file_commands_without_a_file_fail() {
|
|||
fails(&mut s, "execute_command", json!({"command": command, "params": {}}));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- cell references ----------
|
||||
|
||||
fn cmd_fails(s: &mut Server, command: &str, params: Value) -> String {
|
||||
fails(s, "execute_command", json!({"command": command, "params": params}))
|
||||
}
|
||||
|
||||
fn a1_display(s: &mut Server) -> String {
|
||||
ok(s, "get_cell", json!({"cell": "A1"}))["text"].as_str().unwrap_or("").to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_cell_invalid_ref_is_error() {
|
||||
let mut s = server();
|
||||
for bad in ["ZZZZ0", "B0", "A", "1", "A1:B2", "not a cell", "", "XFE1", "A1048577"] {
|
||||
fails(&mut s, "set_cell", json!({"cell": bad, "input": "x"}));
|
||||
}
|
||||
assert_eq!(a1_display(&mut s), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cell_ref_boundaries_still_work() {
|
||||
let mut s = server();
|
||||
ok(&mut s, "set_cell", json!({"cell": "A1", "input": "first"}));
|
||||
ok(&mut s, "set_cell", json!({"cell": "$B$2", "input": "abs"}));
|
||||
ok(&mut s, "set_cell", json!({"cell": "XFD1048576", "input": "last"}));
|
||||
ok(&mut s, "set_cell", json!({"cell": "xfd1", "input": "lower"}));
|
||||
assert_eq!(ok(&mut s, "get_cell", json!({"cell": "B2"}))["text"], "abs");
|
||||
assert_eq!(ok(&mut s, "get_cell", json!({"cell": "XFD1048576"}))["text"], "last");
|
||||
assert_eq!(ok(&mut s, "get_cell", json!({"cell": "XFD1"}))["text"], "lower");
|
||||
assert_eq!(a1_display(&mut s), "first");
|
||||
// Ranges are still ranges for the commands that take them.
|
||||
ok(&mut s, "read_range", json!({"range": "A1:B2"}));
|
||||
ok(&mut s, "read_range", json!({"range": "$A$1:$B$2"}));
|
||||
// The active sheet's own name is an accepted prefix; an absent cell still means the selection.
|
||||
ok(&mut s, "set_cell", json!({"cell": "Sheet1!C3", "input": "named"}));
|
||||
assert_eq!(ok(&mut s, "get_cell", json!({"cell": "C3"}))["text"], "named");
|
||||
ok(&mut s, "execute_command", json!({"command": "formulas.tracePrecedents", "params": {}}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_ref_in_command_is_error() {
|
||||
let mut s = server();
|
||||
ok(&mut s, "set_cell", json!({"cell": "A1", "input": "7"}));
|
||||
for (cmd, key) in [("view.freezePanes", "cell"), ("formulas.tracePrecedents", "cell"), ("formulas.traceDependents", "cell"), ("cell.get", "cell")]
|
||||
{
|
||||
cmd_fails(&mut s, cmd, json!({ key: "B0" }));
|
||||
cmd_fails(&mut s, cmd, json!({ key: "nonsense" }));
|
||||
}
|
||||
assert_eq!(a1_display(&mut s), "7");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn other_sheet_prefix_is_error() {
|
||||
let mut s = server();
|
||||
ok(&mut s, "execute_command", json!({"command": "home.insertSheet", "params": {"name": "Other"}}));
|
||||
ok(&mut s, "execute_command", json!({"command": "sheet.activate", "params": {"sheet": 0}}));
|
||||
fails(&mut s, "set_cell", json!({"cell": "Nope!B2", "input": "x"}));
|
||||
// A command that cannot honour a sheet prefix must not drop it and use the active sheet.
|
||||
cmd_fails(&mut s, "view.freezePanes", json!({"cell": "Other!B2"}));
|
||||
cmd_fails(&mut s, "formulas.tracePrecedents", json!({"cell": "Other!B2"}));
|
||||
ok(&mut s, "set_cell", json!({"cell": "Sheet1!B2", "input": "here"}));
|
||||
assert_eq!(ok(&mut s, "get_cell", json!({"cell": "B2"}))["text"], "here");
|
||||
// cell.set resolves another existing sheet itself and writes there, not to the active sheet.
|
||||
ok(&mut s, "set_cell", json!({"cell": "Other!B3", "input": "there"}));
|
||||
assert_eq!(ok(&mut s, "get_cell", json!({"cell": "B3"}))["text"], "");
|
||||
let r = ok(&mut s, "read_range", json!({"range": "Other!B3"}));
|
||||
assert!(r.to_string().contains("there"), "{r}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cross_sheet_cell_refs_resolve() {
|
||||
let mut s = server();
|
||||
ok(&mut s, "execute_command", json!({"command": "home.insertSheet", "params": {"name": "Other"}}));
|
||||
ok(&mut s, "execute_command", json!({"command": "home.insertSheet", "params": {"name": "My Sheet"}}));
|
||||
ok(&mut s, "execute_command", json!({"command": "sheet.activate", "params": {"sheet": 0}}));
|
||||
ok(&mut s, "set_cell", json!({"cell": "Other!B3", "input": "there"}));
|
||||
ok(&mut s, "set_cell", json!({"cell": "'My Sheet'!A1", "input": "quoted"}));
|
||||
// Reads honour the prefix.
|
||||
assert_eq!(ok(&mut s, "get_cell", json!({"cell": "Other!B3"}))["text"], "there");
|
||||
assert_eq!(ok(&mut s, "get_cell", json!({"cell": "'My Sheet'!A1"}))["text"], "quoted");
|
||||
assert_eq!(ok(&mut s, "get_cell", json!({"cell": "B3"}))["text"], "");
|
||||
// Cell-level commands that resolve their own sheet land on it.
|
||||
ok(&mut s, "execute_command", json!({"command": "review.newComment", "params": {"cell": "Other!B3", "text": "note"}}));
|
||||
let c = ok(&mut s, "get_cell", json!({"cell": "Other!B3"}));
|
||||
assert!(c.to_string().contains("note"), "{c}");
|
||||
let c = ok(&mut s, "get_cell", json!({"cell": "B3"}));
|
||||
assert!(!c.to_string().contains("note"), "{c}");
|
||||
// The active sheet may be a quoted name with spaces.
|
||||
ok(&mut s, "execute_command", json!({"command": "sheet.activate", "params": {"sheet": "My Sheet"}}));
|
||||
ok(&mut s, "execute_command", json!({"command": "view.freezePanes", "params": {"cell": "'My Sheet'!B2"}}));
|
||||
cmd_fails(&mut s, "view.freezePanes", json!({"cell": "Other!B2"}));
|
||||
// A prefix naming a different sheet than the `sheet` argument is an error, not resolved in favour of either.
|
||||
cmd_fails(&mut s, "cell.get", json!({"cell": "Other!B3", "sheet": "Sheet1"}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_to_columns_destination_follows_its_sheet() {
|
||||
let mut s = server();
|
||||
ok(&mut s, "execute_command", json!({"command": "home.insertSheet", "params": {"name": "Other"}}));
|
||||
ok(&mut s, "execute_command", json!({"command": "sheet.activate", "params": {"sheet": 0}}));
|
||||
ok(&mut s, "set_cell", json!({"cell": "Other!A1", "input": "a,b"}));
|
||||
ok(
|
||||
&mut s,
|
||||
"execute_command",
|
||||
json!({"command": "data.textToColumns", "params": {"range": "Other!A1", "delimiters": [","], "destination": "Other!D1"}}),
|
||||
);
|
||||
let r = ok(&mut s, "read_range", json!({"range": "Other!D1:E1"}));
|
||||
assert!(r.to_string().contains('a') && r.to_string().contains('b'), "{r}");
|
||||
// An unparsable destination and a prefix that contradicts the command's sheet are errors, not A1.
|
||||
cmd_fails(&mut s, "data.textToColumns", json!({"range": "Other!A1", "delimiters": [","], "destination": "D0"}));
|
||||
cmd_fails(&mut s, "data.textToColumns", json!({"range": "Other!A1", "delimiters": [","], "destination": "Sheet1!D1"}));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue