This commit is contained in:
Brandon Thomas
2026-11-03 09:55:38 -05:00
6 changed files with 76 additions and 5 deletions
+11 -2
View File
@@ -62,6 +62,8 @@ pub(super) fn find(s: &mut Session, p: &Json) -> Result<Json> {
let by_cols = bool_param(p, "byColumns").unwrap_or(false);
let all = bool_param(p, "all").unwrap_or(false);
let workbook = str_param(p, "within") == Some("workbook");
// `selection`: only the selected cells of the active sheet, as Excel searches a selected range.
let in_selection = str_param(p, "within") == Some("selection");
s.last_find = Some(p.clone());
let d = s.doc()?;
let sheets: Vec<usize> = if workbook { (0..d.wb.sheets.len()).collect() } else { vec![d.wb.active_sheet()] };
@@ -71,6 +73,7 @@ pub(super) fn find(s: &mut Session, p: &Json) -> Result<Json> {
let mut cells: Vec<(CellRef, String)> = sh
.cells
.iter()
.filter(|(c, _)| !in_selection || d.selection.contains(*c))
.filter_map(|(c, cell)| {
let text = if values || cell.formula.is_none() || crate::display::formula_hidden(&d.wb, sh, c) {
crate::display::cell_text(&d.wb, sh, c)
@@ -100,7 +103,12 @@ pub(super) fn find(s: &mut Session, p: &Json) -> Result<Json> {
let Some((si, c, t)) = next else { return Err(EngineError::Other("not found".into())) };
let dd = s.doc_mut()?;
dd.wb_switch_sheet(si);
dd.selection = crate::Selection::at(c);
if in_selection {
// The range stays selected and the active cell moves through it, as in Excel.
dd.selection.active = c;
} else {
dd.selection = crate::Selection::at(c);
}
Ok(json!({"cell": c.a1(), "sheet": si, "value": t, "count": hits.len()}))
}
@@ -114,6 +122,7 @@ pub(super) fn replace(s: &mut Session, p: &Json) -> Result<Json> {
let whole = bool_param(p, "wholeCell").unwrap_or(false);
let all = bool_param(p, "all").unwrap_or(true);
let workbook = str_param(p, "within") == Some("workbook");
let selection = (str_param(p, "within") == Some("selection")).then(|| s.doc().map(|d| d.selection.clone())).transpose()?;
let active = s.doc()?.selection.active;
let mut skipped = 0usize;
let n = edit(s, |cx| {
@@ -123,7 +132,7 @@ pub(super) fn replace(s: &mut Session, p: &Json) -> Result<Json> {
let cells: Vec<(CellRef, String)> =
cx.wb.sheet(si).map(|sh| sh.cells.iter().map(|(c, _)| (c, sh.input_text(c))).collect()).unwrap_or_default();
for (c, text) in cells {
if !all && c != active {
if (!all && c != active) || selection.as_ref().is_some_and(|sel| !sel.contains(c)) {
continue;
}
if !matches(&text, &what, case, whole) {
+28
View File
@@ -552,3 +552,31 @@ fn a_field_that_is_not_really_quoted_keeps_its_separators() {
let hostile = "\"x\t".repeat(50_000);
assert_eq!(parse_tsv(&hostile).first().map(Vec::len), Some(50_001));
}
/// `within: selection` searches only the selected cells, as Excel does with a range selected
/// (#3224): Find All lists them, Find Next moves the active cell through them and keeps the range
/// selected, and Replace changes only them.
#[test]
fn find_and_replace_within_the_selection() {
let mut s = Session::new();
s.new_workbook();
s.execute("range.setValues", json!({"range": "A1:B4", "values": [["pear", "pear"], ["plum", "pear"], ["pear", "fig"], ["pear", "pear"]]}))
.unwrap();
s.execute("selection.set", json!({"range": "B1:B4"})).unwrap();
let all = s.execute("edit.find", json!({"what": "pear", "all": true, "within": "selection"})).unwrap();
let cells: Vec<&str> = all["results"].as_array().unwrap().iter().filter_map(|r| r["cell"].as_str()).collect();
assert_eq!(cells, ["B1", "B2", "B4"], "only column B");
assert_eq!(s.execute("edit.find", json!({"what": "pear", "within": "selection"})).unwrap()["cell"], "B2");
assert_eq!(s.execute("edit.find", json!({"what": "pear", "within": "selection"})).unwrap()["cell"], "B4");
assert_eq!(s.execute("edit.find", json!({"what": "pear", "within": "selection"})).unwrap()["cell"], "B1", "wraps inside the range");
let sel = &s.doc().unwrap().selection;
assert_eq!((sel.a1(), sel.active.a1()), ("B1:B4".to_string(), "B1".to_string()), "the range stays selected");
assert!(s.execute("edit.find", json!({"what": "plum", "within": "selection"})).is_err(), "A2 is outside the range");
assert_eq!(s.execute("edit.replace", json!({"what": "pear", "with": "kiwi", "within": "selection"})).unwrap()["replaced"], 3);
let sh = s.doc().unwrap().wb.active().unwrap();
let at = |a: &str| sh.value(CellRef::parse(a).unwrap());
assert_eq!((at("A1"), at("B1"), at("B4"), at("A4")), (Value::text("pear"), Value::text("kiwi"), Value::text("kiwi"), Value::text("pear")));
// The sheet scope is unchanged.
let all = s.execute("edit.find", json!({"what": "pear", "all": true})).unwrap();
assert_eq!(all["count"], 3, "A1, A3 and A4 on the whole sheet");
}