From d5b8c21a754f7eee1f9d281548a78a367e0510bc Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 13 Jun 2025 13:19:03 +0200 Subject: [PATCH] debugger: Mark DapLocator::create_scenario as an async function (#32680) Paves way for locators in extensions. Release Notes: - N/A --- crates/dap/src/registry.rs | 4 +- crates/debugger_ui/src/new_process_modal.rs | 188 ++++++++++-------- crates/debugger_ui/src/session/running.rs | 26 ++- crates/editor/src/editor.rs | 30 ++- crates/project/src/debugger/dap_store.rs | 16 +- crates/project/src/debugger/locators/cargo.rs | 6 +- crates/project/src/debugger/locators/go.rs | 62 +++--- crates/project/src/debugger/locators/node.rs | 6 +- .../project/src/debugger/locators/python.rs | 6 +- crates/project/src/task_inventory.rs | 164 ++++++++------- 10 files changed, 290 insertions(+), 218 deletions(-) diff --git a/crates/dap/src/registry.rs b/crates/dap/src/registry.rs index cfaa74828d..e540cc7294 100644 --- a/crates/dap/src/registry.rs +++ b/crates/dap/src/registry.rs @@ -19,11 +19,11 @@ use std::{collections::BTreeMap, sync::Arc}; pub trait DapLocator: Send + Sync { fn name(&self) -> SharedString; /// Determines whether this locator can generate debug target for given task. - fn create_scenario( + async fn create_scenario( &self, build_config: &TaskTemplate, resolved_label: &str, - adapter: DebugAdapterName, + adapter: &DebugAdapterName, ) -> Option; async fn run(&self, build_config: SpawnInTerminal) -> Result; diff --git a/crates/debugger_ui/src/new_process_modal.rs b/crates/debugger_ui/src/new_process_modal.rs index 921568156d..420cddb444 100644 --- a/crates/debugger_ui/src/new_process_modal.rs +++ b/crates/debugger_ui/src/new_process_modal.rs @@ -17,7 +17,7 @@ use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ Action, App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, HighlightStyle, InteractiveText, KeyContext, PromptButton, PromptLevel, Render, StyledText, - Subscription, TextStyle, UnderlineStyle, WeakEntity, + Subscription, Task, TextStyle, UnderlineStyle, WeakEntity, }; use itertools::Itertools as _; use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch}; @@ -201,20 +201,24 @@ impl NewProcessModal { })? .await; - debug_picker - .update_in(cx, |picker, window, cx| { - picker.delegate.tasks_loaded( - task_contexts.clone(), - languages, - lsp_tasks.clone(), - current_resolved_tasks.clone(), - add_current_language_tasks, - cx, - ); - picker.refresh(window, cx); - cx.notify(); - }) - .ok(); + if let Ok(task) = debug_picker.update(cx, |picker, cx| { + picker.delegate.tasks_loaded( + task_contexts.clone(), + languages, + lsp_tasks.clone(), + current_resolved_tasks.clone(), + add_current_language_tasks, + cx, + ) + }) { + task.await; + debug_picker + .update_in(cx, |picker, window, cx| { + picker.refresh(window, cx); + cx.notify(); + }) + .ok(); + } if let Some(active_cwd) = task_contexts .active_context() @@ -1143,61 +1147,67 @@ impl DebugDelegate { current_resolved_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, add_current_language_tasks: bool, cx: &mut Context>, - ) { + ) -> Task<()> { self.task_contexts = Some(task_contexts.clone()); - - let (recent, scenarios) = self - .task_store - .update(cx, |task_store, cx| { - task_store.task_inventory().map(|inventory| { - inventory.update(cx, |inventory, cx| { - inventory.list_debug_scenarios( - &task_contexts, - lsp_tasks, - current_resolved_tasks, - add_current_language_tasks, - cx, - ) - }) + let task = self.task_store.update(cx, |task_store, cx| { + task_store.task_inventory().map(|inventory| { + inventory.update(cx, |inventory, cx| { + inventory.list_debug_scenarios( + &task_contexts, + lsp_tasks, + current_resolved_tasks, + add_current_language_tasks, + cx, + ) }) }) - .unwrap_or_default(); - - if !recent.is_empty() { - self.last_used_candidate_index = Some(recent.len() - 1); - } - - let dap_registry = cx.global::(); - let hide_vscode = scenarios.iter().any(|(kind, _)| match kind { - TaskSourceKind::Worktree { - id: _, - directory_in_worktree: dir, - id_base: _, - } => dir.ends_with(".zed"), - _ => false, }); + cx.spawn(async move |this, cx| { + let (recent, scenarios) = if let Some(task) = task { + task.await + } else { + (Vec::new(), Vec::new()) + }; - self.candidates = recent - .into_iter() - .map(|scenario| Self::get_scenario_kind(&languages, &dap_registry, scenario)) - .chain( - scenarios + this.update(cx, |this, cx| { + if !recent.is_empty() { + this.delegate.last_used_candidate_index = Some(recent.len() - 1); + } + + let dap_registry = cx.global::(); + let hide_vscode = scenarios.iter().any(|(kind, _)| match kind { + TaskSourceKind::Worktree { + id: _, + directory_in_worktree: dir, + id_base: _, + } => dir.ends_with(".zed"), + _ => false, + }); + + this.delegate.candidates = recent .into_iter() - .filter(|(kind, _)| match kind { - TaskSourceKind::Worktree { - id: _, - directory_in_worktree: dir, - id_base: _, - } => !(hide_vscode && dir.ends_with(".vscode")), - _ => true, - }) - .map(|(kind, scenario)| { - let (language, scenario) = - Self::get_scenario_kind(&languages, &dap_registry, scenario); - (language.or(Some(kind)), scenario) - }), - ) - .collect(); + .map(|scenario| Self::get_scenario_kind(&languages, &dap_registry, scenario)) + .chain( + scenarios + .into_iter() + .filter(|(kind, _)| match kind { + TaskSourceKind::Worktree { + id: _, + directory_in_worktree: dir, + id_base: _, + } => !(hide_vscode && dir.ends_with(".vscode")), + _ => true, + }) + .map(|(kind, scenario)| { + let (language, scenario) = + Self::get_scenario_kind(&languages, &dap_registry, scenario); + (language.or(Some(kind)), scenario) + }), + ) + .collect(); + }) + .ok(); + }) } } @@ -1355,24 +1365,44 @@ impl PickerDelegate for DebugDelegate { else { return; }; - let Some(debug_scenario) = cx - .global::() - .locators() - .iter() - .find_map(|locator| locator.1.create_scenario(&task, "one-off", adapter.clone())) - else { - return; - }; + let locators = cx.global::().locators(); + cx.spawn_in(window, async move |this, cx| { + let Some(debug_scenario) = cx + .background_spawn(async move { + for locator in locators { + if let Some(scenario) = + locator.1.create_scenario(&task, "one-off", &adapter).await + { + return Some(scenario); + } + } + None + }) + .await + else { + return; + }; - send_telemetry(&debug_scenario, TelemetrySpawnLocation::ScenarioList, cx); - - self.debug_panel - .update(cx, |panel, cx| { - panel.start_session(debug_scenario, task_context, None, worktree_id, window, cx); + this.update_in(cx, |this, window, cx| { + send_telemetry(&debug_scenario, TelemetrySpawnLocation::ScenarioList, cx); + this.delegate + .debug_panel + .update(cx, |panel, cx| { + panel.start_session( + debug_scenario, + task_context, + None, + worktree_id, + window, + cx, + ); + }) + .ok(); + cx.emit(DismissEvent); }) .ok(); - - cx.emit(DismissEvent); + }) + .detach(); } fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context>) { diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index 05625a389f..cd8775c506 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -855,7 +855,7 @@ impl RunningState { debug_assert!(!config_is_valid); Some(locator_name) } else if !config_is_valid { - dap_store + let task = dap_store .update(cx, |this, cx| { this.debug_scenario_for_build_task( task.original_task().clone(), @@ -863,17 +863,21 @@ impl RunningState { task.display_label().to_owned().into(), cx, ) - .and_then(|scenario| { - match scenario.build { - Some(BuildTaskDefinition::Template { - locator_name, .. - }) => locator_name, - _ => None, - } - }) + + }); + if let Ok(t) = task { + t.await.and_then(|scenario| { + match scenario.build { + Some(BuildTaskDefinition::Template { + locator_name, .. + }) => locator_name, + _ => None, + } }) - .ok() - .flatten() + } else { + None + } + } else { None }; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cf9dc99232..eb9e7041f6 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5801,9 +5801,11 @@ impl Editor { tasks.column, )), }); - let debug_scenarios = editor.update(cx, |editor, cx| { - editor.debug_scenarios(&resolved_tasks, &buffer, cx) - })?; + let debug_scenarios = editor + .update(cx, |editor, cx| { + editor.debug_scenarios(&resolved_tasks, &buffer, cx) + })? + .await; anyhow::Ok((resolved_tasks, debug_scenarios, task_context)) } }) @@ -5859,7 +5861,7 @@ impl Editor { resolved_tasks: &Option, buffer: &Entity, cx: &mut App, - ) -> Vec { + ) -> Task> { if cx.has_flag::() { maybe!({ let project = self.project.as_ref()?; @@ -5877,21 +5879,27 @@ impl Editor { dap_store.update(cx, |dap_store, cx| { for (_, task) in &resolved_tasks.templates { - if let Some(scenario) = dap_store.debug_scenario_for_build_task( + let maybe_scenario = dap_store.debug_scenario_for_build_task( task.original_task().clone(), debug_adapter.clone().into(), task.display_label().to_owned().into(), cx, - ) { - scenarios.push(scenario); - } + ); + scenarios.push(maybe_scenario); } }); - Some(scenarios) + Some(cx.background_spawn(async move { + let scenarios = futures::future::join_all(scenarios) + .await + .into_iter() + .flatten() + .collect::>(); + scenarios + })) }) - .unwrap_or_default() + .unwrap_or_else(|| Task::ready(vec![])) } else { - vec![] + Task::ready(vec![]) } } diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index 713ec5b838..68053952e0 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -287,11 +287,17 @@ impl DapStore { adapter: DebugAdapterName, label: SharedString, cx: &mut App, - ) -> Option { - DapRegistry::global(cx) - .locators() - .values() - .find_map(|locator| locator.create_scenario(&build, &label, adapter.clone())) + ) -> Task> { + let locators = DapRegistry::global(cx).locators(); + + cx.background_spawn(async move { + for locator in locators.values() { + if let Some(scenario) = locator.create_scenario(&build, &label, &adapter).await { + return Some(scenario); + } + } + None + }) } pub fn run_debug_locator( diff --git a/crates/project/src/debugger/locators/cargo.rs b/crates/project/src/debugger/locators/cargo.rs index 7e3b4a506d..4cf4233ca3 100644 --- a/crates/project/src/debugger/locators/cargo.rs +++ b/crates/project/src/debugger/locators/cargo.rs @@ -41,11 +41,11 @@ impl DapLocator for CargoLocator { fn name(&self) -> SharedString { SharedString::new_static("rust-cargo-locator") } - fn create_scenario( + async fn create_scenario( &self, build_config: &TaskTemplate, resolved_label: &str, - adapter: DebugAdapterName, + adapter: &DebugAdapterName, ) -> Option { if build_config.command != "cargo" { return None; @@ -77,7 +77,7 @@ impl DapLocator for CargoLocator { } Some(DebugScenario { - adapter: adapter.0, + adapter: adapter.0.clone(), label: resolved_label.to_string().into(), build: Some(BuildTaskDefinition::Template { task_template, diff --git a/crates/project/src/debugger/locators/go.rs b/crates/project/src/debugger/locators/go.rs index e53cd7fbbd..79d7a1721c 100644 --- a/crates/project/src/debugger/locators/go.rs +++ b/crates/project/src/debugger/locators/go.rs @@ -89,11 +89,11 @@ impl DapLocator for GoLocator { SharedString::new_static("go-debug-locator") } - fn create_scenario( + async fn create_scenario( &self, build_config: &TaskTemplate, resolved_label: &str, - adapter: DebugAdapterName, + adapter: &DebugAdapterName, ) -> Option { if build_config.command != "go" { return None; @@ -170,7 +170,7 @@ impl DapLocator for GoLocator { Some(DebugScenario { label: resolved_label.to_string().into(), - adapter: adapter.0, + adapter: adapter.0.clone(), build: None, config: config, tcp_connection: None, @@ -214,7 +214,7 @@ impl DapLocator for GoLocator { Some(DebugScenario { label: resolved_label.to_string().into(), - adapter: adapter.0, + adapter: adapter.0.clone(), build: None, config, tcp_connection: None, @@ -232,10 +232,11 @@ impl DapLocator for GoLocator { #[cfg(test)] mod tests { use super::*; + use gpui::TestAppContext; use task::{HideStrategy, RevealStrategy, RevealTarget, Shell, TaskTemplate}; - #[test] - fn test_create_scenario_for_go_build() { + #[gpui::test] + async fn test_create_scenario_for_go_build(_: &mut TestAppContext) { let locator = GoLocator; let task = TaskTemplate { label: "go build".into(), @@ -254,14 +255,15 @@ mod tests { show_command: true, }; - let scenario = - locator.create_scenario(&task, "test label", DebugAdapterName("Delve".into())); + let scenario = locator + .create_scenario(&task, "test label", &DebugAdapterName("Delve".into())) + .await; assert!(scenario.is_none()); } - #[test] - fn test_skip_non_go_commands_with_non_delve_adapter() { + #[gpui::test] + async fn test_skip_non_go_commands_with_non_delve_adapter(_: &mut TestAppContext) { let locator = GoLocator; let task = TaskTemplate { label: "cargo build".into(), @@ -280,19 +282,22 @@ mod tests { show_command: true, }; - let scenario = locator.create_scenario( - &task, - "test label", - DebugAdapterName("SomeOtherAdapter".into()), - ); + let scenario = locator + .create_scenario( + &task, + "test label", + &DebugAdapterName("SomeOtherAdapter".into()), + ) + .await; assert!(scenario.is_none()); - let scenario = - locator.create_scenario(&task, "test label", DebugAdapterName("Delve".into())); + let scenario = locator + .create_scenario(&task, "test label", &DebugAdapterName("Delve".into())) + .await; assert!(scenario.is_none()); } - #[test] - fn test_go_locator_run() { + #[gpui::test] + async fn test_go_locator_run(_: &mut TestAppContext) { let locator = GoLocator; let delve = DebugAdapterName("Delve".into()); @@ -319,7 +324,8 @@ mod tests { }; let scenario = locator - .create_scenario(&task, "test run label", delve) + .create_scenario(&task, "test run label", &delve) + .await .unwrap(); let config: DelveLaunchRequest = serde_json::from_value(scenario.config).unwrap(); @@ -350,8 +356,8 @@ mod tests { ); } - #[test] - fn test_go_locator_test() { + #[gpui::test] + async fn test_go_locator_test(_: &mut TestAppContext) { let locator = GoLocator; let delve = DebugAdapterName("Delve".into()); @@ -370,7 +376,8 @@ mod tests { ..Default::default() }; let result = locator - .create_scenario(&task_with_tags, "", delve.clone()) + .create_scenario(&task_with_tags, "", &delve) + .await .unwrap(); let config: DelveLaunchRequest = serde_json::from_value(result.config).unwrap(); @@ -393,8 +400,8 @@ mod tests { ); } - #[test] - fn test_skip_unsupported_go_commands() { + #[gpui::test] + async fn test_skip_unsupported_go_commands(_: &mut TestAppContext) { let locator = GoLocator; let task = TaskTemplate { label: "go clean".into(), @@ -413,8 +420,9 @@ mod tests { show_command: true, }; - let scenario = - locator.create_scenario(&task, "test label", DebugAdapterName("Delve".into())); + let scenario = locator + .create_scenario(&task, "test label", &DebugAdapterName("Delve".into())) + .await; assert!(scenario.is_none()); } } diff --git a/crates/project/src/debugger/locators/node.rs b/crates/project/src/debugger/locators/node.rs index cf81991acf..87eef5718a 100644 --- a/crates/project/src/debugger/locators/node.rs +++ b/crates/project/src/debugger/locators/node.rs @@ -19,11 +19,11 @@ impl DapLocator for NodeLocator { } /// Determines whether this locator can generate debug target for given task. - fn create_scenario( + async fn create_scenario( &self, build_config: &TaskTemplate, resolved_label: &str, - adapter: DebugAdapterName, + adapter: &DebugAdapterName, ) -> Option { if adapter.0.as_ref() != "JavaScript" { return None; @@ -68,7 +68,7 @@ impl DapLocator for NodeLocator { }); Some(DebugScenario { - adapter: adapter.0, + adapter: adapter.0.clone(), label: resolved_label.to_string().into(), build: None, config, diff --git a/crates/project/src/debugger/locators/python.rs b/crates/project/src/debugger/locators/python.rs index c45cc3c11f..3de1281aed 100644 --- a/crates/project/src/debugger/locators/python.rs +++ b/crates/project/src/debugger/locators/python.rs @@ -16,11 +16,11 @@ impl DapLocator for PythonLocator { } /// Determines whether this locator can generate debug target for given task. - fn create_scenario( + async fn create_scenario( &self, build_config: &TaskTemplate, resolved_label: &str, - adapter: DebugAdapterName, + adapter: &DebugAdapterName, ) -> Option { if adapter.0.as_ref() != "Debugpy" { return None; @@ -92,7 +92,7 @@ impl DapLocator for PythonLocator { } Some(DebugScenario { - adapter: adapter.0, + adapter: adapter.0.clone(), label: resolved_label.to_string().into(), build: None, config, diff --git a/crates/project/src/task_inventory.rs b/crates/project/src/task_inventory.rs index fa270f322a..51399bd0ef 100644 --- a/crates/project/src/task_inventory.rs +++ b/crates/project/src/task_inventory.rs @@ -265,7 +265,7 @@ impl Inventory { current_resolved_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, add_current_language_tasks: bool, cx: &mut App, - ) -> (Vec, Vec<(TaskSourceKind, DebugScenario)>) { + ) -> Task<(Vec, Vec<(TaskSourceKind, DebugScenario)>)> { let mut scenarios = Vec::new(); if let Some(worktree_id) = task_contexts @@ -279,9 +279,13 @@ impl Inventory { } scenarios.extend(self.global_debug_scenarios_from_settings()); - if let Some(location) = task_contexts.location() { - let file = location.buffer.read(cx).file(); - let language = location.buffer.read(cx).language(); + let last_scheduled_scenarios = self.last_scheduled_scenarios.iter().cloned().collect(); + + let adapter = task_contexts.location().and_then(|location| { + let (file, language) = { + let buffer = location.buffer.read(cx); + (buffer.file(), buffer.language()) + }; let language_name = language.as_ref().map(|l| l.name()); let adapter = language_settings(language_name, file, cx) .debuggers @@ -290,7 +294,10 @@ impl Inventory { .or_else(|| { language.and_then(|l| l.config().debuggers.first().map(SharedString::from)) }); - if let Some(adapter) = adapter { + adapter.map(|adapter| (adapter, DapRegistry::global(cx).locators())) + }); + cx.background_spawn(async move { + if let Some((adapter, locators)) = adapter { for (kind, task) in lsp_tasks .into_iter() @@ -299,28 +306,21 @@ impl Inventory { || !matches!(kind, TaskSourceKind::Language { .. }) })) { - if let Some(scenario) = - DapRegistry::global(cx) - .locators() - .values() - .find_map(|locator| { - locator.create_scenario( - &task.original_task().clone(), - &task.display_label(), - adapter.clone().into(), - ) - }) - { - scenarios.push((kind, scenario)); + let adapter = adapter.clone().into(); + + for locator in locators.values() { + if let Some(scenario) = locator + .create_scenario(&task.original_task(), &task.display_label(), &adapter) + .await + { + scenarios.push((kind, scenario)); + break; + } } } } - } - - ( - self.last_scheduled_scenarios.iter().cloned().collect(), - scenarios, - ) + (last_scheduled_scenarios, scenarios) + }) } pub fn task_template_by_label( @@ -1275,7 +1275,7 @@ mod tests { init_test(cx); let fs = FakeFs::new(cx.executor()); let inventory = cx.update(|cx| Inventory::new(fs, cx)); - inventory.update(cx, |inventory, cx| { + inventory.update(cx, |inventory, _| { inventory .update_file_based_scenarios( TaskSettingsLocation::Global(Path::new("")), @@ -1291,31 +1291,40 @@ mod tests { ), ) .unwrap(); + }); - let (_, scenario) = inventory - .list_debug_scenarios(&TaskContexts::default(), vec![], vec![], false, cx) - .1 + let (_, scenario) = inventory + .update(cx, |this, cx| { + this.list_debug_scenarios(&TaskContexts::default(), vec![], vec![], false, cx) + }) + .await + .1 + .first() + .unwrap() + .clone(); + + inventory.update(cx, |this, _| { + this.scenario_scheduled(scenario.clone()); + }); + + assert_eq!( + inventory + .update(cx, |this, cx| { + this.list_debug_scenarios(&TaskContexts::default(), vec![], vec![], false, cx) + }) + .await + .0 .first() .unwrap() - .clone(); + .clone(), + scenario + ); - inventory.scenario_scheduled(scenario.clone()); - - assert_eq!( - inventory - .list_debug_scenarios(&TaskContexts::default(), vec![], vec![], false, cx) - .0 - .first() - .unwrap() - .clone(), - scenario - ); - - inventory - .update_file_based_scenarios( - TaskSettingsLocation::Global(Path::new("")), - Some( - r#" + inventory.update(cx, |this, _| { + this.update_file_based_scenarios( + TaskSettingsLocation::Global(Path::new("")), + Some( + r#" [{ "label": "test scenario", "adapter": "Delve", @@ -1323,25 +1332,29 @@ mod tests { "program": "wowzer", }] "#, - ), - ) - .unwrap(); - - assert_eq!( - inventory - .list_debug_scenarios(&TaskContexts::default(), vec![], vec![], false, cx) - .0 - .first() - .unwrap() - .adapter, - "Delve", - ); + ), + ) + .unwrap(); + }); + assert_eq!( inventory - .update_file_based_scenarios( - TaskSettingsLocation::Global(Path::new("")), - Some( - r#" + .update(cx, |this, cx| { + this.list_debug_scenarios(&TaskContexts::default(), vec![], vec![], false, cx) + }) + .await + .0 + .first() + .unwrap() + .adapter, + "Delve", + ); + + inventory.update(cx, |this, _| { + this.update_file_based_scenarios( + TaskSettingsLocation::Global(Path::new("")), + Some( + r#" [{ "label": "testing scenario", "adapter": "Delve", @@ -1349,18 +1362,21 @@ mod tests { "program": "wowzer", }] "#, - ), - ) - .unwrap(); - - assert_eq!( - inventory - .list_debug_scenarios(&TaskContexts::default(), vec![], vec![], false, cx) - .0 - .first(), - None - ); + ), + ) + .unwrap(); }); + + assert_eq!( + inventory + .update(cx, |this, cx| { + this.list_debug_scenarios(&TaskContexts::default(), vec![], vec![], false, cx) + }) + .await + .0 + .first(), + None + ); } #[gpui::test]