diff --git a/crates/engine/src/cmd/edit.rs b/crates/engine/src/cmd/edit.rs index c69bac208..f2d0ca521 100644 --- a/crates/engine/src/cmd/edit.rs +++ b/crates/engine/src/cmd/edit.rs @@ -601,7 +601,8 @@ fn undo(s: &mut Session, p: &Json) -> Result { let now = d.wb.clone(); for _ in 0..steps { let Some(e) = d.undo.pop() else { break }; - let cur = crate::HistoryEntry { label: e.label.clone(), wb: d.wb.clone(), selection: d.selection.clone() }; + // Redo goes back to the undone edit's cells, not wherever the user clicked before undoing (#1855). + let cur = crate::HistoryEntry { label: e.label.clone(), wb: d.wb.clone(), selection: e.selection.clone() }; d.redo.push(cur); label = e.label.clone(); d.wb = e.wb; diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index ae499a189..513366848 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -253,6 +253,22 @@ fn undo_redo() { assert!(s.doc().unwrap().is_dirty()); } +#[test] +fn redo_selects_the_redone_cell() { + // #1855: type into A1..A3, click A1, undo, redo: the selection follows the edit to A3. + let mut s = s(); + for (cell, text) in [("A1", "first"), ("A2", "second"), ("A3", "third")] { + s.execute("selection.set", json!({"cell": cell})).unwrap(); + s.execute("cell.set", json!({"cell": cell, "input": text})).unwrap(); + } + s.execute("selection.set", json!({"cell": "A1"})).unwrap(); + s.execute("edit.undo", json!({})).unwrap(); + assert_eq!(s.doc().unwrap().selection.a1(), "A3"); + s.execute("edit.redo", json!({})).unwrap(); + assert_eq!(v(&s, "A3"), Value::from("third")); + assert_eq!(s.doc().unwrap().selection.a1(), "A3"); +} + #[test] fn shape_text_edit_undo_redo_and_noop() { let mut s = s();