From ef4aecfd172051b08f47ee814f274906865c9deb Mon Sep 17 00:00:00 2001 From: Logan Jennings Date: Thu, 22 Oct 2026 07:24:22 -0700 Subject: [PATCH] engine: restore tabs closed inside a failed command group --- crates/engine/src/batch_rollback_tests.rs | 22 ++++++++++++++++++++++ crates/engine/src/lib.rs | 11 ++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/batch_rollback_tests.rs b/crates/engine/src/batch_rollback_tests.rs index 12d958cd..4b1ac2b1 100644 --- a/crates/engine/src/batch_rollback_tests.rs +++ b/crates/engine/src/batch_rollback_tests.rs @@ -34,6 +34,28 @@ fn failed_group_closes_documents_opened_inside_it() { assert_eq!(s.journal, before_journal); } +#[test] +fn failed_group_reopens_documents_closed_inside_it() { + let mut s = Session::new(); + s.execute("file.new", &json!({"name": "Second"})).unwrap(); + assert_eq!((s.docs.len(), s.active), (2, 1)); + + let result = s.execute( + "command.batch", + &json!({ + "commands": [ + {"command": "file.close", "params": {"index": 1, "force": true}}, + {"command": "node.set", "params": {"id": 999999, "props": {"name": "must fail"}}} + ] + }), + ); + + assert!(result.is_err()); + assert_eq!(s.docs.len(), 2); + assert_eq!(s.active, 1); + assert!(s.ds().is_ok()); +} + #[test] fn failed_plugin_run_restores_each_document_without_restoring_unrelated_state() { let mut s = Session::new(); diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 79e7be0d..aaa022f1 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -1277,6 +1277,8 @@ impl Session { self.docs.iter().map(|ds| (ds.uid, ds.doc.clone(), ds.selection.clone(), ds.page, ds.history.undo.len(), ds.history.limit)).collect(); // An edit inside clears the redo steps; a failed group has to bring them back. let redo_before: Vec<_> = self.docs.iter().map(|ds| ds.history.redo.clone()).collect(); + // Tabs the group closes (or reorders) come back from this. + let tabs_before = self.docs.clone(); let active = self.active; let journal_len = self.journal.len(); @@ -1293,7 +1295,14 @@ impl Session { } if result.is_err() { - self.docs.retain(|ds| before.iter().any(|(uid, _, _, _, _, _)| *uid == ds.uid)); + let mut current = std::mem::take(&mut self.docs); + self.docs = tabs_before + .into_iter() + .map(|t| match current.iter().position(|d| d.uid == t.uid) { + Some(i) => current.swap_remove(i), + None => t, + }) + .collect(); for ((uid, doc, selection, page, undo_len, limit), redo) in before.iter().zip(redo_before) { if let Some(ds) = self.docs.iter_mut().find(|ds| ds.uid == *uid) { ds.history.redo = redo;