diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index cd95a69d..d7fd8f71 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -743,6 +743,10 @@ fn code_action_on_save_step( }; let build_request = move |editor: &mut Editor| -> Option { + // The document could have been closed mid-chain + if !editor.documents.contains_key(&doc_id) { + return None; + } let doc = doc!(editor, &doc_id); let version = doc.version(); let full_range = helix_core::Range::new(0, doc.text().len_chars()); diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 92ee9a38..9a5bf971 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -419,6 +419,10 @@ fn write_impl( let tail = (auto_format || run_code_actions).then(|| { let path = path.clone(); let callback = Callback::Followup(Box::new(move |editor| { + // The document could have been closed mid-chain + if !editor.documents.contains_key(&doc_id) { + return None; + } let doc = doc!(editor, &doc_id); let fmt_job = auto_format .then(|| doc.auto_format(editor)) @@ -914,6 +918,10 @@ pub fn write_all_impl( // built when there is pre-save work; otherwise a synchronous save below. let tail = (auto_format || run_code_actions).then(|| { let callback: job::Callback = Callback::Followup(Box::new(move |editor| { + // The document could have been closed mid-chain + if !editor.documents.contains_key(&doc_id) { + return None; + } let doc = doc!(editor, &doc_id); let fmt_job = auto_format .then(|| doc.auto_format(editor)) diff --git a/helix-term/tests/test/commands/write.rs b/helix-term/tests/test/commands/write.rs index fa6c97c5..06ae1c29 100644 --- a/helix-term/tests/test/commands/write.rs +++ b/helix-term/tests/test/commands/write.rs @@ -1022,3 +1022,20 @@ async fn test_move_file_when_given_dir_only() -> anyhow::Result<()> { Ok(()) } + +#[tokio::test(flavor = "multi_thread")] +async fn test_write_then_open_does_not_panic_on_closed_scratch() -> anyhow::Result<()> { + let other = tempfile::NamedTempFile::new()?; + + test_key_sequence( + &mut AppBuilder::new().build()?, // scratch buffer + Some(format!(":write:open {}", other.path().to_string_lossy()).as_ref()), + Some(&|app| { + assert_eq!(1, app.editor.documents().count()); + }), // Reaching here at all means the deferred tail did not panic + false, + ) + .await?; + + Ok(()) +}