debugger: Fix up Rust test tasks definitions (#30232)

Piotr Osiewicz and Conrad Irwin created

Closes #ISSUE

Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>

Change summary

crates/dap/src/adapters.rs                      |  5 ++
crates/dap/src/registry.rs                      |  7 +++
crates/debugger_ui/src/session/running.rs       | 21 ++++++++----
crates/editor/src/editor.rs                     |  3 +
crates/languages/src/rust.rs                    |  2 
crates/project/src/debugger/dap_store.rs        |  5 +-
crates/project/src/debugger/locators/cargo.rs   | 33 ++++++++++--------
crates/project/src/lsp_store/lsp_ext_command.rs |  4 +
8 files changed, 53 insertions(+), 27 deletions(-)

Detailed changes

crates/dap/src/adapters.rs 🔗

@@ -78,6 +78,11 @@ impl From<DebugAdapterName> for SharedString {
         name.0
     }
 }
+impl From<SharedString> for DebugAdapterName {
+    fn from(name: SharedString) -> Self {
+        DebugAdapterName(name)
+    }
+}
 
 impl<'a> From<&'a str> for DebugAdapterName {
     fn from(str: &'a str) -> DebugAdapterName {

crates/dap/src/registry.rs 🔗

@@ -16,7 +16,12 @@ 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(&self, build_config: &TaskTemplate, adapter: &str) -> Option<DebugScenario>;
+    fn create_scenario(
+        &self,
+        build_config: &TaskTemplate,
+        resolved_label: &str,
+        adapter: DebugAdapterName,
+    ) -> Option<DebugScenario>;
 
     async fn run(&self, build_config: SpawnInTerminal) -> Result<DebugRequest>;
 }

crates/debugger_ui/src/session/running.rs 🔗

@@ -731,19 +731,30 @@ impl RunningState {
                         (task, None)
                     }
                 };
+                let Some(task) = task.resolve_task("debug-build-task", &task_context) else {
+                    anyhow::bail!("Could not resolve task variables within a debug scenario");
+                };
+
                 let locator_name = if let Some(locator_name) = locator_name {
                     debug_assert!(request.is_none());
                     Some(locator_name)
                 } else if request.is_none() {
                     dap_store
                         .update(cx, |this, cx| {
-                            this.debug_scenario_for_build_task(task.clone(), adapter.clone(), cx)
-                                .and_then(|scenario| match scenario.build {
+                            this.debug_scenario_for_build_task(
+                                task.original_task().clone(),
+                                adapter.clone().into(),
+                                task.display_label().to_owned().into(),
+                                cx,
+                            )
+                            .and_then(|scenario| {
+                                match scenario.build {
                                     Some(BuildTaskDefinition::Template {
                                         locator_name, ..
                                     }) => locator_name,
                                     _ => None,
-                                })
+                                }
+                            })
                         })
                         .ok()
                         .flatten()
@@ -751,10 +762,6 @@ impl RunningState {
                     None
                 };
 
-                let Some(task) = task.resolve_task("debug-build-task", &task_context) else {
-                    anyhow::bail!("Could not resolve task variables within a debug scenario");
-                };
-
                 let builder = ShellBuilder::new(is_local, &task.resolved.shell);
                 let command_label = builder.command_label(&task.resolved.command_label);
                 let (command, args) =

crates/editor/src/editor.rs 🔗

@@ -5276,7 +5276,8 @@ impl Editor {
                                             if let Some(scenario) = this
                                                 .debug_scenario_for_build_task(
                                                     task.original_task().clone(),
-                                                    debug_adapter.clone(),
+                                                    debug_adapter.clone().into(),
+                                                    task.display_label().to_owned().into(),
                                                     cx,
                                                 )
                                             {

crates/languages/src/rust.rs 🔗

@@ -685,8 +685,8 @@ impl ContextProvider for RustContextProvider {
                     "-p".into(),
                     RUST_PACKAGE_TASK_VARIABLE.template_value(),
                     "--".into(),
-                    RUST_TEST_NAME_TASK_VARIABLE.template_value(),
                     "--nocapture".into(),
+                    RUST_TEST_NAME_TASK_VARIABLE.template_value(),
                 ],
                 tags: vec!["rust-test".to_owned()],
                 cwd: Some("$ZED_DIRNAME".to_owned()),

crates/project/src/debugger/dap_store.rs 🔗

@@ -283,13 +283,14 @@ impl DapStore {
     pub fn debug_scenario_for_build_task(
         &self,
         build: TaskTemplate,
-        adapter: SharedString,
+        adapter: DebugAdapterName,
+        label: SharedString,
         cx: &mut App,
     ) -> Option<DebugScenario> {
         DapRegistry::global(cx)
             .locators()
             .values()
-            .find_map(|locator| locator.create_scenario(&build, &adapter))
+            .find_map(|locator| locator.create_scenario(&build, &label, adapter.clone()))
     }
 
     pub fn run_debug_locator(

crates/project/src/debugger/locators/cargo.rs 🔗

@@ -1,6 +1,6 @@
 use anyhow::{Result, anyhow};
 use async_trait::async_trait;
-use dap::{DapLocator, DebugRequest};
+use dap::{DapLocator, DebugRequest, adapters::DebugAdapterName};
 use gpui::SharedString;
 use serde_json::Value;
 use smol::{
@@ -41,7 +41,12 @@ impl DapLocator for CargoLocator {
     fn name(&self) -> SharedString {
         SharedString::new_static("rust-cargo-locator")
     }
-    fn create_scenario(&self, build_config: &TaskTemplate, adapter: &str) -> Option<DebugScenario> {
+    fn create_scenario(
+        &self,
+        build_config: &TaskTemplate,
+        resolved_label: &str,
+        adapter: DebugAdapterName,
+    ) -> Option<DebugScenario> {
         if build_config.command != "cargo" {
             return None;
         }
@@ -70,9 +75,9 @@ impl DapLocator for CargoLocator {
             }
             _ => {}
         }
-        let label = format!("Debug `{}`", build_config.label);
+        let label = format!("Debug `{resolved_label}`");
         Some(DebugScenario {
-            adapter: adapter.to_owned().into(),
+            adapter: adapter.0,
             label: SharedString::from(label),
             build: Some(BuildTaskDefinition::Template {
                 task_template,
@@ -136,20 +141,20 @@ impl DapLocator for CargoLocator {
 
         let mut test_name = None;
         if is_test {
-            if let Some(package_index) = build_config
+            test_name = build_config
                 .args
                 .iter()
-                .position(|arg| arg == "-p" || arg == "--package")
-            {
-                test_name = build_config
-                    .args
-                    .get(package_index + 2)
-                    .filter(|name| !name.starts_with("--"))
-                    .cloned();
-            }
+                .rev()
+                .take_while(|name| "--" != name.as_str())
+                .find(|name| !name.starts_with("-"))
+                .cloned();
         }
         let executable = {
-            if let Some(ref name) = test_name {
+            if let Some(ref name) = test_name.as_ref().and_then(|name| {
+                name.strip_prefix('$')
+                    .map(|name| build_config.env.get(name))
+                    .unwrap_or(Some(name))
+            }) {
                 find_best_executable(&executables, &name).await
             } else {
                 None

crates/project/src/lsp_store/lsp_ext_command.rs 🔗

@@ -663,7 +663,9 @@ impl LspCommand for GetLspRunnables {
                                 // We cannot escape all shell arguments unconditionally, as we use this for ssh commands, which may involve paths starting with `~`.
                                 // That bit is not auto-expanded when using single quotes.
                                 // Escape extra cargo args unconditionally as those are unlikely to contain `~`.
-                                .map(|extra_arg| format!("'{extra_arg}'")),
+                                .flat_map(|extra_arg| {
+                                    shlex::try_quote(&extra_arg).ok().map(|s| s.to_string())
+                                }),
                         );
                     }
                 }