From 9d35f0389dbdebaeedf732112b23e4d3758a03b8 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 21 Apr 2025 10:00:03 -0600 Subject: [PATCH] debugger: More tidy up for SSH (#28993) Split `locator` out of DebugTaskDefinition to make it clearer when location needs to happen. Release Notes: - N/A --------- Co-authored-by: Anthony Eid Co-authored-by: Anthony Co-authored-by: Cole Miller --- Cargo.lock | 5 +- .../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 | 51 ++- crates/dap_adapters/src/codelldb.rs | 25 +- crates/dap_adapters/src/dap_adapters.rs | 25 +- 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 | 344 +++++++++++------- crates/project/src/debugger/locator_store.rs | 19 +- .../debugger/{locator_store => }/locators.rs | 4 +- .../{locator_store => locators}/cargo.rs | 11 +- crates/project/src/debugger/session.rs | 2 +- crates/project/src/debugger/test.rs | 5 +- crates/project/src/project.rs | 117 +++--- 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 +- .../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 | 183 ++++++---- 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, 1146 insertions(+), 884 deletions(-) rename crates/project/src/debugger/{locator_store => }/locators.rs (53%) rename crates/project/src/debugger/{locator_store => locators}/cargo.rs (93%) diff --git a/Cargo.lock b/Cargo.lock index 95b85b13a7..c7c878f654 100644 --- a/Cargo.lock +++ b/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", diff --git a/crates/collab/src/tests/remote_editing_collaboration_tests.rs b/crates/collab/src/tests/remote_editing_collaboration_tests.rs index 41bf79fc6c..09e712b46d 100644 --- a/crates/collab/src/tests/remote_editing_collaboration_tests.rs +++ b/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, diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index f7b2d7ce6d..835d637b9f 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/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, diff --git a/crates/dap/Cargo.toml b/crates/dap/Cargo.toml index 0fdd19c93e..531276e708 100644 --- a/crates/dap/Cargo.toml +++ b/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 diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index 75e8a3814f..7b4294dccd 100644 --- a/crates/dap/src/adapters.rs +++ b/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, } + +impl TcpArguments { + pub fn from_proto(proto: proto::TcpHost) -> anyhow::Result { + 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>, - pub envs: Option>, + pub arguments: Vec, + pub envs: HashMap, pub cwd: Option, pub connection: Option, pub request_args: StartDebuggingRequestArguments, } +impl DebugAdapterBinary { + pub fn from_proto(binary: proto::DebugAdapterBinary) -> anyhow::Result { + 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 { 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), }) diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs index a5eebc73df..869b352edf 100644 --- a/crates/dap/src/client.rs +++ b/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 { 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(), diff --git a/crates/dap/src/dap.rs b/crates/dap/src/dap.rs index 2647ac0314..9673d9a19d 100644 --- a/crates/dap/src/dap.rs +++ b/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; diff --git a/crates/dap/src/registry.rs b/crates/dap/src/registry.rs index b6c8efea40..4e08c5cce8 100644 --- a/crates/dap/src/registry.rs +++ b/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>); +impl Global for DapRegistry {} impl DapRegistry { + pub fn global(cx: &mut App) -> &mut Self { + let ret = cx.default_global::(); + + #[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) { 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> { self.0.read().adapters.get(name).cloned() } + pub fn enumerate_adapters(&self) -> Vec { 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 - } } diff --git a/crates/dap/src/transport.rs b/crates/dap/src/transport.rs index c7857e20dd..68e9d5a4b3 100644 --- a/crates/dap/src/transport.rs +++ b/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)> { - #[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)> { + #[cfg(any(test, feature = "test-support"))] + if cfg!(any(test, feature = "test-support")) { + return FakeTransport::start(cx) + .await + .map(|(transports, fake)| (transports, Self::Fake(fake))); + } + 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 { + pub async fn port(host: &TcpArgumentsTemplate) -> Result { 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 { + 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()) diff --git a/crates/dap_adapters/src/codelldb.rs b/crates/dap_adapters/src/codelldb.rs index 8fe2497738..e1b34e2d73 100644 --- a/crates/dap_adapters/src/codelldb.rs +++ b/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, }) } diff --git a/crates/dap_adapters/src/dap_adapters.rs b/crates/dap_adapters/src/dap_adapters.rs index ddcaeff314..25e9c32679 100644 --- a/crates/dap_adapters/src/dap_adapters.rs +++ b/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; +use task::TcpArgumentsTemplate; -pub fn init(registry: Arc) { - 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 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)> { 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, diff --git a/crates/dap_adapters/src/gdb.rs b/crates/dap_adapters/src/gdb.rs index 1510d1a90c..257af7f136 100644 --- a/crates/dap_adapters/src/gdb.rs +++ b/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), diff --git a/crates/dap_adapters/src/go.rs b/crates/dap_adapters/src/go.rs index 535e0b59e3..e440020e20 100644 --- a/crates/dap_adapters/src/go.rs +++ b/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, diff --git a/crates/dap_adapters/src/javascript.rs b/crates/dap_adapters/src/javascript.rs index aa6c052d3f..a83768ffdb 100644 --- a/crates/dap_adapters/src/javascript.rs +++ b/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, diff --git a/crates/dap_adapters/src/php.rs b/crates/dap_adapters/src/php.rs index 895d8c52b2..8dd84ce28c 100644 --- a/crates/dap_adapters/src/php.rs +++ b/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 { 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)?, }) } diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index 01b008f371..3c3f587ecc 100644 --- a/crates/dap_adapters/src/python.rs +++ b/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), }) } diff --git a/crates/debugger_tools/src/dap_log.rs b/crates/debugger_tools/src/dap_log.rs index 80b34a851d..cfe96115f6 100644 --- a/crates/debugger_tools/src/dap_log.rs +++ b/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), }) diff --git a/crates/debugger_ui/src/attach_modal.rs b/crates/debugger_ui/src/attach_modal.rs index e6a327e56c..a09a9bac36 100644 --- a/crates/debugger_ui/src/attach_modal.rs +++ b/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; } diff --git a/crates/debugger_ui/src/new_session_modal.rs b/crates/debugger_ui/src/new_session_modal.rs index 9f5c53a4f1..cf59a7055b 100644 --- a/crates/debugger_ui/src/new_session_modal.rs +++ b/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, } -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 = workspace + let available_adapters: Vec = 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, + past_launch_config: Option, window: &mut Window, cx: &mut App, ) -> Entity { @@ -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 { 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, + past_launch_config: Option, window: &mut Window, cx: &mut Context, ) -> Self { diff --git a/crates/debugger_ui/src/tests/attach_modal.rs b/crates/debugger_ui/src/tests/attach_modal.rs index 34c67b8690..9266dcc46c 100644 --- a/crates/debugger_ui/src/tests/attach_modal.rs +++ b/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![ diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3961e7e75a..39d26ff7d1 100644 --- a/crates/editor/src/editor.rs +++ b/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, ); }) diff --git a/crates/eval/Cargo.toml b/crates/eval/Cargo.toml index 4192a26d62..67d9b82197 100644 --- a/crates/eval/Cargo.toml +++ b/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 diff --git a/crates/eval/src/example.rs b/crates/eval/src/example.rs index 5aff8522de..058254996f 100644 --- a/crates/eval/src/example.rs +++ b/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, diff --git a/crates/project/src/debugger.rs b/crates/project/src/debugger.rs index 4312d56c1d..d078988a51 100644 --- a/crates/project/src/debugger.rs +++ b/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))] diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index 61a9c31706..eb3524a77b 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/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, node_runtime: NodeRuntime, - next_session_id: AtomicU32, http_client: Arc, environment: Entity, language_registry: Arc, + worktree_store: Entity, toolchain_store: Arc, - locator_store: Arc, - start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>, - _start_debugging_task: Task<()>, + locators: HashMap>, } -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 { - let locator_store = self.locator_store.clone(); - executor.spawn(async move { - let _ = locator_store.resolve_debug_config(&mut definition).await; - definition - }) - } -} - -pub struct RemoteDapStore { +pub struct SshDapStore { upstream_client: AnyProtoClient, upstream_project_id: u64, - event_queue: Option>, } pub struct DapStore { @@ -117,25 +98,17 @@ pub struct DapStore { downstream_client: Option<(AnyProtoClient, u64)>, breakpoint_store: Entity, sessions: BTreeMap>, + next_session_id: u32, + start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>, + _start_debugging_task: Task<()>, } impl EventEmitter 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::); - // client.add_entity_request_handler(Self::handle_dap_command::); - // client.add_entity_request_handler(Self::handle_dap_command::); - // client.add_entity_request_handler(Self::handle_dap_command::); - // client.add_entity_request_handler(Self::handle_dap_command::); - // client.add_entity_request_handler(Self::handle_dap_command::); - // client.add_entity_request_handler(Self::handle_dap_command::); - // client.add_entity_request_handler(Self::handle_dap_command::); - // client.add_entity_request_handler(Self::handle_dap_command::); - // client.add_entity_request_handler(Self::handle_dap_command::); - // client.add_entity_request_handler(Self::handle_dap_command::); - // client.add_entity_request_handler(Self::handle_dap_command::); + 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, environment: Entity, toolchain_store: Arc, + worktree_store: Entity, breakpoint_store: Entity, cx: &mut Context, ) -> 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, + cx: &mut Context, + ) -> 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, + cx: &mut Context, + ) -> Self { + Self::new(DapStoreMode::Collab, breakpoint_store, cx) + } + + fn new( + mode: DapStoreMode, + breakpoint_store: Entity, + cx: &mut Context, + ) -> 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(), - }), + mode, + _start_debugging_task: task, + start_debugging_tx, + next_session_id: 0, downstream_client: None, breakpoint_store, sessions: Default::default(), } } - pub fn new_remote( - project_id: u64, - upstream_client: AnyProtoClient, - breakpoint_store: Entity, - ) -> Self { - Self { - mode: DapStoreMode::Remote(RemoteDapStore { - upstream_client, - upstream_project_id: project_id, - event_queue: Some(VecDeque::default()), - }), - 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, + ) -> Task> { 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"))); + }; + + 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 = 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 remote_event_queue(&mut self) -> Option> { - if let DapStoreMode::Remote(remote) = &mut self.mode { - remote.event_queue.take() - } else { - None + pub fn run_debug_locator( + &mut self, + template: DebugTaskTemplate, + cx: &mut Context, + ) -> Task> { + let Some(locator_name) = template.locator else { + return Task::ready(Ok(template.definition)); + }; + + match &self.mode { + 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(&self) -> Option<&LocalDapStore> { + fn as_local(&self) -> Option<&LocalDapStore> { match &self.mode { DapStoreMode::Local(local_dap_store) => Some(local_dap_store), _ => None, } } - pub fn as_local_mut(&mut self) -> Option<&mut LocalDapStore> { - match &mut 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, cx: &mut Context, ) { - 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, cx: &mut App) -> DapAdapterDelegate { + fn delegate(&self, worktree: &Entity, 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>, cx: &mut Context, ) -> (SessionId, Task>>) { - 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, ) -> Task> { - 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, + envelope: TypedEnvelope, + mut cx: AsyncApp, + ) -> Result { + 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, + envelope: TypedEnvelope, + mut cx: AsyncApp, + ) -> Result { + 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( diff --git a/crates/project/src/debugger/locator_store.rs b/crates/project/src/debugger/locator_store.rs index f599b95615..d542537833 100644 --- a/crates/project/src/debugger/locator_store.rs +++ b/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>, @@ -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, - )]); 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 { + 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)) } diff --git a/crates/project/src/debugger/locator_store/locators.rs b/crates/project/src/debugger/locators.rs similarity index 53% rename from crates/project/src/debugger/locator_store/locators.rs rename to crates/project/src/debugger/locators.rs index e360d31019..f8946f12df 100644 --- a/crates/project/src/debugger/locator_store/locators.rs +++ b/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; } diff --git a/crates/project/src/debugger/locator_store/cargo.rs b/crates/project/src/debugger/locators/cargo.rs similarity index 93% rename from crates/project/src/debugger/locator_store/cargo.rs rename to crates/project/src/debugger/locators/cargo.rs index e6e65c506d..d8de869db7 100644 --- a/crates/project/src/debugger/locator_store/cargo.rs +++ b/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 { 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 { 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) } } diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index ec9b0540bd..0783b85c4f 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -396,7 +396,7 @@ impl LocalMode { } fn request_initialization(&self, cx: &App) -> Task> { - 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()) } diff --git a/crates/project/src/debugger/test.rs b/crates/project/src/debugger/test.rs index d4319061b2..6c8f422124 100644 --- a/crates/project/src/debugger/test.rs +++ b/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) + '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, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9e99a093e3..9a4be5b85e 100644 --- a/crates/project/src/project.rs +++ b/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, buffer_ordered_messages_tx: mpsc::UnboundedSender, languages: Arc, - debug_adapters: Arc, dap_store: Entity, breakpoint_store: Entity, @@ -834,7 +837,6 @@ impl Project { node: NodeRuntime, user_store: Entity, languages: Arc, - debug_adapters: Arc, fs: Arc, env: Option>, 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, ) -> Task>> { 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 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()) + 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) }) - }) - })?; - let config = if let Some(task) = task { - task.await - } else { - config - }; - let binary = adapter - .get_binary(&delegate, &config, user_installed_path, cx) + })? .await?; + 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 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 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 { - &self.debug_adapters - } - pub fn client(&self) -> Arc { self.client.clone() } diff --git a/crates/project/src/task_inventory.rs b/crates/project/src/task_inventory.rs index feded6a9eb..3653ea4401 100644 --- a/crates/project/src/task_inventory.rs +++ b/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::(raw_template).log_err(), - TaskKind::Debug => serde_json::from_value::(raw_template) + TaskKind::Debug => serde_json::from_value::(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; diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index e489841752..8ce6a7f8da 100644 --- a/crates/project/src/terminals.rs +++ b/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>, } @@ -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, + pub arguments: Vec, +} + +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)>, path: Option<&Path>, @@ -559,9 +563,14 @@ fn wrap_for_ssh( venv_directory: Option<&Path>, ) -> (String, Vec) { 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> = 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() }; diff --git a/crates/proto/proto/debugger.proto b/crates/proto/proto/debugger.proto index 7618c80d23..14751c8a38 100644 --- a/crates/proto/proto/debugger.proto +++ b/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 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; +} diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index 9ab143704f..a183a1de5b 100644 --- a/crates/proto/proto/zed.proto +++ b/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; diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index 990e3d51de..fb38c282df 100644 --- a/crates/proto/src/proto.rs +++ b/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!( diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index ef54fcf837..f37551d561 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/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, diff --git a/crates/remote_server/Cargo.toml b/crates/remote_server/Cargo.toml index 01e2bb97ff..43b157483b 100644 --- a/crates/remote_server/Cargo.toml +++ b/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 diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 155534a8fd..c4e1f18852 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/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, pub lsp_store: Entity, pub task_store: Entity, + pub dap_store: Entity, pub settings_observer: Entity, pub next_entry_id: Arc, pub languages: Arc, @@ -54,7 +55,6 @@ pub struct HeadlessAppState { pub http_client: Arc, pub node_runtime: NodeRuntime, pub languages: Arc, - pub debug_adapters: Arc, pub extension_host_proxy: Arc, } @@ -72,7 +72,6 @@ impl HeadlessProject { http_client, node_runtime, languages, - debug_adapters: _debug_adapters, extension_host_proxy: proxy, }: HeadlessAppState, cx: &mut Context, @@ -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, diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index f81af17be9..d23d74537f 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/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, diff --git a/crates/remote_server/src/unix.rs b/crates/remote_server/src/unix.rs index 0218835f5d..56c8f2c13b 100644 --- a/crates/remote_server/src/unix.rs +++ b/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, diff --git a/crates/task/Cargo.toml b/crates/task/Cargo.toml index 89b68e762d..f39b462a13 100644 --- a/crates/task/Cargo.toml +++ b/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 diff --git a/crates/task/src/debug_format.rs b/crates/task/src/debug_format.rs index 8f478aa46f..52c0e84c51 100644 --- a/crates/task/src/debug_format.rs +++ b/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, } -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 { + 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, } /// 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 for DebugRequestType { - fn from(launch_config: LaunchConfig) -> Self { - DebugRequestType::Launch(launch_config) +impl From for DebugRequest { + fn from(launch_config: LaunchRequest) -> Self { + DebugRequest::Launch(launch_config) } } -impl From for DebugRequestType { - fn from(attach_config: AttachConfig) -> Self { - DebugRequestType::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 { - match self { - Self::UserConfigured(DebugRequestType::Launch(launch_config)) => { - launch_config.cwd.clone() - } - _ => None, - } +impl From for DebugRequest { + fn from(attach_config: AttachRequest) -> Self { + DebugRequest::Attach(attach_config) } } -impl TryFrom for DebugTaskDefinition { +impl TryFrom for DebugTaskTemplate { type Error = (); fn try_from(value: TaskTemplate) -> Result { @@ -108,40 +96,40 @@ impl TryFrom 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 { - 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, + #[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, - /// Locator to use - /// -- cargo - pub locator: Option, + pub tcp_connection: Option, /// Whether to tell the debug adapter to stop on entry pub stop_on_entry: Option, } +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 { + 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); +pub struct DebugTaskFile(pub Vec); impl DebugTaskFile { /// Generates JSON schema of Tasks JSON template format. @@ -222,31 +274,17 @@ impl DebugTaskFile { } } -impl TryFrom for TaskTemplates { - type Error = anyhow::Error; - - fn try_from(value: DebugTaskFile) -> Result { - 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() }) diff --git a/crates/task/src/lib.rs b/crates/task/src/lib.rs index dc3bbdb58e..0b2fb63d90 100644 --- a/crates/task/src/lib.rs +++ b/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 { + pub fn resolved_debug_adapter_config(&self) -> Option { 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, } +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()), diff --git a/crates/task/src/task_template.rs b/crates/task/src/task_template.rs index f8f6e82676..b112d5b4a9 100644 --- a/crates/task/src/task_template.rs +++ b/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, + pub tcp_connection: Option, /// Args to send to debug adapter pub initialize_args: Option, /// the locator to use diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index 537209b02f..a027db8774 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/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>, ) { 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 = - 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>, ) { 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); } }; }) diff --git a/crates/tasks_ui/src/tasks_ui.rs b/crates/tasks_ui/src/tasks_ui.rs index 0a1fdfe8a9..7bd60aac28 100644 --- a/crates/tasks_ui/src/tasks_ui.rs +++ b/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 diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 6b562aec23..8b28097127 100644 --- a/crates/terminal/src/terminal.rs +++ b/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), Open(MaybeNavigationTarget), - TaskLocatorReady { task_id: TaskId, success: bool }, } #[derive(Clone, Debug)] @@ -351,7 +351,7 @@ impl TerminalBuilder { max_scroll_history_lines: Option, is_ssh_terminal: bool, window: AnyWindowHandle, - completion_tx: Sender<()>, + completion_tx: Sender>, debug_terminal: bool, cx: &App, ) -> Result { @@ -639,7 +639,7 @@ pub enum SelectionPhase { pub struct Terminal { pty_tx: Notifier, - completion_tx: Sender<()>, + completion_tx: Sender>, term: Arc>>, term_config: Config, events: VecDeque, @@ -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>, 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> { 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, cx: &mut Context) { - self.completion_tx.try_send(()).ok(); + let e: Option = 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 => { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 2f558eab0b..d9cd791068 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/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) { + fn spawn_task( + &mut self, + task: &SpawnInTerminal, + window: &mut Window, + cx: &mut Context, + ) -> Task>> { 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, window: &mut Window, cx: &mut Context, - ) -> Task> { + ) -> Task>> { 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); + +impl workspace::TerminalProvider for TerminalProvider { + fn spawn( + &self, + task: SpawnInTerminal, + window: &mut Window, + cx: &mut App, + ) -> Task> { + 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, } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index c2dbc72b1f..38004378d5 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/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] diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 881f3dbb09..0bba323571 100644 --- a/crates/vim/src/command.rs +++ b/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; }; diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 63a57fe14a..a682a64522 100644 --- a/crates/workspace/Cargo.toml +++ b/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 diff --git a/crates/workspace/src/tasks.rs b/crates/workspace/src/tasks.rs index 21adb4ede5..5b5f0b4f72 100644 --- a/crates/workspace/src/tasks.rs +++ b/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, -) { - 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"); +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, + ) { + 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) + { + self.schedule_resolved_task( + task_source_kind, + spawn_in_terminal, + omit_history, + window, + cx, + ); + } + } + + 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, + ) { + 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(terminal_provider) = self.terminal_provider.as_ref() { + terminal_provider + .spawn(spawn_in_terminal, window, cx) + .detach_and_log_err(cx); + } + } + } + + pub fn schedule_debug_task( + &mut self, + task: ResolvedTask, + window: &mut Window, + cx: &mut Context, + ) { + let Some(debug_config) = task.resolved_debug_adapter_config() else { + log::error!("Debug task has no debug adapter config"); 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, - ); - } -} + 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) + })?; -pub fn schedule_resolved_task( - workspace: &mut Workspace, - task_source_kind: TaskSourceKind, - mut resolved_task: ResolvedTask, - omit_history: bool, - cx: &mut Context, -) { - let debug_config = resolved_task.resolved_debug_adapter_config(); - - 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 !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); - }) + 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, + ) -> Task> { + 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"))) + } } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 9ef35a69a4..06923452dd 100644 --- a/crates/workspace/src/workspace.rs +++ b/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>> = LazyLock::new(|| { .and_then(parse_pixel_position_env_var) }); +pub trait TerminalProvider { + fn spawn( + &self, + task: SpawnInTerminal, + window: &mut Window, + cx: &mut App, + ) -> Task>; +} + actions!( workspace, [ @@ -626,7 +635,6 @@ pub fn register_serializable_item(cx: &mut App) { pub struct AppState { pub languages: Arc, - pub debug_adapters: Arc, pub client: Arc, pub user_store: Entity, pub workspace_store: Entity, @@ -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), - SpawnTask { - action: Box, - }, OpenBundledFile { text: Cow<'static, str>, title: &'static str, @@ -856,11 +859,11 @@ pub struct Workspace { bounds_save_task_queued: Option>, on_prompt_for_new_path: Option, on_prompt_for_open_path: Option, + terminal_provider: Option>, serializable_items_tx: UnboundedSender>, serialized_ssh_project: Option, _items_serializer: Task>, session_id: Option, - debug_task_queue: HashMap, } impl EventEmitter 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 { 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( diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 272bf56677..16f1c6a4c2 100644 --- a/crates/zed/Cargo.toml +++ b/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 diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 9339128ee0..c418e76b01 100644 --- a/crates/zed/src/main.rs +++ b/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(), diff --git a/crates/zlog/src/filter.rs b/crates/zlog/src/filter.rs index 1efeae7bc4..747a3e2261 100644 --- a/crates/zlog/src/filter.rs +++ b/crates/zlog/src/filter.rs @@ -14,7 +14,7 @@ use log; static ENV_FILTER: OnceLock = OnceLock::new(); static SCOPE_MAP: RwLock> = 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 {