debugger: More tidy up for SSH (#28993)

Conrad Irwin , Anthony Eid , Anthony , and Cole Miller created

Split `locator` out of DebugTaskDefinition to make it clearer when
location needs to happen.

Release Notes:

- N/A

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Anthony <anthony@zed.dev>
Co-authored-by: Cole Miller <m@cole-miller.net>

Change summary

Cargo.lock                                                    |   5 
crates/collab/src/tests/remote_editing_collaboration_tests.rs |   5 
crates/collab/src/tests/test_server.rs                        |   5 
crates/dap/Cargo.toml                                         |   1 
crates/dap/src/adapters.rs                                    | 112 +
crates/dap/src/client.rs                                      |   9 
crates/dap/src/dap.rs                                         |   2 
crates/dap/src/registry.rs                                    |  24 
crates/dap/src/transport.rs                                   |  49 
crates/dap_adapters/src/codelldb.rs                           |  25 
crates/dap_adapters/src/dap_adapters.rs                       |  27 
crates/dap_adapters/src/gdb.rs                                |  17 
crates/dap_adapters/src/go.rs                                 |  15 
crates/dap_adapters/src/javascript.rs                         |  28 
crates/dap_adapters/src/php.rs                                |  38 
crates/dap_adapters/src/python.rs                             |  28 
crates/debugger_tools/src/dap_log.rs                          |   8 
crates/debugger_ui/src/attach_modal.rs                        |   6 
crates/debugger_ui/src/new_session_modal.rs                   |  69 
crates/debugger_ui/src/tests/attach_modal.rs                  |  10 
crates/editor/src/editor.rs                                   |  43 
crates/eval/Cargo.toml                                        |   1 
crates/eval/src/example.rs                                    |   2 
crates/project/src/debugger.rs                                |   2 
crates/project/src/debugger/dap_store.rs                      | 340 +++-
crates/project/src/debugger/locator_store.rs                  |  19 
crates/project/src/debugger/locators.rs                       |   4 
crates/project/src/debugger/locators/cargo.rs                 |  11 
crates/project/src/debugger/session.rs                        |   2 
crates/project/src/debugger/test.rs                           |   5 
crates/project/src/project.rs                                 | 115 
crates/project/src/task_inventory.rs                          |   6 
crates/project/src/terminals.rs                               |  27 
crates/proto/proto/debugger.proto                             |  54 
crates/proto/proto/zed.proto                                  |   7 
crates/proto/src/proto.rs                                     |   8 
crates/recent_projects/src/ssh_connections.rs                 |   1 
crates/remote_server/Cargo.toml                               |   2 
crates/remote_server/src/headless_project.rs                  |   7 
crates/remote_server/src/remote_editing_tests.rs              |   4 
crates/remote_server/src/unix.rs                              |   5 
crates/task/Cargo.toml                                        |   2 
crates/task/src/debug_format.rs                               | 234 ++-
crates/task/src/lib.rs                                        |  49 
crates/task/src/task_template.rs                              |   7 
crates/tasks_ui/src/modal.rs                                  |  58 
crates/tasks_ui/src/tasks_ui.rs                               |  19 
crates/terminal/src/terminal.rs                               |  37 
crates/terminal_view/src/terminal_panel.rs                    | 185 +-
crates/terminal_view/src/terminal_view.rs                     |   9 
crates/vim/src/command.rs                                     |  44 
crates/workspace/Cargo.toml                                   |   1 
crates/workspace/src/tasks.rs                                 | 175 +
crates/workspace/src/workspace.rs                             |  39 
crates/zed/Cargo.toml                                         |   1 
crates/zed/src/main.rs                                        |   4 
crates/zlog/src/filter.rs                                     |   4 
57 files changed, 1,139 insertions(+), 877 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4013,6 +4013,7 @@ dependencies = [
  "node_runtime",
  "parking_lot",
  "paths",
+ "proto",
  "schemars",
  "serde",
  "serde_json",
@@ -4897,7 +4898,6 @@ dependencies = [
  "client",
  "collections",
  "context_server",
- "dap",
  "dirs 5.0.1",
  "env_logger 0.11.8",
  "extension",
@@ -11743,6 +11743,7 @@ dependencies = [
  "client",
  "clock",
  "dap",
+ "dap_adapters",
  "env_logger 0.11.8",
  "extension",
  "extension_host",
@@ -14214,11 +14215,11 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "collections",
- "dap-types",
  "futures 0.3.31",
  "gpui",
  "hex",
  "parking_lot",
+ "proto",
  "schemars",
  "serde",
  "serde_json",

crates/collab/src/tests/remote_editing_collaboration_tests.rs 🔗

@@ -1,7 +1,7 @@
 use crate::tests::TestServer;
 use call::ActiveCall;
 use collections::{HashMap, HashSet};
-use dap::DapRegistry;
+
 use extension::ExtensionHostProxy;
 use fs::{FakeFs, Fs as _, RemoveOptions};
 use futures::StreamExt as _;
@@ -86,7 +86,6 @@ async fn test_sharing_an_ssh_remote_project(
                 http_client: remote_http_client,
                 node_runtime: node,
                 languages,
-                debug_adapters: Arc::new(DapRegistry::fake()),
                 extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
             },
             cx,
@@ -254,7 +253,6 @@ async fn test_ssh_collaboration_git_branches(
                 http_client: remote_http_client,
                 node_runtime: node,
                 languages,
-                debug_adapters: Arc::new(DapRegistry::fake()),
                 extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
             },
             cx,
@@ -460,7 +458,6 @@ async fn test_ssh_collaboration_formatting_with_prettier(
                 http_client: remote_http_client,
                 node_runtime: NodeRuntime::unavailable(),
                 languages,
-                debug_adapters: Arc::new(DapRegistry::fake()),
                 extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
             },
             cx,

crates/collab/src/tests/test_server.rs 🔗

@@ -14,7 +14,7 @@ use client::{
 use clock::FakeSystemClock;
 use collab_ui::channel_view::ChannelView;
 use collections::{HashMap, HashSet};
-use dap::DapRegistry;
+
 use fs::FakeFs;
 use futures::{StreamExt as _, channel::oneshot};
 use git::GitHostingProviderRegistry;
@@ -275,14 +275,12 @@ impl TestServer {
         let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
         let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
         let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
-        let debug_adapters = Arc::new(DapRegistry::default());
         let session = cx.new(|cx| AppSession::new(Session::test(), cx));
         let app_state = Arc::new(workspace::AppState {
             client: client.clone(),
             user_store: user_store.clone(),
             workspace_store,
             languages: language_registry,
-            debug_adapters,
             fs: fs.clone(),
             build_window_options: |_, _| Default::default(),
             node_runtime: NodeRuntime::unavailable(),
@@ -798,7 +796,6 @@ impl TestClient {
                 self.app_state.node_runtime.clone(),
                 self.app_state.user_store.clone(),
                 self.app_state.languages.clone(),
-                self.app_state.debug_adapters.clone(),
                 self.app_state.fs.clone(),
                 None,
                 cx,

crates/dap/Cargo.toml 🔗

@@ -39,6 +39,7 @@ log.workspace = true
 node_runtime.workspace = true
 parking_lot.workspace = true
 paths.workspace = true
+proto.workspace = true
 schemars.workspace = true
 serde.workspace = true
 serde_json.workspace = true

crates/dap/src/adapters.rs 🔗

@@ -3,7 +3,8 @@ use anyhow::{Context as _, Result, anyhow};
 use async_compression::futures::bufread::GzipDecoder;
 use async_tar::Archive;
 use async_trait::async_trait;
-use dap_types::StartDebuggingRequestArguments;
+use collections::HashMap;
+use dap_types::{StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest};
 use futures::io::BufReader;
 use gpui::{AsyncApp, SharedString};
 pub use http_client::{HttpClient, github::latest_github_release};
@@ -13,16 +14,10 @@ use serde::{Deserialize, Serialize};
 use settings::WorktreeId;
 use smol::{self, fs::File, lock::Mutex};
 use std::{
-    borrow::Borrow,
-    collections::{HashMap, HashSet},
-    ffi::{OsStr, OsString},
-    fmt::Debug,
-    net::Ipv4Addr,
-    ops::Deref,
-    path::PathBuf,
-    sync::Arc,
+    borrow::Borrow, collections::HashSet, ffi::OsStr, fmt::Debug, net::Ipv4Addr, ops::Deref,
+    path::PathBuf, sync::Arc,
 };
-use task::DebugTaskDefinition;
+use task::{DebugTaskDefinition, TcpArgumentsTemplate};
 use util::ResultExt;
 
 #[derive(Clone, Debug, PartialEq, Eq)]
@@ -93,17 +88,91 @@ pub struct TcpArguments {
     pub port: u16,
     pub timeout: Option<u64>,
 }
+
+impl TcpArguments {
+    pub fn from_proto(proto: proto::TcpHost) -> anyhow::Result<Self> {
+        let host = TcpArgumentsTemplate::from_proto(proto)?;
+        Ok(TcpArguments {
+            host: host.host.ok_or_else(|| anyhow!("missing host"))?,
+            port: host.port.ok_or_else(|| anyhow!("missing port"))?,
+            timeout: host.timeout,
+        })
+    }
+
+    pub fn to_proto(&self) -> proto::TcpHost {
+        TcpArgumentsTemplate {
+            host: Some(self.host),
+            port: Some(self.port),
+            timeout: self.timeout,
+        }
+        .to_proto()
+    }
+}
+
 #[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 arguments: Vec<String>,
+    pub envs: HashMap<String, String>,
     pub cwd: Option<PathBuf>,
     pub connection: Option<TcpArguments>,
     pub request_args: StartDebuggingRequestArguments,
 }
 
+impl DebugAdapterBinary {
+    pub fn from_proto(binary: proto::DebugAdapterBinary) -> anyhow::Result<Self> {
+        let request = match binary.launch_type() {
+            proto::debug_adapter_binary::LaunchType::Launch => {
+                StartDebuggingRequestArgumentsRequest::Launch
+            }
+            proto::debug_adapter_binary::LaunchType::Attach => {
+                StartDebuggingRequestArgumentsRequest::Attach
+            }
+        };
+
+        Ok(DebugAdapterBinary {
+            command: binary.command,
+            arguments: binary.arguments,
+            envs: binary.envs.into_iter().collect(),
+            connection: binary
+                .connection
+                .map(TcpArguments::from_proto)
+                .transpose()?,
+            request_args: StartDebuggingRequestArguments {
+                configuration: serde_json::from_str(&binary.configuration)?,
+                request,
+            },
+            cwd: binary.cwd.map(|cwd| cwd.into()),
+        })
+    }
+
+    pub fn to_proto(&self) -> proto::DebugAdapterBinary {
+        proto::DebugAdapterBinary {
+            command: self.command.clone(),
+            arguments: self.arguments.clone(),
+            envs: self
+                .envs
+                .iter()
+                .map(|(k, v)| (k.clone(), v.clone()))
+                .collect(),
+            cwd: self
+                .cwd
+                .as_ref()
+                .map(|cwd| cwd.to_string_lossy().to_string()),
+            connection: self.connection.as_ref().map(|c| c.to_proto()),
+            launch_type: match self.request_args.request {
+                StartDebuggingRequestArgumentsRequest::Launch => {
+                    proto::debug_adapter_binary::LaunchType::Launch.into()
+                }
+                StartDebuggingRequestArgumentsRequest::Attach => {
+                    proto::debug_adapter_binary::LaunchType::Attach.into()
+                }
+            },
+            configuration: self.request_args.configuration.to_string(),
+        }
+    }
+}
+
 #[derive(Debug)]
 pub struct AdapterVersion {
     pub tag_name: String,
@@ -318,22 +387,22 @@ impl FakeAdapter {
 
     fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments {
         use serde_json::json;
-        use task::DebugRequestType;
+        use task::DebugRequest;
 
         let value = json!({
             "request": match config.request {
-                DebugRequestType::Launch(_) => "launch",
-                DebugRequestType::Attach(_) => "attach",
+                DebugRequest::Launch(_) => "launch",
+                DebugRequest::Attach(_) => "attach",
             },
-            "process_id": if let DebugRequestType::Attach(attach_config) = &config.request {
+            "process_id": if let DebugRequest::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,
+            DebugRequest::Launch(_) => dap_types::StartDebuggingRequestArgumentsRequest::Launch,
+            DebugRequest::Attach(_) => dap_types::StartDebuggingRequestArgumentsRequest::Attach,
         };
         StartDebuggingRequestArguments {
             configuration: value,
@@ -357,11 +426,10 @@ impl DebugAdapter for FakeAdapter {
         _: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
         Ok(DebugAdapterBinary {
-            adapter_name: Self::ADAPTER_NAME.into(),
             command: "command".into(),
-            arguments: None,
+            arguments: vec![],
             connection: None,
-            envs: None,
+            envs: HashMap::default(),
             cwd: None,
             request_args: self.request_args(config),
         })

crates/dap/src/client.rs 🔗

@@ -1,5 +1,5 @@
 use crate::{
-    adapters::{DebugAdapterBinary, DebugAdapterName},
+    adapters::DebugAdapterBinary,
     transport::{IoKind, LogKind, TransportDelegate},
 };
 use anyhow::{Result, anyhow};
@@ -88,7 +88,6 @@ 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,
@@ -219,9 +218,6 @@ impl DebugAdapterClient {
         self.id
     }
 
-    pub fn name(&self) -> DebugAdapterName {
-        self.binary.adapter_name.clone()
-    }
     pub fn binary(&self) -> &DebugAdapterBinary {
         &self.binary
     }
@@ -322,7 +318,6 @@ mod tests {
         let client = DebugAdapterClient::start(
             crate::client::SessionId(1),
             DebugAdapterBinary {
-                adapter_name: "adapter".into(),
                 command: "command".into(),
                 arguments: Default::default(),
                 envs: Default::default(),
@@ -393,7 +388,6 @@ mod tests {
         let client = DebugAdapterClient::start(
             crate::client::SessionId(1),
             DebugAdapterBinary {
-                adapter_name: "adapter".into(),
                 command: "command".into(),
                 arguments: Default::default(),
                 envs: Default::default(),
@@ -447,7 +441,6 @@ mod tests {
         let client = DebugAdapterClient::start(
             crate::client::SessionId(1),
             DebugAdapterBinary {
-                adapter_name: "test-adapter".into(),
                 command: "command".into(),
                 arguments: Default::default(),
                 envs: Default::default(),

crates/dap/src/dap.rs 🔗

@@ -7,7 +7,7 @@ pub mod transport;
 
 pub use dap_types::*;
 pub use registry::DapRegistry;
-pub use task::DebugRequestType;
+pub use task::DebugRequest;
 
 pub type ScopeId = u64;
 pub type VariableReference = u64;

crates/dap/src/registry.rs 🔗

@@ -1,3 +1,4 @@
+use gpui::{App, Global};
 use parking_lot::RwLock;
 
 use crate::adapters::{DebugAdapter, DebugAdapterName};
@@ -11,8 +12,20 @@ struct DapRegistryState {
 #[derive(Clone, Default)]
 /// Stores available debug adapters.
 pub struct DapRegistry(Arc<RwLock<DapRegistryState>>);
+impl Global for DapRegistry {}
 
 impl DapRegistry {
+    pub fn global(cx: &mut App) -> &mut Self {
+        let ret = cx.default_global::<Self>();
+
+        #[cfg(any(test, feature = "test-support"))]
+        if ret.adapter(crate::FakeAdapter::ADAPTER_NAME).is_none() {
+            ret.add_adapter(Arc::new(crate::FakeAdapter::new()));
+        }
+
+        ret
+    }
+
     pub fn add_adapter(&self, adapter: Arc<dyn DebugAdapter>) {
         let name = adapter.name();
         let _previous_value = self.0.write().adapters.insert(name, adapter);
@@ -21,19 +34,12 @@ impl DapRegistry {
             "Attempted to insert a new debug adapter when one is already registered"
         );
     }
+
     pub fn adapter(&self, name: &str) -> Option<Arc<dyn DebugAdapter>> {
         self.0.read().adapters.get(name).cloned()
     }
+
     pub fn enumerate_adapters(&self) -> Vec<DebugAdapterName> {
         self.0.read().adapters.keys().cloned().collect()
     }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn fake() -> Self {
-        use crate::FakeAdapter;
-
-        let register = Self::default();
-        register.add_adapter(Arc::new(FakeAdapter::new()));
-        register
-    }
 }

crates/dap/src/transport.rs 🔗

@@ -21,7 +21,7 @@ use std::{
     sync::Arc,
     time::Duration,
 };
-use task::TCPHost;
+use task::TcpArgumentsTemplate;
 use util::ResultExt as _;
 
 use crate::{adapters::DebugAdapterBinary, debugger_settings::DebuggerSettings};
@@ -74,16 +74,14 @@ pub enum Transport {
 }
 
 impl Transport {
-    #[cfg(any(test, feature = "test-support"))]
-    async fn start(_: &DebugAdapterBinary, cx: AsyncApp) -> Result<(TransportPipe, Self)> {
+    async fn start(binary: &DebugAdapterBinary, cx: AsyncApp) -> Result<(TransportPipe, Self)> {
         #[cfg(any(test, feature = "test-support"))]
-        return FakeTransport::start(cx)
-            .await
-            .map(|(transports, fake)| (transports, Self::Fake(fake)));
-    }
+        if cfg!(any(test, feature = "test-support")) {
+            return FakeTransport::start(cx)
+                .await
+                .map(|(transports, fake)| (transports, Self::Fake(fake)));
+        }
 
-    #[cfg(not(any(test, feature = "test-support")))]
-    async fn start(binary: &DebugAdapterBinary, cx: AsyncApp) -> Result<(TransportPipe, Self)> {
         if binary.connection.is_some() {
             TcpTransport::start(binary, cx)
                 .await
@@ -520,18 +518,21 @@ pub struct TcpTransport {
 
 impl TcpTransport {
     /// Get an open port to use with the tcp client when not supplied by debug config
-    pub async fn port(host: &TCPHost) -> Result<u16> {
+    pub async fn port(host: &TcpArgumentsTemplate) -> Result<u16> {
         if let Some(port) = host.port {
             Ok(port)
         } else {
-            Ok(TcpListener::bind(SocketAddrV4::new(host.host(), 0))
-                .await?
-                .local_addr()?
-                .port())
+            Self::unused_port(host.host()).await
         }
     }
 
-    #[allow(dead_code, reason = "This is used in non test builds of Zed")]
+    pub async fn unused_port(host: Ipv4Addr) -> Result<u16> {
+        Ok(TcpListener::bind(SocketAddrV4::new(host, 0))
+            .await?
+            .local_addr()?
+            .port())
+    }
+
     async fn start(binary: &DebugAdapterBinary, cx: AsyncApp) -> Result<(TransportPipe, Self)> {
         let Some(connection_args) = binary.connection.as_ref() else {
             return Err(anyhow!("No connection arguments provided"));
@@ -546,13 +547,8 @@ impl TcpTransport {
             command.current_dir(cwd);
         }
 
-        if let Some(args) = &binary.arguments {
-            command.args(args);
-        }
-
-        if let Some(envs) = &binary.envs {
-            command.envs(envs);
-        }
+        command.args(&binary.arguments);
+        command.envs(&binary.envs);
 
         command
             .stdin(Stdio::null())
@@ -635,13 +631,8 @@ impl StdioTransport {
             command.current_dir(cwd);
         }
 
-        if let Some(args) = &binary.arguments {
-            command.args(args);
-        }
-
-        if let Some(envs) = &binary.envs {
-            command.envs(envs);
-        }
+        command.args(&binary.arguments);
+        command.envs(&binary.envs);
 
         command
             .stdin(Stdio::piped())

crates/dap_adapters/src/codelldb.rs 🔗

@@ -1,10 +1,10 @@
-use std::{path::PathBuf, sync::OnceLock};
+use std::{collections::HashMap, path::PathBuf, sync::OnceLock};
 
 use anyhow::{Result, bail};
 use async_trait::async_trait;
 use dap::adapters::latest_github_release;
 use gpui::AsyncApp;
-use task::{DebugRequestType, DebugTaskDefinition};
+use task::{DebugRequest, DebugTaskDefinition};
 
 use crate::*;
 
@@ -19,8 +19,8 @@ impl CodeLldbDebugAdapter {
     fn request_args(&self, config: &DebugTaskDefinition) -> dap::StartDebuggingRequestArguments {
         let mut configuration = json!({
             "request": match config.request {
-                DebugRequestType::Launch(_) => "launch",
-                DebugRequestType::Attach(_) => "attach",
+                DebugRequest::Launch(_) => "launch",
+                DebugRequest::Attach(_) => "attach",
             },
         });
         let map = configuration.as_object_mut().unwrap();
@@ -28,10 +28,10 @@ impl CodeLldbDebugAdapter {
         map.insert("name".into(), Value::String(config.label.clone()));
         let request = config.request.to_dap();
         match &config.request {
-            DebugRequestType::Attach(attach) => {
+            DebugRequest::Attach(attach) => {
                 map.insert("pid".into(), attach.process_id.into());
             }
-            DebugRequestType::Launch(launch) => {
+            DebugRequest::Launch(launch) => {
                 map.insert("program".into(), launch.program.clone().into());
 
                 if !launch.args.is_empty() {
@@ -140,16 +140,13 @@ impl DebugAdapter for CodeLldbDebugAdapter {
             .ok_or_else(|| anyhow!("Adapter path is expected to be valid UTF-8"))?;
         Ok(DebugAdapterBinary {
             command,
-            cwd: Some(adapter_dir),
-            arguments: Some(vec![
+            cwd: None,
+            arguments: vec![
                 "--settings".into(),
-                json!({"sourceLanguages": ["cpp", "rust"]})
-                    .to_string()
-                    .into(),
-            ]),
+                json!({"sourceLanguages": ["cpp", "rust"]}).to_string(),
+            ],
             request_args: self.request_args(config),
-            adapter_name: "test".into(),
-            envs: None,
+            envs: HashMap::default(),
             connection: None,
         })
     }

crates/dap_adapters/src/dap_adapters.rs 🔗

@@ -11,7 +11,7 @@ use anyhow::{Result, anyhow};
 use async_trait::async_trait;
 use codelldb::CodeLldbDebugAdapter;
 use dap::{
-    DapRegistry, DebugRequestType,
+    DapRegistry, DebugRequest,
     adapters::{
         self, AdapterVersion, DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName,
         GithubRepo,
@@ -19,23 +19,26 @@ use dap::{
 };
 use gdb::GdbDebugAdapter;
 use go::GoDebugAdapter;
+use gpui::{App, BorrowAppContext};
 use javascript::JsDebugAdapter;
 use php::PhpDebugAdapter;
 use python::PythonDebugAdapter;
 use serde_json::{Value, json};
-use task::TCPHost;
-
-pub fn init(registry: Arc<DapRegistry>) {
-    registry.add_adapter(Arc::from(CodeLldbDebugAdapter::default()));
-    registry.add_adapter(Arc::from(PythonDebugAdapter));
-    registry.add_adapter(Arc::from(PhpDebugAdapter));
-    registry.add_adapter(Arc::from(JsDebugAdapter));
-    registry.add_adapter(Arc::from(GoDebugAdapter));
-    registry.add_adapter(Arc::from(GdbDebugAdapter));
+use task::TcpArgumentsTemplate;
+
+pub fn init(cx: &mut App) {
+    cx.update_default_global(|registry: &mut DapRegistry, _cx| {
+        registry.add_adapter(Arc::from(CodeLldbDebugAdapter::default()));
+        registry.add_adapter(Arc::from(PythonDebugAdapter));
+        registry.add_adapter(Arc::from(PhpDebugAdapter));
+        registry.add_adapter(Arc::from(JsDebugAdapter));
+        registry.add_adapter(Arc::from(GoDebugAdapter));
+        registry.add_adapter(Arc::from(GdbDebugAdapter));
+    })
 }
 
 pub(crate) async fn configure_tcp_connection(
-    tcp_connection: TCPHost,
+    tcp_connection: TcpArgumentsTemplate,
 ) -> Result<(Ipv4Addr, u16, Option<u64>)> {
     let host = tcp_connection.host();
     let timeout = tcp_connection.timeout;
@@ -53,7 +56,7 @@ trait ToDap {
     fn to_dap(&self) -> dap::StartDebuggingRequestArgumentsRequest;
 }
 
-impl ToDap for DebugRequestType {
+impl ToDap for DebugRequest {
     fn to_dap(&self) -> dap::StartDebuggingRequestArgumentsRequest {
         match self {
             Self::Launch(_) => dap::StartDebuggingRequestArgumentsRequest::Launch,

crates/dap_adapters/src/gdb.rs 🔗

@@ -1,10 +1,10 @@
-use std::ffi::OsStr;
+use std::{collections::HashMap, ffi::OsStr};
 
 use anyhow::{Result, bail};
 use async_trait::async_trait;
 use dap::StartDebuggingRequestArguments;
 use gpui::AsyncApp;
-use task::{DebugRequestType, DebugTaskDefinition};
+use task::{DebugRequest, DebugTaskDefinition};
 
 use crate::*;
 
@@ -17,18 +17,18 @@ impl GdbDebugAdapter {
     fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments {
         let mut args = json!({
             "request": match config.request {
-                DebugRequestType::Launch(_) => "launch",
-                DebugRequestType::Attach(_) => "attach",
+                DebugRequest::Launch(_) => "launch",
+                DebugRequest::Attach(_) => "attach",
             },
         });
 
         let map = args.as_object_mut().unwrap();
         match &config.request {
-            DebugRequestType::Attach(attach) => {
+            DebugRequest::Attach(attach) => {
                 map.insert("pid".into(), attach.process_id.into());
             }
 
-            DebugRequestType::Launch(launch) => {
+            DebugRequest::Launch(launch) => {
                 map.insert("program".into(), launch.program.clone().into());
 
                 if !launch.args.is_empty() {
@@ -82,10 +82,9 @@ 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,
+            arguments: vec!["-i=dap".into()],
+            envs: HashMap::default(),
             cwd: None,
             connection: None,
             request_args: self.request_args(config),

crates/dap_adapters/src/go.rs 🔗

@@ -1,6 +1,6 @@
 use dap::StartDebuggingRequestArguments;
 use gpui::AsyncApp;
-use std::{ffi::OsStr, path::PathBuf};
+use std::{collections::HashMap, ffi::OsStr, path::PathBuf};
 use task::DebugTaskDefinition;
 
 use crate::*;
@@ -12,12 +12,12 @@ 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) => {
+            dap::DebugRequest::Attach(attach_config) => {
                 json!({
                     "processId": attach_config.process_id,
                 })
             }
-            dap::DebugRequestType::Launch(launch_config) => json!({
+            dap::DebugRequest::Launch(launch_config) => json!({
                 "program": launch_config.program,
                 "cwd": launch_config.cwd,
                 "args": launch_config.args
@@ -92,15 +92,14 @@ 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![
+            arguments: vec![
                 "dap".into(),
                 "--listen".into(),
-                format!("{}:{}", host, port).into(),
-            ]),
+                format!("{}:{}", host, port),
+            ],
             cwd: None,
-            envs: None,
+            envs: HashMap::default(),
             connection: Some(adapters::TcpArguments {
                 host,
                 port,

crates/dap_adapters/src/javascript.rs 🔗

@@ -1,8 +1,8 @@
 use adapters::latest_github_release;
 use dap::StartDebuggingRequestArguments;
 use gpui::AsyncApp;
-use std::path::PathBuf;
-use task::{DebugRequestType, DebugTaskDefinition};
+use std::{collections::HashMap, path::PathBuf};
+use task::{DebugRequest, DebugTaskDefinition};
 
 use crate::*;
 
@@ -18,16 +18,16 @@ impl JsDebugAdapter {
         let mut args = json!({
             "type": "pwa-node",
             "request": match config.request {
-                DebugRequestType::Launch(_) => "launch",
-                DebugRequestType::Attach(_) => "attach",
+                DebugRequest::Launch(_) => "launch",
+                DebugRequest::Attach(_) => "attach",
             },
         });
         let map = args.as_object_mut().unwrap();
         match &config.request {
-            DebugRequestType::Attach(attach) => {
+            DebugRequest::Attach(attach) => {
                 map.insert("processId".into(), attach.process_id.into());
             }
-            DebugRequestType::Launch(launch) => {
+            DebugRequest::Launch(launch) => {
                 map.insert("program".into(), launch.program.clone().into());
 
                 if !launch.args.is_empty() {
@@ -106,20 +106,22 @@ 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()
                 .await?
                 .to_string_lossy()
                 .into_owned(),
-            arguments: Some(vec![
-                adapter_path.join(Self::ADAPTER_PATH).into(),
-                port.to_string().into(),
-                host.to_string().into(),
-            ]),
+            arguments: vec![
+                adapter_path
+                    .join(Self::ADAPTER_PATH)
+                    .to_string_lossy()
+                    .to_string(),
+                port.to_string(),
+                host.to_string(),
+            ],
             cwd: None,
-            envs: None,
+            envs: HashMap::default(),
             connection: Some(adapters::TcpArguments {
                 host,
                 port,

crates/dap_adapters/src/php.rs 🔗

@@ -1,7 +1,7 @@
 use adapters::latest_github_release;
 use dap::adapters::TcpArguments;
 use gpui::AsyncApp;
-use std::path::PathBuf;
+use std::{collections::HashMap, path::PathBuf};
 use task::DebugTaskDefinition;
 
 use crate::*;
@@ -19,20 +19,18 @@ impl PhpDebugAdapter {
         config: &DebugTaskDefinition,
     ) -> Result<dap::StartDebuggingRequestArguments> {
         match &config.request {
-            dap::DebugRequestType::Attach(_) => {
+            dap::DebugRequest::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(),
-                })
-            }
+            dap::DebugRequest::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(),
+            }),
         }
     }
 }
@@ -94,24 +92,26 @@ 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()
                 .await?
                 .to_string_lossy()
                 .into_owned(),
-            arguments: Some(vec![
-                adapter_path.join(Self::ADAPTER_PATH).into(),
-                format!("--server={}", port).into(),
-            ]),
+            arguments: vec![
+                adapter_path
+                    .join(Self::ADAPTER_PATH)
+                    .to_string_lossy()
+                    .to_string(),
+                format!("--server={}", port),
+            ],
             connection: Some(TcpArguments {
                 port,
                 host,
                 timeout,
             }),
             cwd: None,
-            envs: None,
+            envs: HashMap::default(),
             request_args: self.request_args(config)?,
         })
     }

crates/dap_adapters/src/python.rs 🔗

@@ -1,7 +1,7 @@
 use crate::*;
-use dap::{DebugRequestType, StartDebuggingRequestArguments};
+use dap::{DebugRequest, StartDebuggingRequestArguments};
 use gpui::AsyncApp;
-use std::{ffi::OsStr, path::PathBuf};
+use std::{collections::HashMap, ffi::OsStr, path::PathBuf};
 use task::DebugTaskDefinition;
 
 #[derive(Default)]
@@ -16,18 +16,18 @@ impl PythonDebugAdapter {
     fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments {
         let mut args = json!({
             "request": match config.request {
-                DebugRequestType::Launch(_) => "launch",
-                DebugRequestType::Attach(_) => "attach",
+                DebugRequest::Launch(_) => "launch",
+                DebugRequest::Attach(_) => "attach",
             },
             "subProcess": true,
             "redirectOutput": true,
         });
         let map = args.as_object_mut().unwrap();
         match &config.request {
-            DebugRequestType::Attach(attach) => {
+            DebugRequest::Attach(attach) => {
                 map.insert("processId".into(), attach.process_id.into());
             }
-            DebugRequestType::Launch(launch) => {
+            DebugRequest::Launch(launch) => {
                 map.insert("program".into(), launch.program.clone().into());
                 map.insert("args".into(), launch.args.clone().into());
 
@@ -141,20 +141,22 @@ 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(),
-                format!("--port={}", port).into(),
-                format!("--host={}", host).into(),
-            ]),
+            arguments: vec![
+                debugpy_dir
+                    .join(Self::ADAPTER_PATH)
+                    .to_string_lossy()
+                    .to_string(),
+                format!("--port={}", port),
+                format!("--host={}", host),
+            ],
             connection: Some(adapters::TcpArguments {
                 host,
                 port,
                 timeout,
             }),
             cwd: None,
-            envs: None,
+            envs: HashMap::default(),
             request_args: self.request_args(config),
         })
     }

crates/debugger_tools/src/dap_log.rs 🔗

@@ -566,11 +566,13 @@ impl DapLogView {
             .dap_store()
             .read(cx)
             .sessions()
-            .filter_map(|client| {
-                let client = client.read(cx).adapter_client()?;
+            .filter_map(|session| {
+                let session = session.read(cx);
+                session.adapter_name();
+                let client = session.adapter_client()?;
                 Some(DapMenuItem {
                     client_id: client.id(),
-                    client_name: client.name().0.as_ref().into(),
+                    client_name: session.adapter_name().to_string(),
                     has_adapter_logs: client.has_adapter_logs(),
                     selected_entry: self.current_view.map_or(LogKind::Adapter, |(_, kind)| kind),
                 })

crates/debugger_ui/src/attach_modal.rs 🔗

@@ -1,4 +1,4 @@
-use dap::DebugRequestType;
+use dap::DebugRequest;
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::Subscription;
 use gpui::{DismissEvent, Entity, EventEmitter, Focusable, Render};
@@ -216,10 +216,10 @@ impl PickerDelegate for AttachModalDelegate {
         };
 
         match &mut self.debug_config.request {
-            DebugRequestType::Attach(config) => {
+            DebugRequest::Attach(config) => {
                 config.process_id = Some(candidate.pid);
             }
-            DebugRequestType::Launch(_) => {
+            DebugRequest::Launch(_) => {
                 debug_panic!("Debugger attach modal used on launch debug config");
                 return;
             }

crates/debugger_ui/src/new_session_modal.rs 🔗

@@ -5,7 +5,7 @@ use std::{
 };
 
 use anyhow::{Result, anyhow};
-use dap::DebugRequestType;
+use dap::{DapRegistry, DebugRequest};
 use editor::{Editor, EditorElement, EditorStyle};
 use gpui::{
     App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, TextStyle,
@@ -13,7 +13,7 @@ use gpui::{
 };
 use project::Project;
 use settings::Settings;
-use task::{DebugTaskDefinition, LaunchConfig};
+use task::{DebugTaskDefinition, DebugTaskTemplate, LaunchRequest};
 use theme::ThemeSettings;
 use ui::{
     ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context,
@@ -37,9 +37,9 @@ pub(super) struct NewSessionModal {
     last_selected_profile_name: Option<SharedString>,
 }
 
-fn suggested_label(request: &DebugRequestType, debugger: &str) -> String {
+fn suggested_label(request: &DebugRequest, debugger: &str) -> String {
     match request {
-        DebugRequestType::Launch(config) => {
+        DebugRequest::Launch(config) => {
             let last_path_component = Path::new(&config.program)
                 .file_name()
                 .map(|name| name.to_string_lossy())
@@ -47,7 +47,7 @@ fn suggested_label(request: &DebugRequestType, debugger: &str) -> String {
 
             format!("{} ({debugger})", last_path_component)
         }
-        DebugRequestType::Attach(config) => format!(
+        DebugRequest::Attach(config) => format!(
             "pid: {} ({debugger})",
             config.process_id.unwrap_or(u32::MAX)
         ),
@@ -71,7 +71,7 @@ impl NewSessionModal {
             .and_then(|def| def.stop_on_entry);
 
         let launch_config = match past_debug_definition.map(|def| def.request) {
-            Some(DebugRequestType::Launch(launch_config)) => Some(launch_config),
+            Some(DebugRequest::Launch(launch_config)) => Some(launch_config),
             _ => None,
         };
 
@@ -96,7 +96,6 @@ impl NewSessionModal {
             request,
             initialize_args: self.initialize_args.clone(),
             tcp_connection: None,
-            locator: None,
             stop_on_entry: match self.stop_on_entry {
                 ToggleState::Selected => Some(true),
                 _ => None,
@@ -131,20 +130,16 @@ impl NewSessionModal {
             let project = workspace.update(cx, |workspace, _| workspace.project().clone())?;
 
             let task = project.update(cx, |this, cx| {
-                if let Some(debug_config) =
-                    config
-                        .clone()
-                        .to_zed_format()
-                        .ok()
-                        .and_then(|task_template| {
-                            task_template
-                                .resolve_task("debug_task", &task_context)
-                                .and_then(|resolved_task| {
-                                    resolved_task.resolved_debug_adapter_config()
-                                })
-                        })
+                let template = DebugTaskTemplate {
+                    locator: None,
+                    definition: config.clone(),
+                };
+                if let Some(debug_config) = template
+                    .to_zed_format()
+                    .resolve_task("debug_task", &task_context)
+                    .and_then(|resolved_task| resolved_task.resolved_debug_adapter_config())
                 {
-                    this.start_debug_session(debug_config, cx)
+                    this.start_debug_session(debug_config.definition, cx)
                 } else {
                     this.start_debug_session(config, cx)
                 }
@@ -214,12 +209,7 @@ impl NewSessionModal {
                 };
 
                 let available_adapters = workspace
-                    .update(cx, |this, cx| {
-                        this.project()
-                            .read(cx)
-                            .debug_adapters()
-                            .enumerate_adapters()
-                    })
+                    .update(cx, |_, cx| DapRegistry::global(cx).enumerate_adapters())
                     .ok()
                     .unwrap_or_default();
 
@@ -251,14 +241,14 @@ impl NewSessionModal {
                             this.debugger = Some(task.adapter.clone().into());
                             this.initialize_args = task.initialize_args.clone();
                             match &task.request {
-                                DebugRequestType::Launch(launch_config) => {
+                                DebugRequest::Launch(launch_config) => {
                                     this.mode = NewSessionMode::launch(
                                         Some(launch_config.clone()),
                                         window,
                                         cx,
                                     );
                                 }
-                                DebugRequestType::Attach(_) => {
+                                DebugRequest::Attach(_) => {
                                     let Ok(project) = this
                                         .workspace
                                         .read_with(cx, |this, _| this.project().clone())
@@ -285,7 +275,7 @@ impl NewSessionModal {
                     }
                 };
 
-                let available_adapters: Vec<DebugTaskDefinition> = workspace
+                let available_adapters: Vec<DebugTaskTemplate> = workspace
                     .update(cx, |this, cx| {
                         this.project()
                             .read(cx)
@@ -303,9 +293,9 @@ impl NewSessionModal {
 
                 for debug_definition in available_adapters {
                     menu = menu.entry(
-                        debug_definition.label.clone(),
+                        debug_definition.definition.label.clone(),
                         None,
-                        setter_for_name(debug_definition),
+                        setter_for_name(debug_definition.definition),
                     );
                 }
                 menu
@@ -322,7 +312,7 @@ struct LaunchMode {
 
 impl LaunchMode {
     fn new(
-        past_launch_config: Option<LaunchConfig>,
+        past_launch_config: Option<LaunchRequest>,
         window: &mut Window,
         cx: &mut App,
     ) -> Entity<Self> {
@@ -348,9 +338,9 @@ impl LaunchMode {
         cx.new(|_| Self { program, cwd })
     }
 
-    fn debug_task(&self, cx: &App) -> task::LaunchConfig {
+    fn debug_task(&self, cx: &App) -> task::LaunchRequest {
         let path = self.cwd.read(cx).text(cx);
-        task::LaunchConfig {
+        task::LaunchRequest {
             program: self.program.read(cx).text(cx),
             cwd: path.is_empty().not().then(|| PathBuf::from(path)),
             args: Default::default(),
@@ -373,10 +363,9 @@ impl AttachMode {
     ) -> Entity<Self> {
         let debug_definition = DebugTaskDefinition {
             label: "Attach New Session Setup".into(),
-            request: dap::DebugRequestType::Attach(task::AttachConfig { process_id: None }),
+            request: dap::DebugRequest::Attach(task::AttachRequest { process_id: None }),
             tcp_connection: None,
             adapter: debugger.clone().unwrap_or_default().into(),
-            locator: None,
             initialize_args: None,
             stop_on_entry: Some(false),
         };
@@ -391,8 +380,8 @@ impl AttachMode {
             attach_picker,
         })
     }
-    fn debug_task(&self) -> task::AttachConfig {
-        task::AttachConfig { process_id: None }
+    fn debug_task(&self) -> task::AttachRequest {
+        task::AttachRequest { process_id: None }
     }
 }
 
@@ -406,7 +395,7 @@ enum NewSessionMode {
 }
 
 impl NewSessionMode {
-    fn debug_task(&self, cx: &App) -> DebugRequestType {
+    fn debug_task(&self, cx: &App) -> DebugRequest {
         match self {
             NewSessionMode::Launch(entity) => entity.read(cx).debug_task(cx).into(),
             NewSessionMode::Attach(entity) => entity.read(cx).debug_task().into(),
@@ -488,7 +477,7 @@ impl NewSessionMode {
         Self::Attach(AttachMode::new(debugger, project, window, cx))
     }
     fn launch(
-        past_launch_config: Option<LaunchConfig>,
+        past_launch_config: Option<LaunchRequest>,
         window: &mut Window,
         cx: &mut Context<NewSessionModal>,
     ) -> Self {

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

@@ -5,7 +5,7 @@ use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
 use menu::Confirm;
 use project::{FakeFs, Project};
 use serde_json::json;
-use task::{AttachConfig, DebugTaskDefinition, TCPHost};
+use task::{AttachRequest, DebugTaskDefinition, TcpArgumentsTemplate};
 use tests::{init_test, init_test_workspace};
 
 #[gpui::test]
@@ -31,13 +31,12 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te
         cx,
         DebugTaskDefinition {
             adapter: "fake-adapter".to_string(),
-            request: dap::DebugRequestType::Attach(AttachConfig {
+            request: dap::DebugRequest::Attach(AttachRequest {
                 process_id: Some(10),
             }),
             label: "label".to_string(),
             initialize_args: None,
             tcp_connection: None,
-            locator: None,
             stop_on_entry: None,
         },
         |client| {
@@ -105,11 +104,10 @@ async fn test_show_attach_modal_and_select_process(
                     project.clone(),
                     DebugTaskDefinition {
                         adapter: FakeAdapter::ADAPTER_NAME.into(),
-                        request: dap::DebugRequestType::Attach(AttachConfig::default()),
+                        request: dap::DebugRequest::Attach(AttachRequest::default()),
                         label: "attach example".into(),
                         initialize_args: None,
-                        tcp_connection: Some(TCPHost::default()),
-                        locator: None,
+                        tcp_connection: Some(TcpArgumentsTemplate::default()),
                         stop_on_entry: None,
                     },
                     vec![

crates/editor/src/editor.rs 🔗

@@ -5111,44 +5111,21 @@ impl Editor {
             CodeActionsItem::Task(task_source_kind, resolved_task) => {
                 match resolved_task.task_type() {
                     task::TaskType::Script => workspace.update(cx, |workspace, cx| {
-                        workspace::tasks::schedule_resolved_task(
-                            workspace,
+                        workspace.schedule_resolved_task(
                             task_source_kind,
                             resolved_task,
                             false,
+                            window,
                             cx,
                         );
 
                         Some(Task::ready(Ok(())))
                     }),
-                    task::TaskType::Debug(debug_args) => {
-                        if debug_args.locator.is_some() {
-                            workspace.update(cx, |workspace, cx| {
-                                workspace::tasks::schedule_resolved_task(
-                                    workspace,
-                                    task_source_kind,
-                                    resolved_task,
-                                    false,
-                                    cx,
-                                );
-                            });
-
-                            return Some(Task::ready(Ok(())));
-                        }
-
-                        if let Some(project) = self.project.as_ref() {
-                            project
-                                .update(cx, |project, cx| {
-                                    project.start_debug_session(
-                                        resolved_task.resolved_debug_adapter_config().unwrap(),
-                                        cx,
-                                    )
-                                })
-                                .detach_and_log_err(cx);
-                            Some(Task::ready(Ok(())))
-                        } else {
-                            Some(Task::ready(Ok(())))
-                        }
+                    task::TaskType::Debug(_) => {
+                        workspace.update(cx, |workspace, cx| {
+                            workspace.schedule_debug_task(resolved_task, window, cx);
+                        });
+                        Some(Task::ready(Ok(())))
                     }
                 }
             }
@@ -6845,12 +6822,12 @@ impl Editor {
             resolved.reveal = reveal_strategy;
 
             workspace
-                .update(cx, |workspace, cx| {
-                    workspace::tasks::schedule_resolved_task(
-                        workspace,
+                .update_in(cx, |workspace, window, cx| {
+                    workspace.schedule_resolved_task(
                         task_source_kind,
                         resolved_task,
                         false,
+                        window,
                         cx,
                     );
                 })

crates/eval/Cargo.toml 🔗

@@ -15,7 +15,6 @@ clap.workspace = true
 client.workspace = true
 collections.workspace = true
 context_server.workspace = true
-dap.workspace = true
 dirs = "5.0"
 env_logger.workspace = true
 extension.workspace = true

crates/eval/src/example.rs 🔗

@@ -3,7 +3,6 @@ use agent::{ThreadEvent, ThreadStore};
 use anyhow::{Context as _, Result, anyhow};
 use assistant_tool::ToolWorkingSet;
 use client::proto::LspWorkProgress;
-use dap::DapRegistry;
 use futures::channel::mpsc;
 use futures::{FutureExt, StreamExt as _, select_biased};
 use gpui::{App, AppContext as _, AsyncApp, Entity, Task};
@@ -243,7 +242,6 @@ impl Example {
             app_state.node_runtime.clone(),
             app_state.user_store.clone(),
             app_state.languages.clone(),
-            Arc::new(DapRegistry::default()),
             app_state.fs.clone(),
             None,
             cx,

crates/project/src/debugger.rs 🔗

@@ -14,7 +14,7 @@
 pub mod breakpoint_store;
 pub mod dap_command;
 pub mod dap_store;
-mod locator_store;
+pub mod locators;
 pub mod session;
 
 #[cfg(any(feature = "test-support", test))]

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

@@ -1,16 +1,18 @@
 use super::{
     breakpoint_store::BreakpointStore,
-    locator_store::LocatorStore,
+    locators::DapLocator,
     session::{self, Session, SessionStateEvent},
 };
-use crate::{ProjectEnvironment, debugger};
+use crate::{
+    ProjectEnvironment, debugger, project_settings::ProjectSettings, worktree_store::WorktreeStore,
+};
 use anyhow::{Result, anyhow};
 use async_trait::async_trait;
 use collections::HashMap;
 use dap::{
-    Capabilities, CompletionItem, CompletionsArguments, ErrorResponse, EvaluateArguments,
-    EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, Source,
-    StartDebuggingRequestArguments,
+    Capabilities, CompletionItem, CompletionsArguments, DapRegistry, ErrorResponse,
+    EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments,
+    Source, StartDebuggingRequestArguments,
     adapters::{DapStatus, DebugAdapterBinary, DebugAdapterName},
     client::SessionId,
     messages::Message,
@@ -22,8 +24,7 @@ use futures::{
     future::{Shared, join_all},
 };
 use gpui::{
-    App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, SharedString,
-    Task, WeakEntity,
+    App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity,
 };
 use http_client::HttpClient;
 use language::{BinaryStatus, LanguageRegistry, LanguageToolchainStore};
@@ -35,17 +36,16 @@ use rpc::{
     proto::{self},
 };
 use serde_json::Value;
-use settings::WorktreeId;
+use settings::{Settings, WorktreeId};
 use smol::{lock::Mutex, stream::StreamExt};
 use std::{
     borrow::Borrow,
     collections::{BTreeMap, HashSet},
     ffi::OsStr,
     path::{Path, PathBuf},
-    sync::{Arc, atomic::Ordering::SeqCst},
+    sync::Arc,
 };
-use std::{collections::VecDeque, sync::atomic::AtomicU32};
-use task::DebugTaskDefinition;
+use task::{DebugTaskDefinition, DebugTaskTemplate};
 use util::ResultExt as _;
 use worktree::Worktree;
 
@@ -71,45 +71,26 @@ pub enum DapStoreEvent {
 }
 
 #[allow(clippy::large_enum_variant)]
-pub enum DapStoreMode {
-    Local(LocalDapStore),   // ssh host and collab host
-    Remote(RemoteDapStore), // collab guest
+enum DapStoreMode {
+    Local(LocalDapStore),
+    Ssh(SshDapStore),
+    Collab,
 }
 
 pub struct LocalDapStore {
     fs: Arc<dyn Fs>,
     node_runtime: NodeRuntime,
-    next_session_id: AtomicU32,
     http_client: Arc<dyn HttpClient>,
     environment: Entity<ProjectEnvironment>,
     language_registry: Arc<LanguageRegistry>,
+    worktree_store: Entity<WorktreeStore>,
     toolchain_store: Arc<dyn LanguageToolchainStore>,
-    locator_store: Arc<LocatorStore>,
-    start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
-    _start_debugging_task: Task<()>,
-}
-
-impl LocalDapStore {
-    fn next_session_id(&self) -> SessionId {
-        SessionId(self.next_session_id.fetch_add(1, SeqCst))
-    }
-    pub(crate) fn locate_binary(
-        &self,
-        mut definition: DebugTaskDefinition,
-        executor: BackgroundExecutor,
-    ) -> Task<DebugTaskDefinition> {
-        let locator_store = self.locator_store.clone();
-        executor.spawn(async move {
-            let _ = locator_store.resolve_debug_config(&mut definition).await;
-            definition
-        })
-    }
+    locators: HashMap<String, Arc<dyn DapLocator>>,
 }
 
-pub struct RemoteDapStore {
+pub struct SshDapStore {
     upstream_client: AnyProtoClient,
     upstream_project_id: u64,
-    event_queue: Option<VecDeque<DapStoreEvent>>,
 }
 
 pub struct DapStore {
@@ -117,25 +98,17 @@ pub struct DapStore {
     downstream_client: Option<(AnyProtoClient, u64)>,
     breakpoint_store: Entity<BreakpointStore>,
     sessions: BTreeMap<SessionId, Entity<Session>>,
+    next_session_id: u32,
+    start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
+    _start_debugging_task: Task<()>,
 }
 
 impl EventEmitter<DapStoreEvent> for DapStore {}
 
 impl DapStore {
-    pub fn init(_client: &AnyProtoClient) {
-        // todo(debugger): Reenable these after we finish handle_dap_command refactor
-        // client.add_entity_request_handler(Self::handle_dap_command::<NextCommand>);
-        // client.add_entity_request_handler(Self::handle_dap_command::<StepInCommand>);
-        // client.add_entity_request_handler(Self::handle_dap_command::<StepOutCommand>);
-        // client.add_entity_request_handler(Self::handle_dap_command::<StepBackCommand>);
-        // client.add_entity_request_handler(Self::handle_dap_command::<ContinueCommand>);
-        // client.add_entity_request_handler(Self::handle_dap_command::<PauseCommand>);
-        // client.add_entity_request_handler(Self::handle_dap_command::<DisconnectCommand>);
-        // client.add_entity_request_handler(Self::handle_dap_command::<TerminateThreadsCommand>);
-        // client.add_entity_request_handler(Self::handle_dap_command::<TerminateCommand>);
-        // client.add_entity_request_handler(Self::handle_dap_command::<RestartCommand>);
-        // client.add_entity_request_handler(Self::handle_dap_command::<VariablesCommand>);
-        // client.add_entity_request_handler(Self::handle_dap_command::<RestartStackFrameCommand>);
+    pub fn init(client: &AnyProtoClient) {
+        client.add_entity_request_handler(Self::handle_run_debug_locator);
+        client.add_entity_request_handler(Self::handle_get_debug_adapter_binary);
     }
 
     #[expect(clippy::too_many_arguments)]
@@ -146,15 +119,62 @@ impl DapStore {
         language_registry: Arc<LanguageRegistry>,
         environment: Entity<ProjectEnvironment>,
         toolchain_store: Arc<dyn LanguageToolchainStore>,
+        worktree_store: Entity<WorktreeStore>,
         breakpoint_store: Entity<BreakpointStore>,
         cx: &mut Context<Self>,
     ) -> Self {
         cx.on_app_quit(Self::shutdown_sessions).detach();
 
+        let locators = HashMap::from_iter([(
+            "cargo".to_string(),
+            Arc::new(super::locators::cargo::CargoLocator {}) as _,
+        )]);
+
+        let mode = DapStoreMode::Local(LocalDapStore {
+            fs,
+            environment,
+            http_client,
+            node_runtime,
+            toolchain_store,
+            worktree_store,
+            language_registry,
+            locators,
+        });
+
+        Self::new(mode, breakpoint_store, cx)
+    }
+
+    pub fn new_ssh(
+        project_id: u64,
+        upstream_client: AnyProtoClient,
+        breakpoint_store: Entity<BreakpointStore>,
+        cx: &mut Context<Self>,
+    ) -> Self {
+        let mode = DapStoreMode::Ssh(SshDapStore {
+            upstream_client,
+            upstream_project_id: project_id,
+        });
+
+        Self::new(mode, breakpoint_store, cx)
+    }
+
+    pub fn new_collab(
+        _project_id: u64,
+        _upstream_client: AnyProtoClient,
+        breakpoint_store: Entity<BreakpointStore>,
+        cx: &mut Context<Self>,
+    ) -> Self {
+        Self::new(DapStoreMode::Collab, breakpoint_store, cx)
+    }
+
+    fn new(
+        mode: DapStoreMode,
+        breakpoint_store: Entity<BreakpointStore>,
+        cx: &mut Context<Self>,
+    ) -> Self {
         let (start_debugging_tx, mut message_rx) =
             futures::channel::mpsc::unbounded::<(SessionId, Message)>();
-
-        let _start_debugging_task = cx.spawn(async move |this, cx| {
+        let task = cx.spawn(async move |this, cx| {
             while let Some((session_id, message)) = message_rx.next().await {
                 match message {
                     Message::Request(request) => {
@@ -174,94 +194,135 @@ impl DapStore {
                 }
             }
         });
-        Self {
-            mode: DapStoreMode::Local(LocalDapStore {
-                fs,
-                environment,
-                http_client,
-                node_runtime,
-                toolchain_store,
-                language_registry,
-                start_debugging_tx,
-                _start_debugging_task,
-                locator_store: Arc::from(LocatorStore::new()),
-                next_session_id: Default::default(),
-            }),
-            downstream_client: None,
-            breakpoint_store,
-            sessions: Default::default(),
-        }
-    }
 
-    pub fn new_remote(
-        project_id: u64,
-        upstream_client: AnyProtoClient,
-        breakpoint_store: Entity<BreakpointStore>,
-    ) -> Self {
         Self {
-            mode: DapStoreMode::Remote(RemoteDapStore {
-                upstream_client,
-                upstream_project_id: project_id,
-                event_queue: Some(VecDeque::default()),
-            }),
+            mode,
+            _start_debugging_task: task,
+            start_debugging_tx,
+            next_session_id: 0,
             downstream_client: None,
             breakpoint_store,
             sessions: Default::default(),
         }
     }
 
-    pub fn as_remote(&self) -> Option<&RemoteDapStore> {
+    pub fn get_debug_adapter_binary(
+        &mut self,
+        definition: DebugTaskDefinition,
+        cx: &mut Context<Self>,
+    ) -> Task<Result<DebugAdapterBinary>> {
         match &self.mode {
-            DapStoreMode::Remote(remote_dap_store) => Some(remote_dap_store),
-            _ => None,
-        }
-    }
+            DapStoreMode::Local(local) => {
+                let Some(worktree) = local.worktree_store.read(cx).visible_worktrees(cx).next()
+                else {
+                    return Task::ready(Err(anyhow!("Failed to find a worktree")));
+                };
+                let Some(adapter) = DapRegistry::global(cx).adapter(&definition.adapter) else {
+                    return Task::ready(Err(anyhow!("Failed to find a debug adapter")));
+                };
 
-    pub fn remote_event_queue(&mut self) -> Option<VecDeque<DapStoreEvent>> {
-        if let DapStoreMode::Remote(remote) = &mut self.mode {
-            remote.event_queue.take()
-        } else {
-            None
+                let user_installed_path = ProjectSettings::get_global(cx)
+                    .dap
+                    .get(&adapter.name())
+                    .and_then(|s| s.binary.as_ref().map(PathBuf::from));
+
+                let delegate = self.delegate(&worktree, cx);
+                let cwd: Arc<Path> = definition
+                    .cwd()
+                    .unwrap_or(worktree.read(cx).abs_path().as_ref())
+                    .into();
+
+                cx.spawn(async move |this, cx| {
+                    let mut binary = adapter
+                        .get_binary(&delegate, &definition, user_installed_path, cx)
+                        .await?;
+
+                    let env = this
+                        .update(cx, |this, cx| {
+                            this.as_local()
+                                .unwrap()
+                                .environment
+                                .update(cx, |environment, cx| {
+                                    environment.get_directory_environment(cwd, cx)
+                                })
+                        })?
+                        .await;
+
+                    if let Some(mut env) = env {
+                        env.extend(std::mem::take(&mut binary.envs));
+                        binary.envs = env;
+                    }
+
+                    Ok(binary)
+                })
+            }
+            DapStoreMode::Ssh(ssh) => {
+                let request = ssh.upstream_client.request(proto::GetDebugAdapterBinary {
+                    project_id: ssh.upstream_project_id,
+                    task: Some(definition.to_proto()),
+                });
+
+                cx.background_spawn(async move {
+                    let response = request.await?;
+                    DebugAdapterBinary::from_proto(response)
+                })
+            }
+            DapStoreMode::Collab => {
+                Task::ready(Err(anyhow!("Debugging is not yet supported via collab")))
+            }
         }
     }
 
-    pub fn as_local(&self) -> Option<&LocalDapStore> {
+    pub fn run_debug_locator(
+        &mut self,
+        template: DebugTaskTemplate,
+        cx: &mut Context<Self>,
+    ) -> Task<Result<DebugTaskDefinition>> {
+        let Some(locator_name) = template.locator else {
+            return Task::ready(Ok(template.definition));
+        };
+
         match &self.mode {
-            DapStoreMode::Local(local_dap_store) => Some(local_dap_store),
-            _ => None,
+            DapStoreMode::Local(local) => {
+                if let Some(locator) = local.locators.get(&locator_name).cloned() {
+                    cx.background_spawn(
+                        async move { locator.run_locator(template.definition).await },
+                    )
+                } else {
+                    Task::ready(Err(anyhow!("Couldn't find locator {}", locator_name)))
+                }
+            }
+            DapStoreMode::Ssh(ssh) => {
+                let request = ssh.upstream_client.request(proto::RunDebugLocator {
+                    project_id: ssh.upstream_project_id,
+                    locator: locator_name,
+                    task: Some(template.definition.to_proto()),
+                });
+                cx.background_spawn(async move {
+                    let response = request.await?;
+                    DebugTaskDefinition::from_proto(response)
+                })
+            }
+            DapStoreMode::Collab => {
+                Task::ready(Err(anyhow!("Debugging is not yet supported via collab")))
+            }
         }
     }
 
-    pub fn as_local_mut(&mut self) -> Option<&mut LocalDapStore> {
-        match &mut self.mode {
+    fn as_local(&self) -> Option<&LocalDapStore> {
+        match &self.mode {
             DapStoreMode::Local(local_dap_store) => Some(local_dap_store),
             _ => None,
         }
     }
 
-    pub fn upstream_client(&self) -> Option<(AnyProtoClient, u64)> {
-        match &self.mode {
-            DapStoreMode::Remote(RemoteDapStore {
-                upstream_client,
-                upstream_project_id,
-                ..
-            }) => Some((upstream_client.clone(), *upstream_project_id)),
-
-            DapStoreMode::Local(_) => None,
-        }
-    }
-
-    pub fn downstream_client(&self) -> Option<&(AnyProtoClient, u64)> {
-        self.downstream_client.as_ref()
-    }
-
     pub fn add_remote_client(
         &mut self,
         session_id: SessionId,
         ignore: Option<bool>,
         cx: &mut Context<Self>,
     ) {
-        if let DapStoreMode::Remote(remote) = &self.mode {
+        if let DapStoreMode::Ssh(remote) = &self.mode {
             self.sessions.insert(
                 session_id,
                 cx.new(|_| {
@@ -328,7 +389,7 @@ impl DapStore {
         Ok(())
     }
 
-    pub fn delegate(&self, worktree: &Entity<Worktree>, cx: &mut App) -> DapAdapterDelegate {
+    fn delegate(&self, worktree: &Entity<Worktree>, cx: &mut App) -> DapAdapterDelegate {
         let Some(local_store) = self.as_local() else {
             unimplemented!("Starting session on remote side");
         };
@@ -354,11 +415,7 @@ impl DapStore {
         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();
+        let session_id = SessionId(util::post_inc(&mut self.next_session_id));
 
         if let Some(session) = &parent_session {
             session.update(cx, |session, _| {
@@ -368,7 +425,7 @@ impl DapStore {
 
         let (initialized_tx, initialized_rx) = oneshot::channel();
 
-        let start_debugging_tx = local_store.start_debugging_tx.clone();
+        let start_debugging_tx = self.start_debugging_tx.clone();
 
         let task = cx.spawn(async move |this, cx| {
             let start_client_task = this.update(cx, |this, cx| {
@@ -682,10 +739,6 @@ impl DapStore {
         session_id: SessionId,
         cx: &mut Context<Self>,
     ) -> Task<Result<()>> {
-        let Some(_) = self.as_local_mut() else {
-            return Task::ready(Err(anyhow!("Cannot shutdown session on remote side")));
-        };
-
         let Some(session) = self.sessions.remove(&session_id) else {
             return Task::ready(Err(anyhow!("Could not find session: {:?}", session_id)));
         };
@@ -748,6 +801,45 @@ impl DapStore {
 
         cx.notify();
     }
+
+    async fn handle_run_debug_locator(
+        this: Entity<Self>,
+        envelope: TypedEnvelope<proto::RunDebugLocator>,
+        mut cx: AsyncApp,
+    ) -> Result<proto::DebugTaskDefinition> {
+        let template = DebugTaskTemplate {
+            locator: Some(envelope.payload.locator),
+            definition: DebugTaskDefinition::from_proto(
+                envelope
+                    .payload
+                    .task
+                    .ok_or_else(|| anyhow!("missing definition"))?,
+            )?,
+        };
+        let definition = this
+            .update(&mut cx, |this, cx| this.run_debug_locator(template, cx))?
+            .await?;
+        Ok(definition.to_proto())
+    }
+
+    async fn handle_get_debug_adapter_binary(
+        this: Entity<Self>,
+        envelope: TypedEnvelope<proto::GetDebugAdapterBinary>,
+        mut cx: AsyncApp,
+    ) -> Result<proto::DebugAdapterBinary> {
+        let definition = DebugTaskDefinition::from_proto(
+            envelope
+                .payload
+                .task
+                .ok_or_else(|| anyhow!("missing definition"))?,
+        )?;
+        let binary = this
+            .update(&mut cx, |this, cx| {
+                this.get_debug_adapter_binary(definition, cx)
+            })?
+            .await?;
+        Ok(binary.to_proto())
+    }
 }
 
 fn create_new_session(

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

@@ -3,10 +3,10 @@ use cargo::CargoLocator;
 use collections::HashMap;
 use gpui::SharedString;
 use locators::DapLocator;
-use task::DebugTaskDefinition;
+use task::{DebugTaskDefinition, DebugTaskTemplate};
 
 mod cargo;
-mod locators;
+pub mod locators;
 
 pub(super) struct LocatorStore {
     locators: HashMap<SharedString, Box<dyn DapLocator>>,
@@ -14,24 +14,19 @@ pub(super) struct LocatorStore {
 
 impl LocatorStore {
     pub(super) fn new() -> Self {
-        let locators = HashMap::from_iter([(
-            SharedString::new("cargo"),
-            Box::new(CargoLocator {}) as Box<dyn DapLocator>,
-        )]);
         Self { locators }
     }
 
     pub(super) async fn resolve_debug_config(
         &self,
-        debug_config: &mut DebugTaskDefinition,
-    ) -> Result<()> {
-        let Some(locator_name) = &debug_config.locator else {
-            log::debug!("Attempted to resolve debug config without a locator field");
-            return Ok(());
+        template: DebugTaskTemplate,
+    ) -> Result<DebugTaskDefinition> {
+        let Some(locator_name) = &template.locator else {
+            return Ok(template.definition);
         };
 
         if let Some(locator) = self.locators.get(locator_name as &str) {
-            locator.run_locator(debug_config).await
+            locator.run_locator(template.definition).await
         } else {
             Err(anyhow!("Couldn't find locator {}", locator_name))
         }

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

@@ -2,7 +2,9 @@ use anyhow::Result;
 use async_trait::async_trait;
 use task::DebugTaskDefinition;
 
+pub(crate) mod cargo;
+
 #[async_trait]
 pub(super) trait DapLocator: Send + Sync {
-    async fn run_locator(&self, debug_config: &mut DebugTaskDefinition) -> Result<()>;
+    async fn run_locator(&self, debug_config: DebugTaskDefinition) -> Result<DebugTaskDefinition>;
 }

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

@@ -8,7 +8,7 @@ use smol::{
 };
 use task::DebugTaskDefinition;
 
-pub(super) struct CargoLocator;
+pub(crate) struct CargoLocator;
 
 async fn find_best_executable(executables: &[String], test_name: &str) -> Option<String> {
     if executables.len() == 1 {
@@ -37,9 +37,12 @@ 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 DebugTaskDefinition) -> Result<()> {
+    async fn run_locator(
+        &self,
+        mut debug_config: DebugTaskDefinition,
+    ) -> Result<DebugTaskDefinition> {
         let Some(launch_config) = (match &mut debug_config.request {
-            task::DebugRequestType::Launch(launch_config) => Some(launch_config),
+            task::DebugRequest::Launch(launch_config) => Some(launch_config),
             _ => None,
         }) else {
             return Err(anyhow!("Couldn't get launch config in locator"));
@@ -119,6 +122,6 @@ impl DapLocator for CargoLocator {
         if let Some(test_name) = test_name {
             launch_config.args.push(test_name);
         }
-        Ok(())
+        Ok(debug_config)
     }
 }

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

@@ -396,7 +396,7 @@ impl LocalMode {
     }
 
     fn request_initialization(&self, cx: &App) -> Task<Result<Capabilities>> {
-        let adapter_id = self.binary.adapter_name.to_string();
+        let adapter_id = self.definition.adapter.clone();
 
         self.request(Initialize { adapter_id }, cx.background_executor().clone())
     }

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

@@ -1,7 +1,7 @@
 use std::{path::Path, sync::Arc};
 
 use anyhow::Result;
-use dap::{DebugRequestType, client::DebugAdapterClient};
+use dap::{DebugRequest, client::DebugAdapterClient};
 use gpui::{App, AppContext, Entity, Subscription, Task};
 use task::DebugTaskDefinition;
 
@@ -53,11 +53,10 @@ pub fn start_debug_session<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
         cx,
         DebugTaskDefinition {
             adapter: "fake-adapter".to_string(),
-            request: DebugRequestType::Launch(Default::default()),
+            request: DebugRequest::Launch(Default::default()),
             label: "test".to_string(),
             initialize_args: None,
             tcp_connection: None,
-            locator: None,
             stop_on_entry: None,
         },
         configure,

crates/project/src/project.rs 🔗

@@ -39,7 +39,10 @@ use client::{
 };
 use clock::ReplicaId;
 
-use dap::{DapRegistry, client::DebugAdapterClient};
+use dap::{
+    adapters::{DebugAdapterBinary, TcpArguments},
+    client::DebugAdapterClient,
+};
 
 use collections::{BTreeSet, HashMap, HashSet};
 use debounced_delay::DebouncedDelay;
@@ -94,6 +97,7 @@ use snippet::Snippet;
 use snippet_provider::SnippetProvider;
 use std::{
     borrow::Cow,
+    net::Ipv4Addr,
     ops::Range,
     path::{Component, Path, PathBuf},
     pin::pin,
@@ -103,7 +107,7 @@ use std::{
 };
 
 use task_store::TaskStore;
-use terminals::Terminals;
+use terminals::{SshCommand, Terminals, wrap_for_ssh};
 use text::{Anchor, BufferId};
 use toolchain_store::EmptyToolchainStore;
 use util::{
@@ -165,7 +169,6 @@ pub struct Project {
     active_entry: Option<ProjectEntryId>,
     buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,
     languages: Arc<LanguageRegistry>,
-    debug_adapters: Arc<DapRegistry>,
     dap_store: Entity<DapStore>,
 
     breakpoint_store: Entity<BreakpointStore>,
@@ -834,7 +837,6 @@ impl Project {
         node: NodeRuntime,
         user_store: Entity<UserStore>,
         languages: Arc<LanguageRegistry>,
-        debug_adapters: Arc<DapRegistry>,
         fs: Arc<dyn Fs>,
         env: Option<HashMap<String, String>>,
         cx: &mut App,
@@ -873,6 +875,7 @@ impl Project {
                     languages.clone(),
                     environment.clone(),
                     toolchain_store.read(cx).as_language_toolchain_store(),
+                    worktree_store.clone(),
                     breakpoint_store.clone(),
                     cx,
                 )
@@ -955,7 +958,6 @@ impl Project {
                 active_entry: None,
                 snippets,
                 languages,
-                debug_adapters,
                 client,
                 task_store,
                 user_store,
@@ -1065,13 +1067,14 @@ impl Project {
             cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
 
             let breakpoint_store =
-                cx.new(|_| BreakpointStore::remote(SSH_PROJECT_ID, client.clone().into()));
+                cx.new(|_| BreakpointStore::remote(SSH_PROJECT_ID, ssh_proto.clone()));
 
-            let dap_store = cx.new(|_| {
-                DapStore::new_remote(
+            let dap_store = cx.new(|cx| {
+                DapStore::new_ssh(
                     SSH_PROJECT_ID,
-                    client.clone().into(),
+                    ssh_proto.clone(),
                     breakpoint_store.clone(),
+                    cx,
                 )
             });
 
@@ -1113,7 +1116,6 @@ impl Project {
                 active_entry: None,
                 snippets,
                 languages,
-                debug_adapters: Arc::new(DapRegistry::default()),
                 client,
                 task_store,
                 user_store,
@@ -1251,8 +1253,13 @@ impl Project {
 
         let breakpoint_store =
             cx.new(|_| BreakpointStore::remote(remote_id, client.clone().into()))?;
-        let dap_store = cx.new(|_cx| {
-            DapStore::new_remote(remote_id, client.clone().into(), breakpoint_store.clone())
+        let dap_store = cx.new(|cx| {
+            DapStore::new_collab(
+                remote_id,
+                client.clone().into(),
+                breakpoint_store.clone(),
+                cx,
+            )
         })?;
 
         let lsp_store = cx.new(|cx| {
@@ -1337,7 +1344,6 @@ impl Project {
                 collaborators: Default::default(),
                 join_project_response_message_id: response.message_id,
                 languages,
-                debug_adapters: Arc::new(DapRegistry::default()),
                 user_store: user_store.clone(),
                 task_store,
                 snippets,
@@ -1459,49 +1465,68 @@ impl Project {
 
     pub fn start_debug_session(
         &mut self,
-        config: DebugTaskDefinition,
+        definition: DebugTaskDefinition,
         cx: &mut Context<Self>,
     ) -> Task<Result<Entity<Session>>> {
         let Some(worktree) = self.worktrees(cx).find(|tree| tree.read(cx).is_visible()) else {
             return Task::ready(Err(anyhow!("Failed to find a worktree")));
         };
 
-        let Some(adapter) = self.debug_adapters.adapter(&config.adapter) else {
-            return Task::ready(Err(anyhow!("Failed to find a debug adapter")));
-        };
-
-        let user_installed_path = ProjectSettings::get_global(cx)
-            .dap
-            .get(&adapter.name())
-            .and_then(|s| s.binary.as_ref().map(PathBuf::from));
+        let ssh_client = self.ssh_client().clone();
 
         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 mut binary = this
+                .update(cx, |this, cx| {
+                    this.dap_store.update(cx, |dap_store, cx| {
+                        dap_store.get_debug_adapter_binary(definition.clone(), cx)
+                    })
+                })?
+                .await?;
 
-            let task = this.update(cx, |project, cx| {
-                project.dap_store.read(cx).as_local().and_then(|local| {
-                    config.locator.is_some().then(|| {
-                        local.locate_binary(config.clone(), cx.background_executor().clone())
+            if let Some(ssh_client) = ssh_client {
+                let mut ssh_command = ssh_client.update(cx, |ssh, _| {
+                    anyhow::Ok(SshCommand {
+                        arguments: ssh
+                            .ssh_args()
+                            .ok_or_else(|| anyhow!("SSH arguments not found"))?,
                     })
-                })
-            })?;
-            let config = if let Some(task) = task {
-                task.await
-            } else {
-                config
+                })??;
+
+                let mut connection = None;
+                if let Some(c) = binary.connection {
+                    let local_bind_addr = Ipv4Addr::new(127, 0, 0, 1);
+                    let port = dap::transport::TcpTransport::unused_port(local_bind_addr).await?;
+
+                    ssh_command.add_port_forwarding(port, c.host.to_string(), c.port);
+                    connection = Some(TcpArguments {
+                        port: c.port,
+                        host: local_bind_addr,
+                        timeout: c.timeout,
+                    })
+                }
+
+                let (program, args) = wrap_for_ssh(
+                    &ssh_command,
+                    Some((&binary.command, &binary.arguments)),
+                    binary.cwd.as_deref(),
+                    binary.envs,
+                    None,
+                );
+
+                binary = DebugAdapterBinary {
+                    command: program,
+                    arguments: args,
+                    envs: HashMap::default(),
+                    cwd: None,
+                    connection,
+                    request_args: binary.request_args,
+                }
             };
-            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, worktree.downgrade(), None, cx)
+                        dap_store.new_session(binary, definition, worktree.downgrade(), None, cx)
                     })
                 })?
                 .1
@@ -1520,7 +1545,6 @@ impl Project {
 
         let fs = Arc::new(RealFs::new(None, cx.background_executor().clone()));
         let languages = LanguageRegistry::test(cx.background_executor().clone());
-        let debug_adapters = DapRegistry::default().into();
         let clock = Arc::new(FakeSystemClock::new());
         let http_client = http_client::FakeHttpClient::with_404_response();
         let client = cx
@@ -1534,7 +1558,6 @@ impl Project {
                     node_runtime::NodeRuntime::unavailable(),
                     user_store,
                     Arc::new(languages),
-                    debug_adapters,
                     fs,
                     None,
                     cx,
@@ -1565,7 +1588,6 @@ impl Project {
         use clock::FakeSystemClock;
 
         let languages = LanguageRegistry::test(cx.executor());
-        let debug_adapters = DapRegistry::fake();
         let clock = Arc::new(FakeSystemClock::new());
         let http_client = http_client::FakeHttpClient::with_404_response();
         let client = cx.update(|cx| client::Client::new(clock, http_client.clone(), cx));
@@ -1576,7 +1598,6 @@ impl Project {
                 node_runtime::NodeRuntime::unavailable(),
                 user_store,
                 Arc::new(languages),
-                Arc::new(debug_adapters),
                 fs,
                 None,
                 cx,
@@ -1620,10 +1641,6 @@ impl Project {
         &self.languages
     }
 
-    pub fn debug_adapters(&self) -> &Arc<DapRegistry> {
-        &self.debug_adapters
-    }
-
     pub fn client(&self) -> Arc<Client> {
         self.client.clone()
     }

crates/project/src/task_inventory.rs 🔗

@@ -19,7 +19,7 @@ use language::{
 use lsp::{LanguageServerId, LanguageServerName};
 use settings::{InvalidSettingsError, TaskKind, parse_json_with_comments};
 use task::{
-    DebugTaskDefinition, ResolvedTask, TaskContext, TaskId, TaskTemplate, TaskTemplates,
+    DebugTaskTemplate, ResolvedTask, TaskContext, TaskId, TaskTemplate, TaskTemplates,
     TaskVariables, VariableName,
 };
 use text::{BufferId, Point, ToPoint};
@@ -435,9 +435,9 @@ impl Inventory {
             .into_iter()
             .filter_map(|raw_template| match &task_kind {
                 TaskKind::Script => serde_json::from_value::<TaskTemplate>(raw_template).log_err(),
-                TaskKind::Debug => serde_json::from_value::<DebugTaskDefinition>(raw_template)
+                TaskKind::Debug => serde_json::from_value::<DebugTaskTemplate>(raw_template)
                     .log_err()
-                    .and_then(|content| content.to_zed_format().log_err()),
+                    .map(|content| content.to_zed_format()),
             });
 
         let parsed_templates = &mut self.templates_from_settings;

crates/project/src/terminals.rs 🔗

@@ -9,20 +9,16 @@ use smol::channel::bounded;
 use std::{
     borrow::Cow,
     env::{self},
-    iter,
     path::{Path, PathBuf},
     sync::Arc,
 };
-use task::{Shell, ShellBuilder, SpawnInTerminal};
+use task::{DEFAULT_REMOTE_SHELL, Shell, ShellBuilder, SpawnInTerminal};
 use terminal::{
     TaskState, TaskStatus, Terminal, TerminalBuilder,
     terminal_settings::{self, TerminalSettings, VenvSettings},
 };
 use util::ResultExt;
 
-// #[cfg(target_os = "macos")]
-// use std::os::unix::ffi::OsStrExt;
-
 pub struct Terminals {
     pub(crate) local_handles: Vec<WeakEntity<terminal::Terminal>>,
 }
@@ -48,7 +44,15 @@ pub enum TerminalKind {
 /// SshCommand describes how to connect to a remote server
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct SshCommand {
-    arguments: Vec<String>,
+    pub arguments: Vec<String>,
+}
+
+impl SshCommand {
+    pub fn add_port_forwarding(&mut self, local_port: u16, host: String, remote_port: u16) {
+        self.arguments.push("-L".to_string());
+        self.arguments
+            .push(format!("{}:{}:{}", local_port, host, remote_port));
+    }
 }
 
 impl Project {
@@ -551,7 +555,7 @@ impl Project {
     }
 }
 
-fn wrap_for_ssh(
+pub fn wrap_for_ssh(
     ssh_command: &SshCommand,
     command: Option<(&String, &Vec<String>)>,
     path: Option<&Path>,
@@ -559,9 +563,14 @@ fn wrap_for_ssh(
     venv_directory: Option<&Path>,
 ) -> (String, Vec<String>) {
     let to_run = if let Some((command, args)) = command {
-        let command = Cow::Borrowed(command.as_str());
+        // DEFAULT_REMOTE_SHELL is '"${SHELL:-sh}"' so must not be escaped
+        let command: Option<Cow<str>> = if command == DEFAULT_REMOTE_SHELL {
+            Some(command.into())
+        } else {
+            shlex::try_quote(command).ok()
+        };
         let args = args.iter().filter_map(|arg| shlex::try_quote(arg).ok());
-        iter::once(command).chain(args).join(" ")
+        command.into_iter().chain(args).join(" ")
     } else {
         "exec ${SHELL:-sh} -l".to_string()
     };

crates/proto/proto/debugger.proto 🔗

@@ -508,7 +508,6 @@ enum DapStackPresentationHint {
     Subtle = 2;
     StackUnknown = 3;
 }
-
 message DapModule {
     DapModuleId id = 1;
     string name = 2;
@@ -522,9 +521,62 @@ message DapModule {
     optional string address_range = 10;
 }
 
+message DebugTaskDefinition {
+    string adapter = 1;
+    string label = 2;
+    oneof request {
+        DebugLaunchRequest debug_launch_request = 3;
+        DebugAttachRequest debug_attach_request = 4;
+    }
+    optional string initialize_args = 5;
+    optional TcpHost tcp_connection = 6;
+    optional bool stop_on_entry = 7;
+}
+
+message TcpHost {
+    optional uint32 port = 1;
+    optional string host = 2;
+    optional uint64 timeout = 3;
+}
+
+message DebugLaunchRequest {
+    string program = 1;
+    optional string cwd = 2;
+    repeated string args = 3;
+}
+
+message DebugAttachRequest {
+    uint32 process_id = 1;
+}
+
 message DapModuleId {
     oneof id {
         uint32 number = 1;
         string string = 2;
     }
 }
+
+message GetDebugAdapterBinary {
+    uint64 project_id = 1;
+    DebugTaskDefinition task = 2;
+}
+
+message DebugAdapterBinary {
+    string command = 1;
+    repeated string arguments = 2;
+    map<string, string> envs = 3;
+    optional string cwd = 4;
+    optional TcpHost connection = 5;
+    string configuration = 7;
+    LaunchType launch_type = 8;
+    enum LaunchType {
+        Attach = 0;
+        Launch = 1;
+    }
+}
+
+message RunDebugLocator {
+    uint64 project_id = 1;
+    string locator = 2;
+    DebugTaskDefinition task = 3;
+}

crates/proto/proto/zed.proto 🔗

@@ -380,7 +380,12 @@ message Envelope {
         StopLanguageServers stop_language_servers = 336;
 
         LspExtRunnables lsp_ext_runnables = 337;
-        LspExtRunnablesResponse lsp_ext_runnables_response = 338; // current max
+        LspExtRunnablesResponse lsp_ext_runnables_response = 338;
+
+        GetDebugAdapterBinary get_debug_adapter_binary = 339;
+        DebugAdapterBinary debug_adapter_binary = 340;
+        RunDebugLocator run_debug_locator = 341;
+        DebugTaskDefinition debug_task_definition = 342; // current max
     }
 
     reserved 87 to 88;

crates/proto/src/proto.rs 🔗

@@ -302,6 +302,10 @@ messages!(
     (GitDiff, Background),
     (GitDiffResponse, Background),
     (GitInit, Background),
+    (GetDebugAdapterBinary, Background),
+    (DebugAdapterBinary, Background),
+    (RunDebugLocator, Background),
+    (DebugTaskDefinition, Background),
 );
 
 request_messages!(
@@ -460,6 +464,8 @@ request_messages!(
     (GitDiff, GitDiffResponse),
     (GitInit, Ack),
     (ToggleBreakpoint, Ack),
+    (GetDebugAdapterBinary, DebugAdapterBinary),
+    (RunDebugLocator, DebugTaskDefinition),
 );
 
 entity_messages!(
@@ -579,6 +585,8 @@ entity_messages!(
     GitInit,
     BreakpointsForFile,
     ToggleBreakpoint,
+    RunDebugLocator,
+    GetDebugAdapterBinary,
 );
 
 entity_messages!(

crates/recent_projects/src/ssh_connections.rs 🔗

@@ -570,7 +570,6 @@ pub async fn open_ssh_project(
                 app_state.node_runtime.clone(),
                 app_state.user_store.clone(),
                 app_state.languages.clone(),
-                app_state.debug_adapters.clone(),
                 app_state.fs.clone(),
                 None,
                 cx,

crates/remote_server/Cargo.toml 🔗

@@ -29,7 +29,7 @@ backtrace = "0.3"
 chrono.workspace = true
 clap.workspace = true
 client.workspace = true
-dap.workspace = true
+dap_adapters.workspace = true
 env_logger.workspace = true
 extension.workspace = true
 extension_host.workspace = true

crates/remote_server/src/headless_project.rs 🔗

@@ -1,6 +1,6 @@
 use ::proto::{FromProto, ToProto};
 use anyhow::{Result, anyhow};
-use dap::DapRegistry;
+
 use extension::ExtensionHostProxy;
 use extension_host::headless_host::HeadlessExtensionStore;
 use fs::Fs;
@@ -41,6 +41,7 @@ pub struct HeadlessProject {
     pub buffer_store: Entity<BufferStore>,
     pub lsp_store: Entity<LspStore>,
     pub task_store: Entity<TaskStore>,
+    pub dap_store: Entity<DapStore>,
     pub settings_observer: Entity<SettingsObserver>,
     pub next_entry_id: Arc<AtomicUsize>,
     pub languages: Arc<LanguageRegistry>,
@@ -54,7 +55,6 @@ pub struct HeadlessAppState {
     pub http_client: Arc<dyn HttpClient>,
     pub node_runtime: NodeRuntime,
     pub languages: Arc<LanguageRegistry>,
-    pub debug_adapters: Arc<DapRegistry>,
     pub extension_host_proxy: Arc<ExtensionHostProxy>,
 }
 
@@ -72,7 +72,6 @@ impl HeadlessProject {
             http_client,
             node_runtime,
             languages,
-            debug_adapters: _debug_adapters,
             extension_host_proxy: proxy,
         }: HeadlessAppState,
         cx: &mut Context<Self>,
@@ -114,6 +113,7 @@ impl HeadlessProject {
                 languages.clone(),
                 environment.clone(),
                 toolchain_store.read(cx).as_language_toolchain_store(),
+                worktree_store.clone(),
                 breakpoint_store.clone(),
                 cx,
             )
@@ -258,6 +258,7 @@ impl HeadlessProject {
             buffer_store,
             lsp_store,
             task_store,
+            dap_store,
             next_entry_id: Default::default(),
             languages,
             extensions,

crates/remote_server/src/remote_editing_tests.rs 🔗

@@ -4,7 +4,7 @@
 use crate::headless_project::HeadlessProject;
 use client::{Client, UserStore};
 use clock::FakeSystemClock;
-use dap::DapRegistry;
+
 use extension::ExtensionHostProxy;
 use fs::{FakeFs, Fs};
 use gpui::{AppContext as _, Entity, SemanticVersion, TestAppContext};
@@ -1566,7 +1566,6 @@ pub async fn init_test(
     let http_client = Arc::new(BlockedHttpClient);
     let node_runtime = NodeRuntime::unavailable();
     let languages = Arc::new(LanguageRegistry::new(cx.executor()));
-    let debug_adapters = DapRegistry::default().into();
     let proxy = Arc::new(ExtensionHostProxy::new());
     server_cx.update(HeadlessProject::init);
     let headless = server_cx.new(|cx| {
@@ -1579,7 +1578,6 @@ pub async fn init_test(
                 http_client,
                 node_runtime,
                 languages,
-                debug_adapters,
                 extension_host_proxy: proxy,
             },
             cx,

crates/remote_server/src/unix.rs 🔗

@@ -3,7 +3,7 @@ use crate::headless_project::HeadlessAppState;
 use anyhow::{Context as _, Result, anyhow};
 use chrono::Utc;
 use client::{ProxySettings, telemetry};
-use dap::DapRegistry;
+
 use extension::ExtensionHostProxy;
 use fs::{Fs, RealFs};
 use futures::channel::mpsc;
@@ -441,6 +441,7 @@ pub fn execute_run(
 
         GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
         git_hosting_providers::init(cx);
+        dap_adapters::init(cx);
 
         extension::init(cx);
         let extension_host_proxy = ExtensionHostProxy::global(cx);
@@ -472,7 +473,6 @@ pub fn execute_run(
             let mut languages = LanguageRegistry::new(cx.background_executor().clone());
             languages.set_language_server_download_dir(paths::languages_dir().clone());
             let languages = Arc::new(languages);
-            let debug_adapters = DapRegistry::default().into();
 
             HeadlessProject::new(
                 HeadlessAppState {
@@ -481,7 +481,6 @@ pub fn execute_run(
                     http_client,
                     node_runtime,
                     languages,
-                    debug_adapters,
                     extension_host_proxy,
                 },
                 cx,

crates/task/Cargo.toml 🔗

@@ -17,11 +17,11 @@ workspace = true
 [dependencies]
 anyhow.workspace = true
 collections.workspace = true
-dap-types.workspace = true
 futures.workspace = true
 gpui.workspace = true
 hex.workspace = true
 parking_lot.workspace = true
+proto.workspace = true
 schemars.workspace = true
 serde.workspace = true
 serde_json.workspace = true

crates/task/src/debug_format.rs 🔗

@@ -1,21 +1,14 @@
-use dap_types::StartDebuggingRequestArguments;
+use anyhow::Result;
 use schemars::{JsonSchema, r#gen::SchemaSettings};
 use serde::{Deserialize, Serialize};
-use std::net::Ipv4Addr;
 use std::path::PathBuf;
-use util::ResultExt;
+use std::{net::Ipv4Addr, path::Path};
 
-use crate::{TaskTemplate, TaskTemplates, TaskType, task_template::DebugArgs};
-
-impl Default for DebugConnectionType {
-    fn default() -> Self {
-        DebugConnectionType::TCP(TCPHost::default())
-    }
-}
+use crate::{TaskTemplate, TaskType, task_template::DebugArgs};
 
 /// Represents the host information of the debug adapter
 #[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
-pub struct TCPHost {
+pub struct TcpArgumentsTemplate {
     /// The port that the debug adapter is listening on
     ///
     /// Default: We will try to find an open port
@@ -30,23 +23,39 @@ pub struct TCPHost {
     pub timeout: Option<u64>,
 }
 
-impl TCPHost {
+impl TcpArgumentsTemplate {
     /// Get the host or fallback to the default host
     pub fn host(&self) -> Ipv4Addr {
         self.host.unwrap_or_else(|| Ipv4Addr::new(127, 0, 0, 1))
     }
+
+    pub fn from_proto(proto: proto::TcpHost) -> Result<Self> {
+        Ok(Self {
+            port: proto.port.map(|p| p.try_into()).transpose()?,
+            host: proto.host.map(|h| h.parse()).transpose()?,
+            timeout: proto.timeout,
+        })
+    }
+
+    pub fn to_proto(&self) -> proto::TcpHost {
+        proto::TcpHost {
+            port: self.port.map(|p| p.into()),
+            host: self.host.map(|h| h.to_string()),
+            timeout: self.timeout,
+        }
+    }
 }
 
 /// Represents the attach request information of the debug adapter
 #[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
-pub struct AttachConfig {
+pub struct AttachRequest {
     /// The processId to attach to, if left empty we will show a process picker
     pub process_id: Option<u32>,
 }
 
 /// Represents the launch request information of the debug adapter
 #[derive(Deserialize, Serialize, Default, PartialEq, Eq, JsonSchema, Clone, Debug)]
-pub struct LaunchConfig {
+pub struct LaunchRequest {
     /// The program that you trying to debug
     pub program: String,
     /// The current working directory of your project
@@ -59,47 +68,26 @@ pub struct LaunchConfig {
 /// Represents the type that will determine which request to call on the debug adapter
 #[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
 #[serde(rename_all = "lowercase", untagged)]
-pub enum DebugRequestType {
+pub enum DebugRequest {
     /// Call the `launch` request on the debug adapter
-    Launch(LaunchConfig),
+    Launch(LaunchRequest),
     /// Call the `attach` request on the debug adapter
-    Attach(AttachConfig),
+    Attach(AttachRequest),
 }
 
-impl From<LaunchConfig> for DebugRequestType {
-    fn from(launch_config: LaunchConfig) -> Self {
-        DebugRequestType::Launch(launch_config)
+impl From<LaunchRequest> for DebugRequest {
+    fn from(launch_config: LaunchRequest) -> Self {
+        DebugRequest::Launch(launch_config)
     }
 }
 
-impl From<AttachConfig> for DebugRequestType {
-    fn from(attach_config: AttachConfig) -> Self {
-        DebugRequestType::Attach(attach_config)
+impl From<AttachRequest> for DebugRequest {
+    fn from(attach_config: AttachRequest) -> Self {
+        DebugRequest::Attach(attach_config)
     }
 }
-/// Represents a request for starting the debugger.
-/// Contrary to `DebugRequestType`, `DebugRequestDisposition` is not Serializable.
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub enum DebugRequestDisposition {
-    /// Debug session configured by the user.
-    UserConfigured(DebugRequestType),
-    /// Debug session configured by the debug adapter
-    ReverseRequest(StartDebuggingRequestArguments),
-}
 
-impl DebugRequestDisposition {
-    /// Get the current working directory from request if it's a launch request and exits
-    pub fn cwd(&self) -> Option<PathBuf> {
-        match self {
-            Self::UserConfigured(DebugRequestType::Launch(launch_config)) => {
-                launch_config.cwd.clone()
-            }
-            _ => None,
-        }
-    }
-}
-
-impl TryFrom<TaskTemplate> for DebugTaskDefinition {
+impl TryFrom<TaskTemplate> for DebugTaskTemplate {
     type Error = ();
 
     fn try_from(value: TaskTemplate) -> Result<Self, Self::Error> {
@@ -108,40 +96,40 @@ impl TryFrom<TaskTemplate> for DebugTaskDefinition {
         };
 
         let request = match debug_args.request {
-            crate::DebugArgsRequest::Launch => DebugRequestType::Launch(LaunchConfig {
+            crate::DebugArgsRequest::Launch => DebugRequest::Launch(LaunchRequest {
                 program: value.command,
                 cwd: value.cwd.map(PathBuf::from),
                 args: value.args,
             }),
-            crate::DebugArgsRequest::Attach(attach_config) => {
-                DebugRequestType::Attach(attach_config)
-            }
+            crate::DebugArgsRequest::Attach(attach_config) => DebugRequest::Attach(attach_config),
         };
 
-        Ok(DebugTaskDefinition {
-            adapter: debug_args.adapter,
-            request,
-            label: value.label,
-            initialize_args: debug_args.initialize_args,
-            tcp_connection: debug_args.tcp_connection,
+        Ok(DebugTaskTemplate {
             locator: debug_args.locator,
-            stop_on_entry: debug_args.stop_on_entry,
+            definition: DebugTaskDefinition {
+                adapter: debug_args.adapter,
+                request,
+                label: value.label,
+                initialize_args: debug_args.initialize_args,
+                tcp_connection: debug_args.tcp_connection,
+                stop_on_entry: debug_args.stop_on_entry,
+            },
         })
     }
 }
 
-impl DebugTaskDefinition {
+impl DebugTaskTemplate {
     /// Translate from debug definition to a task template
-    pub fn to_zed_format(self) -> anyhow::Result<TaskTemplate> {
-        let (command, cwd, request) = match self.request {
-            DebugRequestType::Launch(launch_config) => (
+    pub fn to_zed_format(self) -> TaskTemplate {
+        let (command, cwd, request) = match self.definition.request {
+            DebugRequest::Launch(launch_config) => (
                 launch_config.program,
                 launch_config
                     .cwd
                     .map(|cwd| cwd.to_string_lossy().to_string()),
                 crate::task_template::DebugArgsRequest::Launch,
             ),
-            DebugRequestType::Attach(attach_config) => (
+            DebugRequest::Attach(attach_config) => (
                 "".to_owned(),
                 None,
                 crate::task_template::DebugArgsRequest::Attach(attach_config),
@@ -149,34 +137,33 @@ impl DebugTaskDefinition {
         };
 
         let task_type = TaskType::Debug(DebugArgs {
-            adapter: self.adapter,
+            adapter: self.definition.adapter,
             request,
-            initialize_args: self.initialize_args,
+            initialize_args: self.definition.initialize_args,
             locator: self.locator,
-            tcp_connection: self.tcp_connection,
-            stop_on_entry: self.stop_on_entry,
+            tcp_connection: self.definition.tcp_connection,
+            stop_on_entry: self.definition.stop_on_entry,
         });
 
-        let label = self.label.clone();
+        let label = self.definition.label.clone();
 
-        Ok(TaskTemplate {
+        TaskTemplate {
             label,
             command,
             args: vec![],
             task_type,
             cwd,
             ..Default::default()
-        })
+        }
     }
 }
-/// Represents the type of the debugger adapter connection
+
 #[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
-#[serde(rename_all = "lowercase", tag = "connection")]
-pub enum DebugConnectionType {
-    /// Connect to the debug adapter via TCP
-    TCP(TCPHost),
-    /// Connect to the debug adapter via STDIO
-    STDIO,
+#[serde(rename_all = "snake_case")]
+pub struct DebugTaskTemplate {
+    pub locator: Option<String>,
+    #[serde(flatten)]
+    pub definition: DebugTaskDefinition,
 }
 
 /// This struct represent a user created debug task
@@ -187,7 +174,7 @@ pub struct DebugTaskDefinition {
     pub adapter: String,
     /// The type of request that should be called on the debug adapter
     #[serde(flatten)]
-    pub request: DebugRequestType,
+    pub request: DebugRequest,
     /// Name of the debug task
     pub label: String,
     /// Additional initialization arguments to be sent on DAP initialization
@@ -197,18 +184,83 @@ pub struct DebugTaskDefinition {
     /// If provided, this will be used to connect to the debug adapter instead of
     /// spawning a new process. This is useful for connecting to a debug adapter
     /// that is already running or is started by another process.
-    pub tcp_connection: Option<TCPHost>,
-    /// Locator to use
-    /// -- cargo
-    pub locator: Option<String>,
+    pub tcp_connection: Option<TcpArgumentsTemplate>,
     /// Whether to tell the debug adapter to stop on entry
     pub stop_on_entry: Option<bool>,
 }
 
+impl DebugTaskDefinition {
+    pub fn cwd(&self) -> Option<&Path> {
+        if let DebugRequest::Launch(config) = &self.request {
+            config.cwd.as_deref()
+        } else {
+            None
+        }
+    }
+    pub fn to_proto(&self) -> proto::DebugTaskDefinition {
+        proto::DebugTaskDefinition {
+            adapter: self.adapter.clone(),
+            request: Some(match &self.request {
+                DebugRequest::Launch(config) => {
+                    proto::debug_task_definition::Request::DebugLaunchRequest(
+                        proto::DebugLaunchRequest {
+                            program: config.program.clone(),
+                            cwd: config.cwd.as_ref().map(|c| c.to_string_lossy().to_string()),
+                            args: config.args.clone(),
+                        },
+                    )
+                }
+                DebugRequest::Attach(attach_request) => {
+                    proto::debug_task_definition::Request::DebugAttachRequest(
+                        proto::DebugAttachRequest {
+                            process_id: attach_request.process_id.unwrap_or_default(),
+                        },
+                    )
+                }
+            }),
+            label: self.label.clone(),
+            initialize_args: self.initialize_args.as_ref().map(|v| v.to_string()),
+            tcp_connection: self.tcp_connection.as_ref().map(|t| t.to_proto()),
+            stop_on_entry: self.stop_on_entry,
+        }
+    }
+
+    pub fn from_proto(proto: proto::DebugTaskDefinition) -> Result<Self> {
+        let request = proto
+            .request
+            .ok_or_else(|| anyhow::anyhow!("request is required"))?;
+        Ok(Self {
+            label: proto.label,
+            initialize_args: proto.initialize_args.map(|v| v.into()),
+            tcp_connection: proto
+                .tcp_connection
+                .map(TcpArgumentsTemplate::from_proto)
+                .transpose()?,
+            stop_on_entry: proto.stop_on_entry,
+            adapter: proto.adapter.clone(),
+            request: match request {
+                proto::debug_task_definition::Request::DebugAttachRequest(config) => {
+                    DebugRequest::Attach(AttachRequest {
+                        process_id: Some(config.process_id),
+                    })
+                }
+
+                proto::debug_task_definition::Request::DebugLaunchRequest(config) => {
+                    DebugRequest::Launch(LaunchRequest {
+                        program: config.program,
+                        cwd: config.cwd.map(|cwd| cwd.into()),
+                        args: config.args,
+                    })
+                }
+            },
+        })
+    }
+}
+
 /// A group of Debug Tasks defined in a JSON file.
 #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
 #[serde(transparent)]
-pub struct DebugTaskFile(pub Vec<DebugTaskDefinition>);
+pub struct DebugTaskFile(pub Vec<DebugTaskTemplate>);
 
 impl DebugTaskFile {
     /// Generates JSON schema of Tasks JSON template format.
@@ -222,31 +274,17 @@ impl DebugTaskFile {
     }
 }
 
-impl TryFrom<DebugTaskFile> for TaskTemplates {
-    type Error = anyhow::Error;
-
-    fn try_from(value: DebugTaskFile) -> Result<Self, Self::Error> {
-        let templates = value
-            .0
-            .into_iter()
-            .filter_map(|debug_definition| debug_definition.to_zed_format().log_err())
-            .collect();
-
-        Ok(Self(templates))
-    }
-}
-
 #[cfg(test)]
 mod tests {
-    use crate::{DebugRequestType, LaunchConfig};
+    use crate::{DebugRequest, LaunchRequest};
 
     #[test]
     fn test_can_deserialize_non_attach_task() {
-        let deserialized: DebugRequestType =
+        let deserialized: DebugRequest =
             serde_json::from_str(r#"{"program": "cafebabe"}"#).unwrap();
         assert_eq!(
             deserialized,
-            DebugRequestType::Launch(LaunchConfig {
+            DebugRequest::Launch(LaunchRequest {
                 program: "cafebabe".to_owned(),
                 ..Default::default()
             })

crates/task/src/lib.rs 🔗

@@ -1,5 +1,4 @@
 //! Baseline interface of Tasks in Zed: all tasks in Zed are intended to use those for implementing their own logic.
-#![deny(missing_docs)]
 
 mod debug_format;
 mod serde_helpers;
@@ -16,8 +15,8 @@ use std::path::PathBuf;
 use std::str::FromStr;
 
 pub use debug_format::{
-    AttachConfig, DebugConnectionType, DebugRequestDisposition, DebugRequestType,
-    DebugTaskDefinition, DebugTaskFile, LaunchConfig, TCPHost,
+    AttachRequest, DebugRequest, DebugTaskDefinition, DebugTaskFile, DebugTaskTemplate,
+    LaunchRequest, TcpArgumentsTemplate,
 };
 pub use task_template::{
     DebugArgs, DebugArgsRequest, HideStrategy, RevealStrategy, TaskModal, TaskTemplate,
@@ -104,7 +103,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<DebugTaskDefinition> {
+    pub fn resolved_debug_adapter_config(&self) -> Option<DebugTaskTemplate> {
         match self.original_task.task_type.clone() {
             TaskType::Debug(debug_args) if self.resolved.is_some() => {
                 let resolved = self
@@ -127,25 +126,27 @@ impl ResolvedTask {
                     })
                     .collect();
 
-                Some(DebugTaskDefinition {
-                    label: resolved.label.clone(),
-                    adapter: debug_args.adapter.clone(),
-                    request: match debug_args.request {
-                        crate::task_template::DebugArgsRequest::Launch => {
-                            DebugRequestType::Launch(LaunchConfig {
-                                program: resolved.command.clone(),
-                                cwd: resolved.cwd.clone(),
-                                args,
-                            })
-                        }
-                        crate::task_template::DebugArgsRequest::Attach(attach_config) => {
-                            DebugRequestType::Attach(attach_config)
-                        }
-                    },
-                    initialize_args: debug_args.initialize_args,
-                    tcp_connection: debug_args.tcp_connection,
+                Some(DebugTaskTemplate {
                     locator: debug_args.locator.clone(),
-                    stop_on_entry: debug_args.stop_on_entry,
+                    definition: DebugTaskDefinition {
+                        label: resolved.label.clone(),
+                        adapter: debug_args.adapter.clone(),
+                        request: match debug_args.request {
+                            crate::task_template::DebugArgsRequest::Launch => {
+                                DebugRequest::Launch(LaunchRequest {
+                                    program: resolved.command.clone(),
+                                    cwd: resolved.cwd.clone(),
+                                    args,
+                                })
+                            }
+                            crate::task_template::DebugArgsRequest::Attach(attach_config) => {
+                                DebugRequest::Attach(attach_config)
+                            }
+                        },
+                        initialize_args: debug_args.initialize_args,
+                        tcp_connection: debug_args.tcp_connection,
+                        stop_on_entry: debug_args.stop_on_entry,
+                    },
                 })
             }
             _ => None,
@@ -366,6 +367,8 @@ pub struct ShellBuilder {
     args: Vec<String>,
 }
 
+pub static DEFAULT_REMOTE_SHELL: &str = "\"${SHELL:-sh}\"";
+
 impl ShellBuilder {
     /// Create a new ShellBuilder as configured.
     pub fn new(is_local: bool, shell: &Shell) -> Self {
@@ -374,7 +377,7 @@ impl ShellBuilder {
                 if is_local {
                     (Self::system_shell(), Vec::new())
                 } else {
-                    ("\"${SHELL:-sh}\"".to_string(), Vec::new())
+                    (DEFAULT_REMOTE_SHELL.to_string(), Vec::new())
                 }
             }
             Shell::Program(shell) => (shell.clone(), Vec::new()),

crates/task/src/task_template.rs 🔗

@@ -7,8 +7,9 @@ use std::path::PathBuf;
 use util::serde::default_true;
 use util::{ResultExt, truncate_and_remove_front};
 
+use crate::debug_format::TcpArgumentsTemplate;
 use crate::{
-    AttachConfig, ResolvedTask, RevealTarget, Shell, SpawnInTerminal, TCPHost, TaskContext, TaskId,
+    AttachRequest, ResolvedTask, RevealTarget, Shell, SpawnInTerminal, TaskContext, TaskId,
     VariableName, ZED_VARIABLE_NAME_PREFIX,
     serde_helpers::{non_empty_string_vec, non_empty_string_vec_json_schema},
 };
@@ -83,7 +84,7 @@ pub enum DebugArgsRequest {
     /// launch (program, cwd) are stored in TaskTemplate as (command, cwd)
     Launch,
     /// Attach
-    Attach(AttachConfig),
+    Attach(AttachRequest),
 }
 
 #[derive(Deserialize, Eq, PartialEq, Clone, Debug)]
@@ -94,7 +95,7 @@ pub struct DebugArgs {
     /// Adapter choice
     pub adapter: String,
     /// TCP connection to make with debug adapter
-    pub tcp_connection: Option<TCPHost>,
+    pub tcp_connection: Option<TcpArgumentsTemplate>,
     /// Args to send to debug adapter
     pub initialize_args: Option<serde_json::value::Value>,
     /// the locator to use

crates/tasks_ui/src/modal.rs 🔗

@@ -11,7 +11,7 @@ use itertools::Itertools;
 use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
 use project::{TaskSourceKind, task_store::TaskStore};
 use task::{
-    DebugRequestType, DebugTaskDefinition, ResolvedTask, RevealTarget, TaskContext, TaskModal,
+    DebugRequest, DebugTaskDefinition, ResolvedTask, RevealTarget, TaskContext, TaskModal,
     TaskTemplate, TaskType,
 };
 use ui::{
@@ -21,7 +21,7 @@ use ui::{
 };
 
 use util::{ResultExt, truncate_and_trailoff};
-use workspace::{ModalView, Workspace, tasks::schedule_resolved_task};
+use workspace::{ModalView, Workspace};
 pub use zed_actions::{Rerun, Spawn};
 
 /// A modal used to spawn new tasks.
@@ -334,7 +334,7 @@ impl PickerDelegate for TasksModalDelegate {
     fn confirm(
         &mut self,
         omit_history_entry: bool,
-        _: &mut Window,
+        window: &mut Window,
         cx: &mut Context<picker::Picker<Self>>,
     ) {
         let current_match_index = self.selected_index();
@@ -360,17 +360,14 @@ 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()
-                else {
+            TaskType::Debug(_) => {
+                let Some(config) = task.resolved_debug_adapter_config() else {
                     return;
                 };
+                let config = config.definition;
 
                 match &config.request {
-                    DebugRequestType::Attach(attach_config)
-                        if attach_config.process_id.is_none() =>
-                    {
+                    DebugRequest::Attach(attach_config) if attach_config.process_id.is_none() => {
                         cx.emit(ShowAttachModal {
                             debug_config: config.clone(),
                         });
@@ -379,24 +376,20 @@ impl PickerDelegate for TasksModalDelegate {
                     _ => {
                         self.workspace
                             .update(cx, |workspace, cx| {
-                                workspace.project().update(cx, |project, cx| {
-                                    project
-                                        .start_debug_session(config, cx)
-                                        .detach_and_log_err(cx);
-                                });
+                                workspace.schedule_debug_task(task, window, cx);
                             })
                             .ok();
                     }
                 }
             }
-            _ => {
+            TaskType::Script => {
                 self.workspace
                     .update(cx, |workspace, cx| {
-                        schedule_resolved_task(
-                            workspace,
+                        workspace.schedule_resolved_task(
                             task_source_kind,
                             task,
                             omit_history_entry,
+                            window,
                             cx,
                         );
                     })
@@ -566,7 +559,7 @@ impl PickerDelegate for TasksModalDelegate {
     fn confirm_input(
         &mut self,
         omit_history_entry: bool,
-        _: &mut Window,
+        window: &mut Window,
         cx: &mut Context<Picker<Self>>,
     ) {
         let Some((task_source_kind, mut task)) = self.spawn_oneshot() else {
@@ -584,36 +577,17 @@ impl PickerDelegate for TasksModalDelegate {
         self.workspace
             .update(cx, |workspace, cx| {
                 match task.task_type() {
-                    TaskType::Script => schedule_resolved_task(
-                        workspace,
+                    TaskType::Script => workspace.schedule_resolved_task(
                         task_source_kind,
                         task,
                         omit_history_entry,
+                        window,
                         cx,
                     ),
                     // todo(debugger): Should create a schedule_resolved_debug_task function
                     // This would allow users to access to debug history and other issues
-                    TaskType::Debug(debug_args) => {
-                        let Some(debug_config) = task.resolved_debug_adapter_config() else {
-                            // todo(debugger) log an error, this should never happen
-                            return;
-                        };
-
-                        if debug_args.locator.is_some() {
-                            schedule_resolved_task(
-                                workspace,
-                                task_source_kind,
-                                task,
-                                omit_history_entry,
-                                cx,
-                            );
-                        } else {
-                            workspace.project().update(cx, |project, cx| {
-                                project
-                                    .start_debug_session(debug_config, cx)
-                                    .detach_and_log_err(cx);
-                            });
-                        }
+                    TaskType::Debug(_) => {
+                        workspace.schedule_debug_task(task, window, cx);
                     }
                 };
             })

crates/tasks_ui/src/tasks_ui.rs 🔗

@@ -8,8 +8,7 @@ use project::{Location, TaskContexts, TaskSourceKind, Worktree};
 use task::{
     RevealTarget, TaskContext, TaskId, TaskModal, TaskTemplate, TaskVariables, VariableName,
 };
-use workspace::tasks::schedule_task;
-use workspace::{Workspace, tasks::schedule_resolved_task};
+use workspace::Workspace;
 
 mod modal;
 
@@ -50,15 +49,15 @@ pub fn init(cx: &mut App) {
                                 let task_contexts = task_contexts.await;
                                 let default_context = TaskContext::default();
                                 workspace
-                                    .update_in(cx, |workspace, _, cx| {
-                                        schedule_task(
-                                            workspace,
+                                    .update_in(cx, |workspace, window, cx| {
+                                        workspace.schedule_task(
                                             task_source_kind,
                                             &original_task,
                                             task_contexts
                                                 .active_context()
                                                 .unwrap_or(&default_context),
                                             false,
+                                            window,
                                             cx,
                                         )
                                     })
@@ -75,11 +74,11 @@ pub fn init(cx: &mut App) {
                                 }
                             }
 
-                            schedule_resolved_task(
-                                workspace,
+                            workspace.schedule_resolved_task(
                                 task_source_kind,
                                 last_scheduled_task,
                                 false,
+                                window,
                                 cx,
                             );
                         }
@@ -217,7 +216,7 @@ where
         })?;
 
         let did_spawn = workspace
-            .update(cx, |workspace, cx| {
+            .update_in(cx, |workspace, window, cx| {
                 let default_context = TaskContext::default();
                 let active_context = task_contexts.active_context().unwrap_or(&default_context);
 
@@ -228,12 +227,12 @@ where
                                 target_task.reveal_target = target_override;
                             }
                         }
-                        schedule_task(
-                            workspace,
+                        workspace.schedule_task(
                             task_source_kind.clone(),
                             target_task,
                             active_context,
                             false,
+                            window,
                             cx,
                         );
                         true

crates/terminal/src/terminal.rs 🔗

@@ -46,13 +46,14 @@ use smol::channel::{Receiver, Sender};
 use task::{HideStrategy, Shell, TaskId};
 use terminal_settings::{AlternateScroll, CursorShape, TerminalSettings};
 use theme::{ActiveTheme, Theme};
-use util::{paths::home_dir, truncate_and_trailoff};
+use util::{ResultExt, paths::home_dir, truncate_and_trailoff};
 
 use std::{
     cmp::{self, min},
     fmt::Display,
     ops::{Deref, Index, RangeInclusive},
     path::PathBuf,
+    process::ExitStatus,
     sync::{Arc, LazyLock},
     time::Duration,
 };
@@ -109,7 +110,6 @@ pub enum Event {
     SelectionsChanged,
     NewNavigationTarget(Option<MaybeNavigationTarget>),
     Open(MaybeNavigationTarget),
-    TaskLocatorReady { task_id: TaskId, success: bool },
 }
 
 #[derive(Clone, Debug)]
@@ -351,7 +351,7 @@ impl TerminalBuilder {
         max_scroll_history_lines: Option<usize>,
         is_ssh_terminal: bool,
         window: AnyWindowHandle,
-        completion_tx: Sender<()>,
+        completion_tx: Sender<Option<ExitStatus>>,
         debug_terminal: bool,
         cx: &App,
     ) -> Result<TerminalBuilder> {
@@ -639,7 +639,7 @@ pub enum SelectionPhase {
 
 pub struct Terminal {
     pty_tx: Notifier,
-    completion_tx: Sender<()>,
+    completion_tx: Sender<Option<ExitStatus>>,
     term: Arc<FairMutex<Term<ZedListener>>>,
     term_config: Config,
     events: VecDeque<InternalEvent>,
@@ -670,7 +670,7 @@ pub struct TaskState {
     pub label: String,
     pub command_label: String,
     pub status: TaskStatus,
-    pub completion_rx: Receiver<()>,
+    pub completion_rx: Receiver<Option<ExitStatus>>,
     pub hide: HideStrategy,
     pub show_summary: bool,
     pub show_command: bool,
@@ -1859,20 +1859,30 @@ impl Terminal {
         self.debug_terminal
     }
 
-    pub fn wait_for_completed_task(&self, cx: &App) -> Task<()> {
+    pub fn wait_for_completed_task(&self, cx: &App) -> Task<Option<ExitStatus>> {
         if let Some(task) = self.task() {
             if task.status == TaskStatus::Running {
                 let completion_receiver = task.completion_rx.clone();
-                return cx.spawn(async move |_| {
-                    let _ = completion_receiver.recv().await;
-                });
+                return cx
+                    .spawn(async move |_| completion_receiver.recv().await.log_err().flatten());
             }
         }
-        Task::ready(())
+        Task::ready(None)
     }
 
     fn register_task_finished(&mut self, error_code: Option<i32>, cx: &mut Context<Terminal>) {
-        self.completion_tx.try_send(()).ok();
+        let e: Option<ExitStatus> = error_code.map(|code| {
+            #[cfg(unix)]
+            {
+                return std::os::unix::process::ExitStatusExt::from_raw(code);
+            }
+            #[cfg(windows)]
+            {
+                return std::os::windows::process::ExitStatusExt::from_raw(code as u32);
+            }
+        });
+
+        self.completion_tx.try_send(e).ok();
         let task = match &mut self.task {
             Some(task) => task,
             None => {
@@ -1911,11 +1921,6 @@ impl Terminal {
             unsafe { append_text_to_term(&mut self.term.lock(), &lines_to_show) };
         }
 
-        cx.emit(Event::TaskLocatorReady {
-            task_id: task.id.clone(),
-            success: finished_successfully,
-        });
-
         match task.hide {
             HideStrategy::Never => {}
             HideStrategy::Always => {

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -1,4 +1,4 @@
-use std::{cmp, ops::ControlFlow, path::PathBuf, sync::Arc, time::Duration};
+use std::{cmp, ops::ControlFlow, path::PathBuf, process::ExitStatus, sync::Arc, time::Duration};
 
 use crate::{
     TerminalView, default_working_directory,
@@ -9,7 +9,7 @@ use crate::{
 use breadcrumbs::Breadcrumbs;
 use collections::HashMap;
 use db::kvp::KEY_VALUE_STORE;
-use futures::future::join_all;
+use futures::{channel::oneshot, future::join_all};
 use gpui::{
     Action, AnyView, App, AsyncApp, AsyncWindowContext, Context, Corner, Entity, EventEmitter,
     ExternalPaths, FocusHandle, Focusable, IntoElement, ParentElement, Pixels, Render, Styled,
@@ -279,17 +279,9 @@ impl TerminalPanel {
         };
 
         if let Some(workspace) = workspace.upgrade() {
-            terminal_panel
-                .update_in(&mut cx, |_, window, cx| {
-                    cx.subscribe_in(&workspace, window, |terminal_panel, _, e, window, cx| {
-                        if let workspace::Event::SpawnTask {
-                            action: spawn_in_terminal,
-                        } = e
-                        {
-                            terminal_panel.spawn_task(spawn_in_terminal, window, cx);
-                        };
-                    })
-                    .detach();
+            workspace
+                .update(&mut cx, |workspace, _| {
+                    workspace.set_terminal_provider(TerminalProvider(terminal_panel.clone()))
                 })
                 .ok();
         }
@@ -486,12 +478,17 @@ impl TerminalPanel {
             .detach_and_log_err(cx);
     }
 
-    fn spawn_task(&mut self, task: &SpawnInTerminal, window: &mut Window, cx: &mut Context<Self>) {
+    fn spawn_task(
+        &mut self,
+        task: &SpawnInTerminal,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> Task<Result<Entity<Terminal>>> {
         let Ok(is_local) = self
             .workspace
             .update(cx, |workspace, cx| workspace.project().read(cx).is_local())
         else {
-            return;
+            return Task::ready(Err(anyhow!("Project is not local")));
         };
 
         let builder = ShellBuilder::new(is_local, &task.shell);
@@ -506,58 +503,53 @@ impl TerminalPanel {
         };
 
         if task.allow_concurrent_runs && task.use_new_terminal {
-            self.spawn_in_new_terminal(task, window, cx)
-                .detach_and_log_err(cx);
-            return;
+            return self.spawn_in_new_terminal(task, window, cx);
         }
 
         let mut terminals_for_task = self.terminals_for_task(&task.full_label, cx);
         let Some(existing) = terminals_for_task.pop() else {
-            self.spawn_in_new_terminal(task, window, cx)
-                .detach_and_log_err(cx);
-            return;
+            return self.spawn_in_new_terminal(task, window, cx);
         };
 
         let (existing_item_index, task_pane, existing_terminal) = existing;
         if task.allow_concurrent_runs {
-            self.replace_terminal(
+            return self.replace_terminal(
                 task,
                 task_pane,
                 existing_item_index,
                 existing_terminal,
                 window,
                 cx,
-            )
-            .detach();
-            return;
+            );
         }
 
+        let (tx, rx) = oneshot::channel();
+
         self.deferred_tasks.insert(
             task.id.clone(),
             cx.spawn_in(window, async move |terminal_panel, cx| {
                 wait_for_terminals_tasks(terminals_for_task, cx).await;
                 let task = terminal_panel.update_in(cx, |terminal_panel, window, cx| {
                     if task.use_new_terminal {
-                        terminal_panel
-                            .spawn_in_new_terminal(task, window, cx)
-                            .detach_and_log_err(cx);
-                        None
+                        terminal_panel.spawn_in_new_terminal(task, window, cx)
                     } else {
-                        Some(terminal_panel.replace_terminal(
+                        terminal_panel.replace_terminal(
                             task,
                             task_pane,
                             existing_item_index,
                             existing_terminal,
                             window,
                             cx,
-                        ))
+                        )
                     }
                 });
-                if let Ok(Some(task)) = task {
-                    task.await;
+                if let Ok(task) = task {
+                    tx.send(task.await).ok();
                 }
             }),
         );
+
+        cx.spawn(async move |_, _| rx.await?)
     }
 
     pub fn spawn_in_new_terminal(
@@ -810,60 +802,47 @@ impl TerminalPanel {
         terminal_to_replace: Entity<TerminalView>,
         window: &mut Window,
         cx: &mut Context<Self>,
-    ) -> Task<Option<()>> {
+    ) -> Task<Result<Entity<Terminal>>> {
         let reveal = spawn_task.reveal;
         let reveal_target = spawn_task.reveal_target;
         let window_handle = window.window_handle();
         let task_workspace = self.workspace.clone();
         cx.spawn_in(window, async move |terminal_panel, cx| {
-            let project = terminal_panel
-                .update(cx, |this, cx| {
-                    this.workspace
-                        .update(cx, |workspace, _| workspace.project().clone())
-                        .ok()
-                })
-                .ok()
-                .flatten()?;
+            let project = terminal_panel.update(cx, |this, cx| {
+                this.workspace
+                    .update(cx, |workspace, _| workspace.project().clone())
+            })??;
             let new_terminal = project
                 .update(cx, |project, cx| {
                     project.create_terminal(TerminalKind::Task(spawn_task), window_handle, cx)
-                })
-                .ok()?
-                .await
-                .log_err()?;
-            terminal_to_replace
-                .update_in(cx, |terminal_to_replace, window, cx| {
-                    terminal_to_replace.set_terminal(new_terminal, window, cx);
-                })
-                .ok()?;
+                })?
+                .await?;
+            terminal_to_replace.update_in(cx, |terminal_to_replace, window, cx| {
+                terminal_to_replace.set_terminal(new_terminal.clone(), window, cx);
+            })?;
 
             match reveal {
                 RevealStrategy::Always => match reveal_target {
                     RevealTarget::Center => {
-                        task_workspace
-                            .update_in(cx, |workspace, window, cx| {
-                                workspace
-                                    .active_item(cx)
-                                    .context("retrieving active terminal item in the workspace")
-                                    .log_err()?
-                                    .item_focus_handle(cx)
-                                    .focus(window);
-                                Some(())
-                            })
-                            .ok()??;
+                        task_workspace.update_in(cx, |workspace, window, cx| {
+                            workspace
+                                .active_item(cx)
+                                .context("retrieving active terminal item in the workspace")?
+                                .item_focus_handle(cx)
+                                .focus(window);
+                            anyhow::Ok(())
+                        })??;
                     }
                     RevealTarget::Dock => {
-                        terminal_panel
-                            .update_in(cx, |terminal_panel, window, cx| {
-                                terminal_panel.activate_terminal_view(
-                                    &task_pane,
-                                    terminal_item_index,
-                                    true,
-                                    window,
-                                    cx,
-                                )
-                            })
-                            .ok()?;
+                        terminal_panel.update_in(cx, |terminal_panel, window, cx| {
+                            terminal_panel.activate_terminal_view(
+                                &task_pane,
+                                terminal_item_index,
+                                true,
+                                window,
+                                cx,
+                            )
+                        })?;
 
                         cx.spawn(async move |cx| {
                             task_workspace
@@ -877,24 +856,20 @@ impl TerminalPanel {
                 },
                 RevealStrategy::NoFocus => match reveal_target {
                     RevealTarget::Center => {
-                        task_workspace
-                            .update_in(cx, |workspace, window, cx| {
-                                workspace.active_pane().focus_handle(cx).focus(window);
-                            })
-                            .ok()?;
+                        task_workspace.update_in(cx, |workspace, window, cx| {
+                            workspace.active_pane().focus_handle(cx).focus(window);
+                        })?;
                     }
                     RevealTarget::Dock => {
-                        terminal_panel
-                            .update_in(cx, |terminal_panel, window, cx| {
-                                terminal_panel.activate_terminal_view(
-                                    &task_pane,
-                                    terminal_item_index,
-                                    false,
-                                    window,
-                                    cx,
-                                )
-                            })
-                            .ok()?;
+                        terminal_panel.update_in(cx, |terminal_panel, window, cx| {
+                            terminal_panel.activate_terminal_view(
+                                &task_pane,
+                                terminal_item_index,
+                                false,
+                                window,
+                                cx,
+                            )
+                        })?;
 
                         cx.spawn(async move |cx| {
                             task_workspace
@@ -909,7 +884,7 @@ impl TerminalPanel {
                 RevealStrategy::Never => {}
             }
 
-            Some(())
+            Ok(new_terminal)
         })
     }
 
@@ -1158,7 +1133,7 @@ async fn wait_for_terminals_tasks(
             })
             .ok()
     });
-    let _: Vec<()> = join_all(pending_tasks).await;
+    let _: Vec<_> = join_all(pending_tasks).await;
 }
 
 fn add_paths_to_terminal(
@@ -1475,6 +1450,34 @@ impl Panel for TerminalPanel {
     }
 }
 
+struct TerminalProvider(Entity<TerminalPanel>);
+
+impl workspace::TerminalProvider for TerminalProvider {
+    fn spawn(
+        &self,
+        task: SpawnInTerminal,
+        window: &mut Window,
+        cx: &mut App,
+    ) -> Task<Result<ExitStatus>> {
+        let this = self.0.clone();
+        window.spawn(cx, async move |cx| {
+            let terminal = this
+                .update_in(cx, |terminal_panel, window, cx| {
+                    terminal_panel.spawn_task(&task, window, cx)
+                })?
+                .await?;
+            let Some(exit_code) = terminal
+                .read_with(cx, |terminal, cx| terminal.wait_for_completed_task(cx))?
+                .await
+            else {
+                return Err(anyhow!("Task cancelled"));
+            };
+
+            Ok(exit_code)
+        })
+    }
+}
+
 struct InlineAssistTabBarButton {
     focus_handle: FocusHandle,
 }

crates/terminal_view/src/terminal_view.rs 🔗

@@ -982,15 +982,6 @@ fn subscribe_for_terminal_events(
                 window.invalidate_character_coordinates();
                 cx.emit(SearchEvent::ActiveMatchChanged)
             }
-            Event::TaskLocatorReady { task_id, success } => {
-                if *success {
-                    workspace
-                        .update(cx, |workspace, cx| {
-                            workspace.debug_task_ready(task_id, cx);
-                        })
-                        .log_err();
-                }
-            }
         },
     );
     vec![terminal_subscription, terminal_events_subscription]

crates/vim/src/command.rs 🔗

@@ -1446,27 +1446,29 @@ impl ShellExec {
                 let project = workspace.project().read(cx);
                 let cwd = project.first_project_directory(cx);
                 let shell = project.terminal_settings(&cwd, cx).shell.clone();
-                cx.emit(workspace::Event::SpawnTask {
-                    action: Box::new(SpawnInTerminal {
-                        id: TaskId("vim".to_string()),
-                        full_label: command.clone(),
-                        label: command.clone(),
-                        command: command.clone(),
-                        args: Vec::new(),
-                        command_label: command.clone(),
-                        cwd,
-                        env: HashMap::default(),
-                        use_new_terminal: true,
-                        allow_concurrent_runs: true,
-                        reveal: RevealStrategy::NoFocus,
-                        reveal_target: RevealTarget::Dock,
-                        hide: HideStrategy::Never,
-                        shell,
-                        show_summary: false,
-                        show_command: false,
-                        show_rerun: false,
-                    }),
-                });
+
+                let spawn_in_terminal = SpawnInTerminal {
+                    id: TaskId("vim".to_string()),
+                    full_label: command.clone(),
+                    label: command.clone(),
+                    command: command.clone(),
+                    args: Vec::new(),
+                    command_label: command.clone(),
+                    cwd,
+                    env: HashMap::default(),
+                    use_new_terminal: true,
+                    allow_concurrent_runs: true,
+                    reveal: RevealStrategy::NoFocus,
+                    reveal_target: RevealTarget::Dock,
+                    hide: HideStrategy::Never,
+                    shell,
+                    show_summary: false,
+                    show_command: false,
+                    show_rerun: false,
+                };
+                workspace
+                    .spawn_in_terminal(spawn_in_terminal, window, cx)
+                    .detach_and_log_err(cx);
             });
             return;
         };

crates/workspace/Cargo.toml 🔗

@@ -35,7 +35,6 @@ client.workspace = true
 clock.workspace = true
 collections.workspace = true
 component.workspace = true
-dap.workspace = true
 db.workspace = true
 derive_more.workspace = true
 fs.workspace = true

crates/workspace/src/tasks.rs 🔗

@@ -1,75 +1,132 @@
-use gpui::Context;
+use std::process::ExitStatus;
+
+use anyhow::{Result, anyhow};
+use gpui::{Context, Task};
 use project::TaskSourceKind;
 use remote::ConnectionState;
-use task::{ResolvedTask, TaskContext, TaskTemplate};
+use task::{ResolvedTask, SpawnInTerminal, TaskContext, TaskTemplate};
+use ui::Window;
 
 use crate::Workspace;
 
-pub fn schedule_task(
-    workspace: &mut Workspace,
-    task_source_kind: TaskSourceKind,
-    task_to_resolve: &TaskTemplate,
-    task_cx: &TaskContext,
-    omit_history: bool,
-    cx: &mut Context<Workspace>,
-) {
-    match workspace.project.read(cx).ssh_connection_state(cx) {
-        None | Some(ConnectionState::Connected) => {}
-        Some(
-            ConnectionState::Connecting
-            | ConnectionState::Disconnected
-            | ConnectionState::HeartbeatMissed
-            | ConnectionState::Reconnecting,
-        ) => {
-            log::warn!("Cannot schedule tasks when disconnected from a remote host");
-            return;
+impl Workspace {
+    pub fn schedule_task(
+        self: &mut Workspace,
+        task_source_kind: TaskSourceKind,
+        task_to_resolve: &TaskTemplate,
+        task_cx: &TaskContext,
+        omit_history: bool,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        match self.project.read(cx).ssh_connection_state(cx) {
+            None | Some(ConnectionState::Connected) => {}
+            Some(
+                ConnectionState::Connecting
+                | ConnectionState::Disconnected
+                | ConnectionState::HeartbeatMissed
+                | ConnectionState::Reconnecting,
+            ) => {
+                log::warn!("Cannot schedule tasks when disconnected from a remote host");
+                return;
+            }
         }
-    }
 
-    if let Some(spawn_in_terminal) =
-        task_to_resolve.resolve_task(&task_source_kind.to_id_base(), task_cx)
-    {
-        schedule_resolved_task(
-            workspace,
-            task_source_kind,
-            spawn_in_terminal,
-            omit_history,
-            cx,
-        );
+        if let Some(spawn_in_terminal) =
+            task_to_resolve.resolve_task(&task_source_kind.to_id_base(), task_cx)
+        {
+            self.schedule_resolved_task(
+                task_source_kind,
+                spawn_in_terminal,
+                omit_history,
+                window,
+                cx,
+            );
+        }
     }
-}
 
-pub fn schedule_resolved_task(
-    workspace: &mut Workspace,
-    task_source_kind: TaskSourceKind,
-    mut resolved_task: ResolvedTask,
-    omit_history: bool,
-    cx: &mut Context<Workspace>,
-) {
-    let debug_config = resolved_task.resolved_debug_adapter_config();
+    pub fn schedule_resolved_task(
+        self: &mut Workspace,
+        task_source_kind: TaskSourceKind,
+        mut resolved_task: ResolvedTask,
+        omit_history: bool,
+        window: &mut Window,
+        cx: &mut Context<Workspace>,
+    ) {
+        if let Some(spawn_in_terminal) = resolved_task.resolved.take() {
+            if !omit_history {
+                resolved_task.resolved = Some(spawn_in_terminal.clone());
+                self.project().update(cx, |project, cx| {
+                    if let Some(task_inventory) =
+                        project.task_store().read(cx).task_inventory().cloned()
+                    {
+                        task_inventory.update(cx, |inventory, _| {
+                            inventory.task_scheduled(task_source_kind, resolved_task);
+                        })
+                    }
+                });
+            }
 
-    if let Some(spawn_in_terminal) = resolved_task.resolved.take() {
-        if let Some(debug_config) = debug_config {
-            workspace
-                .debug_task_queue
-                .insert(resolved_task.id.clone(), debug_config);
+            if let Some(terminal_provider) = self.terminal_provider.as_ref() {
+                terminal_provider
+                    .spawn(spawn_in_terminal, window, cx)
+                    .detach_and_log_err(cx);
+            }
         }
+    }
 
-        if !omit_history {
-            resolved_task.resolved = Some(spawn_in_terminal.clone());
-            workspace.project().update(cx, |project, cx| {
-                if let Some(task_inventory) =
-                    project.task_store().read(cx).task_inventory().cloned()
-                {
-                    task_inventory.update(cx, |inventory, _| {
-                        inventory.task_scheduled(task_source_kind, resolved_task);
-                    })
+    pub fn schedule_debug_task(
+        &mut self,
+        task: ResolvedTask,
+        window: &mut Window,
+        cx: &mut Context<Workspace>,
+    ) {
+        let Some(debug_config) = task.resolved_debug_adapter_config() else {
+            log::error!("Debug task has no debug adapter config");
+            return;
+        };
+
+        let project = self.project().clone();
+        cx.spawn_in(window, async move |workspace, cx| {
+            let config = if debug_config.locator.is_some() {
+                let task = workspace.update_in(cx, |workspace, window, cx| {
+                    workspace.spawn_in_terminal(task.resolved.unwrap(), window, cx)
+                })?;
+
+                let exit_code = task.await?;
+                if !exit_code.success() {
+                    return anyhow::Ok(());
                 }
-            });
-        }
+                let ret = project
+                    .update(cx, |project, cx| {
+                        project.dap_store().update(cx, |dap_store, cx| {
+                            dap_store.run_debug_locator(debug_config, cx)
+                        })
+                    })?
+                    .await?;
+                ret
+            } else {
+                debug_config.definition
+            };
 
-        cx.emit(crate::Event::SpawnTask {
-            action: Box::new(spawn_in_terminal),
-        });
+            project
+                .update(cx, |project, cx| project.start_debug_session(config, cx))?
+                .await?;
+            anyhow::Ok(())
+        })
+        .detach_and_log_err(cx);
+    }
+
+    pub fn spawn_in_terminal(
+        self: &mut Workspace,
+        spawn_in_terminal: SpawnInTerminal,
+        window: &mut Window,
+        cx: &mut Context<Workspace>,
+    ) -> Task<Result<ExitStatus>> {
+        if let Some(terminal_provider) = self.terminal_provider.as_ref() {
+            terminal_provider.spawn(spawn_in_terminal, window, cx)
+        } else {
+            Task::ready(Err(anyhow!("No terminal provider")))
+        }
     }
 }

crates/workspace/src/workspace.rs 🔗

@@ -15,7 +15,6 @@ mod toast_layer;
 mod toolbar;
 mod workspace_settings;
 
-use dap::DapRegistry;
 pub use toast_layer::{RunAction, ToastAction, ToastLayer, ToastView};
 
 use anyhow::{Context as _, Result, anyhow};
@@ -92,11 +91,12 @@ use std::{
     env,
     hash::{Hash, Hasher},
     path::{Path, PathBuf},
+    process::ExitStatus,
     rc::Rc,
     sync::{Arc, LazyLock, Weak, atomic::AtomicUsize},
     time::Duration,
 };
-use task::{DebugTaskDefinition, SpawnInTerminal, TaskId};
+use task::SpawnInTerminal;
 use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
 pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
 pub use ui;
@@ -130,6 +130,15 @@ static ZED_WINDOW_POSITION: LazyLock<Option<Point<Pixels>>> = LazyLock::new(|| {
         .and_then(parse_pixel_position_env_var)
 });
 
+pub trait TerminalProvider {
+    fn spawn(
+        &self,
+        task: SpawnInTerminal,
+        window: &mut Window,
+        cx: &mut App,
+    ) -> Task<Result<ExitStatus>>;
+}
+
 actions!(
     workspace,
     [
@@ -626,7 +635,6 @@ pub fn register_serializable_item<I: SerializableItem>(cx: &mut App) {
 
 pub struct AppState {
     pub languages: Arc<LanguageRegistry>,
-    pub debug_adapters: Arc<DapRegistry>,
     pub client: Arc<Client>,
     pub user_store: Entity<UserStore>,
     pub workspace_store: Entity<WorkspaceStore>,
@@ -678,7 +686,6 @@ impl AppState {
 
         let fs = fs::FakeFs::new(cx.background_executor().clone());
         let languages = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
-        let debug_adapters = Arc::new(DapRegistry::fake());
         let clock = Arc::new(clock::FakeSystemClock::new());
         let http_client = http_client::FakeHttpClient::with_404_response();
         let client = Client::new(clock, http_client.clone(), cx);
@@ -694,7 +701,6 @@ impl AppState {
             client,
             fs,
             languages,
-            debug_adapters,
             user_store,
             workspace_store,
             node_runtime: NodeRuntime::unavailable(),
@@ -772,9 +778,6 @@ pub enum Event {
     },
     ContactRequestedJoin(u64),
     WorkspaceCreated(WeakEntity<Workspace>),
-    SpawnTask {
-        action: Box<SpawnInTerminal>,
-    },
     OpenBundledFile {
         text: Cow<'static, str>,
         title: &'static str,
@@ -856,11 +859,11 @@ pub struct Workspace {
     bounds_save_task_queued: Option<Task<()>>,
     on_prompt_for_new_path: Option<PromptForNewPath>,
     on_prompt_for_open_path: Option<PromptForOpenPath>,
+    terminal_provider: Option<Box<dyn TerminalProvider>>,
     serializable_items_tx: UnboundedSender<Box<dyn SerializableItemHandle>>,
     serialized_ssh_project: Option<SerializedSshProject>,
     _items_serializer: Task<Result<()>>,
     session_id: Option<String>,
-    debug_task_queue: HashMap<task::TaskId, DebugTaskDefinition>,
 }
 
 impl EventEmitter<Event> for Workspace {}
@@ -1182,11 +1185,11 @@ impl Workspace {
             bounds_save_task_queued: None,
             on_prompt_for_new_path: None,
             on_prompt_for_open_path: None,
+            terminal_provider: None,
             serializable_items_tx,
             _items_serializer,
             session_id: Some(session_id),
             serialized_ssh_project: None,
-            debug_task_queue: Default::default(),
         }
     }
 
@@ -1207,7 +1210,6 @@ impl Workspace {
             app_state.node_runtime.clone(),
             app_state.user_store.clone(),
             app_state.languages.clone(),
-            app_state.debug_adapters.clone(),
             app_state.fs.clone(),
             env,
             cx,
@@ -1699,6 +1701,10 @@ impl Workspace {
         self.on_prompt_for_open_path = Some(prompt)
     }
 
+    pub fn set_terminal_provider(&mut self, provider: impl TerminalProvider + 'static) {
+        self.terminal_provider = Some(Box::new(provider));
+    }
+
     pub fn serialized_ssh_project(&self) -> Option<SerializedSshProject> {
         self.serialized_ssh_project.clone()
     }
@@ -5082,7 +5088,6 @@ impl Workspace {
         window.activate_window();
         let app_state = Arc::new(AppState {
             languages: project.read(cx).languages().clone(),
-            debug_adapters: project.read(cx).debug_adapters().clone(),
             workspace_store,
             client,
             user_store,
@@ -5238,16 +5243,6 @@ impl Workspace {
             .update(cx, |_, window, _| window.activate_window())
             .ok();
     }
-
-    pub fn debug_task_ready(&mut self, task_id: &TaskId, cx: &mut App) {
-        if let Some(debug_config) = self.debug_task_queue.remove(task_id) {
-            self.project.update(cx, |project, cx| {
-                project
-                    .start_debug_session(debug_config, cx)
-                    .detach_and_log_err(cx);
-            })
-        }
-    }
 }
 
 fn leader_border_for_pane(

crates/zed/Cargo.toml 🔗

@@ -42,7 +42,6 @@ command_palette.workspace = true
 command_palette_hooks.workspace = true
 component_preview.workspace = true
 copilot.workspace = true
-dap.workspace = true
 dap_adapters.workspace = true
 debugger_ui.workspace = true
 debugger_tools.workspace = true

crates/zed/src/main.rs 🔗

@@ -10,7 +10,6 @@ use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
 use client::{Client, ProxySettings, UserStore, parse_zed_link};
 use collab_ui::channel_view::ChannelView;
 use collections::HashMap;
-use dap::DapRegistry;
 use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
 use editor::Editor;
 use extension::ExtensionHostProxy;
@@ -449,7 +448,6 @@ fn main() {
 
         let app_state = Arc::new(AppState {
             languages: languages.clone(),
-            debug_adapters: DapRegistry::default().into(),
             client: client.clone(),
             user_store: user_store.clone(),
             fs: fs.clone(),
@@ -461,7 +459,7 @@ fn main() {
         AppState::set_global(Arc::downgrade(&app_state), cx);
 
         auto_update::init(client.http_client(), cx);
-        dap_adapters::init(app_state.debug_adapters.clone());
+        dap_adapters::init(cx);
         auto_update_ui::init(cx);
         reliability::init(
             client.http_client(),

crates/zlog/src/filter.rs 🔗

@@ -14,7 +14,7 @@ use log;
 static ENV_FILTER: OnceLock<env_config::EnvFilter> = OnceLock::new();
 static SCOPE_MAP: RwLock<Option<ScopeMap>> = RwLock::new(None);
 
-const LEVEL_ENABLED_MAX_DEFAULT: log::LevelFilter = log::LevelFilter::Info;
+pub const LEVEL_ENABLED_MAX_DEFAULT: log::LevelFilter = log::LevelFilter::Info;
 /// The maximum log level of verbosity that is enabled by default.
 /// All messages more verbose than this level will be discarded
 /// by default unless specially configured.
@@ -34,7 +34,7 @@ static mut LEVEL_ENABLED_MAX_STATIC: log::LevelFilter = LEVEL_ENABLED_MAX_DEFAUL
 /// `trace` logs will be discarded.
 /// Therefore, it should always be `>= LEVEL_ENABLED_MAX_STATIC`
 // PERF: this doesn't need to be an atomic, we don't actually care about race conditions here
-static LEVEL_ENABLED_MAX_CONFIG: AtomicU8 = AtomicU8::new(LEVEL_ENABLED_MAX_DEFAULT as u8);
+pub static LEVEL_ENABLED_MAX_CONFIG: AtomicU8 = AtomicU8::new(LEVEL_ENABLED_MAX_DEFAULT as u8);
 
 pub fn init_env_filter(filter: env_config::EnvFilter) {
     if let Some(level_max) = filter.level_global {