diff --git a/crates/editor/src/runnables.rs b/crates/editor/src/runnables.rs index dc97a3ea310b5b519c13887996c3bb3c0d6274a8..b17b9944173629c347b8300a45b9f82f80b511b1 100644 --- a/crates/editor/src/runnables.rs +++ b/crates/editor/src/runnables.rs @@ -118,11 +118,14 @@ impl Editor { return; } if let Some(buffer) = self.buffer().read(cx).as_singleton() { - let buffer_id = buffer.read(cx).remote_id(); + let buffer_read = buffer.read(cx); + if buffer_read.file().is_none() { + self.clear_runnables(None); + return; + } + let buffer_id = buffer_read.remote_id(); if invalidate_buffer_data != Some(buffer_id) - && self - .runnables - .has_cached(buffer_id, &buffer.read(cx).version()) + && self.runnables.has_cached(buffer_id, &buffer_read.version()) { return; } @@ -711,13 +714,14 @@ mod tests { use lsp::LanguageServerName; use multi_buffer::{MultiBuffer, PathKey}; use project::{ - FakeFs, Project, + FakeFs, Project, ProjectPath, lsp_store::lsp_ext_command::{CargoRunnableArgs, Runnable, RunnableArgs, RunnableKind}, }; use serde_json::json; use task::{TaskTemplate, TaskTemplates}; use text::Point; use util::path; + use util::rel_path::rel_path; use crate::{ Editor, UPDATE_DEBOUNCE, editor_tests::init_test, scroll::scroll_amount::ScrollAmount, @@ -1079,4 +1083,95 @@ mod tests { "Runnables should be removed after #[test] is deleted and LSP returns empty" ); } + + #[gpui::test] + async fn test_no_runnables_for_unsaved_buffer(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree(path!("/project"), json!({})).await; + + let project = Project::test(fs, [path!("/project").as_ref()], cx).await; + let language_registry = project.read_with(cx, |project, _| project.languages().clone()); + language_registry.add(rust_lang_with_task_context()); + + let rust_language = language_registry.language_for_name("Rust").await.unwrap(); + let buffer = cx.new(|cx| { + let mut buffer = language::Buffer::local( + indoc! {" + fn main() { + println!(\"hello\"); + } + + #[test] + fn test_one() { + assert!(true); + } + "}, + cx, + ); + buffer.set_language(Some(rust_language), cx); + buffer + }); + + let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx)); + let editor = cx.add_window(|window, cx| { + build_editor_with_project(project.clone(), multi_buffer, window, cx) + }); + + editor + .update(cx, |editor, window, cx| { + editor.refresh_runnables(None, window, cx); + }) + .expect("editor update"); + cx.executor().advance_clock(UPDATE_DEBOUNCE); + cx.executor().run_until_parked(); + + let labels = editor + .update(cx, |editor, _, _| collect_runnable_labels(editor)) + .expect("editor update"); + assert_eq!( + labels, + Vec::<(text::BufferId, language::BufferRow, Vec)>::new(), + "No runnables should appear for an unsaved buffer without a file on disk" + ); + + let worktree_id = project.update(cx, |project, cx| { + project + .worktrees(cx) + .next() + .expect("worktree") + .read(cx) + .id() + }); + project + .update(cx, |project, cx| { + project.save_buffer_as( + buffer.clone(), + ProjectPath { + worktree_id, + path: rel_path("main.rs").into(), + }, + cx, + ) + }) + .await + .expect("save buffer as"); + + editor + .update(cx, |editor, window, cx| { + editor.refresh_runnables(None, window, cx); + }) + .expect("editor update"); + cx.executor().advance_clock(UPDATE_DEBOUNCE); + cx.executor().run_until_parked(); + + let labels = editor + .update(cx, |editor, _, _| collect_runnable_labels(editor)) + .expect("editor update"); + assert!( + !labels.is_empty(), + "Runnables should appear after the buffer is saved to disk" + ); + } }