Tidy up DAP initialization (#28730)

Conrad Irwin , Anthony Eid , Piotr , Piotr Osiewicz , and Anthony created

To make DAP work over SSH we want to create the binary
at the project level (so we can wrap it in an `ssh` invocation
transparently).

This means not pushing the adapter down into the session, and resolving
more information ahead-of-time.

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Piotr <piotr@zed.dev>

Release Notes:

- N/A

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Piotr <piotr@zed.dev>
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Co-authored-by: Anthony <anthony@zed.dev>

Change summary

crates/dap/src/adapters.rs                            |  63 
crates/dap/src/client.rs                              |  57 
crates/dap/src/transport.rs                           |  95 -
crates/dap_adapters/src/codelldb.rs                   |  77 
crates/dap_adapters/src/dap_adapters.rs               |  17 
crates/dap_adapters/src/gdb.rs                        |  82 
crates/dap_adapters/src/go.rs                         |  55 
crates/dap_adapters/src/javascript.rs                 |  70 
crates/dap_adapters/src/lldb.rs                       |  72 
crates/dap_adapters/src/php.rs                        |  43 
crates/dap_adapters/src/python.rs                     |  67 
crates/debugger_ui/src/attach_modal.rs                |   5 
crates/debugger_ui/src/new_session_modal.rs           |   2 
crates/debugger_ui/src/tests/attach_modal.rs          |  40 
crates/debugger_ui/src/tests/console.rs               |  26 
crates/debugger_ui/src/tests/debugger_panel.rs        | 611 +++++-------
crates/debugger_ui/src/tests/module_list.rs           |  85 -
crates/debugger_ui/src/tests/stack_frame_list.rs      | 173 +--
crates/debugger_ui/src/tests/variable_list.rs         | 596 +++++-------
crates/project/src/debugger.rs                        |   3 
crates/project/src/debugger/dap_store.rs              | 163 --
crates/project/src/debugger/locator_store.rs          |   4 
crates/project/src/debugger/locator_store/cargo.rs    |   8 
crates/project/src/debugger/locator_store/locators.rs |   4 
crates/project/src/debugger/session.rs                | 347 +------
crates/project/src/debugger/test.rs                   |  98 ++
crates/project/src/project.rs                         |  83 -
crates/project/src/project_tests.rs                   |   2 
crates/remote_server/src/headless_project.rs          |   4 
crates/task/src/lib.rs                                |   8 
crates/tasks_ui/src/modal.rs                          |   7 
crates/workspace/src/workspace.rs                     |   4 
32 files changed, 1,276 insertions(+), 1,695 deletions(-)

Detailed changes

crates/dap/src/adapters.rs 🔗

@@ -3,13 +3,13 @@ use anyhow::{Context as _, Ok, Result, anyhow};
 use async_compression::futures::bufread::GzipDecoder;
 use async_tar::Archive;
 use async_trait::async_trait;
+use dap_types::StartDebuggingRequestArguments;
 use futures::io::BufReader;
 use gpui::{AsyncApp, SharedString};
 pub use http_client::{HttpClient, github::latest_github_release};
 use language::LanguageToolchainStore;
 use node_runtime::NodeRuntime;
 use serde::{Deserialize, Serialize};
-use serde_json::Value;
 use settings::WorktreeId;
 use smol::{self, fs::File, lock::Mutex};
 use std::{
@@ -22,7 +22,7 @@ use std::{
     path::PathBuf,
     sync::Arc,
 };
-use task::{DebugAdapterConfig, DebugTaskDefinition};
+use task::DebugTaskDefinition;
 use util::ResultExt;
 
 #[derive(Clone, Debug, PartialEq, Eq)]
@@ -93,13 +93,15 @@ pub struct TcpArguments {
     pub port: u16,
     pub timeout: Option<u64>,
 }
-#[derive(Default, Debug, Clone)]
+#[derive(Debug, Clone)]
 pub struct DebugAdapterBinary {
+    pub adapter_name: DebugAdapterName,
     pub command: String,
     pub arguments: Option<Vec<OsString>>,
     pub envs: Option<HashMap<String, String>>,
     pub cwd: Option<PathBuf>,
     pub connection: Option<TcpArguments>,
+    pub request_args: StartDebuggingRequestArguments,
 }
 
 #[derive(Debug)]
@@ -220,7 +222,7 @@ pub trait DebugAdapter: 'static + Send + Sync {
     async fn get_binary(
         &self,
         delegate: &dyn DapDelegate,
-        config: &DebugAdapterConfig,
+        config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         cx: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
@@ -284,13 +286,10 @@ pub trait DebugAdapter: 'static + Send + Sync {
     async fn get_installed_binary(
         &self,
         delegate: &dyn DapDelegate,
-        config: &DebugAdapterConfig,
+        config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         cx: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary>;
-
-    /// Should return base configuration to make the debug adapter work
-    fn request_args(&self, config: &DebugTaskDefinition) -> Value;
 }
 #[cfg(any(test, feature = "test-support"))]
 pub struct FakeAdapter {}
@@ -302,6 +301,31 @@ impl FakeAdapter {
     pub fn new() -> Self {
         Self {}
     }
+
+    fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments {
+        use serde_json::json;
+        use task::DebugRequestType;
+
+        let value = json!({
+            "request": match config.request {
+                DebugRequestType::Launch(_) => "launch",
+                DebugRequestType::Attach(_) => "attach",
+            },
+            "process_id": if let DebugRequestType::Attach(attach_config) = &config.request {
+                attach_config.process_id
+            } else {
+                None
+            },
+        });
+        let request = match config.request {
+            DebugRequestType::Launch(_) => dap_types::StartDebuggingRequestArgumentsRequest::Launch,
+            DebugRequestType::Attach(_) => dap_types::StartDebuggingRequestArgumentsRequest::Attach,
+        };
+        StartDebuggingRequestArguments {
+            configuration: value,
+            request,
+        }
+    }
 }
 
 #[cfg(any(test, feature = "test-support"))]
@@ -314,16 +338,18 @@ impl DebugAdapter for FakeAdapter {
     async fn get_binary(
         &self,
         _: &dyn DapDelegate,
-        _: &DebugAdapterConfig,
+        config: &DebugTaskDefinition,
         _: Option<PathBuf>,
         _: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
         Ok(DebugAdapterBinary {
+            adapter_name: Self::ADAPTER_NAME.into(),
             command: "command".into(),
             arguments: None,
             connection: None,
             envs: None,
             cwd: None,
+            request_args: self.request_args(config),
         })
     }
 
@@ -345,27 +371,10 @@ impl DebugAdapter for FakeAdapter {
     async fn get_installed_binary(
         &self,
         _: &dyn DapDelegate,
-        _: &DebugAdapterConfig,
+        _: &DebugTaskDefinition,
         _: Option<PathBuf>,
         _: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
         unimplemented!("get installed binary");
     }
-
-    fn request_args(&self, config: &DebugTaskDefinition) -> Value {
-        use serde_json::json;
-        use task::DebugRequestType;
-
-        json!({
-            "request": match config.request {
-                DebugRequestType::Launch(_) => "launch",
-                DebugRequestType::Attach(_) => "attach",
-            },
-            "process_id": if let DebugRequestType::Attach(attach_config) = &config.request {
-                attach_config.process_id
-            } else {
-                None
-            },
-        })
-    }
 }

crates/dap/src/client.rs 🔗

@@ -39,7 +39,6 @@ impl SessionId {
 /// Represents a connection to the debug adapter process, either via stdout/stdin or a socket.
 pub struct DebugAdapterClient {
     id: SessionId,
-    name: DebugAdapterName,
     sequence_count: AtomicU64,
     binary: DebugAdapterBinary,
     executor: BackgroundExecutor,
@@ -51,7 +50,6 @@ pub type DapMessageHandler = Box<dyn FnMut(Message) + 'static + Send + Sync>;
 impl DebugAdapterClient {
     pub async fn start(
         id: SessionId,
-        name: DebugAdapterName,
         binary: DebugAdapterBinary,
         message_handler: DapMessageHandler,
         cx: AsyncApp,
@@ -60,7 +58,6 @@ impl DebugAdapterClient {
             TransportDelegate::start(&binary, cx.clone()).await?;
         let this = Self {
             id,
-            name,
             binary,
             transport_delegate,
             sequence_count: AtomicU64::new(1),
@@ -91,6 +88,7 @@ impl DebugAdapterClient {
     ) -> Result<Self> {
         let binary = match self.transport_delegate.transport() {
             crate::transport::Transport::Tcp(tcp_transport) => DebugAdapterBinary {
+                adapter_name: binary.adapter_name,
                 command: binary.command,
                 arguments: binary.arguments,
                 envs: binary.envs,
@@ -100,11 +98,12 @@ impl DebugAdapterClient {
                     port: tcp_transport.port,
                     timeout: Some(tcp_transport.timeout),
                 }),
+                request_args: binary.request_args,
             },
             _ => self.binary.clone(),
         };
 
-        Self::start(session_id, self.name(), binary, message_handler, cx).await
+        Self::start(session_id, binary, message_handler, cx).await
     }
 
     async fn handle_receive_messages(
@@ -189,7 +188,17 @@ impl DebugAdapterClient {
 
                 let response = response??;
                 match response.success {
-                    true => Ok(serde_json::from_value(response.body.unwrap_or_default())?),
+                    true => {
+                        if let Some(json) = response.body {
+                            Ok(serde_json::from_value(json)?)
+                        // Note: dap types configure themselves to return `None` when an empty object is received,
+                        // which then fails here...
+                        } else if let Ok(result) = serde_json::from_value(serde_json::Value::Object(Default::default())) {
+                            Ok(result)
+                        } else {
+                            Ok(serde_json::from_value(Default::default())?)
+                        }
+                    }
                     false => Err(anyhow!("Request failed: {}", response.message.unwrap_or_default())),
                 }
             }
@@ -211,7 +220,7 @@ impl DebugAdapterClient {
     }
 
     pub fn name(&self) -> DebugAdapterName {
-        self.name.clone()
+        self.binary.adapter_name.clone()
     }
     pub fn binary(&self) -> &DebugAdapterBinary {
         &self.binary
@@ -238,14 +247,14 @@ impl DebugAdapterClient {
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    pub async fn on_request<R: dap_types::requests::Request, F>(&self, handler: F)
+    pub fn on_request<R: dap_types::requests::Request, F>(&self, handler: F)
     where
         F: 'static
             + Send
             + FnMut(u64, R::Arguments) -> Result<R::Response, dap_types::ErrorResponse>,
     {
         let transport = self.transport_delegate.transport().as_fake();
-        transport.on_request::<R, F>(handler).await;
+        transport.on_request::<R, F>(handler);
     }
 
     #[cfg(any(test, feature = "test-support"))]
@@ -282,7 +291,7 @@ mod tests {
     use crate::{client::DebugAdapterClient, debugger_settings::DebuggerSettings};
     use dap_types::{
         Capabilities, InitializeRequestArguments, InitializeRequestArgumentsPathFormat,
-        RunInTerminalRequestArguments,
+        RunInTerminalRequestArguments, StartDebuggingRequestArguments,
         messages::Events,
         requests::{Initialize, Request, RunInTerminal},
     };
@@ -312,13 +321,17 @@ mod tests {
 
         let client = DebugAdapterClient::start(
             crate::client::SessionId(1),
-            DebugAdapterName("adapter".into()),
             DebugAdapterBinary {
+                adapter_name: "adapter".into(),
                 command: "command".into(),
                 arguments: Default::default(),
                 envs: Default::default(),
                 connection: None,
                 cwd: None,
+                request_args: StartDebuggingRequestArguments {
+                    configuration: serde_json::Value::Null,
+                    request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
+                },
             },
             Box::new(|_| panic!("Did not expect to hit this code path")),
             cx.to_async(),
@@ -326,14 +339,12 @@ mod tests {
         .await
         .unwrap();
 
-        client
-            .on_request::<Initialize, _>(move |_, _| {
-                Ok(dap_types::Capabilities {
-                    supports_configuration_done_request: Some(true),
-                    ..Default::default()
-                })
+        client.on_request::<Initialize, _>(move |_, _| {
+            Ok(dap_types::Capabilities {
+                supports_configuration_done_request: Some(true),
+                ..Default::default()
             })
-            .await;
+        });
 
         cx.run_until_parked();
 
@@ -381,13 +392,17 @@ mod tests {
 
         let client = DebugAdapterClient::start(
             crate::client::SessionId(1),
-            DebugAdapterName("adapter".into()),
             DebugAdapterBinary {
+                adapter_name: "adapter".into(),
                 command: "command".into(),
                 arguments: Default::default(),
                 envs: Default::default(),
                 connection: None,
                 cwd: None,
+                request_args: StartDebuggingRequestArguments {
+                    configuration: serde_json::Value::Null,
+                    request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
+                },
             },
             Box::new({
                 let called_event_handler = called_event_handler.clone();
@@ -431,13 +446,17 @@ mod tests {
 
         let client = DebugAdapterClient::start(
             crate::client::SessionId(1),
-            DebugAdapterName("test-adapter".into()),
             DebugAdapterBinary {
+                adapter_name: "test-adapter".into(),
                 command: "command".into(),
                 arguments: Default::default(),
                 envs: Default::default(),
                 connection: None,
                 cwd: None,
+                request_args: dap_types::StartDebuggingRequestArguments {
+                    configuration: serde_json::Value::Null,
+                    request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
+                },
             },
             Box::new({
                 let called_event_handler = called_event_handler.clone();

crates/dap/src/transport.rs 🔗

@@ -699,14 +699,8 @@ impl StdioTransport {
 }
 
 #[cfg(any(test, feature = "test-support"))]
-type RequestHandler = Box<
-    dyn Send
-        + FnMut(
-            u64,
-            serde_json::Value,
-            Arc<Mutex<async_pipe::PipeWriter>>,
-        ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>,
->;
+type RequestHandler =
+    Box<dyn Send + FnMut(u64, serde_json::Value) -> dap_types::messages::Response>;
 
 #[cfg(any(test, feature = "test-support"))]
 type ResponseHandler = Box<dyn Send + Fn(Response)>;
@@ -714,45 +708,41 @@ type ResponseHandler = Box<dyn Send + Fn(Response)>;
 #[cfg(any(test, feature = "test-support"))]
 pub struct FakeTransport {
     // for sending fake response back from adapter side
-    request_handlers: Arc<Mutex<HashMap<&'static str, RequestHandler>>>,
+    request_handlers: Arc<parking_lot::Mutex<HashMap<&'static str, RequestHandler>>>,
     // for reverse request responses
-    response_handlers: Arc<Mutex<HashMap<&'static str, ResponseHandler>>>,
+    response_handlers: Arc<parking_lot::Mutex<HashMap<&'static str, ResponseHandler>>>,
 }
 
 #[cfg(any(test, feature = "test-support"))]
 impl FakeTransport {
-    pub async fn on_request<R: dap_types::requests::Request, F>(&self, mut handler: F)
+    pub fn on_request<R: dap_types::requests::Request, F>(&self, mut handler: F)
     where
         F: 'static + Send + FnMut(u64, R::Arguments) -> Result<R::Response, ErrorResponse>,
     {
-        self.request_handlers.lock().await.insert(
+        self.request_handlers.lock().insert(
             R::COMMAND,
-            Box::new(
-                move |seq, args, writer: Arc<Mutex<async_pipe::PipeWriter>>| {
-                    let response = handler(seq, serde_json::from_value(args).unwrap());
-
-                    let message = serde_json::to_string(&Message::Response(Response {
+            Box::new(move |seq, args| {
+                let result = handler(seq, serde_json::from_value(args).unwrap());
+                let response = match result {
+                    Ok(response) => Response {
                         seq: seq + 1,
                         request_seq: seq,
-                        success: response.as_ref().is_ok(),
+                        success: true,
                         command: R::COMMAND.into(),
-                        body: util::maybe!({ serde_json::to_value(response.ok()?).ok() }),
+                        body: Some(serde_json::to_value(response).unwrap()),
                         message: None,
-                    }))
-                    .unwrap();
-
-                    let writer = writer.clone();
-
-                    Box::pin(async move {
-                        let mut writer = writer.lock().await;
-                        writer
-                            .write_all(TransportDelegate::build_rpc_message(message).as_bytes())
-                            .await
-                            .unwrap();
-                        writer.flush().await.unwrap();
-                    })
-                },
-            ),
+                    },
+                    Err(response) => Response {
+                        seq: seq + 1,
+                        request_seq: seq,
+                        success: false,
+                        command: R::COMMAND.into(),
+                        body: Some(serde_json::to_value(response).unwrap()),
+                        message: None,
+                    },
+                };
+                response
+            }),
         );
     }
 
@@ -762,14 +752,13 @@ impl FakeTransport {
     {
         self.response_handlers
             .lock()
-            .await
             .insert(R::COMMAND, Box::new(handler));
     }
 
     async fn start(cx: AsyncApp) -> Result<(TransportPipe, Self)> {
         let this = Self {
-            request_handlers: Arc::new(Mutex::new(HashMap::default())),
-            response_handlers: Arc::new(Mutex::new(HashMap::default())),
+            request_handlers: Arc::new(parking_lot::Mutex::new(HashMap::default())),
+            response_handlers: Arc::new(parking_lot::Mutex::new(HashMap::default())),
         };
         use dap_types::requests::{Request, RunInTerminal, StartDebugging};
         use serde_json::json;
@@ -816,23 +805,31 @@ impl FakeTransport {
                                             .unwrap();
                                         writer.flush().await.unwrap();
                                     } else {
-                                        if let Some(handle) = request_handlers
+                                        let response = if let Some(handle) = request_handlers
                                             .lock()
-                                            .await
                                             .get_mut(request.command.as_str())
                                         {
                                             handle(
                                                 request.seq,
                                                 request.arguments.unwrap_or(json!({})),
-                                                stdout_writer.clone(),
                                             )
-                                            .await;
                                         } else {
-                                            log::error!(
-                                                "No request handler for {}",
-                                                request.command
-                                            );
-                                        }
+                                            panic!("No request handler for {}", request.command);
+                                        };
+                                        let message =
+                                            serde_json::to_string(&Message::Response(response))
+                                                .unwrap();
+
+                                        let mut writer = stdout_writer.lock().await;
+
+                                        writer
+                                            .write_all(
+                                                TransportDelegate::build_rpc_message(message)
+                                                    .as_bytes(),
+                                            )
+                                            .await
+                                            .unwrap();
+                                        writer.flush().await.unwrap();
                                     }
                                 }
                                 Message::Event(event) => {
@@ -850,10 +847,8 @@ impl FakeTransport {
                                     writer.flush().await.unwrap();
                                 }
                                 Message::Response(response) => {
-                                    if let Some(handle) = response_handlers
-                                        .lock()
-                                        .await
-                                        .get(response.command.as_str())
+                                    if let Some(handle) =
+                                        response_handlers.lock().get(response.command.as_str())
                                     {
                                         handle(response);
                                     } else {

crates/dap_adapters/src/codelldb.rs 🔗

@@ -4,7 +4,7 @@ use anyhow::{Result, bail};
 use async_trait::async_trait;
 use dap::adapters::latest_github_release;
 use gpui::AsyncApp;
-use task::{DebugAdapterConfig, DebugRequestType, DebugTaskDefinition};
+use task::{DebugRequestType, DebugTaskDefinition};
 
 use crate::*;
 
@@ -15,6 +15,42 @@ pub(crate) struct CodeLldbDebugAdapter {
 
 impl CodeLldbDebugAdapter {
     const ADAPTER_NAME: &'static str = "CodeLLDB";
+
+    fn request_args(&self, config: &DebugTaskDefinition) -> dap::StartDebuggingRequestArguments {
+        let mut configuration = json!({
+            "request": match config.request {
+                DebugRequestType::Launch(_) => "launch",
+                DebugRequestType::Attach(_) => "attach",
+            },
+        });
+        let map = configuration.as_object_mut().unwrap();
+        // CodeLLDB uses `name` for a terminal label.
+        map.insert("name".into(), Value::String(config.label.clone()));
+        let request = config.request.to_dap();
+        match &config.request {
+            DebugRequestType::Attach(attach) => {
+                map.insert("pid".into(), attach.process_id.into());
+            }
+            DebugRequestType::Launch(launch) => {
+                map.insert("program".into(), launch.program.clone().into());
+
+                if !launch.args.is_empty() {
+                    map.insert("args".into(), launch.args.clone().into());
+                }
+
+                if let Some(stop_on_entry) = config.stop_on_entry {
+                    map.insert("stopOnEntry".into(), stop_on_entry.into());
+                }
+                if let Some(cwd) = launch.cwd.as_ref() {
+                    map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
+                }
+            }
+        }
+        dap::StartDebuggingRequestArguments {
+            request,
+            configuration,
+        }
+    }
 }
 
 #[async_trait(?Send)]
@@ -86,7 +122,7 @@ impl DebugAdapter for CodeLldbDebugAdapter {
     async fn get_installed_binary(
         &self,
         _: &dyn DapDelegate,
-        _: &DebugAdapterConfig,
+        config: &DebugTaskDefinition,
         _: Option<PathBuf>,
         _: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
@@ -111,39 +147,10 @@ impl DebugAdapter for CodeLldbDebugAdapter {
                     .to_string()
                     .into(),
             ]),
-            ..Default::default()
+            request_args: self.request_args(config),
+            adapter_name: "test".into(),
+            envs: None,
+            connection: None,
         })
     }
-
-    fn request_args(&self, config: &DebugTaskDefinition) -> Value {
-        let mut args = json!({
-            "request": match config.request {
-                DebugRequestType::Launch(_) => "launch",
-                DebugRequestType::Attach(_) => "attach",
-            },
-        });
-        let map = args.as_object_mut().unwrap();
-        // CodeLLDB uses `name` for a terminal label.
-        map.insert("name".into(), Value::String(config.label.clone()));
-        match &config.request {
-            DebugRequestType::Attach(attach) => {
-                map.insert("pid".into(), attach.process_id.into());
-            }
-            DebugRequestType::Launch(launch) => {
-                map.insert("program".into(), launch.program.clone().into());
-
-                if !launch.args.is_empty() {
-                    map.insert("args".into(), launch.args.clone().into());
-                }
-
-                if let Some(stop_on_entry) = config.stop_on_entry {
-                    map.insert("stopOnEntry".into(), stop_on_entry.into());
-                }
-                if let Some(cwd) = launch.cwd.as_ref() {
-                    map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
-                }
-            }
-        }
-        args
-    }
 }

crates/dap_adapters/src/dap_adapters.rs 🔗

@@ -12,7 +12,7 @@ use anyhow::{Result, anyhow};
 use async_trait::async_trait;
 use codelldb::CodeLldbDebugAdapter;
 use dap::{
-    DapRegistry,
+    DapRegistry, DebugRequestType,
     adapters::{
         self, AdapterVersion, DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName,
         GithubRepo,
@@ -25,7 +25,7 @@ use lldb::LldbDebugAdapter;
 use php::PhpDebugAdapter;
 use python::PythonDebugAdapter;
 use serde_json::{Value, json};
-use task::{DebugAdapterConfig, TCPHost};
+use task::TCPHost;
 
 pub fn init(registry: Arc<DapRegistry>) {
     registry.add_adapter(Arc::from(CodeLldbDebugAdapter::default()));
@@ -51,3 +51,16 @@ pub(crate) async fn configure_tcp_connection(
 
     Ok((host, port, timeout))
 }
+
+trait ToDap {
+    fn to_dap(&self) -> dap::StartDebuggingRequestArgumentsRequest;
+}
+
+impl ToDap for DebugRequestType {
+    fn to_dap(&self) -> dap::StartDebuggingRequestArgumentsRequest {
+        match self {
+            Self::Launch(_) => dap::StartDebuggingRequestArgumentsRequest::Launch,
+            Self::Attach(_) => dap::StartDebuggingRequestArgumentsRequest::Attach,
+        }
+    }
+}

crates/dap_adapters/src/gdb.rs 🔗

@@ -2,8 +2,9 @@ use std::ffi::OsStr;
 
 use anyhow::{Result, bail};
 use async_trait::async_trait;
+use dap::StartDebuggingRequestArguments;
 use gpui::AsyncApp;
-use task::{DebugAdapterConfig, DebugRequestType, DebugTaskDefinition};
+use task::{DebugRequestType, DebugTaskDefinition};
 
 use crate::*;
 
@@ -12,6 +13,44 @@ pub(crate) struct GdbDebugAdapter;
 
 impl GdbDebugAdapter {
     const ADAPTER_NAME: &'static str = "GDB";
+
+    fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments {
+        let mut args = json!({
+            "request": match config.request {
+                DebugRequestType::Launch(_) => "launch",
+                DebugRequestType::Attach(_) => "attach",
+            },
+        });
+
+        let map = args.as_object_mut().unwrap();
+        match &config.request {
+            DebugRequestType::Attach(attach) => {
+                map.insert("pid".into(), attach.process_id.into());
+            }
+
+            DebugRequestType::Launch(launch) => {
+                map.insert("program".into(), launch.program.clone().into());
+
+                if !launch.args.is_empty() {
+                    map.insert("args".into(), launch.args.clone().into());
+                }
+
+                if let Some(stop_on_entry) = config.stop_on_entry {
+                    map.insert(
+                        "stopAtBeginningOfMainSubprogram".into(),
+                        stop_on_entry.into(),
+                    );
+                }
+                if let Some(cwd) = launch.cwd.as_ref() {
+                    map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
+                }
+            }
+        }
+        StartDebuggingRequestArguments {
+            configuration: args,
+            request: config.request.to_dap(),
+        }
+    }
 }
 
 #[async_trait(?Send)]
@@ -23,7 +62,7 @@ impl DebugAdapter for GdbDebugAdapter {
     async fn get_binary(
         &self,
         delegate: &dyn DapDelegate,
-        _: &DebugAdapterConfig,
+        config: &DebugTaskDefinition,
         user_installed_path: Option<std::path::PathBuf>,
         _: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
@@ -43,11 +82,13 @@ impl DebugAdapter for GdbDebugAdapter {
         let gdb_path = user_setting_path.unwrap_or(gdb_path?);
 
         Ok(DebugAdapterBinary {
+            adapter_name: Self::ADAPTER_NAME.into(),
             command: gdb_path,
             arguments: Some(vec!["-i=dap".into()]),
             envs: None,
             cwd: None,
             connection: None,
+            request_args: self.request_args(config),
         })
     }
 
@@ -66,45 +107,10 @@ impl DebugAdapter for GdbDebugAdapter {
     async fn get_installed_binary(
         &self,
         _: &dyn DapDelegate,
-        _: &DebugAdapterConfig,
+        _: &DebugTaskDefinition,
         _: Option<std::path::PathBuf>,
         _: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
         unimplemented!("GDB cannot be installed by Zed (yet)")
     }
-
-    fn request_args(&self, config: &DebugTaskDefinition) -> Value {
-        let mut args = json!({
-            "request": match config.request {
-                DebugRequestType::Launch(_) => "launch",
-                DebugRequestType::Attach(_) => "attach",
-            },
-        });
-
-        let map = args.as_object_mut().unwrap();
-        match &config.request {
-            DebugRequestType::Attach(attach) => {
-                map.insert("pid".into(), attach.process_id.into());
-            }
-
-            DebugRequestType::Launch(launch) => {
-                map.insert("program".into(), launch.program.clone().into());
-
-                if !launch.args.is_empty() {
-                    map.insert("args".into(), launch.args.clone().into());
-                }
-
-                if let Some(stop_on_entry) = config.stop_on_entry {
-                    map.insert(
-                        "stopAtBeginningOfMainSubprogram".into(),
-                        stop_on_entry.into(),
-                    );
-                }
-                if let Some(cwd) = launch.cwd.as_ref() {
-                    map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
-                }
-            }
-        }
-        args
-    }
 }

crates/dap_adapters/src/go.rs 🔗

@@ -1,3 +1,4 @@
+use dap::StartDebuggingRequestArguments;
 use gpui::AsyncApp;
 use std::{ffi::OsStr, path::PathBuf};
 use task::DebugTaskDefinition;
@@ -9,6 +10,31 @@ pub(crate) struct GoDebugAdapter;
 
 impl GoDebugAdapter {
     const ADAPTER_NAME: &'static str = "Delve";
+    fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments {
+        let mut args = match &config.request {
+            dap::DebugRequestType::Attach(attach_config) => {
+                json!({
+                    "processId": attach_config.process_id,
+                })
+            }
+            dap::DebugRequestType::Launch(launch_config) => json!({
+                "program": launch_config.program,
+                "cwd": launch_config.cwd,
+                "args": launch_config.args
+            }),
+        };
+
+        let map = args.as_object_mut().unwrap();
+
+        if let Some(stop_on_entry) = config.stop_on_entry {
+            map.insert("stopOnEntry".into(), stop_on_entry.into());
+        }
+
+        StartDebuggingRequestArguments {
+            configuration: args,
+            request: config.request.to_dap(),
+        }
+    }
 }
 
 #[async_trait(?Send)]
@@ -20,7 +46,7 @@ impl DebugAdapter for GoDebugAdapter {
     async fn get_binary(
         &self,
         delegate: &dyn DapDelegate,
-        config: &DebugAdapterConfig,
+        config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         cx: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
@@ -53,7 +79,7 @@ impl DebugAdapter for GoDebugAdapter {
     async fn get_installed_binary(
         &self,
         delegate: &dyn DapDelegate,
-        config: &DebugAdapterConfig,
+        config: &DebugTaskDefinition,
         _: Option<PathBuf>,
         _: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
@@ -66,6 +92,7 @@ impl DebugAdapter for GoDebugAdapter {
         let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
 
         Ok(DebugAdapterBinary {
+            adapter_name: self.name(),
             command: delve_path,
             arguments: Some(vec![
                 "dap".into(),
@@ -79,29 +106,7 @@ impl DebugAdapter for GoDebugAdapter {
                 port,
                 timeout,
             }),
+            request_args: self.request_args(config),
         })
     }
-
-    fn request_args(&self, config: &DebugTaskDefinition) -> Value {
-        let mut args = match &config.request {
-            dap::DebugRequestType::Attach(attach_config) => {
-                json!({
-                    "processId": attach_config.process_id,
-                })
-            }
-            dap::DebugRequestType::Launch(launch_config) => json!({
-                "program": launch_config.program,
-                "cwd": launch_config.cwd,
-                "args": launch_config.args
-            }),
-        };
-
-        let map = args.as_object_mut().unwrap();
-
-        if let Some(stop_on_entry) = config.stop_on_entry {
-            map.insert("stopOnEntry".into(), stop_on_entry.into());
-        }
-
-        args
-    }
 }

crates/dap_adapters/src/javascript.rs 🔗

@@ -1,4 +1,5 @@
 use adapters::latest_github_release;
+use dap::StartDebuggingRequestArguments;
 use gpui::AsyncApp;
 use std::path::PathBuf;
 use task::{DebugRequestType, DebugTaskDefinition};
@@ -12,6 +13,40 @@ impl JsDebugAdapter {
     const ADAPTER_NAME: &'static str = "JavaScript";
     const ADAPTER_NPM_NAME: &'static str = "vscode-js-debug";
     const ADAPTER_PATH: &'static str = "js-debug/src/dapDebugServer.js";
+
+    fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments {
+        let mut args = json!({
+            "type": "pwa-node",
+            "request": match config.request {
+                DebugRequestType::Launch(_) => "launch",
+                DebugRequestType::Attach(_) => "attach",
+            },
+        });
+        let map = args.as_object_mut().unwrap();
+        match &config.request {
+            DebugRequestType::Attach(attach) => {
+                map.insert("processId".into(), attach.process_id.into());
+            }
+            DebugRequestType::Launch(launch) => {
+                map.insert("program".into(), launch.program.clone().into());
+
+                if !launch.args.is_empty() {
+                    map.insert("args".into(), launch.args.clone().into());
+                }
+
+                if let Some(stop_on_entry) = config.stop_on_entry {
+                    map.insert("stopOnEntry".into(), stop_on_entry.into());
+                }
+                if let Some(cwd) = launch.cwd.as_ref() {
+                    map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
+                }
+            }
+        }
+        StartDebuggingRequestArguments {
+            configuration: args,
+            request: config.request.to_dap(),
+        }
+    }
 }
 
 #[async_trait(?Send)]
@@ -49,7 +84,7 @@ impl DebugAdapter for JsDebugAdapter {
     async fn get_installed_binary(
         &self,
         delegate: &dyn DapDelegate,
-        config: &DebugAdapterConfig,
+        config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         _: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
@@ -71,6 +106,7 @@ impl DebugAdapter for JsDebugAdapter {
         let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
 
         Ok(DebugAdapterBinary {
+            adapter_name: self.name(),
             command: delegate
                 .node_runtime()
                 .binary_path()
@@ -89,6 +125,7 @@ impl DebugAdapter for JsDebugAdapter {
                 port,
                 timeout,
             }),
+            request_args: self.request_args(config),
         })
     }
 
@@ -107,35 +144,4 @@ impl DebugAdapter for JsDebugAdapter {
 
         return Ok(());
     }
-
-    fn request_args(&self, config: &DebugTaskDefinition) -> Value {
-        let mut args = json!({
-            "type": "pwa-node",
-            "request": match config.request {
-                DebugRequestType::Launch(_) => "launch",
-                DebugRequestType::Attach(_) => "attach",
-            },
-        });
-        let map = args.as_object_mut().unwrap();
-        match &config.request {
-            DebugRequestType::Attach(attach) => {
-                map.insert("processId".into(), attach.process_id.into());
-            }
-            DebugRequestType::Launch(launch) => {
-                map.insert("program".into(), launch.program.clone().into());
-
-                if !launch.args.is_empty() {
-                    map.insert("args".into(), launch.args.clone().into());
-                }
-
-                if let Some(stop_on_entry) = config.stop_on_entry {
-                    map.insert("stopOnEntry".into(), stop_on_entry.into());
-                }
-                if let Some(cwd) = launch.cwd.as_ref() {
-                    map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
-                }
-            }
-        }
-        args
-    }
 }

crates/dap_adapters/src/lldb.rs 🔗

@@ -3,7 +3,7 @@ use std::{ffi::OsStr, path::PathBuf};
 use anyhow::Result;
 use async_trait::async_trait;
 use gpui::AsyncApp;
-use task::{DebugAdapterConfig, DebugRequestType, DebugTaskDefinition};
+use task::{DebugRequestType, DebugTaskDefinition};
 
 use crate::*;
 
@@ -12,6 +12,40 @@ pub(crate) struct LldbDebugAdapter;
 
 impl LldbDebugAdapter {
     const ADAPTER_NAME: &'static str = "LLDB";
+
+    fn request_args(&self, config: &DebugTaskDefinition) -> dap::StartDebuggingRequestArguments {
+        let request = config.request.to_dap();
+        let mut args = json!({
+            "request": match config.request {
+                DebugRequestType::Launch(_) => "launch",
+                DebugRequestType::Attach(_) => "attach",
+            },
+        });
+        let map = args.as_object_mut().unwrap();
+        match &config.request {
+            DebugRequestType::Attach(attach) => {
+                map.insert("pid".into(), attach.process_id.into());
+            }
+            DebugRequestType::Launch(launch) => {
+                map.insert("program".into(), launch.program.clone().into());
+
+                if !launch.args.is_empty() {
+                    map.insert("args".into(), launch.args.clone().into());
+                }
+
+                if let Some(stop_on_entry) = config.stop_on_entry {
+                    map.insert("stopOnEntry".into(), stop_on_entry.into());
+                }
+                if let Some(cwd) = launch.cwd.as_ref() {
+                    map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
+                }
+            }
+        }
+        dap::StartDebuggingRequestArguments {
+            request,
+            configuration: args,
+        }
+    }
 }
 
 #[async_trait(?Send)]
@@ -23,7 +57,7 @@ impl DebugAdapter for LldbDebugAdapter {
     async fn get_binary(
         &self,
         delegate: &dyn DapDelegate,
-        _: &DebugAdapterConfig,
+        config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         _: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
@@ -37,11 +71,13 @@ impl DebugAdapter for LldbDebugAdapter {
         };
 
         Ok(DebugAdapterBinary {
+            adapter_name: Self::ADAPTER_NAME.into(),
             command: lldb_dap_path,
             arguments: None,
             envs: None,
             cwd: None,
             connection: None,
+            request_args: self.request_args(config),
         })
     }
 
@@ -60,40 +96,10 @@ impl DebugAdapter for LldbDebugAdapter {
     async fn get_installed_binary(
         &self,
         _: &dyn DapDelegate,
-        _: &DebugAdapterConfig,
+        _: &DebugTaskDefinition,
         _: Option<PathBuf>,
         _: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
         unimplemented!("LLDB debug adapter cannot be installed by Zed (yet)")
     }
-
-    fn request_args(&self, config: &DebugTaskDefinition) -> Value {
-        let mut args = json!({
-            "request": match config.request {
-                DebugRequestType::Launch(_) => "launch",
-                DebugRequestType::Attach(_) => "attach",
-            },
-        });
-        let map = args.as_object_mut().unwrap();
-        match &config.request {
-            DebugRequestType::Attach(attach) => {
-                map.insert("pid".into(), attach.process_id.into());
-            }
-            DebugRequestType::Launch(launch) => {
-                map.insert("program".into(), launch.program.clone().into());
-
-                if !launch.args.is_empty() {
-                    map.insert("args".into(), launch.args.clone().into());
-                }
-
-                if let Some(stop_on_entry) = config.stop_on_entry {
-                    map.insert("stopOnEntry".into(), stop_on_entry.into());
-                }
-                if let Some(cwd) = launch.cwd.as_ref() {
-                    map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
-                }
-            }
-        }
-        args
-    }
 }

crates/dap_adapters/src/php.rs 🔗

@@ -13,6 +13,28 @@ impl PhpDebugAdapter {
     const ADAPTER_NAME: &'static str = "PHP";
     const ADAPTER_PACKAGE_NAME: &'static str = "vscode-php-debug";
     const ADAPTER_PATH: &'static str = "extension/out/phpDebug.js";
+
+    fn request_args(
+        &self,
+        config: &DebugTaskDefinition,
+    ) -> Result<dap::StartDebuggingRequestArguments> {
+        match &config.request {
+            dap::DebugRequestType::Attach(_) => {
+                anyhow::bail!("php adapter does not support attaching")
+            }
+            dap::DebugRequestType::Launch(launch_config) => {
+                Ok(dap::StartDebuggingRequestArguments {
+                    configuration: json!({
+                        "program": launch_config.program,
+                        "cwd": launch_config.cwd,
+                        "args": launch_config.args,
+                        "stopOnEntry": config.stop_on_entry.unwrap_or_default(),
+                    }),
+                    request: config.request.to_dap(),
+                })
+            }
+        }
+    }
 }
 
 #[async_trait(?Send)]
@@ -50,7 +72,7 @@ impl DebugAdapter for PhpDebugAdapter {
     async fn get_installed_binary(
         &self,
         delegate: &dyn DapDelegate,
-        config: &DebugAdapterConfig,
+        config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         _: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
@@ -72,6 +94,7 @@ impl DebugAdapter for PhpDebugAdapter {
         let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
 
         Ok(DebugAdapterBinary {
+            adapter_name: self.name(),
             command: delegate
                 .node_runtime()
                 .binary_path()
@@ -89,6 +112,7 @@ impl DebugAdapter for PhpDebugAdapter {
             }),
             cwd: None,
             envs: None,
+            request_args: self.request_args(config)?,
         })
     }
 
@@ -107,21 +131,4 @@ impl DebugAdapter for PhpDebugAdapter {
 
         Ok(())
     }
-
-    fn request_args(&self, config: &DebugTaskDefinition) -> Value {
-        match &config.request {
-            dap::DebugRequestType::Attach(_) => {
-                // php adapter does not support attaching
-                json!({})
-            }
-            dap::DebugRequestType::Launch(launch_config) => {
-                json!({
-                    "program": launch_config.program,
-                    "cwd": launch_config.cwd,
-                    "args": launch_config.args,
-                    "stopOnEntry": config.stop_on_entry.unwrap_or_default(),
-                })
-            }
-        }
-    }
 }

crates/dap_adapters/src/python.rs 🔗

@@ -1,5 +1,5 @@
 use crate::*;
-use dap::DebugRequestType;
+use dap::{DebugRequestType, StartDebuggingRequestArguments};
 use gpui::AsyncApp;
 use std::{ffi::OsStr, path::PathBuf};
 use task::DebugTaskDefinition;
@@ -12,6 +12,38 @@ impl PythonDebugAdapter {
     const ADAPTER_PACKAGE_NAME: &'static str = "debugpy";
     const ADAPTER_PATH: &'static str = "src/debugpy/adapter";
     const LANGUAGE_NAME: &'static str = "Python";
+
+    fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments {
+        let mut args = json!({
+            "request": match config.request {
+                DebugRequestType::Launch(_) => "launch",
+                DebugRequestType::Attach(_) => "attach",
+            },
+            "subProcess": true,
+            "redirectOutput": true,
+        });
+        let map = args.as_object_mut().unwrap();
+        match &config.request {
+            DebugRequestType::Attach(attach) => {
+                map.insert("processId".into(), attach.process_id.into());
+            }
+            DebugRequestType::Launch(launch) => {
+                map.insert("program".into(), launch.program.clone().into());
+                map.insert("args".into(), launch.args.clone().into());
+
+                if let Some(stop_on_entry) = config.stop_on_entry {
+                    map.insert("stopOnEntry".into(), stop_on_entry.into());
+                }
+                if let Some(cwd) = launch.cwd.as_ref() {
+                    map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
+                }
+            }
+        }
+        StartDebuggingRequestArguments {
+            configuration: args,
+            request: config.request.to_dap(),
+        }
+    }
 }
 
 #[async_trait(?Send)]
@@ -64,7 +96,7 @@ impl DebugAdapter for PythonDebugAdapter {
     async fn get_installed_binary(
         &self,
         delegate: &dyn DapDelegate,
-        config: &DebugAdapterConfig,
+        config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         cx: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
@@ -109,6 +141,7 @@ impl DebugAdapter for PythonDebugAdapter {
         };
 
         Ok(DebugAdapterBinary {
+            adapter_name: self.name(),
             command: python_path.ok_or(anyhow!("failed to find binary path for python"))?,
             arguments: Some(vec![
                 debugpy_dir.join(Self::ADAPTER_PATH).into(),
@@ -122,35 +155,7 @@ impl DebugAdapter for PythonDebugAdapter {
             }),
             cwd: None,
             envs: None,
+            request_args: self.request_args(config),
         })
     }
-
-    fn request_args(&self, config: &DebugTaskDefinition) -> Value {
-        let mut args = json!({
-            "request": match config.request {
-                DebugRequestType::Launch(_) => "launch",
-                DebugRequestType::Attach(_) => "attach",
-            },
-            "subProcess": true,
-            "redirectOutput": true,
-        });
-        let map = args.as_object_mut().unwrap();
-        match &config.request {
-            DebugRequestType::Attach(attach) => {
-                map.insert("processId".into(), attach.process_id.into());
-            }
-            DebugRequestType::Launch(launch) => {
-                map.insert("program".into(), launch.program.clone().into());
-                map.insert("args".into(), launch.args.clone().into());
-
-                if let Some(stop_on_entry) = config.stop_on_entry {
-                    map.insert("stopOnEntry".into(), stop_on_entry.into());
-                }
-                if let Some(cwd) = launch.cwd.as_ref() {
-                    map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
-                }
-            }
-        }
-        args
-    }
 }

crates/debugger_ui/src/attach_modal.rs 🔗

@@ -228,10 +228,7 @@ impl PickerDelegate for AttachModalDelegate {
         let config = self.debug_config.clone();
         self.project
             .update(cx, |project, cx| {
-                #[cfg(any(test, feature = "test-support"))]
-                let ret = project.fake_debug_session(config.request, None, false, cx);
-                #[cfg(not(any(test, feature = "test-support")))]
-                let ret = project.start_debug_session(config.into(), cx);
+                let ret = project.start_debug_session(config, cx);
                 ret
             })
             .detach_and_log_err(cx);

crates/debugger_ui/src/new_session_modal.rs 🔗

@@ -146,7 +146,7 @@ impl NewSessionModal {
                 {
                     this.start_debug_session(debug_config, cx)
                 } else {
-                    this.start_debug_session(config.into(), cx)
+                    this.start_debug_session(config, cx)
                 }
             })?;
             let spawn_result = task.await;

crates/debugger_ui/src/tests/attach_modal.rs 🔗

@@ -26,18 +26,30 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te
     let workspace = init_test_workspace(&project, cx).await;
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Attach(AttachConfig {
+    let session = debugger::test::start_debug_session_with(
+        &project,
+        cx,
+        DebugTaskDefinition {
+            adapter: "fake-adapter".to_string(),
+            request: dap::DebugRequestType::Attach(AttachConfig {
                 process_id: Some(10),
             }),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let session = task.await.unwrap();
+            label: "label".to_string(),
+            initialize_args: None,
+            tcp_connection: None,
+            locator: None,
+            stop_on_entry: None,
+        },
+        |client| {
+            client.on_request::<dap::requests::Attach, _>(move |_, args| {
+                assert_eq!(json!({"request": "attach", "process_id": 10}), args.raw);
+
+                Ok(())
+            });
+        },
+    )
+    .await
+    .unwrap();
 
     cx.run_until_parked();
 
@@ -77,7 +89,15 @@ async fn test_show_attach_modal_and_select_process(
     let project = Project::test(fs, ["/project".as_ref()], cx).await;
     let workspace = init_test_workspace(&project, cx).await;
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
+    // Set up handlers for sessions spawned via modal.
+    let _initialize_subscription =
+        project::debugger::test::intercept_debug_sessions(cx, |client| {
+            client.on_request::<dap::requests::Attach, _>(move |_, args| {
+                assert_eq!(json!({"request": "attach", "process_id": 1}), args.raw);
 
+                Ok(())
+            });
+        });
     let attach_modal = workspace
         .update(cx, |workspace, window, cx| {
             workspace.toggle_modal(window, cx, |window, cx| {

crates/debugger_ui/src/tests/console.rs 🔗

@@ -3,7 +3,6 @@ use dap::requests::StackTrace;
 use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
 use project::{FakeFs, Project};
 use serde_json::json;
-use task::LaunchConfig;
 use tests::{init_test, init_test_workspace};
 
 #[gpui::test]
@@ -29,26 +28,17 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
         })
         .unwrap();
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let session = task.await.unwrap();
+    let session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    client
-        .on_request::<StackTrace, _>(move |_, _| {
-            Ok(dap::StackTraceResponse {
-                stack_frames: Vec::default(),
-                total_frames: None,
-            })
+    client.on_request::<StackTrace, _>(move |_, _| {
+        Ok(dap::StackTraceResponse {
+            stack_frames: Vec::default(),
+            total_frames: None,
         })
-        .await;
+    });
 
     client
         .fake_event(dap::messages::Events::Output(dap::OutputEvent {

crates/debugger_ui/src/tests/debugger_panel.rs 🔗

@@ -1,7 +1,7 @@
 use crate::*;
 use dap::{
-    ErrorResponse, RunInTerminalRequestArguments, SourceBreakpoint, StartDebuggingRequestArguments,
-    StartDebuggingRequestArgumentsRequest,
+    ErrorResponse, Message, RunInTerminalRequestArguments, SourceBreakpoint,
+    StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
     client::SessionId,
     requests::{
         Continue, Disconnect, Launch, Next, RunInTerminal, SetBreakpoints, StackTrace,
@@ -25,7 +25,6 @@ use std::{
         atomic::{AtomicBool, Ordering},
     },
 };
-use task::LaunchConfig;
 use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
 use tests::{active_debug_session_panel, init_test, init_test_workspace};
 use util::path;
@@ -49,37 +48,26 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
     let workspace = init_test_workspace(&project, cx).await;
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let session = task.await.unwrap();
+    let session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    client
-        .on_request::<Threads, _>(move |_, _| {
-            Ok(dap::ThreadsResponse {
-                threads: vec![dap::Thread {
-                    id: 1,
-                    name: "Thread 1".into(),
-                }],
-            })
+    client.on_request::<Threads, _>(move |_, _| {
+        Ok(dap::ThreadsResponse {
+            threads: vec![dap::Thread {
+                id: 1,
+                name: "Thread 1".into(),
+            }],
         })
-        .await;
+    });
 
-    client
-        .on_request::<StackTrace, _>(move |_, _| {
-            Ok(dap::StackTraceResponse {
-                stack_frames: Vec::default(),
-                total_frames: None,
-            })
+    client.on_request::<StackTrace, _>(move |_, _| {
+        Ok(dap::StackTraceResponse {
+            stack_frames: Vec::default(),
+            total_frames: None,
         })
-        .await;
+    });
 
     cx.run_until_parked();
 
@@ -199,37 +187,26 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
     let workspace = init_test_workspace(&project, cx).await;
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let session = task.await.unwrap();
+    let session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    client
-        .on_request::<Threads, _>(move |_, _| {
-            Ok(dap::ThreadsResponse {
-                threads: vec![dap::Thread {
-                    id: 1,
-                    name: "Thread 1".into(),
-                }],
-            })
+    client.on_request::<Threads, _>(move |_, _| {
+        Ok(dap::ThreadsResponse {
+            threads: vec![dap::Thread {
+                id: 1,
+                name: "Thread 1".into(),
+            }],
         })
-        .await;
+    });
 
-    client
-        .on_request::<StackTrace, _>(move |_, _| {
-            Ok(dap::StackTraceResponse {
-                stack_frames: Vec::default(),
-                total_frames: None,
-            })
+    client.on_request::<StackTrace, _>(move |_, _| {
+        Ok(dap::StackTraceResponse {
+            stack_frames: Vec::default(),
+            total_frames: None,
         })
-        .await;
+    });
 
     cx.run_until_parked();
 
@@ -377,16 +354,9 @@ async fn test_handle_successful_run_in_terminal_reverse_request(
     let workspace = init_test_workspace(&project, cx).await;
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let session = task.await.unwrap();
+    let session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 
     client
@@ -474,16 +444,9 @@ async fn test_handle_error_run_in_terminal_reverse_request(
     let workspace = init_test_workspace(&project, cx).await;
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let session = task.await.unwrap();
+    let session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 
     client
@@ -559,28 +522,19 @@ async fn test_handle_start_debugging_reverse_request(
     let workspace = init_test_workspace(&project, cx).await;
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let session = task.await.unwrap();
+    let session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    client
-        .on_request::<dap::requests::Threads, _>(move |_, _| {
-            Ok(dap::ThreadsResponse {
-                threads: vec![dap::Thread {
-                    id: 1,
-                    name: "Thread 1".into(),
-                }],
-            })
+    client.on_request::<dap::requests::Threads, _>(move |_, _| {
+        Ok(dap::ThreadsResponse {
+            threads: vec![dap::Thread {
+                id: 1,
+                name: "Thread 1".into(),
+            }],
         })
-        .await;
+    });
 
     client
         .on_response::<StartDebugging, _>({
@@ -593,7 +547,9 @@ async fn test_handle_start_debugging_reverse_request(
             }
         })
         .await;
-
+    // Set up handlers for sessions spawned with reverse request too.
+    let _reverse_request_subscription =
+        project::debugger::test::intercept_debug_sessions(cx, |_| {});
     client
         .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
             configuration: json!({}),
@@ -612,20 +568,16 @@ async fn test_handle_start_debugging_reverse_request(
     });
     let child_client = child_session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    child_client
-        .on_request::<dap::requests::Threads, _>(move |_, _| {
-            Ok(dap::ThreadsResponse {
-                threads: vec![dap::Thread {
-                    id: 1,
-                    name: "Thread 1".into(),
-                }],
-            })
+    child_client.on_request::<dap::requests::Threads, _>(move |_, _| {
+        Ok(dap::ThreadsResponse {
+            threads: vec![dap::Thread {
+                id: 1,
+                name: "Thread 1".into(),
+            }],
         })
-        .await;
+    });
 
-    child_client
-        .on_request::<Disconnect, _>(move |_, _| Ok(()))
-        .await;
+    child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 
     child_client
         .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
@@ -677,31 +629,24 @@ async fn test_shutdown_children_when_parent_session_shutdown(
     let workspace = init_test_workspace(&project, cx).await;
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let parent_session = task.await.unwrap();
+    let parent_session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    client
-        .on_request::<dap::requests::Threads, _>(move |_, _| {
-            Ok(dap::ThreadsResponse {
-                threads: vec![dap::Thread {
-                    id: 1,
-                    name: "Thread 1".into(),
-                }],
-            })
+    client.on_request::<dap::requests::Threads, _>(move |_, _| {
+        Ok(dap::ThreadsResponse {
+            threads: vec![dap::Thread {
+                id: 1,
+                name: "Thread 1".into(),
+            }],
         })
-        .await;
+    });
 
     client.on_response::<StartDebugging, _>(move |_| {}).await;
-
+    // Set up handlers for sessions spawned with reverse request too.
+    let _reverse_request_subscription =
+        project::debugger::test::intercept_debug_sessions(cx, |_| {});
     // start first child session
     client
         .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
@@ -729,9 +674,7 @@ async fn test_shutdown_children_when_parent_session_shutdown(
     let first_child_client =
         first_child_session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    first_child_client
-        .on_request::<Disconnect, _>(move |_, _| Ok(()))
-        .await;
+    first_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 
     // configure second child session
     let second_child_session = dap_store.read_with(cx, |dap_store, _| {
@@ -740,9 +683,7 @@ async fn test_shutdown_children_when_parent_session_shutdown(
     let second_child_client =
         second_child_session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    second_child_client
-        .on_request::<Disconnect, _>(move |_, _| Ok(()))
-        .await;
+    second_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 
     cx.run_until_parked();
 
@@ -796,20 +737,15 @@ async fn test_shutdown_parent_session_if_all_children_are_shutdown(
     let workspace = init_test_workspace(&project, cx).await;
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let parent_session = task.await.unwrap();
+    let parent_session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
 
     client.on_response::<StartDebugging, _>(move |_| {}).await;
-
+    // Set up handlers for sessions spawned with reverse request too.
+    let _reverse_request_subscription =
+        project::debugger::test::intercept_debug_sessions(cx, |_| {});
     // start first child session
     client
         .fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
@@ -837,9 +773,7 @@ async fn test_shutdown_parent_session_if_all_children_are_shutdown(
     let first_child_client =
         first_child_session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    first_child_client
-        .on_request::<Disconnect, _>(move |_, _| Ok(()))
-        .await;
+    first_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 
     // configure second child session
     let second_child_session = dap_store.read_with(cx, |dap_store, _| {
@@ -848,9 +782,7 @@ async fn test_shutdown_parent_session_if_all_children_are_shutdown(
     let second_child_client =
         second_child_session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    second_child_client
-        .on_request::<Disconnect, _>(move |_, _| Ok(()))
-        .await;
+    second_child_client.on_request::<Disconnect, _>(move |_, _| Ok(()));
 
     cx.run_until_parked();
 
@@ -926,123 +858,107 @@ async fn test_debug_panel_item_thread_status_reset_on_failure(
     let workspace = init_test_workspace(&project, cx).await;
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            Some(dap::Capabilities {
+    let session = debugger::test::start_debug_session(&project, cx, |client| {
+        client.on_request::<dap::requests::Initialize, _>(move |_, _| {
+            Ok(dap::Capabilities {
                 supports_step_back: Some(true),
                 ..Default::default()
-            }),
-            false,
-            cx,
-        )
-    });
+            })
+        });
+    })
+    .await
+    .unwrap();
 
-    let session = task.await.unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
     const THREAD_ID_NUM: u64 = 1;
 
-    client
-        .on_request::<dap::requests::Threads, _>(move |_, _| {
-            Ok(dap::ThreadsResponse {
-                threads: vec![dap::Thread {
-                    id: THREAD_ID_NUM,
-                    name: "Thread 1".into(),
-                }],
-            })
+    client.on_request::<dap::requests::Threads, _>(move |_, _| {
+        Ok(dap::ThreadsResponse {
+            threads: vec![dap::Thread {
+                id: THREAD_ID_NUM,
+                name: "Thread 1".into(),
+            }],
         })
-        .await;
+    });
 
-    client.on_request::<Launch, _>(move |_, _| Ok(())).await;
+    client.on_request::<Launch, _>(move |_, _| Ok(()));
 
-    client
-        .on_request::<StackTrace, _>(move |_, _| {
-            Ok(dap::StackTraceResponse {
-                stack_frames: Vec::default(),
-                total_frames: None,
-            })
+    client.on_request::<StackTrace, _>(move |_, _| {
+        Ok(dap::StackTraceResponse {
+            stack_frames: Vec::default(),
+            total_frames: None,
         })
-        .await;
+    });
 
-    client
-        .on_request::<Next, _>(move |_, _| {
-            Err(ErrorResponse {
-                error: Some(dap::Message {
-                    id: 1,
-                    format: "error".into(),
-                    variables: None,
-                    send_telemetry: None,
-                    show_user: None,
-                    url: None,
-                    url_label: None,
-                }),
-            })
+    client.on_request::<Next, _>(move |_, _| {
+        Err(ErrorResponse {
+            error: Some(dap::Message {
+                id: 1,
+                format: "error".into(),
+                variables: None,
+                send_telemetry: None,
+                show_user: None,
+                url: None,
+                url_label: None,
+            }),
         })
-        .await;
+    });
 
-    client
-        .on_request::<StepOut, _>(move |_, _| {
-            Err(ErrorResponse {
-                error: Some(dap::Message {
-                    id: 1,
-                    format: "error".into(),
-                    variables: None,
-                    send_telemetry: None,
-                    show_user: None,
-                    url: None,
-                    url_label: None,
-                }),
-            })
+    client.on_request::<StepOut, _>(move |_, _| {
+        Err(ErrorResponse {
+            error: Some(dap::Message {
+                id: 1,
+                format: "error".into(),
+                variables: None,
+                send_telemetry: None,
+                show_user: None,
+                url: None,
+                url_label: None,
+            }),
         })
-        .await;
+    });
 
-    client
-        .on_request::<StepIn, _>(move |_, _| {
-            Err(ErrorResponse {
-                error: Some(dap::Message {
-                    id: 1,
-                    format: "error".into(),
-                    variables: None,
-                    send_telemetry: None,
-                    show_user: None,
-                    url: None,
-                    url_label: None,
-                }),
-            })
+    client.on_request::<StepIn, _>(move |_, _| {
+        Err(ErrorResponse {
+            error: Some(dap::Message {
+                id: 1,
+                format: "error".into(),
+                variables: None,
+                send_telemetry: None,
+                show_user: None,
+                url: None,
+                url_label: None,
+            }),
         })
-        .await;
+    });
 
-    client
-        .on_request::<StepBack, _>(move |_, _| {
-            Err(ErrorResponse {
-                error: Some(dap::Message {
-                    id: 1,
-                    format: "error".into(),
-                    variables: None,
-                    send_telemetry: None,
-                    show_user: None,
-                    url: None,
-                    url_label: None,
-                }),
-            })
+    client.on_request::<StepBack, _>(move |_, _| {
+        Err(ErrorResponse {
+            error: Some(dap::Message {
+                id: 1,
+                format: "error".into(),
+                variables: None,
+                send_telemetry: None,
+                show_user: None,
+                url: None,
+                url_label: None,
+            }),
         })
-        .await;
+    });
 
-    client
-        .on_request::<Continue, _>(move |_, _| {
-            Err(ErrorResponse {
-                error: Some(dap::Message {
-                    id: 1,
-                    format: "error".into(),
-                    variables: None,
-                    send_telemetry: None,
-                    show_user: None,
-                    url: None,
-                    url_label: None,
-                }),
-            })
+    client.on_request::<Continue, _>(move |_, _| {
+        Err(ErrorResponse {
+            error: Some(dap::Message {
+                id: 1,
+                format: "error".into(),
+                variables: None,
+                send_telemetry: None,
+                show_user: None,
+                url: None,
+                url_label: None,
+            }),
         })
-        .await;
+    });
 
     client
         .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
@@ -1157,16 +1073,9 @@ async fn test_send_breakpoints_when_editor_has_been_saved(
         .update(cx, |_, _, cx| worktree.read(cx).id())
         .unwrap();
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let session = task.await.unwrap();
+    let session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 
     let buffer = project
@@ -1186,16 +1095,14 @@ async fn test_send_breakpoints_when_editor_has_been_saved(
         )
     });
 
-    client.on_request::<Launch, _>(move |_, _| Ok(())).await;
+    client.on_request::<Launch, _>(move |_, _| Ok(()));
 
-    client
-        .on_request::<StackTrace, _>(move |_, _| {
-            Ok(dap::StackTraceResponse {
-                stack_frames: Vec::default(),
-                total_frames: None,
-            })
+    client.on_request::<StackTrace, _>(move |_, _| {
+        Ok(dap::StackTraceResponse {
+            stack_frames: Vec::default(),
+            total_frames: None,
         })
-        .await;
+    });
 
     client
         .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
@@ -1210,32 +1117,30 @@ async fn test_send_breakpoints_when_editor_has_been_saved(
         .await;
 
     let called_set_breakpoints = Arc::new(AtomicBool::new(false));
-    client
-        .on_request::<SetBreakpoints, _>({
-            let called_set_breakpoints = called_set_breakpoints.clone();
-            move |_, args| {
-                assert_eq!(path!("/project/main.rs"), args.source.path.unwrap());
-                assert_eq!(
-                    vec![SourceBreakpoint {
-                        line: 2,
-                        column: None,
-                        condition: None,
-                        hit_condition: None,
-                        log_message: None,
-                        mode: None
-                    }],
-                    args.breakpoints.unwrap()
-                );
-                assert!(!args.source_modified.unwrap());
+    client.on_request::<SetBreakpoints, _>({
+        let called_set_breakpoints = called_set_breakpoints.clone();
+        move |_, args| {
+            assert_eq!(path!("/project/main.rs"), args.source.path.unwrap());
+            assert_eq!(
+                vec![SourceBreakpoint {
+                    line: 2,
+                    column: None,
+                    condition: None,
+                    hit_condition: None,
+                    log_message: None,
+                    mode: None
+                }],
+                args.breakpoints.unwrap()
+            );
+            assert!(!args.source_modified.unwrap());
 
-                called_set_breakpoints.store(true, Ordering::SeqCst);
+            called_set_breakpoints.store(true, Ordering::SeqCst);
 
-                Ok(dap::SetBreakpointsResponse {
-                    breakpoints: Vec::default(),
-                })
-            }
-        })
-        .await;
+            Ok(dap::SetBreakpointsResponse {
+                breakpoints: Vec::default(),
+            })
+        }
+    });
 
     editor.update_in(cx, |editor, window, cx| {
         editor.move_down(&actions::MoveDown, window, cx);
@@ -1250,32 +1155,30 @@ async fn test_send_breakpoints_when_editor_has_been_saved(
     );
 
     let called_set_breakpoints = Arc::new(AtomicBool::new(false));
-    client
-        .on_request::<SetBreakpoints, _>({
-            let called_set_breakpoints = called_set_breakpoints.clone();
-            move |_, args| {
-                assert_eq!(path!("/project/main.rs"), args.source.path.unwrap());
-                assert_eq!(
-                    vec![SourceBreakpoint {
-                        line: 3,
-                        column: None,
-                        condition: None,
-                        hit_condition: None,
-                        log_message: None,
-                        mode: None
-                    }],
-                    args.breakpoints.unwrap()
-                );
-                assert!(args.source_modified.unwrap());
+    client.on_request::<SetBreakpoints, _>({
+        let called_set_breakpoints = called_set_breakpoints.clone();
+        move |_, args| {
+            assert_eq!(path!("/project/main.rs"), args.source.path.unwrap());
+            assert_eq!(
+                vec![SourceBreakpoint {
+                    line: 3,
+                    column: None,
+                    condition: None,
+                    hit_condition: None,
+                    log_message: None,
+                    mode: None
+                }],
+                args.breakpoints.unwrap()
+            );
+            assert!(args.source_modified.unwrap());
 
-                called_set_breakpoints.store(true, Ordering::SeqCst);
+            called_set_breakpoints.store(true, Ordering::SeqCst);
 
-                Ok(dap::SetBreakpointsResponse {
-                    breakpoints: Vec::default(),
-                })
-            }
-        })
-        .await;
+            Ok(dap::SetBreakpointsResponse {
+                breakpoints: Vec::default(),
+            })
+        }
+    });
 
     editor.update_in(cx, |editor, window, cx| {
         editor.move_up(&actions::MoveUp, window, cx);
@@ -1387,49 +1290,40 @@ async fn test_unsetting_breakpoints_on_clear_breakpoint_action(
         editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
     });
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let session = task.await.unwrap();
+    let session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 
     let called_set_breakpoints = Arc::new(AtomicBool::new(false));
 
-    client
-        .on_request::<SetBreakpoints, _>({
-            let called_set_breakpoints = called_set_breakpoints.clone();
-            move |_, args| {
-                assert!(
-                    args.breakpoints.is_none_or(|bps| bps.is_empty()),
-                    "Send empty breakpoint sets to clear them from DAP servers"
-                );
+    client.on_request::<SetBreakpoints, _>({
+        let called_set_breakpoints = called_set_breakpoints.clone();
+        move |_, args| {
+            assert!(
+                args.breakpoints.is_none_or(|bps| bps.is_empty()),
+                "Send empty breakpoint sets to clear them from DAP servers"
+            );
 
-                match args
-                    .source
-                    .path
-                    .expect("We should always send a breakpoint's path")
-                    .as_str()
-                {
-                    "/project/main.rs" | "/project/second.rs" => {}
-                    _ => {
-                        panic!("Unset breakpoints for path that doesn't have any")
-                    }
+            match args
+                .source
+                .path
+                .expect("We should always send a breakpoint's path")
+                .as_str()
+            {
+                "/project/main.rs" | "/project/second.rs" => {}
+                _ => {
+                    panic!("Unset breakpoints for path that doesn't have any")
                 }
+            }
 
-                called_set_breakpoints.store(true, Ordering::SeqCst);
+            called_set_breakpoints.store(true, Ordering::SeqCst);
 
-                Ok(dap::SetBreakpointsResponse {
-                    breakpoints: Vec::default(),
-                })
-            }
-        })
-        .await;
+            Ok(dap::SetBreakpointsResponse {
+                breakpoints: Vec::default(),
+            })
+        }
+    });
 
     cx.dispatch_action(crate::ClearAllBreakpoints);
     cx.run_until_parked();
@@ -1464,13 +1358,20 @@ async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails(
     let workspace = init_test_workspace(&project, cx).await;
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            true,
-            cx,
-        )
+    let task = project::debugger::test::start_debug_session(&project, cx, |client| {
+        client.on_request::<dap::requests::Initialize, _>(|_, _| {
+            Err(ErrorResponse {
+                error: Some(Message {
+                    format: "failed to launch".to_string(),
+                    id: 1,
+                    variables: None,
+                    send_telemetry: None,
+                    show_user: None,
+                    url: None,
+                    url_label: None,
+                }),
+            })
+        });
     });
 
     assert!(

crates/debugger_ui/src/tests/module_list.rs 🔗

@@ -4,15 +4,17 @@ use crate::{
 };
 use dap::{
     StoppedEvent,
-    requests::{Modules, StackTrace, Threads},
+    requests::{Initialize, Modules},
 };
 use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
-use project::{FakeFs, Project};
+use project::{
+    FakeFs, Project,
+    debugger::{self},
+};
 use std::sync::{
     Arc,
     atomic::{AtomicBool, AtomicI32, Ordering},
 };
-use task::LaunchConfig;
 
 #[gpui::test]
 async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext) {
@@ -29,31 +31,19 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext)
         .unwrap();
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            Some(dap::Capabilities {
+    let session = debugger::test::start_debug_session(&project, cx, |client| {
+        client.on_request::<Initialize, _>(move |_, _| {
+            Ok(dap::Capabilities {
                 supports_modules_request: Some(true),
                 ..Default::default()
-            }),
-            false,
-            cx,
-        )
-    });
+            })
+        });
+    })
+    .await
+    .unwrap();
 
-    let session = task.await.unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    client
-        .on_request::<StackTrace, _>(move |_, args| {
-            assert!(args.thread_id == 1);
-            Ok(dap::StackTraceResponse {
-                stack_frames: Vec::default(),
-                total_frames: None,
-            })
-        })
-        .await;
-
     let called_modules = Arc::new(AtomicBool::new(false));
     let modules = vec![
         dap::Module {
@@ -82,38 +72,25 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext)
         },
     ];
 
-    client
-        .on_request::<Threads, _>(move |_, _| {
-            Ok(dap::ThreadsResponse {
-                threads: vec![dap::Thread {
-                    id: 1,
-                    name: "Thread 1".into(),
-                }],
+    client.on_request::<Modules, _>({
+        let called_modules = called_modules.clone();
+        let modules_request_count = AtomicI32::new(0);
+        let modules = modules.clone();
+        move |_, _| {
+            modules_request_count.fetch_add(1, Ordering::SeqCst);
+            assert_eq!(
+                1,
+                modules_request_count.load(Ordering::SeqCst),
+                "This request should only be called once from the host"
+            );
+            called_modules.store(true, Ordering::SeqCst);
+
+            Ok(dap::ModulesResponse {
+                modules: modules.clone(),
+                total_modules: Some(2u64),
             })
-        })
-        .await;
-
-    client
-        .on_request::<Modules, _>({
-            let called_modules = called_modules.clone();
-            let modules_request_count = AtomicI32::new(0);
-            let modules = modules.clone();
-            move |_, _| {
-                modules_request_count.fetch_add(1, Ordering::SeqCst);
-                assert_eq!(
-                    1,
-                    modules_request_count.load(Ordering::SeqCst),
-                    "This request should only be called once from the host"
-                );
-                called_modules.store(true, Ordering::SeqCst);
-
-                Ok(dap::ModulesResponse {
-                    modules: modules.clone(),
-                    total_modules: Some(2u64),
-                })
-            }
-        })
-        .await;
+        }
+    });
 
     client
         .fake_event(dap::messages::Events::Stopped(StoppedEvent {

crates/debugger_ui/src/tests/stack_frame_list.rs 🔗

@@ -5,14 +5,13 @@ use crate::{
 };
 use dap::{
     StackFrame,
-    requests::{StackTrace, Threads},
+    requests::{Scopes, StackTrace, Threads},
 };
 use editor::{Editor, ToPoint as _};
 use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
-use project::{FakeFs, Project};
+use project::{FakeFs, Project, debugger};
 use serde_json::json;
 use std::sync::Arc;
-use task::LaunchConfig;
 use unindent::Unindent as _;
 use util::path;
 
@@ -51,29 +50,20 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame(
     let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
     let workspace = init_test_workspace(&project, cx).await;
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
-
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let session = task.await.unwrap();
+    let session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
-
-    client
-        .on_request::<Threads, _>(move |_, _| {
-            Ok(dap::ThreadsResponse {
-                threads: vec![dap::Thread {
-                    id: 1,
-                    name: "Thread 1".into(),
-                }],
-            })
+    client.on_request::<Scopes, _>(move |_, _| Ok(dap::ScopesResponse { scopes: vec![] }));
+
+    client.on_request::<Threads, _>(move |_, _| {
+        Ok(dap::ThreadsResponse {
+            threads: vec![dap::Thread {
+                id: 1,
+                name: "Thread 1".into(),
+            }],
         })
-        .await;
+    });
 
     let stack_frames = vec![
         StackFrame {
@@ -122,19 +112,17 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame(
         },
     ];
 
-    client
-        .on_request::<StackTrace, _>({
-            let stack_frames = Arc::new(stack_frames.clone());
-            move |_, args| {
-                assert_eq!(1, args.thread_id);
-
-                Ok(dap::StackTraceResponse {
-                    stack_frames: (*stack_frames).clone(),
-                    total_frames: None,
-                })
-            }
-        })
-        .await;
+    client.on_request::<StackTrace, _>({
+        let stack_frames = Arc::new(stack_frames.clone());
+        move |_, args| {
+            assert_eq!(1, args.thread_id);
+
+            Ok(dap::StackTraceResponse {
+                stack_frames: (*stack_frames).clone(),
+                total_frames: None,
+            })
+        }
+    });
 
     client
         .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
@@ -241,29 +229,21 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
     });
 
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
-
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let session = task.await.unwrap();
+    let session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    client
-        .on_request::<Threads, _>(move |_, _| {
-            Ok(dap::ThreadsResponse {
-                threads: vec![dap::Thread {
-                    id: 1,
-                    name: "Thread 1".into(),
-                }],
-            })
+    client.on_request::<Threads, _>(move |_, _| {
+        Ok(dap::ThreadsResponse {
+            threads: vec![dap::Thread {
+                id: 1,
+                name: "Thread 1".into(),
+            }],
         })
-        .await;
+    });
+
+    client.on_request::<Scopes, _>(move |_, _| Ok(dap::ScopesResponse { scopes: vec![] }));
 
     let stack_frames = vec![
         StackFrame {
@@ -312,19 +292,17 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
         },
     ];
 
-    client
-        .on_request::<StackTrace, _>({
-            let stack_frames = Arc::new(stack_frames.clone());
-            move |_, args| {
-                assert_eq!(1, args.thread_id);
-
-                Ok(dap::StackTraceResponse {
-                    stack_frames: (*stack_frames).clone(),
-                    total_frames: None,
-                })
-            }
-        })
-        .await;
+    client.on_request::<StackTrace, _>({
+        let stack_frames = Arc::new(stack_frames.clone());
+        move |_, args| {
+            assert_eq!(1, args.thread_id);
+
+            Ok(dap::StackTraceResponse {
+                stack_frames: (*stack_frames).clone(),
+                total_frames: None,
+            })
+        }
+    });
 
     client
         .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
@@ -517,28 +495,21 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo
     let workspace = init_test_workspace(&project, cx).await;
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let session = task.await.unwrap();
+    let session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    client
-        .on_request::<Threads, _>(move |_, _| {
-            Ok(dap::ThreadsResponse {
-                threads: vec![dap::Thread {
-                    id: 1,
-                    name: "Thread 1".into(),
-                }],
-            })
+    client.on_request::<Threads, _>(move |_, _| {
+        Ok(dap::ThreadsResponse {
+            threads: vec![dap::Thread {
+                id: 1,
+                name: "Thread 1".into(),
+            }],
         })
-        .await;
+    });
+
+    client.on_request::<Scopes, _>(move |_, _| Ok(dap::ScopesResponse { scopes: vec![] }));
 
     let stack_frames = vec![
         StackFrame {
@@ -697,19 +668,17 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo
         },
     ];
 
-    client
-        .on_request::<StackTrace, _>({
-            let stack_frames = Arc::new(stack_frames.clone());
-            move |_, args| {
-                assert_eq!(1, args.thread_id);
-
-                Ok(dap::StackTraceResponse {
-                    stack_frames: (*stack_frames).clone(),
-                    total_frames: None,
-                })
-            }
-        })
-        .await;
+    client.on_request::<StackTrace, _>({
+        let stack_frames = Arc::new(stack_frames.clone());
+        move |_, args| {
+            assert_eq!(1, args.thread_id);
+
+            Ok(dap::StackTraceResponse {
+                stack_frames: (*stack_frames).clone(),
+                total_frames: None,
+            })
+        }
+    });
 
     client
         .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {

crates/debugger_ui/src/tests/variable_list.rs 🔗

@@ -15,9 +15,8 @@ use dap::{
 };
 use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
 use menu::{SelectFirst, SelectNext, SelectPrevious};
-use project::{FakeFs, Project};
+use project::{FakeFs, Project, debugger};
 use serde_json::json;
-use task::LaunchConfig;
 use unindent::Unindent as _;
 use util::path;
 
@@ -55,29 +54,19 @@ async fn test_basic_fetch_initial_scope_and_variables(
         })
         .unwrap();
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
-
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let session = task.await.unwrap();
+    let session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    client
-        .on_request::<dap::requests::Threads, _>(move |_, _| {
-            Ok(dap::ThreadsResponse {
-                threads: vec![dap::Thread {
-                    id: 1,
-                    name: "Thread 1".into(),
-                }],
-            })
+    client.on_request::<dap::requests::Threads, _>(move |_, _| {
+        Ok(dap::ThreadsResponse {
+            threads: vec![dap::Thread {
+                id: 1,
+                name: "Thread 1".into(),
+            }],
         })
-        .await;
+    });
 
     let stack_frames = vec![StackFrame {
         id: 1,
@@ -102,19 +91,17 @@ async fn test_basic_fetch_initial_scope_and_variables(
         presentation_hint: None,
     }];
 
-    client
-        .on_request::<StackTrace, _>({
-            let stack_frames = Arc::new(stack_frames.clone());
-            move |_, args| {
-                assert_eq!(1, args.thread_id);
-
-                Ok(dap::StackTraceResponse {
-                    stack_frames: (*stack_frames).clone(),
-                    total_frames: None,
-                })
-            }
-        })
-        .await;
+    client.on_request::<StackTrace, _>({
+        let stack_frames = Arc::new(stack_frames.clone());
+        move |_, args| {
+            assert_eq!(1, args.thread_id);
+
+            Ok(dap::StackTraceResponse {
+                stack_frames: (*stack_frames).clone(),
+                total_frames: None,
+            })
+        }
+    });
 
     let scopes = vec![Scope {
         name: "Scope 1".into(),
@@ -130,18 +117,16 @@ async fn test_basic_fetch_initial_scope_and_variables(
         end_column: None,
     }];
 
-    client
-        .on_request::<Scopes, _>({
-            let scopes = Arc::new(scopes.clone());
-            move |_, args| {
-                assert_eq!(1, args.frame_id);
+    client.on_request::<Scopes, _>({
+        let scopes = Arc::new(scopes.clone());
+        move |_, args| {
+            assert_eq!(1, args.frame_id);
 
-                Ok(dap::ScopesResponse {
-                    scopes: (*scopes).clone(),
-                })
-            }
-        })
-        .await;
+            Ok(dap::ScopesResponse {
+                scopes: (*scopes).clone(),
+            })
+        }
+    });
 
     let variables = vec![
         Variable {
@@ -172,18 +157,16 @@ async fn test_basic_fetch_initial_scope_and_variables(
         },
     ];
 
-    client
-        .on_request::<Variables, _>({
-            let variables = Arc::new(variables.clone());
-            move |_, args| {
-                assert_eq!(2, args.variables_reference);
+    client.on_request::<Variables, _>({
+        let variables = Arc::new(variables.clone());
+        move |_, args| {
+            assert_eq!(2, args.variables_reference);
 
-                Ok(dap::VariablesResponse {
-                    variables: (*variables).clone(),
-                })
-            }
-        })
-        .await;
+            Ok(dap::VariablesResponse {
+                variables: (*variables).clone(),
+            })
+        }
+    });
 
     client
         .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
@@ -283,39 +266,28 @@ async fn test_fetch_variables_for_multiple_scopes(
         .unwrap();
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let session = task.await.unwrap();
+    let session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    client
-        .on_request::<dap::requests::Threads, _>(move |_, _| {
-            Ok(dap::ThreadsResponse {
-                threads: vec![dap::Thread {
-                    id: 1,
-                    name: "Thread 1".into(),
-                }],
-            })
+    client.on_request::<dap::requests::Threads, _>(move |_, _| {
+        Ok(dap::ThreadsResponse {
+            threads: vec![dap::Thread {
+                id: 1,
+                name: "Thread 1".into(),
+            }],
         })
-        .await;
+    });
 
-    client
-        .on_request::<Initialize, _>(move |_, _| {
-            Ok(dap::Capabilities {
-                supports_step_back: Some(false),
-                ..Default::default()
-            })
+    client.on_request::<Initialize, _>(move |_, _| {
+        Ok(dap::Capabilities {
+            supports_step_back: Some(false),
+            ..Default::default()
         })
-        .await;
+    });
 
-    client.on_request::<Launch, _>(move |_, _| Ok(())).await;
+    client.on_request::<Launch, _>(move |_, _| Ok(()));
 
     let stack_frames = vec![StackFrame {
         id: 1,
@@ -340,19 +312,17 @@ async fn test_fetch_variables_for_multiple_scopes(
         presentation_hint: None,
     }];
 
-    client
-        .on_request::<StackTrace, _>({
-            let stack_frames = Arc::new(stack_frames.clone());
-            move |_, args| {
-                assert_eq!(1, args.thread_id);
-
-                Ok(dap::StackTraceResponse {
-                    stack_frames: (*stack_frames).clone(),
-                    total_frames: None,
-                })
-            }
-        })
-        .await;
+    client.on_request::<StackTrace, _>({
+        let stack_frames = Arc::new(stack_frames.clone());
+        move |_, args| {
+            assert_eq!(1, args.thread_id);
+
+            Ok(dap::StackTraceResponse {
+                stack_frames: (*stack_frames).clone(),
+                total_frames: None,
+            })
+        }
+    });
 
     let scopes = vec![
         Scope {
@@ -383,18 +353,16 @@ async fn test_fetch_variables_for_multiple_scopes(
         },
     ];
 
-    client
-        .on_request::<Scopes, _>({
-            let scopes = Arc::new(scopes.clone());
-            move |_, args| {
-                assert_eq!(1, args.frame_id);
+    client.on_request::<Scopes, _>({
+        let scopes = Arc::new(scopes.clone());
+        move |_, args| {
+            assert_eq!(1, args.frame_id);
 
-                Ok(dap::ScopesResponse {
-                    scopes: (*scopes).clone(),
-                })
-            }
-        })
-        .await;
+            Ok(dap::ScopesResponse {
+                scopes: (*scopes).clone(),
+            })
+        }
+    });
 
     let mut variables = HashMap::default();
     variables.insert(
@@ -445,16 +413,14 @@ async fn test_fetch_variables_for_multiple_scopes(
         }],
     );
 
-    client
-        .on_request::<Variables, _>({
-            let variables = Arc::new(variables.clone());
-            move |_, args| {
-                Ok(dap::VariablesResponse {
-                    variables: variables.get(&args.variables_reference).unwrap().clone(),
-                })
-            }
-        })
-        .await;
+    client.on_request::<Variables, _>({
+        let variables = Arc::new(variables.clone());
+        move |_, args| {
+            Ok(dap::VariablesResponse {
+                variables: variables.get(&args.variables_reference).unwrap().clone(),
+            })
+        }
+    });
 
     client
         .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
@@ -562,40 +528,28 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp
         })
         .unwrap();
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
-
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let session = task.await.unwrap();
+    let session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    client
-        .on_request::<dap::requests::Threads, _>(move |_, _| {
-            Ok(dap::ThreadsResponse {
-                threads: vec![dap::Thread {
-                    id: 1,
-                    name: "Thread 1".into(),
-                }],
-            })
+    client.on_request::<dap::requests::Threads, _>(move |_, _| {
+        Ok(dap::ThreadsResponse {
+            threads: vec![dap::Thread {
+                id: 1,
+                name: "Thread 1".into(),
+            }],
         })
-        .await;
+    });
 
-    client
-        .on_request::<Initialize, _>(move |_, _| {
-            Ok(dap::Capabilities {
-                supports_step_back: Some(false),
-                ..Default::default()
-            })
+    client.on_request::<Initialize, _>(move |_, _| {
+        Ok(dap::Capabilities {
+            supports_step_back: Some(false),
+            ..Default::default()
         })
-        .await;
+    });
 
-    client.on_request::<Launch, _>(move |_, _| Ok(())).await;
+    client.on_request::<Launch, _>(move |_, _| Ok(()));
 
     let stack_frames = vec![StackFrame {
         id: 1,
@@ -620,19 +574,17 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp
         presentation_hint: None,
     }];
 
-    client
-        .on_request::<StackTrace, _>({
-            let stack_frames = Arc::new(stack_frames.clone());
-            move |_, args| {
-                assert_eq!(1, args.thread_id);
-
-                Ok(dap::StackTraceResponse {
-                    stack_frames: (*stack_frames).clone(),
-                    total_frames: None,
-                })
-            }
-        })
-        .await;
+    client.on_request::<StackTrace, _>({
+        let stack_frames = Arc::new(stack_frames.clone());
+        move |_, args| {
+            assert_eq!(1, args.thread_id);
+
+            Ok(dap::StackTraceResponse {
+                stack_frames: (*stack_frames).clone(),
+                total_frames: None,
+            })
+        }
+    });
 
     let scopes = vec![
         Scope {
@@ -663,18 +615,16 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp
         },
     ];
 
-    client
-        .on_request::<Scopes, _>({
-            let scopes = Arc::new(scopes.clone());
-            move |_, args| {
-                assert_eq!(1, args.frame_id);
+    client.on_request::<Scopes, _>({
+        let scopes = Arc::new(scopes.clone());
+        move |_, args| {
+            assert_eq!(1, args.frame_id);
 
-                Ok(dap::ScopesResponse {
-                    scopes: (*scopes).clone(),
-                })
-            }
-        })
-        .await;
+            Ok(dap::ScopesResponse {
+                scopes: (*scopes).clone(),
+            })
+        }
+    });
 
     let scope1_variables = vec![
         Variable {
@@ -748,25 +698,23 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp
         value_location_reference: None,
     }];
 
-    client
-        .on_request::<Variables, _>({
-            let scope1_variables = Arc::new(scope1_variables.clone());
-            let nested_variables = Arc::new(nested_variables.clone());
-            let scope2_variables = Arc::new(scope2_variables.clone());
-            move |_, args| match args.variables_reference {
-                4 => Ok(dap::VariablesResponse {
-                    variables: (*scope2_variables).clone(),
-                }),
-                3 => Ok(dap::VariablesResponse {
-                    variables: (*nested_variables).clone(),
-                }),
-                2 => Ok(dap::VariablesResponse {
-                    variables: (*scope1_variables).clone(),
-                }),
-                id => unreachable!("unexpected variables reference {id}"),
-            }
-        })
-        .await;
+    client.on_request::<Variables, _>({
+        let scope1_variables = Arc::new(scope1_variables.clone());
+        let nested_variables = Arc::new(nested_variables.clone());
+        let scope2_variables = Arc::new(scope2_variables.clone());
+        move |_, args| match args.variables_reference {
+            4 => Ok(dap::VariablesResponse {
+                variables: (*scope2_variables).clone(),
+            }),
+            3 => Ok(dap::VariablesResponse {
+                variables: (*nested_variables).clone(),
+            }),
+            2 => Ok(dap::VariablesResponse {
+                variables: (*scope1_variables).clone(),
+            }),
+            id => unreachable!("unexpected variables reference {id}"),
+        }
+    });
 
     client
         .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
@@ -1365,39 +1313,28 @@ async fn test_variable_list_only_sends_requests_when_rendering(
     let workspace = init_test_workspace(&project, cx).await;
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let session = task.await.unwrap();
+    let session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    client
-        .on_request::<dap::requests::Threads, _>(move |_, _| {
-            Ok(dap::ThreadsResponse {
-                threads: vec![dap::Thread {
-                    id: 1,
-                    name: "Thread 1".into(),
-                }],
-            })
+    client.on_request::<dap::requests::Threads, _>(move |_, _| {
+        Ok(dap::ThreadsResponse {
+            threads: vec![dap::Thread {
+                id: 1,
+                name: "Thread 1".into(),
+            }],
         })
-        .await;
+    });
 
-    client
-        .on_request::<Initialize, _>(move |_, _| {
-            Ok(dap::Capabilities {
-                supports_step_back: Some(false),
-                ..Default::default()
-            })
+    client.on_request::<Initialize, _>(move |_, _| {
+        Ok(dap::Capabilities {
+            supports_step_back: Some(false),
+            ..Default::default()
         })
-        .await;
+    });
 
-    client.on_request::<Launch, _>(move |_, _| Ok(())).await;
+    client.on_request::<Launch, _>(move |_, _| Ok(()));
 
     let stack_frames = vec![
         StackFrame {
@@ -1446,19 +1383,17 @@ async fn test_variable_list_only_sends_requests_when_rendering(
         },
     ];
 
-    client
-        .on_request::<StackTrace, _>({
-            let stack_frames = Arc::new(stack_frames.clone());
-            move |_, args| {
-                assert_eq!(1, args.thread_id);
-
-                Ok(dap::StackTraceResponse {
-                    stack_frames: (*stack_frames).clone(),
-                    total_frames: None,
-                })
-            }
-        })
-        .await;
+    client.on_request::<StackTrace, _>({
+        let stack_frames = Arc::new(stack_frames.clone());
+        move |_, args| {
+            assert_eq!(1, args.thread_id);
+
+            Ok(dap::StackTraceResponse {
+                stack_frames: (*stack_frames).clone(),
+                total_frames: None,
+            })
+        }
+    });
 
     let frame_1_scopes = vec![Scope {
         name: "Frame 1 Scope 1".into(),
@@ -1476,25 +1411,23 @@ async fn test_variable_list_only_sends_requests_when_rendering(
 
     let made_scopes_request = Arc::new(AtomicBool::new(false));
 
-    client
-        .on_request::<Scopes, _>({
-            let frame_1_scopes = Arc::new(frame_1_scopes.clone());
-            let made_scopes_request = made_scopes_request.clone();
-            move |_, args| {
-                assert_eq!(1, args.frame_id);
-                assert!(
-                    !made_scopes_request.load(Ordering::SeqCst),
-                    "We should be caching the scope request"
-                );
+    client.on_request::<Scopes, _>({
+        let frame_1_scopes = Arc::new(frame_1_scopes.clone());
+        let made_scopes_request = made_scopes_request.clone();
+        move |_, args| {
+            assert_eq!(1, args.frame_id);
+            assert!(
+                !made_scopes_request.load(Ordering::SeqCst),
+                "We should be caching the scope request"
+            );
 
-                made_scopes_request.store(true, Ordering::SeqCst);
+            made_scopes_request.store(true, Ordering::SeqCst);
 
-                Ok(dap::ScopesResponse {
-                    scopes: (*frame_1_scopes).clone(),
-                })
-            }
-        })
-        .await;
+            Ok(dap::ScopesResponse {
+                scopes: (*frame_1_scopes).clone(),
+            })
+        }
+    });
 
     let frame_1_variables = vec![
         Variable {
@@ -1525,18 +1458,16 @@ async fn test_variable_list_only_sends_requests_when_rendering(
         },
     ];
 
-    client
-        .on_request::<Variables, _>({
-            let frame_1_variables = Arc::new(frame_1_variables.clone());
-            move |_, args| {
-                assert_eq!(2, args.variables_reference);
+    client.on_request::<Variables, _>({
+        let frame_1_variables = Arc::new(frame_1_variables.clone());
+        move |_, args| {
+            assert_eq!(2, args.variables_reference);
 
-                Ok(dap::VariablesResponse {
-                    variables: (*frame_1_variables).clone(),
-                })
-            }
-        })
-        .await;
+            Ok(dap::VariablesResponse {
+                variables: (*frame_1_variables).clone(),
+            })
+        }
+    });
 
     cx.run_until_parked();
 
@@ -1629,39 +1560,28 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame(
         .unwrap();
     let cx = &mut VisualTestContext::from_window(*workspace, cx);
 
-    let task = project.update(cx, |project, cx| {
-        project.fake_debug_session(
-            dap::DebugRequestType::Launch(LaunchConfig::default()),
-            None,
-            false,
-            cx,
-        )
-    });
-
-    let session = task.await.unwrap();
+    let session = debugger::test::start_debug_session(&project, cx, |_| {})
+        .await
+        .unwrap();
     let client = session.update(cx, |session, _| session.adapter_client().unwrap());
 
-    client
-        .on_request::<dap::requests::Threads, _>(move |_, _| {
-            Ok(dap::ThreadsResponse {
-                threads: vec![dap::Thread {
-                    id: 1,
-                    name: "Thread 1".into(),
-                }],
-            })
+    client.on_request::<dap::requests::Threads, _>(move |_, _| {
+        Ok(dap::ThreadsResponse {
+            threads: vec![dap::Thread {
+                id: 1,
+                name: "Thread 1".into(),
+            }],
         })
-        .await;
+    });
 
-    client
-        .on_request::<Initialize, _>(move |_, _| {
-            Ok(dap::Capabilities {
-                supports_step_back: Some(false),
-                ..Default::default()
-            })
+    client.on_request::<Initialize, _>(move |_, _| {
+        Ok(dap::Capabilities {
+            supports_step_back: Some(false),
+            ..Default::default()
         })
-        .await;
+    });
 
-    client.on_request::<Launch, _>(move |_, _| Ok(())).await;
+    client.on_request::<Launch, _>(move |_, _| Ok(()));
 
     let stack_frames = vec![
         StackFrame {
@@ -1710,19 +1630,17 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame(
         },
     ];
 
-    client
-        .on_request::<StackTrace, _>({
-            let stack_frames = Arc::new(stack_frames.clone());
-            move |_, args| {
-                assert_eq!(1, args.thread_id);
-
-                Ok(dap::StackTraceResponse {
-                    stack_frames: (*stack_frames).clone(),
-                    total_frames: None,
-                })
-            }
-        })
-        .await;
+    client.on_request::<StackTrace, _>({
+        let stack_frames = Arc::new(stack_frames.clone());
+        move |_, args| {
+            assert_eq!(1, args.thread_id);
+
+            Ok(dap::StackTraceResponse {
+                stack_frames: (*stack_frames).clone(),
+                total_frames: None,
+            })
+        }
+    });
 
     let frame_1_scopes = vec![Scope {
         name: "Frame 1 Scope 1".into(),
@@ -1757,30 +1675,28 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame(
     let called_second_stack_frame = Arc::new(AtomicBool::new(false));
     let called_first_stack_frame = Arc::new(AtomicBool::new(false));
 
-    client
-        .on_request::<Scopes, _>({
-            let frame_1_scopes = Arc::new(frame_1_scopes.clone());
-            let frame_2_scopes = Arc::new(frame_2_scopes.clone());
-            let called_first_stack_frame = called_first_stack_frame.clone();
-            let called_second_stack_frame = called_second_stack_frame.clone();
-            move |_, args| match args.frame_id {
-                1 => {
-                    called_first_stack_frame.store(true, Ordering::SeqCst);
-                    Ok(dap::ScopesResponse {
-                        scopes: (*frame_1_scopes).clone(),
-                    })
-                }
-                2 => {
-                    called_second_stack_frame.store(true, Ordering::SeqCst);
-
-                    Ok(dap::ScopesResponse {
-                        scopes: (*frame_2_scopes).clone(),
-                    })
-                }
-                _ => panic!("Made a scopes request with an invalid frame id"),
+    client.on_request::<Scopes, _>({
+        let frame_1_scopes = Arc::new(frame_1_scopes.clone());
+        let frame_2_scopes = Arc::new(frame_2_scopes.clone());
+        let called_first_stack_frame = called_first_stack_frame.clone();
+        let called_second_stack_frame = called_second_stack_frame.clone();
+        move |_, args| match args.frame_id {
+            1 => {
+                called_first_stack_frame.store(true, Ordering::SeqCst);
+                Ok(dap::ScopesResponse {
+                    scopes: (*frame_1_scopes).clone(),
+                })
             }
-        })
-        .await;
+            2 => {
+                called_second_stack_frame.store(true, Ordering::SeqCst);
+
+                Ok(dap::ScopesResponse {
+                    scopes: (*frame_2_scopes).clone(),
+                })
+            }
+            _ => panic!("Made a scopes request with an invalid frame id"),
+        }
+    });
 
     let frame_1_variables = vec![
         Variable {
@@ -1840,18 +1756,16 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame(
         },
     ];
 
-    client
-        .on_request::<Variables, _>({
-            let frame_1_variables = Arc::new(frame_1_variables.clone());
-            move |_, args| {
-                assert_eq!(2, args.variables_reference);
+    client.on_request::<Variables, _>({
+        let frame_1_variables = Arc::new(frame_1_variables.clone());
+        move |_, args| {
+            assert_eq!(2, args.variables_reference);
 
-                Ok(dap::VariablesResponse {
-                    variables: (*frame_1_variables).clone(),
-                })
-            }
-        })
-        .await;
+            Ok(dap::VariablesResponse {
+                variables: (*frame_1_variables).clone(),
+            })
+        }
+    });
 
     client
         .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
@@ -1907,18 +1821,16 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame(
         assert_eq!(frame_1_variables, variables);
     });
 
-    client
-        .on_request::<Variables, _>({
-            let frame_2_variables = Arc::new(frame_2_variables.clone());
-            move |_, args| {
-                assert_eq!(3, args.variables_reference);
+    client.on_request::<Variables, _>({
+        let frame_2_variables = Arc::new(frame_2_variables.clone());
+        move |_, args| {
+            assert_eq!(3, args.variables_reference);
 
-                Ok(dap::VariablesResponse {
-                    variables: (*frame_2_variables).clone(),
-                })
-            }
-        })
-        .await;
+            Ok(dap::VariablesResponse {
+                variables: (*frame_2_variables).clone(),
+            })
+        }
+    });
 
     running_state
         .update_in(cx, |running_state, window, cx| {

crates/project/src/debugger.rs 🔗

@@ -16,3 +16,6 @@ pub mod dap_command;
 pub mod dap_store;
 mod locator_store;
 pub mod session;
+
+#[cfg(any(feature = "test-support", test))]
+pub mod test;

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

@@ -3,15 +3,15 @@ use super::{
     locator_store::LocatorStore,
     session::{self, Session, SessionStateEvent},
 };
-use crate::{ProjectEnvironment, debugger, worktree_store::WorktreeStore};
+use crate::{ProjectEnvironment, debugger};
 use anyhow::{Result, anyhow};
 use async_trait::async_trait;
 use collections::HashMap;
 use dap::{
-    Capabilities, CompletionItem, CompletionsArguments, DapRegistry, ErrorResponse,
-    EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments,
-    Source, StartDebuggingRequestArguments,
-    adapters::{DapStatus, DebugAdapterName},
+    Capabilities, CompletionItem, CompletionsArguments, ErrorResponse, EvaluateArguments,
+    EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, Source,
+    StartDebuggingRequestArguments,
+    adapters::{DapStatus, DebugAdapterBinary, DebugAdapterName},
     client::SessionId,
     messages::Message,
     requests::{Completions, Evaluate, Request as _, RunInTerminal, StartDebugging},
@@ -42,7 +42,7 @@ use std::{
     sync::{Arc, atomic::Ordering::SeqCst},
 };
 use std::{collections::VecDeque, sync::atomic::AtomicU32};
-use task::{DebugAdapterConfig, DebugRequestDisposition};
+use task::DebugTaskDefinition;
 use util::ResultExt as _;
 use worktree::Worktree;
 
@@ -78,10 +78,8 @@ pub struct LocalDapStore {
     node_runtime: NodeRuntime,
     next_session_id: AtomicU32,
     http_client: Arc<dyn HttpClient>,
-    worktree_store: Entity<WorktreeStore>,
     environment: Entity<ProjectEnvironment>,
     language_registry: Arc<LanguageRegistry>,
-    debug_adapters: Arc<DapRegistry>,
     toolchain_store: Arc<dyn LanguageToolchainStore>,
     locator_store: Arc<LocatorStore>,
     start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
@@ -132,11 +130,9 @@ impl DapStore {
         node_runtime: NodeRuntime,
         fs: Arc<dyn Fs>,
         language_registry: Arc<LanguageRegistry>,
-        debug_adapters: Arc<DapRegistry>,
         environment: Entity<ProjectEnvironment>,
         toolchain_store: Arc<dyn LanguageToolchainStore>,
         breakpoint_store: Entity<BreakpointStore>,
-        worktree_store: Entity<WorktreeStore>,
         cx: &mut Context<Self>,
     ) -> Self {
         cx.on_app_quit(Self::shutdown_sessions).detach();
@@ -170,10 +166,8 @@ impl DapStore {
                 environment,
                 http_client,
                 node_runtime,
-                worktree_store,
                 toolchain_store,
                 language_registry,
-                debug_adapters,
                 start_debugging_tx,
                 _start_debugging_task,
                 locator_store: Arc::from(LocatorStore::new()),
@@ -320,18 +314,12 @@ impl DapStore {
         Ok(())
     }
 
-    pub fn new_session(
-        &mut self,
-        mut config: DebugAdapterConfig,
-        worktree: &Entity<Worktree>,
-        parent_session: Option<Entity<Session>>,
-        cx: &mut Context<Self>,
-    ) -> (SessionId, Task<Result<Entity<Session>>>) {
+    pub fn delegate(&self, worktree: &Entity<Worktree>, cx: &mut App) -> DapAdapterDelegate {
         let Some(local_store) = self.as_local() else {
             unimplemented!("Starting session on remote side");
         };
 
-        let delegate = DapAdapterDelegate::new(
+        DapAdapterDelegate::new(
             local_store.fs.clone(),
             worktree.read(cx).id(),
             local_store.node_runtime.clone(),
@@ -341,7 +329,20 @@ impl DapStore {
             local_store.environment.update(cx, |env, cx| {
                 env.get_worktree_environment(worktree.clone(), cx)
             }),
-        );
+        )
+    }
+
+    pub fn new_session(
+        &mut self,
+        binary: DebugAdapterBinary,
+        mut config: DebugTaskDefinition,
+        parent_session: Option<Entity<Session>>,
+        cx: &mut Context<Self>,
+    ) -> (SessionId, Task<Result<Entity<Session>>>) {
+        let Some(local_store) = self.as_local() else {
+            unimplemented!("Starting session on remote side");
+        };
+
         let session_id = local_store.next_session_id();
 
         if let Some(session) = &parent_session {
@@ -352,7 +353,6 @@ impl DapStore {
 
         let (initialized_tx, initialized_rx) = oneshot::channel();
         let locator_store = local_store.locator_store.clone();
-        let debug_adapters = local_store.debug_adapters.clone();
 
         let start_debugging_tx = local_store.start_debugging_tx.clone();
 
@@ -373,86 +373,31 @@ impl DapStore {
                     this.breakpoint_store.clone(),
                     session_id,
                     parent_session,
-                    delegate,
+                    binary,
                     config,
                     start_debugging_tx.clone(),
                     initialized_tx,
-                    debug_adapters,
                     cx,
                 )
             })?;
 
-            this.update(cx, |_, cx| {
-                create_new_session(session_id, initialized_rx, start_client_task, cx)
-            })?
-            .await
+            let ret = this
+                .update(cx, |_, cx| {
+                    create_new_session(session_id, initialized_rx, start_client_task, cx)
+                })?
+                .await;
+            ret
         });
 
         (session_id, task)
     }
 
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn new_fake_session(
-        &mut self,
-        config: DebugAdapterConfig,
-        worktree: &Entity<Worktree>,
-        parent_session: Option<Entity<Session>>,
-        caps: Capabilities,
-        fails: bool,
-        cx: &mut Context<Self>,
-    ) -> (SessionId, Task<Result<Entity<Session>>>) {
-        let Some(local_store) = self.as_local() else {
-            unimplemented!("Starting session on remote side");
-        };
-
-        let delegate = DapAdapterDelegate::new(
-            local_store.fs.clone(),
-            worktree.read(cx).id(),
-            local_store.node_runtime.clone(),
-            local_store.http_client.clone(),
-            local_store.language_registry.clone(),
-            local_store.toolchain_store.clone(),
-            local_store.environment.update(cx, |env, cx| {
-                env.get_worktree_environment(worktree.clone(), cx)
-            }),
-        );
-        let session_id = local_store.next_session_id();
-
-        if let Some(session) = &parent_session {
-            session.update(cx, |session, _| {
-                session.add_child_session_id(session_id);
-            });
-        }
-
-        let (initialized_tx, initialized_rx) = oneshot::channel();
-
-        let start_client_task = Session::fake(
-            self.breakpoint_store.clone(),
-            session_id,
-            parent_session,
-            delegate,
-            config,
-            local_store.start_debugging_tx.clone(),
-            initialized_tx,
-            caps,
-            fails,
-            cx,
-        );
-
-        let task = create_new_session(session_id, initialized_rx, start_client_task, cx);
-        (session_id, task)
-    }
-
     fn handle_start_debugging_request(
         &mut self,
         session_id: SessionId,
         request: dap::messages::Request,
         cx: &mut Context<Self>,
     ) -> Task<Result<()>> {
-        let Some(local_store) = self.as_local() else {
-            unreachable!("Cannot response for non-local session");
-        };
-
         let Some(parent_session) = self.session_by_id(session_id) else {
             return Task::ready(Err(anyhow!("Session not found")));
         };
@@ -461,41 +406,12 @@ impl DapStore {
             request.arguments.unwrap_or_default(),
         )
         .expect("To parse StartDebuggingRequestArguments");
-        let worktree = local_store
-            .worktree_store
-            .update(cx, |this, _| this.worktrees().next())
-            .expect("worktree-less project");
+        let mut binary = parent_session.read(cx).binary().clone();
+        let config = parent_session.read(cx).configuration().unwrap().clone();
+        binary.request_args = args;
 
-        let Some(config) = parent_session.read(cx).configuration() else {
-            unreachable!("there must be a config for local sessions");
-        };
-
-        let debug_config = DebugAdapterConfig {
-            label: config.label,
-            adapter: config.adapter,
-            request: DebugRequestDisposition::ReverseRequest(args),
-            initialize_args: config.initialize_args.clone(),
-            tcp_connection: config.tcp_connection.clone(),
-            locator: None,
-            stop_on_entry: config.stop_on_entry,
-        };
-
-        #[cfg(any(test, feature = "test-support"))]
-        let new_session_task = {
-            let caps = parent_session.read(cx).capabilities.clone();
-            self.new_fake_session(
-                debug_config,
-                &worktree,
-                Some(parent_session.clone()),
-                caps,
-                false,
-                cx,
-            )
-            .1
-        };
-        #[cfg(not(any(test, feature = "test-support")))]
         let new_session_task = self
-            .new_session(debug_config, &worktree, Some(parent_session.clone()), cx)
+            .new_session(binary, config, Some(parent_session.clone()), cx)
             .1;
 
         let request_seq = request.seq;
@@ -607,13 +523,7 @@ impl DapStore {
             .map(Arc::from)
             .or_else(|| {
                 self.session_by_id(session_id)
-                    .and_then(|session| {
-                        session
-                            .read(cx)
-                            .configuration()
-                            .and_then(|config| config.request.cwd())
-                    })
-                    .map(Arc::from)
+                    .and_then(|session| session.read(cx).binary().cwd.as_deref().map(Arc::from))
             });
         cx.emit(DapStoreEvent::RunInTerminal {
             session_id,
@@ -852,7 +762,7 @@ fn create_new_session(
             cx.emit(DapStoreEvent::DebugClientStarted(session_id));
             cx.notify();
         })?;
-        let seq_result = {
+        let seq_result = async || {
             session
                 .update(cx, |session, cx| session.request_initialize(cx))?
                 .await?;
@@ -863,12 +773,11 @@ fn create_new_session(
                 })?
                 .await
         };
-        match seq_result {
+        match seq_result().await {
             Ok(_) => {}
             Err(error) => {
                 this.update(cx, |this, cx| {
                     cx.emit(DapStoreEvent::Notification(error.to_string()));
-
                     this.shutdown_session(session_id, cx)
                 })?
                 .await

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

@@ -1,9 +1,9 @@
 use anyhow::{Result, anyhow};
 use cargo::CargoLocator;
 use collections::HashMap;
-use dap::DebugAdapterConfig;
 use gpui::SharedString;
 use locators::DapLocator;
+use task::DebugTaskDefinition;
 
 mod cargo;
 mod locators;
@@ -23,7 +23,7 @@ impl LocatorStore {
 
     pub(super) async fn resolve_debug_config(
         &self,
-        debug_config: &mut DebugAdapterConfig,
+        debug_config: &mut DebugTaskDefinition,
     ) -> Result<()> {
         let Some(locator_name) = &debug_config.locator else {
             log::debug!("Attempted to resolve debug config without a locator field");

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

@@ -1,12 +1,12 @@
 use super::DapLocator;
 use anyhow::{Result, anyhow};
 use async_trait::async_trait;
-use dap::DebugAdapterConfig;
 use serde_json::{Value, json};
 use smol::{
     io::AsyncReadExt,
     process::{Command, Stdio},
 };
+use task::DebugTaskDefinition;
 use util::maybe;
 
 pub(super) struct CargoLocator;
@@ -38,11 +38,9 @@ async fn find_best_executable(executables: &[String], test_name: &str) -> Option
 }
 #[async_trait]
 impl DapLocator for CargoLocator {
-    async fn run_locator(&self, debug_config: &mut DebugAdapterConfig) -> Result<()> {
+    async fn run_locator(&self, debug_config: &mut DebugTaskDefinition) -> Result<()> {
         let Some(launch_config) = (match &mut debug_config.request {
-            task::DebugRequestDisposition::UserConfigured(task::DebugRequestType::Launch(
-                launch_config,
-            )) => Some(launch_config),
+            task::DebugRequestType::Launch(launch_config) => Some(launch_config),
             _ => None,
         }) else {
             return Err(anyhow!("Couldn't get launch config in locator"));

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

@@ -1,8 +1,8 @@
 use anyhow::Result;
 use async_trait::async_trait;
-use dap::DebugAdapterConfig;
+use task::DebugTaskDefinition;
 
 #[async_trait]
 pub(super) trait DapLocator: Send + Sync {
-    async fn run_locator(&self, debug_config: &mut DebugAdapterConfig) -> Result<()>;
+    async fn run_locator(&self, debug_config: &mut DebugTaskDefinition) -> Result<()>;
 }

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

@@ -1,5 +1,3 @@
-use crate::project_settings::ProjectSettings;
-
 use super::breakpoint_store::{
     BreakpointStore, BreakpointStoreEvent, BreakpointUpdatedReason, SourceBreakpoint,
 };
@@ -11,22 +9,17 @@ use super::dap_command::{
     StepBackCommand, StepCommand, StepInCommand, StepOutCommand, TerminateCommand,
     TerminateThreadsCommand, ThreadsCommand, VariablesCommand,
 };
-use super::dap_store::DapAdapterDelegate;
 use anyhow::{Context as _, Result, anyhow};
 use collections::{HashMap, HashSet, IndexMap, IndexSet};
-use dap::adapters::{DebugAdapter, DebugAdapterBinary};
+use dap::adapters::DebugAdapterBinary;
 use dap::messages::Response;
 use dap::{
     Capabilities, ContinueArguments, EvaluateArgumentsContext, Module, Source, StackFrameId,
     SteppingGranularity, StoppedEvent, VariableReference,
-    adapters::{DapDelegate, DapStatus},
     client::{DebugAdapterClient, SessionId},
     messages::{Events, Message},
 };
-use dap::{
-    DapRegistry, DebugRequestType, ExceptionBreakpointsFilter, ExceptionFilterOptions,
-    OutputEventCategory,
-};
+use dap::{ExceptionBreakpointsFilter, ExceptionFilterOptions, OutputEventCategory};
 use futures::channel::oneshot;
 use futures::{FutureExt, future::Shared};
 use gpui::{
@@ -35,11 +28,9 @@ use gpui::{
 };
 use rpc::AnyProtoClient;
 use serde_json::{Value, json};
-use settings::Settings;
 use smol::stream::StreamExt;
 use std::any::TypeId;
 use std::collections::BTreeMap;
-use std::path::PathBuf;
 use std::u64;
 use std::{
     any::Any,
@@ -48,7 +39,7 @@ use std::{
     path::Path,
     sync::Arc,
 };
-use task::{DebugAdapterConfig, DebugTaskDefinition};
+use task::DebugTaskDefinition;
 use text::{PointUtf16, ToPointUtf16};
 use util::{ResultExt, merge_json_value_into};
 
@@ -168,9 +159,9 @@ enum Mode {
 #[derive(Clone)]
 pub struct LocalMode {
     client: Arc<DebugAdapterClient>,
-    config: DebugAdapterConfig,
-    adapter: Arc<dyn DebugAdapter>,
-    breakpoint_store: Entity<BreakpointStore>,
+    definition: DebugTaskDefinition,
+    binary: DebugAdapterBinary,
+    pub(crate) breakpoint_store: Entity<BreakpointStore>,
     tmp_breakpoint: Option<SourceBreakpoint>,
 }
 
@@ -191,185 +182,37 @@ fn client_source(abs_path: &Path) -> dap::Source {
 
 impl LocalMode {
     fn new(
-        debug_adapters: Arc<DapRegistry>,
         session_id: SessionId,
         parent_session: Option<Entity<Session>>,
         breakpoint_store: Entity<BreakpointStore>,
-        config: DebugAdapterConfig,
-        delegate: DapAdapterDelegate,
+        config: DebugTaskDefinition,
+        binary: DebugAdapterBinary,
         messages_tx: futures::channel::mpsc::UnboundedSender<Message>,
         cx: AsyncApp,
     ) -> Task<Result<Self>> {
         Self::new_inner(
-            debug_adapters,
             session_id,
             parent_session,
             breakpoint_store,
             config,
-            delegate,
+            binary,
             messages_tx,
             async |_, _| {},
             cx,
         )
     }
-    #[cfg(any(test, feature = "test-support"))]
-    fn new_fake(
-        session_id: SessionId,
-        parent_session: Option<Entity<Session>>,
-        breakpoint_store: Entity<BreakpointStore>,
-        config: DebugAdapterConfig,
-        delegate: DapAdapterDelegate,
-        messages_tx: futures::channel::mpsc::UnboundedSender<Message>,
-        caps: Capabilities,
-        fail: bool,
-        cx: AsyncApp,
-    ) -> Task<Result<Self>> {
-        use task::DebugRequestDisposition;
-
-        let request = match config.request.clone() {
-            DebugRequestDisposition::UserConfigured(request) => request,
-            DebugRequestDisposition::ReverseRequest(reverse_request_args) => {
-                match reverse_request_args.request {
-                    dap::StartDebuggingRequestArgumentsRequest::Launch => {
-                        DebugRequestType::Launch(task::LaunchConfig {
-                            program: "".to_owned(),
-                            cwd: None,
-                            args: Default::default(),
-                        })
-                    }
-                    dap::StartDebuggingRequestArgumentsRequest::Attach => {
-                        DebugRequestType::Attach(task::AttachConfig {
-                            process_id: Some(0),
-                        })
-                    }
-                }
-            }
-        };
-
-        let callback = async move |session: &mut LocalMode, cx: AsyncApp| {
-            session
-                .client
-                .on_request::<dap::requests::Initialize, _>(move |_, _| Ok(caps.clone()))
-                .await;
-
-            let paths = cx
-                .update(|cx| session.breakpoint_store.read(cx).breakpoint_paths())
-                .expect("Breakpoint store should exist in all tests that start debuggers");
-
-            session
-                .client
-                .on_request::<dap::requests::SetBreakpoints, _>(move |_, args| {
-                    let p = Arc::from(Path::new(&args.source.path.unwrap()));
-                    if !paths.contains(&p) {
-                        panic!("Sent breakpoints for path without any")
-                    }
 
-                    Ok(dap::SetBreakpointsResponse {
-                        breakpoints: Vec::default(),
-                    })
-                })
-                .await;
-
-            match request {
-                dap::DebugRequestType::Launch(_) => {
-                    if fail {
-                        session
-                            .client
-                            .on_request::<dap::requests::Launch, _>(move |_, _| {
-                                Err(dap::ErrorResponse {
-                                    error: Some(dap::Message {
-                                        id: 1,
-                                        format: "error".into(),
-                                        variables: None,
-                                        send_telemetry: None,
-                                        show_user: None,
-                                        url: None,
-                                        url_label: None,
-                                    }),
-                                })
-                            })
-                            .await;
-                    } else {
-                        session
-                            .client
-                            .on_request::<dap::requests::Launch, _>(move |_, _| Ok(()))
-                            .await;
-                    }
-                }
-                dap::DebugRequestType::Attach(attach_config) => {
-                    if fail {
-                        session
-                            .client
-                            .on_request::<dap::requests::Attach, _>(move |_, _| {
-                                Err(dap::ErrorResponse {
-                                    error: Some(dap::Message {
-                                        id: 1,
-                                        format: "error".into(),
-                                        variables: None,
-                                        send_telemetry: None,
-                                        show_user: None,
-                                        url: None,
-                                        url_label: None,
-                                    }),
-                                })
-                            })
-                            .await;
-                    } else {
-                        session
-                            .client
-                            .on_request::<dap::requests::Attach, _>(move |_, args| {
-                                assert_eq!(
-                                    json!({"request": "attach", "process_id": attach_config.process_id.unwrap()}),
-                                    args.raw
-                                );
-
-                                Ok(())
-                            })
-                            .await;
-                    }
-                }
-            }
-
-            session
-                .client
-                .on_request::<dap::requests::SetExceptionBreakpoints, _>(move |_, _| {
-                    Ok(dap::SetExceptionBreakpointsResponse { breakpoints: None })
-                })
-                .await;
-
-            session
-                .client
-                .on_request::<dap::requests::Disconnect, _>(move |_, _| Ok(()))
-                .await;
-            session.client.fake_event(Events::Initialized(None)).await;
-        };
-        Self::new_inner(
-            DapRegistry::fake().into(),
-            session_id,
-            parent_session,
-            breakpoint_store,
-            config,
-            delegate,
-            messages_tx,
-            callback,
-            cx,
-        )
-    }
     fn new_inner(
-        registry: Arc<DapRegistry>,
         session_id: SessionId,
         parent_session: Option<Entity<Session>>,
         breakpoint_store: Entity<BreakpointStore>,
-        config: DebugAdapterConfig,
-        delegate: DapAdapterDelegate,
+        config: DebugTaskDefinition,
+        binary: DebugAdapterBinary,
         messages_tx: futures::channel::mpsc::UnboundedSender<Message>,
         on_initialized: impl AsyncFnOnce(&mut LocalMode, AsyncApp) + 'static,
         cx: AsyncApp,
     ) -> Task<Result<Self>> {
         cx.spawn(async move |cx| {
-            let (adapter, binary) =
-                Self::get_adapter_binary(&registry, &config, &delegate, cx).await?;
-
             let message_handler = Box::new(move |message| {
                 messages_tx.unbounded_send(message).ok();
             });
@@ -380,13 +223,12 @@ impl LocalMode {
                     .flatten()
                 {
                     client
-                        .reconnect(session_id, binary, message_handler, cx.clone())
+                        .reconnect(session_id, binary.clone(), message_handler, cx.clone())
                         .await?
                 } else {
                     DebugAdapterClient::start(
                         session_id,
-                        adapter.name(),
-                        binary,
+                        binary.clone(),
                         message_handler,
                         cx.clone(),
                     )
@@ -397,10 +239,10 @@ impl LocalMode {
 
             let mut session = Self {
                 client,
-                adapter,
                 breakpoint_store,
                 tmp_breakpoint: None,
-                config: config.clone(),
+                definition: config,
+                binary,
             };
 
             on_initialized(&mut session, cx.clone()).await;
@@ -533,55 +375,12 @@ impl LocalMode {
         })
     }
 
-    async fn get_adapter_binary(
-        registry: &Arc<DapRegistry>,
-        config: &DebugAdapterConfig,
-        delegate: &DapAdapterDelegate,
-        cx: &mut AsyncApp,
-    ) -> Result<(Arc<dyn DebugAdapter>, DebugAdapterBinary)> {
-        let adapter = registry
-            .adapter(&config.adapter)
-            .ok_or_else(|| anyhow!("Debug adapter with name `{}` was not found", config.adapter))?;
-
-        let binary = cx.update(|cx| {
-            ProjectSettings::get_global(cx)
-                .dap
-                .get(&adapter.name())
-                .and_then(|s| s.binary.as_ref().map(PathBuf::from))
-        })?;
-
-        let binary = match adapter.get_binary(delegate, &config, binary, cx).await {
-            Err(error) => {
-                delegate.update_status(
-                    adapter.name(),
-                    DapStatus::Failed {
-                        error: error.to_string(),
-                    },
-                );
-
-                return Err(error);
-            }
-            Ok(mut binary) => {
-                delegate.update_status(adapter.name(), DapStatus::None);
-
-                let shell_env = delegate.shell_env().await;
-                let mut envs = binary.envs.unwrap_or_default();
-                envs.extend(shell_env);
-                binary.envs = Some(envs);
-
-                binary
-            }
-        };
-
-        Ok((adapter, binary))
-    }
-
     pub fn label(&self) -> String {
-        self.config.label.clone()
+        self.definition.label.clone()
     }
 
     fn request_initialization(&self, cx: &App) -> Task<Result<Capabilities>> {
-        let adapter_id = self.adapter.name().to_string();
+        let adapter_id = self.binary.adapter_name.to_string();
 
         self.request(Initialize { adapter_id }, cx.background_executor().clone())
     }
@@ -592,36 +391,26 @@ impl LocalMode {
         initialized_rx: oneshot::Receiver<()>,
         cx: &App,
     ) -> Task<Result<()>> {
-        let (mut raw, is_launch) = match &self.config.request {
-            task::DebugRequestDisposition::UserConfigured(_) => {
-                let Ok(raw) = DebugTaskDefinition::try_from(self.config.clone()) else {
-                    debug_assert!(false, "This part of code should be unreachable in practice");
-                    return Task::ready(Err(anyhow!(
-                        "Expected debug config conversion to succeed"
-                    )));
-                };
-                let is_launch = matches!(raw.request, DebugRequestType::Launch(_));
-                let raw = self.adapter.request_args(&raw);
-                (raw, is_launch)
-            }
-            task::DebugRequestDisposition::ReverseRequest(start_debugging_request_arguments) => (
-                start_debugging_request_arguments.configuration.clone(),
-                matches!(
-                    start_debugging_request_arguments.request,
-                    dap::StartDebuggingRequestArgumentsRequest::Launch
-                ),
-            ),
-        };
+        let mut raw = self.binary.request_args.clone();
 
         merge_json_value_into(
-            self.config.initialize_args.clone().unwrap_or(json!({})),
-            &mut raw,
+            self.definition.initialize_args.clone().unwrap_or(json!({})),
+            &mut raw.configuration,
         );
         // Of relevance: https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522
-        let launch = if is_launch {
-            self.request(Launch { raw }, cx.background_executor().clone())
-        } else {
-            self.request(Attach { raw }, cx.background_executor().clone())
+        let launch = match raw.request {
+            dap::StartDebuggingRequestArgumentsRequest::Launch => self.request(
+                Launch {
+                    raw: raw.configuration,
+                },
+                cx.background_executor().clone(),
+            ),
+            dap::StartDebuggingRequestArgumentsRequest::Attach => self.request(
+                Attach {
+                    raw: raw.configuration,
+                },
+                cx.background_executor().clone(),
+            ),
         };
 
         let configuration_done_supported = ConfigurationDone::is_supported(capabilities);
@@ -656,12 +445,13 @@ impl LocalMode {
                 })?
                 .await
                 .ok();
-                if configuration_done_supported {
+                let ret = if configuration_done_supported {
                     this.request(ConfigurationDone {}, cx.background_executor().clone())
                 } else {
                     Task::ready(Ok(()))
                 }
-                .await
+                .await;
+                ret
             }
         });
 
@@ -909,68 +699,22 @@ impl Session {
         breakpoint_store: Entity<BreakpointStore>,
         session_id: SessionId,
         parent_session: Option<Entity<Session>>,
-        delegate: DapAdapterDelegate,
-        config: DebugAdapterConfig,
+        binary: DebugAdapterBinary,
+        config: DebugTaskDefinition,
         start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
         initialized_tx: oneshot::Sender<()>,
-        debug_adapters: Arc<DapRegistry>,
         cx: &mut App,
     ) -> Task<Result<Entity<Self>>> {
         let (message_tx, message_rx) = futures::channel::mpsc::unbounded();
 
         cx.spawn(async move |cx| {
             let mode = LocalMode::new(
-                debug_adapters,
-                session_id,
-                parent_session.clone(),
-                breakpoint_store.clone(),
-                config.clone(),
-                delegate,
-                message_tx,
-                cx.clone(),
-            )
-            .await?;
-
-            cx.new(|cx| {
-                create_local_session(
-                    breakpoint_store,
-                    session_id,
-                    parent_session,
-                    start_debugging_requests_tx,
-                    initialized_tx,
-                    message_rx,
-                    mode,
-                    cx,
-                )
-            })
-        })
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub(crate) fn fake(
-        breakpoint_store: Entity<BreakpointStore>,
-        session_id: SessionId,
-        parent_session: Option<Entity<Session>>,
-        delegate: DapAdapterDelegate,
-        config: DebugAdapterConfig,
-        start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
-        initialized_tx: oneshot::Sender<()>,
-        caps: Capabilities,
-        fails: bool,
-        cx: &mut App,
-    ) -> Task<Result<Entity<Session>>> {
-        let (message_tx, message_rx) = futures::channel::mpsc::unbounded();
-
-        cx.spawn(async move |cx| {
-            let mode = LocalMode::new_fake(
                 session_id,
                 parent_session.clone(),
                 breakpoint_store.clone(),
                 config.clone(),
-                delegate,
+                binary,
                 message_tx,
-                caps,
-                fails,
                 cx.clone(),
             )
             .await?;
@@ -1047,16 +791,23 @@ impl Session {
         &self.capabilities
     }
 
+    pub fn binary(&self) -> &DebugAdapterBinary {
+        let Mode::Local(local_mode) = &self.mode else {
+            panic!("Session is not local");
+        };
+        &local_mode.binary
+    }
+
     pub fn adapter_name(&self) -> SharedString {
         match &self.mode {
-            Mode::Local(local_mode) => local_mode.adapter.name().into(),
+            Mode::Local(local_mode) => local_mode.definition.adapter.clone().into(),
             Mode::Remote(remote_mode) => remote_mode._adapter_name.clone(),
         }
     }
 
-    pub fn configuration(&self) -> Option<DebugAdapterConfig> {
+    pub fn configuration(&self) -> Option<DebugTaskDefinition> {
         if let Mode::Local(local_mode) = &self.mode {
-            Some(local_mode.config.clone())
+            Some(local_mode.definition.clone())
         } else {
             None
         }

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

@@ -0,0 +1,98 @@
+use std::{path::Path, sync::Arc};
+
+use anyhow::Result;
+use dap::{DebugRequestType, client::DebugAdapterClient};
+use gpui::{App, AppContext, Entity, Subscription, Task};
+use task::DebugTaskDefinition;
+
+use crate::Project;
+
+use super::session::Session;
+
+pub fn intercept_debug_sessions<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
+    cx: &mut gpui::TestAppContext,
+    configure: T,
+) -> Subscription {
+    cx.update(|cx| {
+        cx.observe_new::<Session>(move |session, _, cx| {
+            let client = session.adapter_client().unwrap();
+            register_default_handlers(session, &client, cx);
+            configure(&client);
+            cx.background_spawn(async move {
+                client
+                    .fake_event(dap::messages::Events::Initialized(Some(Default::default())))
+                    .await
+            })
+            .detach();
+        })
+    })
+}
+
+pub fn start_debug_session_with<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
+    project: &Entity<Project>,
+    cx: &mut gpui::TestAppContext,
+    config: DebugTaskDefinition,
+    configure: T,
+) -> Task<Result<Entity<Session>>> {
+    let subscription = intercept_debug_sessions(cx, configure);
+    let task = project.update(cx, |project, cx| project.start_debug_session(config, cx));
+    cx.spawn(async move |_| {
+        let result = task.await;
+        drop(subscription);
+        result
+    })
+}
+
+pub fn start_debug_session<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
+    project: &Entity<Project>,
+    cx: &mut gpui::TestAppContext,
+    configure: T,
+) -> Task<Result<Entity<Session>>> {
+    start_debug_session_with(
+        project,
+        cx,
+        DebugTaskDefinition {
+            adapter: "fake-adapter".to_string(),
+            request: DebugRequestType::Launch(Default::default()),
+            label: "test".to_string(),
+            initialize_args: None,
+            tcp_connection: None,
+            locator: None,
+            stop_on_entry: None,
+        },
+        configure,
+    )
+}
+
+fn register_default_handlers(session: &Session, client: &Arc<DebugAdapterClient>, cx: &mut App) {
+    client.on_request::<dap::requests::Initialize, _>(move |_, _| Ok(Default::default()));
+    let paths = session
+        .as_local()
+        .unwrap()
+        .breakpoint_store
+        .read(cx)
+        .breakpoint_paths();
+
+    client.on_request::<dap::requests::SetBreakpoints, _>(move |_, args| {
+        let p = Arc::from(Path::new(&args.source.path.unwrap()));
+        if !paths.contains(&p) {
+            panic!("Sent breakpoints for path without any")
+        }
+
+        Ok(dap::SetBreakpointsResponse {
+            breakpoints: Vec::default(),
+        })
+    });
+
+    client.on_request::<dap::requests::Launch, _>(move |_, _| Ok(()));
+
+    client.on_request::<dap::requests::SetExceptionBreakpoints, _>(move |_, _| {
+        Ok(dap::SetExceptionBreakpointsResponse { breakpoints: None })
+    });
+
+    client.on_request::<dap::requests::Disconnect, _>(move |_, _| Ok(()));
+
+    client.on_request::<dap::requests::Threads, _>(move |_, _| {
+        Ok(dap::ThreadsResponse { threads: vec![] })
+    });
+}

crates/project/src/project.rs 🔗

@@ -25,6 +25,7 @@ mod environment;
 use buffer_diff::BufferDiff;
 pub use environment::{EnvironmentErrorMessage, ProjectEnvironmentEvent};
 use git_store::{Repository, RepositoryId};
+use task::DebugTaskDefinition;
 pub mod search_history;
 mod yarn;
 
@@ -38,7 +39,7 @@ use client::{
 };
 use clock::ReplicaId;
 
-use dap::{DapRegistry, DebugAdapterConfig, client::DebugAdapterClient};
+use dap::{DapRegistry, client::DebugAdapterClient};
 
 use collections::{BTreeSet, HashMap, HashSet};
 use debounced_delay::DebouncedDelay;
@@ -106,7 +107,7 @@ use terminals::Terminals;
 use text::{Anchor, BufferId};
 use toolchain_store::EmptyToolchainStore;
 use util::{
-    ResultExt as _, maybe,
+    ResultExt as _,
     paths::{SanitizedPath, compare_paths},
 };
 use worktree::{CreatedEntry, Snapshot, Traversal};
@@ -870,11 +871,9 @@ impl Project {
                     node.clone(),
                     fs.clone(),
                     languages.clone(),
-                    debug_adapters.clone(),
                     environment.clone(),
                     toolchain_store.read(cx).as_language_toolchain_store(),
                     breakpoint_store.clone(),
-                    worktree_store.clone(),
                     cx,
                 )
             });
@@ -1458,64 +1457,46 @@ impl Project {
         }
     }
 
-    pub fn queue_debug_session(&mut self, config: DebugAdapterConfig, cx: &mut Context<Self>) {
-        if config.locator.is_none() {
-            self.start_debug_session(config, cx).detach_and_log_err(cx);
-        }
-    }
-
     pub fn start_debug_session(
         &mut self,
-        config: DebugAdapterConfig,
+        config: DebugTaskDefinition,
         cx: &mut Context<Self>,
     ) -> Task<Result<Entity<Session>>> {
-        let worktree = maybe!({ self.worktrees(cx).next() });
-
-        let Some(worktree) = &worktree else {
+        let Some(worktree) = self.worktrees(cx).next() else {
             return Task::ready(Err(anyhow!("Failed to find a worktree")));
         };
 
-        self.dap_store
-            .update(cx, |dap_store, cx| {
-                dap_store.new_session(config, worktree, None, cx)
-            })
-            .1
-    }
+        let Some(adapter) = self.debug_adapters.adapter(&config.adapter) else {
+            return Task::ready(Err(anyhow!("Failed to find a debug adapter")));
+        };
 
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn fake_debug_session(
-        &mut self,
-        request: task::DebugRequestType,
-        caps: Option<dap::Capabilities>,
-        fails: bool,
-        cx: &mut Context<Self>,
-    ) -> Task<Result<Entity<Session>>> {
-        use dap::{Capabilities, FakeAdapter};
-        use task::DebugRequestDisposition;
+        let user_installed_path = ProjectSettings::get_global(cx)
+            .dap
+            .get(&adapter.name())
+            .and_then(|s| s.binary.as_ref().map(PathBuf::from));
 
-        let worktree = maybe!({ self.worktrees(cx).next() });
+        let result = cx.spawn(async move |this, cx| {
+            let delegate = this.update(cx, |project, cx| {
+                project
+                    .dap_store
+                    .update(cx, |dap_store, cx| dap_store.delegate(&worktree, cx))
+            })?;
 
-        let Some(worktree) = &worktree else {
-            return Task::ready(Err(anyhow!("Failed to find a worktree")));
-        };
-        let config = DebugAdapterConfig {
-            label: "test config".into(),
-            adapter: FakeAdapter::ADAPTER_NAME.into(),
-            request: DebugRequestDisposition::UserConfigured(request),
-            initialize_args: None,
-            tcp_connection: None,
-            locator: None,
-            stop_on_entry: None,
-        };
-        let caps = caps.unwrap_or(Capabilities {
-            supports_step_back: Some(false),
-            ..Default::default()
+            let binary = adapter
+                .get_binary(&delegate, &config, user_installed_path, cx)
+                .await?;
+
+            let ret = this
+                .update(cx, |project, cx| {
+                    project.dap_store.update(cx, |dap_store, cx| {
+                        dap_store.new_session(binary, config, None, cx)
+                    })
+                })?
+                .1
+                .await;
+            ret
         });
-        self.dap_store
-            .update(cx, |dap_store, cx| {
-                dap_store.new_fake_session(config, worktree, None, caps, fails, cx)
-            })
-            .1
+        result
     }
 
     #[cfg(any(test, feature = "test-support"))]

crates/project/src/project_tests.rs 🔗

@@ -39,7 +39,7 @@ use std::{env, mem, num::NonZeroU32, ops::Range, str::FromStr, sync::OnceLock, t
 use task::{ResolvedTask, TaskContext};
 use unindent::Unindent as _;
 use util::{
-    TryFutureExt as _, assert_set_eq, path,
+    TryFutureExt as _, assert_set_eq, maybe, path,
     paths::PathMatcher,
     separator,
     test::{TempTree, marked_text_offsets},

crates/remote_server/src/headless_project.rs 🔗

@@ -72,7 +72,7 @@ impl HeadlessProject {
             http_client,
             node_runtime,
             languages,
-            debug_adapters,
+            debug_adapters: _debug_adapters,
             extension_host_proxy: proxy,
         }: HeadlessAppState,
         cx: &mut Context<Self>,
@@ -112,11 +112,9 @@ impl HeadlessProject {
                 node_runtime.clone(),
                 fs.clone(),
                 languages.clone(),
-                debug_adapters.clone(),
                 environment.clone(),
                 toolchain_store.read(cx).as_language_toolchain_store(),
                 breakpoint_store.clone(),
-                worktree_store.clone(),
                 cx,
             )
         });

crates/task/src/lib.rs 🔗

@@ -104,7 +104,7 @@ impl ResolvedTask {
     }
 
     /// Get the configuration for the debug adapter that should be used for this task.
-    pub fn resolved_debug_adapter_config(&self) -> Option<DebugAdapterConfig> {
+    pub fn resolved_debug_adapter_config(&self) -> Option<DebugTaskDefinition> {
         match self.original_task.task_type.clone() {
             TaskType::Debug(debug_args) if self.resolved.is_some() => {
                 let resolved = self
@@ -127,10 +127,10 @@ impl ResolvedTask {
                     })
                     .collect();
 
-                Some(DebugAdapterConfig {
+                Some(DebugTaskDefinition {
                     label: resolved.label.clone(),
                     adapter: debug_args.adapter.clone(),
-                    request: DebugRequestDisposition::UserConfigured(match debug_args.request {
+                    request: match debug_args.request {
                         crate::task_template::DebugArgsRequest::Launch => {
                             DebugRequestType::Launch(LaunchConfig {
                                 program: resolved.command.clone(),
@@ -141,7 +141,7 @@ impl ResolvedTask {
                         crate::task_template::DebugArgsRequest::Attach(attach_config) => {
                             DebugRequestType::Attach(attach_config)
                         }
-                    }),
+                    },
                     initialize_args: debug_args.initialize_args,
                     tcp_connection: debug_args.tcp_connection,
                     locator: debug_args.locator.clone(),

crates/tasks_ui/src/modal.rs 🔗

@@ -361,9 +361,8 @@ impl PickerDelegate for TasksModalDelegate {
 
         match task.task_type() {
             TaskType::Debug(config) if config.locator.is_none() => {
-                let Some(config): Option<DebugTaskDefinition> = task
-                    .resolved_debug_adapter_config()
-                    .and_then(|config| config.try_into().ok())
+                let Some(config): Option<DebugTaskDefinition> =
+                    task.resolved_debug_adapter_config()
                 else {
                     return;
                 };
@@ -382,7 +381,7 @@ impl PickerDelegate for TasksModalDelegate {
                             .update(cx, |workspace, cx| {
                                 workspace.project().update(cx, |project, cx| {
                                     project
-                                        .start_debug_session(config.into(), cx)
+                                        .start_debug_session(config, cx)
                                         .detach_and_log_err(cx);
                                 });
                             })

crates/workspace/src/workspace.rs 🔗

@@ -96,7 +96,7 @@ use std::{
     sync::{Arc, LazyLock, Weak, atomic::AtomicUsize},
     time::Duration,
 };
-use task::{DebugAdapterConfig, SpawnInTerminal, TaskId};
+use task::{DebugTaskDefinition, SpawnInTerminal, TaskId};
 use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
 pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
 pub use ui;
@@ -858,7 +858,7 @@ pub struct Workspace {
     serialized_ssh_project: Option<SerializedSshProject>,
     _items_serializer: Task<Result<()>>,
     session_id: Option<String>,
-    debug_task_queue: HashMap<task::TaskId, DebugAdapterConfig>,
+    debug_task_queue: HashMap<task::TaskId, DebugTaskDefinition>,
 }
 
 impl EventEmitter<Event> for Workspace {}