Undo: a caught panic leaves the outer command's undo step (#406)

After a panic the guard said that no undoable command is running. A
command that ran the one that panicked and carried on then had each
later command it ran record an undo step of its own. The guard now
puts that back to what it was when the command started.
This commit is contained in:
noah1400
2026-10-11 20:50:26 +02:00
parent ad173006f7
commit 60f463f03c
+23 -2
View File
@@ -36,6 +36,7 @@ impl Session {
/// Run command `id` (`f`) inside the guard.
pub(crate) fn guarded(&mut self, id: &str, f: impl FnOnce(&mut Session) -> Result<Value>) -> Result<Value> {
let before = self.active().map(|d| (d.uid, d.doc.clone(), d.selection.clone(), d.revision));
let in_undo_step = self.in_undo_step;
match catch_unwind(AssertUnwindSafe(|| f(self))) {
Ok(r) => r,
Err(payload) => {
@@ -54,8 +55,8 @@ impl Session {
{
log::error!("crash recovery save after `{id}` failed: {e}");
}
// The command never got to say that it is done.
self.in_undo_step = false;
// The command never got to say that it is done; a command that ran it goes on.
self.in_undo_step = in_undo_step;
log::error!("command `{id}` panicked: {msg}");
Err(EngineError::Internal(id.to_string(), msg))
}
@@ -103,4 +104,24 @@ mod tests {
s.execute("frame.create", &json!({"rect": [36, 36, 200, 200]})).unwrap();
assert_eq!(s.active().unwrap().history.undo.len(), 1);
}
/// A command that panics inside an undoable one leaves that one running: what it runs next
/// is still part of its undo step.
#[test]
fn a_panic_in_a_command_run_by_another_leaves_the_outer_undo_step() {
let mut s = Session::new();
s.execute("file.new", &json!({})).unwrap();
// An undoable command that runs one that panics, carries on and runs another.
s.guarded("test.outer", |s| {
s.in_undo_step = true;
assert!(s.guarded("test.panic", |_| panic!("boom")).is_err());
let made = s.execute("frame.create", &json!({"rect": [36, 36, 200, 200]}));
s.in_undo_step = false;
made
})
.unwrap();
let st = s.active().unwrap();
assert_eq!(st.doc.spreads[0].items.len(), 1);
assert_eq!(st.history.undo.len(), 0, "the frame is left to the outer command's step");
}
}