diff --git a/Cargo.lock b/Cargo.lock index 347b925bed..40c702347f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -581,6 +581,7 @@ dependencies = [ "client", "collections", "context_server", + "dap", "env_logger 0.11.7", "fs", "futures 0.3.31", @@ -3875,6 +3876,7 @@ dependencies = [ "node_runtime", "parking_lot", "paths", + "regex", "schemars", "serde", "serde_json", @@ -3908,7 +3910,6 @@ dependencies = [ "regex", "serde", "serde_json", - "sysinfo", "task", "util", ] @@ -4642,6 +4643,7 @@ dependencies = [ "client", "clock", "collections", + "dap", "env_logger 0.11.7", "feature_flags", "fs", @@ -11349,6 +11351,7 @@ dependencies = [ "clap", "client", "clock", + "dap", "env_logger 0.11.7", "extension", "extension_host", @@ -16908,6 +16911,7 @@ dependencies = [ "clock", "collections", "component", + "dap", "db", "derive_more", "env_logger 0.11.7", @@ -17284,6 +17288,8 @@ dependencies = [ "command_palette_hooks", "component_preview", "copilot", + "dap", + "dap_adapters", "db", "debugger_tools", "debugger_ui", diff --git a/crates/assistant_eval/Cargo.toml b/crates/assistant_eval/Cargo.toml index d5e30d339d..cd4485a5a2 100644 --- a/crates/assistant_eval/Cargo.toml +++ b/crates/assistant_eval/Cargo.toml @@ -21,6 +21,7 @@ clap.workspace = true client.workspace = true collections.workspace = true context_server.workspace = true +dap.workspace = true env_logger.workspace = true fs.workspace = true futures.workspace = true diff --git a/crates/assistant_eval/src/headless_assistant.rs b/crates/assistant_eval/src/headless_assistant.rs index 041d1a8308..69b8fa2fdc 100644 --- a/crates/assistant_eval/src/headless_assistant.rs +++ b/crates/assistant_eval/src/headless_assistant.rs @@ -3,6 +3,7 @@ use assistant2::{RequestKind, Thread, ThreadEvent, ThreadStore}; use assistant_tool::ToolWorkingSet; use client::{Client, UserStore}; use collections::HashMap; +use dap::DapRegistry; use futures::StreamExt; use gpui::{prelude::*, App, AsyncApp, Entity, SemanticVersion, Subscription, Task}; use language::LanguageRegistry; @@ -50,6 +51,7 @@ impl HeadlessAssistant { app_state.node_runtime.clone(), app_state.user_store.clone(), app_state.languages.clone(), + Arc::new(DapRegistry::default()), app_state.fs.clone(), env, cx, diff --git a/crates/collab/src/tests/remote_editing_collaboration_tests.rs b/crates/collab/src/tests/remote_editing_collaboration_tests.rs index 515a7bd2f7..f79e9efe88 100644 --- a/crates/collab/src/tests/remote_editing_collaboration_tests.rs +++ b/crates/collab/src/tests/remote_editing_collaboration_tests.rs @@ -1,6 +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 _; @@ -85,6 +86,7 @@ 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, @@ -252,6 +254,7 @@ 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, @@ -451,6 +454,7 @@ 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 18be5cb21b..1653782dd9 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -14,6 +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::{channel::oneshot, StreamExt as _}; use git::GitHostingProviderRegistry; @@ -277,12 +278,14 @@ 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(), @@ -795,6 +798,7 @@ 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 7c7bab6110..606cd58015 100644 --- a/crates/dap/Cargo.toml +++ b/crates/dap/Cargo.toml @@ -8,6 +8,10 @@ license = "GPL-3.0-or-later" [lints] workspace = true +[lib] +path = "src/dap.rs" +doctest = false + [features] test-support = [ "gpui/test-support", @@ -35,6 +39,7 @@ log.workspace = true node_runtime.workspace = true parking_lot.workspace = true paths.workspace = true +regex.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 a1053b2ff8..a1e0cc0b8f 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -13,15 +13,16 @@ use serde_json::Value; 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::{Path, PathBuf}, - sync::Arc, + path::PathBuf, + sync::{Arc, LazyLock}, }; -use task::DebugAdapterConfig; +use task::{DebugAdapterConfig, DebugTaskDefinition}; use util::ResultExt; #[derive(Clone, Debug, PartialEq, Eq)] @@ -46,7 +47,7 @@ pub trait DapDelegate { } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] -pub struct DebugAdapterName(pub Arc); +pub struct DebugAdapterName(pub SharedString); impl Deref for DebugAdapterName { type Target = str; @@ -62,9 +63,9 @@ impl AsRef for DebugAdapterName { } } -impl AsRef for DebugAdapterName { - fn as_ref(&self) -> &Path { - Path::new(&*self.0) +impl Borrow for DebugAdapterName { + fn borrow(&self) -> &str { + &self.0 } } @@ -76,7 +77,7 @@ impl std::fmt::Display for DebugAdapterName { impl From for SharedString { fn from(name: DebugAdapterName) -> Self { - SharedString::from(name.0) + name.0 } } @@ -123,7 +124,7 @@ pub async fn download_adapter_from_github( file_type: DownloadedFileType, delegate: &dyn DapDelegate, ) -> Result { - let adapter_path = paths::debug_adapters_dir().join(&adapter_name); + let adapter_path = paths::debug_adapters_dir().join(&adapter_name.as_ref()); let version_path = adapter_path.join(format!("{}_{}", adapter_name, github_version.tag_name)); let fs = delegate.fs(); @@ -288,15 +289,21 @@ pub trait DebugAdapter: 'static + Send + Sync { ) -> Result; /// Should return base configuration to make the debug adapter work - fn request_args(&self, config: &DebugAdapterConfig) -> Value; + fn request_args(&self, config: &DebugTaskDefinition) -> Value; + + fn attach_processes_filter(&self) -> regex::Regex { + EMPTY_REGEX.clone() + } } +static EMPTY_REGEX: LazyLock = + LazyLock::new(|| regex::Regex::new("").expect("Regex compilation to succeed")); #[cfg(any(test, feature = "test-support"))] pub struct FakeAdapter {} #[cfg(any(test, feature = "test-support"))] impl FakeAdapter { - const ADAPTER_NAME: &'static str = "fake-adapter"; + pub const ADAPTER_NAME: &'static str = "fake-adapter"; pub fn new() -> Self { Self {} @@ -351,13 +358,13 @@ impl DebugAdapter for FakeAdapter { unimplemented!("get installed binary"); } - fn request_args(&self, config: &DebugAdapterConfig) -> Value { + fn request_args(&self, config: &DebugTaskDefinition) -> Value { use serde_json::json; use task::DebugRequestType; json!({ "request": match config.request { - DebugRequestType::Launch => "launch", + DebugRequestType::Launch(_) => "launch", DebugRequestType::Attach(_) => "attach", }, "process_id": if let DebugRequestType::Attach(attach_config) = &config.request { @@ -367,4 +374,10 @@ impl DebugAdapter for FakeAdapter { }, }) } + + fn attach_processes_filter(&self) -> regex::Regex { + static REGEX: LazyLock = + LazyLock::new(|| regex::Regex::new("^fake-binary").unwrap()); + REGEX.clone() + } } diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs index bddee89564..aa058620da 100644 --- a/crates/dap/src/client.rs +++ b/crates/dap/src/client.rs @@ -71,7 +71,6 @@ impl DebugAdapterClient { let client_id = this.id; // start handling events/reverse requests - cx.background_spawn(Self::handle_receive_messages( client_id, server_rx, @@ -119,7 +118,6 @@ impl DebugAdapterClient { Ok(message) => message, Err(e) => break Err(e.into()), }; - match message { Message::Event(ev) => { log::debug!("Client {} received event `{}`", client_id.0, &ev); @@ -164,7 +162,6 @@ impl DebugAdapterClient { command: R::COMMAND.to_string(), arguments: Some(serialized_arguments), }; - self.transport_delegate .add_pending_request(sequence_id, callback_tx) .await; @@ -434,7 +431,7 @@ mod tests { let client = DebugAdapterClient::start( crate::client::SessionId(1), - DebugAdapterName(Arc::from("test-adapter")), + DebugAdapterName("test-adapter".into()), DebugAdapterBinary { command: "command".into(), arguments: Default::default(), diff --git a/crates/dap/src/dap.rs b/crates/dap/src/dap.rs new file mode 100644 index 0000000000..e2122d98af --- /dev/null +++ b/crates/dap/src/dap.rs @@ -0,0 +1,17 @@ +pub mod adapters; +pub mod client; +pub mod debugger_settings; +pub mod proto_conversions; +mod registry; +pub mod transport; + +pub use dap_types::*; +pub use registry::DapRegistry; +pub use task::{DebugAdapterConfig, DebugRequestType}; + +pub type ScopeId = u64; +pub type VariableReference = u64; +pub type StackFrameId = u64; + +#[cfg(any(test, feature = "test-support"))] +pub use adapters::FakeAdapter; diff --git a/crates/dap/src/lib.rs b/crates/dap/src/lib.rs deleted file mode 100644 index f8e54aa51c..0000000000 --- a/crates/dap/src/lib.rs +++ /dev/null @@ -1,38 +0,0 @@ -pub mod adapters; -pub mod client; -pub mod debugger_settings; -pub mod proto_conversions; -pub mod transport; - -pub use dap_types::*; -pub use task::{DebugAdapterConfig, DebugAdapterKind, DebugRequestType}; - -pub type ScopeId = u64; -pub type VariableReference = u64; -pub type StackFrameId = u64; - -#[cfg(any(test, feature = "test-support"))] -pub use adapters::FakeAdapter; - -#[cfg(any(test, feature = "test-support"))] -pub fn test_config( - request: DebugRequestType, - fail: Option, - caps: Option, -) -> DebugAdapterConfig { - DebugAdapterConfig { - label: "test config".into(), - kind: DebugAdapterKind::Fake(( - fail.unwrap_or_default(), - caps.unwrap_or(Capabilities { - supports_step_back: Some(false), - ..Default::default() - }), - )), - request, - program: None, - supports_attach: false, - cwd: None, - initialize_args: None, - } -} diff --git a/crates/dap/src/registry.rs b/crates/dap/src/registry.rs new file mode 100644 index 0000000000..2a3f0869fb --- /dev/null +++ b/crates/dap/src/registry.rs @@ -0,0 +1,39 @@ +use parking_lot::RwLock; + +use crate::adapters::{DebugAdapter, DebugAdapterName}; +use std::{collections::BTreeMap, sync::Arc}; + +#[derive(Default)] +struct DapRegistryState { + adapters: BTreeMap>, +} + +#[derive(Default)] +/// Stores available debug adapters. +pub struct DapRegistry(Arc>); + +impl DapRegistry { + pub fn add_adapter(&self, adapter: Arc) { + let name = adapter.name(); + let _previous_value = self.0.write().adapters.insert(name, adapter); + debug_assert!( + _previous_value.is_none(), + "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 9ae462fdc0..7106f076dd 100644 --- a/crates/dap/src/transport.rs +++ b/crates/dap/src/transport.rs @@ -261,8 +261,6 @@ impl TransportDelegate { } } } - - smol::future::yield_now().await; }; log::debug!("Handle adapter log dropped"); @@ -319,8 +317,6 @@ impl TransportDelegate { } Err(error) => break Err(error.into()), } - - smol::future::yield_now().await; }; log::debug!("Handle adapter input dropped"); @@ -360,8 +356,6 @@ impl TransportDelegate { } Err(e) => break Err(e), } - - smol::future::yield_now().await; }; drop(client_tx); @@ -393,8 +387,6 @@ impl TransportDelegate { } Err(error) => break Err(error.into()), } - - smol::future::yield_now().await; }; log::debug!("Handle adapter error dropped"); diff --git a/crates/dap_adapters/Cargo.toml b/crates/dap_adapters/Cargo.toml index 0fe055738a..4abf32190f 100644 --- a/crates/dap_adapters/Cargo.toml +++ b/crates/dap_adapters/Cargo.toml @@ -30,7 +30,6 @@ paths.workspace = true regex.workspace = true serde.workspace = true serde_json.workspace = true -sysinfo.workspace = true task.workspace = true util.workspace = true diff --git a/crates/dap_adapters/src/custom.rs b/crates/dap_adapters/src/custom.rs deleted file mode 100644 index 9598cf91cc..0000000000 --- a/crates/dap_adapters/src/custom.rs +++ /dev/null @@ -1,84 +0,0 @@ -use dap::transport::TcpTransport; -use gpui::AsyncApp; -use serde_json::Value; -use std::{collections::HashMap, ffi::OsString, path::PathBuf}; -use sysinfo::{Pid, Process}; -use task::DebugAdapterConfig; - -use crate::*; - -pub(crate) struct CustomDebugAdapter { - custom_args: CustomArgs, -} - -impl CustomDebugAdapter { - const ADAPTER_NAME: &'static str = "custom_dap"; - - pub(crate) async fn new(custom_args: CustomArgs) -> Result { - Ok(CustomDebugAdapter { custom_args }) - } - - pub fn attach_processes(processes: &HashMap) -> Vec<(&Pid, &Process)> { - processes.iter().collect::>() - } -} - -#[async_trait(?Send)] -impl DebugAdapter for CustomDebugAdapter { - fn name(&self) -> DebugAdapterName { - DebugAdapterName(Self::ADAPTER_NAME.into()) - } - - async fn get_binary( - &self, - _: &dyn DapDelegate, - config: &DebugAdapterConfig, - _: Option, - _: &mut AsyncApp, - ) -> Result { - let connection = if let DebugConnectionType::TCP(connection) = &self.custom_args.connection - { - Some(adapters::TcpArguments { - host: connection.host(), - port: TcpTransport::port(&connection).await?, - timeout: connection.timeout, - }) - } else { - None - }; - let ret = DebugAdapterBinary { - command: self.custom_args.command.clone(), - arguments: self - .custom_args - .args - .clone() - .map(|args| args.iter().map(OsString::from).collect()), - cwd: config.cwd.clone(), - envs: self.custom_args.envs.clone(), - connection, - }; - Ok(ret) - } - - async fn fetch_latest_adapter_version(&self, _: &dyn DapDelegate) -> Result { - bail!("Custom debug adapters don't have latest versions") - } - - async fn install_binary(&self, _: AdapterVersion, _: &dyn DapDelegate) -> Result<()> { - bail!("Custom debug adapters cannot be installed") - } - - async fn get_installed_binary( - &self, - _: &dyn DapDelegate, - _: &DebugAdapterConfig, - _: Option, - _: &mut AsyncApp, - ) -> Result { - bail!("Custom debug adapters cannot be installed") - } - - fn request_args(&self, config: &DebugAdapterConfig) -> Value { - json!({"program": config.program}) - } -} diff --git a/crates/dap_adapters/src/dap_adapters.rs b/crates/dap_adapters/src/dap_adapters.rs index 6a576bb621..c9c0a58ed8 100644 --- a/crates/dap_adapters/src/dap_adapters.rs +++ b/crates/dap_adapters/src/dap_adapters.rs @@ -1,5 +1,3 @@ -mod custom; -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] mod gdb; mod go; mod javascript; @@ -7,16 +5,17 @@ mod lldb; mod php; mod python; -use std::{collections::HashMap, sync::Arc}; +use std::{net::Ipv4Addr, sync::Arc}; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, Result}; use async_trait::async_trait; -use custom::CustomDebugAdapter; -use dap::adapters::{ - self, AdapterVersion, DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName, - GithubRepo, +use dap::{ + adapters::{ + self, AdapterVersion, DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName, + GithubRepo, + }, + DapRegistry, }; -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] use gdb::GdbDebugAdapter; use go::GoDebugAdapter; use javascript::JsDebugAdapter; @@ -24,44 +23,28 @@ use lldb::LldbDebugAdapter; use php::PhpDebugAdapter; use python::PythonDebugAdapter; use serde_json::{json, Value}; -use sysinfo::{Pid, Process}; -use task::{CustomArgs, DebugAdapterConfig, DebugAdapterKind, DebugConnectionType, TCPHost}; +use task::{DebugAdapterConfig, TCPHost}; -pub async fn build_adapter(kind: &DebugAdapterKind) -> Result> { - match kind { - DebugAdapterKind::Custom(start_args) => { - Ok(Arc::new(CustomDebugAdapter::new(start_args.clone()).await?)) - } - DebugAdapterKind::Python(host) => Ok(Arc::new(PythonDebugAdapter::new(host).await?)), - DebugAdapterKind::Php(host) => Ok(Arc::new(PhpDebugAdapter::new(host.clone()).await?)), - DebugAdapterKind::Javascript(host) => { - Ok(Arc::new(JsDebugAdapter::new(host.clone()).await?)) - } - DebugAdapterKind::Lldb => Ok(Arc::new(LldbDebugAdapter::new())), - DebugAdapterKind::Go(host) => Ok(Arc::new(GoDebugAdapter::new(host).await?)), - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - DebugAdapterKind::Gdb => Ok(Arc::new(GdbDebugAdapter::new())), - #[cfg(any(test, feature = "test-support"))] - DebugAdapterKind::Fake(_) => Ok(Arc::new(dap::adapters::FakeAdapter::new())), - #[cfg(not(any(test, feature = "test-support")))] - #[allow(unreachable_patterns)] - _ => unreachable!("Fake variant only exists with test-support feature"), - } +pub fn init(registry: Arc) { + registry.add_adapter(Arc::from(PythonDebugAdapter)); + registry.add_adapter(Arc::from(PhpDebugAdapter)); + registry.add_adapter(Arc::from(JsDebugAdapter::default())); + registry.add_adapter(Arc::from(LldbDebugAdapter)); + registry.add_adapter(Arc::from(GoDebugAdapter)); + registry.add_adapter(Arc::from(GdbDebugAdapter)); } -pub fn attach_processes<'a>( - kind: &DebugAdapterKind, - processes: &'a HashMap, -) -> Vec<(&'a Pid, &'a Process)> { - match kind { - #[cfg(any(test, feature = "test-support"))] - DebugAdapterKind::Fake(_) => processes - .iter() - .filter(|(pid, _)| pid.as_u32() == std::process::id()) - .collect::>(), - DebugAdapterKind::Custom(_) => CustomDebugAdapter::attach_processes(processes), - DebugAdapterKind::Javascript(_) => JsDebugAdapter::attach_processes(processes), - DebugAdapterKind::Lldb => LldbDebugAdapter::attach_processes(processes), - _ => processes.iter().collect::>(), - } +pub(crate) async fn configure_tcp_connection( + tcp_connection: TCPHost, +) -> Result<(Ipv4Addr, u16, Option)> { + let host = tcp_connection.host(); + let timeout = tcp_connection.timeout; + + let port = if let Some(port) = tcp_connection.port { + port + } else { + dap::transport::TcpTransport::port(&tcp_connection).await? + }; + + Ok((host, port, timeout)) } diff --git a/crates/dap_adapters/src/gdb.rs b/crates/dap_adapters/src/gdb.rs index 0e3238b900..f0fde352b1 100644 --- a/crates/dap_adapters/src/gdb.rs +++ b/crates/dap_adapters/src/gdb.rs @@ -1,20 +1,17 @@ use std::ffi::OsStr; -use anyhow::Result; +use anyhow::{bail, Result}; use async_trait::async_trait; use gpui::AsyncApp; -use task::DebugAdapterConfig; +use task::{DebugAdapterConfig, DebugTaskDefinition}; use crate::*; -pub(crate) struct GdbDebugAdapter {} +#[derive(Default)] +pub(crate) struct GdbDebugAdapter; impl GdbDebugAdapter { - const ADAPTER_NAME: &'static str = "gdb"; - - pub(crate) fn new() -> Self { - GdbDebugAdapter {} - } + const ADAPTER_NAME: &'static str = "GDB"; } #[async_trait(?Send)] @@ -26,7 +23,7 @@ impl DebugAdapter for GdbDebugAdapter { async fn get_binary( &self, delegate: &dyn DapDelegate, - config: &DebugAdapterConfig, + _: &DebugAdapterConfig, user_installed_path: Option, _: &mut AsyncApp, ) -> Result { @@ -34,7 +31,6 @@ impl DebugAdapter for GdbDebugAdapter { .filter(|p| p.exists()) .and_then(|p| p.to_str().map(|s| s.to_string())); - /* GDB implements DAP natively so just need to */ let gdb_path = delegate .which(OsStr::new("gdb")) .and_then(|p| p.to_str().map(|s| s.to_string())) @@ -50,7 +46,7 @@ impl DebugAdapter for GdbDebugAdapter { command: gdb_path, arguments: Some(vec!["-i=dap".into()]), envs: None, - cwd: config.cwd.clone(), + cwd: None, connection: None, }) } @@ -77,7 +73,14 @@ impl DebugAdapter for GdbDebugAdapter { unimplemented!("GDB cannot be installed by Zed (yet)") } - fn request_args(&self, config: &DebugAdapterConfig) -> Value { - json!({"program": config.program, "cwd": config.cwd}) + fn request_args(&self, config: &DebugTaskDefinition) -> Value { + match &config.request { + dap::DebugRequestType::Attach(attach_config) => { + json!({"pid": attach_config.process_id}) + } + dap::DebugRequestType::Launch(launch_config) => { + json!({"program": launch_config.program, "cwd": launch_config.cwd}) + } + } } } diff --git a/crates/dap_adapters/src/go.rs b/crates/dap_adapters/src/go.rs index 94bd64d057..85244cbb45 100644 --- a/crates/dap_adapters/src/go.rs +++ b/crates/dap_adapters/src/go.rs @@ -1,25 +1,15 @@ -use dap::transport::TcpTransport; +use anyhow::bail; use gpui::AsyncApp; -use std::{ffi::OsStr, net::Ipv4Addr, path::PathBuf}; +use std::{ffi::OsStr, path::PathBuf}; +use task::DebugTaskDefinition; use crate::*; -pub(crate) struct GoDebugAdapter { - port: u16, - host: Ipv4Addr, - timeout: Option, -} +#[derive(Default, Debug)] +pub(crate) struct GoDebugAdapter; impl GoDebugAdapter { - const ADAPTER_NAME: &'static str = "delve"; - - pub(crate) async fn new(host: &TCPHost) -> Result { - Ok(GoDebugAdapter { - port: TcpTransport::port(host).await?, - host: host.host(), - timeout: host.timeout, - }) - } + const ADAPTER_NAME: &'static str = "Delve"; } #[async_trait(?Send)] @@ -73,28 +63,39 @@ impl DebugAdapter for GoDebugAdapter { .and_then(|p| p.to_str().map(|p| p.to_string())) .ok_or(anyhow!("Dlv not found in path"))?; + let Some(tcp_connection) = config.tcp_connection.clone() else { + bail!("Go Debug Adapter expects tcp connection arguments to be provided"); + }; + let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?; + Ok(DebugAdapterBinary { command: delve_path, arguments: Some(vec![ "dap".into(), "--listen".into(), - format!("{}:{}", self.host, self.port).into(), + format!("{}:{}", host, port).into(), ]), - cwd: config.cwd.clone(), + cwd: None, envs: None, connection: Some(adapters::TcpArguments { - host: self.host, - port: self.port, - timeout: self.timeout, + host, + port, + timeout, }), }) } - fn request_args(&self, config: &DebugAdapterConfig) -> Value { - json!({ - "program": config.program, - "cwd": config.cwd, - "subProcess": true, - }) + fn request_args(&self, config: &DebugTaskDefinition) -> Value { + match &config.request { + dap::DebugRequestType::Attach(attach_config) => { + json!({ + "processId": attach_config.process_id + }) + } + dap::DebugRequestType::Launch(launch_config) => json!({ + "program": launch_config.program, + "cwd": launch_config.cwd, + }), + } } } diff --git a/crates/dap_adapters/src/javascript.rs b/crates/dap_adapters/src/javascript.rs index dc28d4401d..0506c96781 100644 --- a/crates/dap_adapters/src/javascript.rs +++ b/crates/dap_adapters/src/javascript.rs @@ -1,39 +1,28 @@ use adapters::latest_github_release; -use dap::transport::TcpTransport; use gpui::AsyncApp; use regex::Regex; -use std::{collections::HashMap, net::Ipv4Addr, path::PathBuf}; -use sysinfo::{Pid, Process}; -use task::DebugRequestType; +use std::path::PathBuf; +use task::{DebugRequestType, DebugTaskDefinition}; use crate::*; +#[derive(Debug)] pub(crate) struct JsDebugAdapter { - port: u16, - host: Ipv4Addr, - timeout: Option, + attach_processes: Regex, } +impl Default for JsDebugAdapter { + fn default() -> Self { + Self { + attach_processes: Regex::new(r"(?i)^(?:node|bun|iojs)(?:$|\b)") + .expect("Regex compilation to succeed"), + } + } +} impl JsDebugAdapter { - const ADAPTER_NAME: &'static str = "vscode-js-debug"; + const ADAPTER_NAME: &'static str = "JavaScript"; + const ADAPTER_NPM_NAME: &'static str = "vscode-js-debug"; const ADAPTER_PATH: &'static str = "js-debug/src/dapDebugServer.js"; - - pub(crate) async fn new(host: TCPHost) -> Result { - Ok(JsDebugAdapter { - host: host.host(), - timeout: host.timeout, - port: TcpTransport::port(&host).await?, - }) - } - - pub fn attach_processes(processes: &HashMap) -> Vec<(&Pid, &Process)> { - let regex = Regex::new(r"(?i)^(?:node|bun|iojs)(?:$|\b)").unwrap(); - - processes - .iter() - .filter(|(_, process)| regex.is_match(&process.name().to_string_lossy())) - .collect::>() - } } #[async_trait(?Send)] @@ -47,7 +36,7 @@ impl DebugAdapter for JsDebugAdapter { delegate: &dyn DapDelegate, ) -> Result { let release = latest_github_release( - &format!("{}/{}", "microsoft", Self::ADAPTER_NAME), + &format!("{}/{}", "microsoft", Self::ADAPTER_NPM_NAME), true, false, delegate.http_client(), @@ -78,7 +67,7 @@ impl DebugAdapter for JsDebugAdapter { let adapter_path = if let Some(user_installed_path) = user_installed_path { user_installed_path } else { - let adapter_path = paths::debug_adapters_dir().join(self.name()); + let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref()); let file_name_prefix = format!("{}_", self.name()); @@ -89,6 +78,13 @@ impl DebugAdapter for JsDebugAdapter { .ok_or_else(|| anyhow!("Couldn't find JavaScript dap directory"))? }; + let Some(tcp_connection) = config.tcp_connection.clone() else { + anyhow::bail!( + "Javascript Debug Adapter expects tcp connection arguments to be provided" + ); + }; + let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?; + Ok(DebugAdapterBinary { command: delegate .node_runtime() @@ -98,15 +94,15 @@ impl DebugAdapter for JsDebugAdapter { .into_owned(), arguments: Some(vec![ adapter_path.join(Self::ADAPTER_PATH).into(), - self.port.to_string().into(), - self.host.to_string().into(), + port.to_string().into(), + host.to_string().into(), ]), - cwd: config.cwd.clone(), + cwd: None, envs: None, connection: Some(adapters::TcpArguments { - host: self.host, - port: self.port, - timeout: self.timeout, + host, + port, + timeout, }), }) } @@ -127,22 +123,35 @@ impl DebugAdapter for JsDebugAdapter { return Ok(()); } - fn request_args(&self, config: &DebugAdapterConfig) -> Value { - let pid = if let DebugRequestType::Attach(attach_config) = &config.request { - attach_config.process_id - } else { - None - }; - - json!({ - "program": config.program, + fn request_args(&self, config: &DebugTaskDefinition) -> Value { + let mut args = json!({ "type": "pwa-node", "request": match config.request { - DebugRequestType::Launch => "launch", + DebugRequestType::Launch(_) => "launch", DebugRequestType::Attach(_) => "attach", }, - "processId": pid, - "cwd": config.cwd, - }) + }); + let map = args.as_object_mut().unwrap(); + match &config.request { + DebugRequestType::Attach(attach) => { + map.insert("processId".into(), attach.process_id.into()); + } + DebugRequestType::Launch(launch) => { + map.insert("program".into(), launch.program.clone().into()); + map.insert( + "cwd".into(), + launch + .cwd + .as_ref() + .map(|s| s.to_string_lossy().into_owned()) + .into(), + ); + } + } + args + } + + fn attach_processes_filter(&self) -> Regex { + self.attach_processes.clone() } } diff --git a/crates/dap_adapters/src/lldb.rs b/crates/dap_adapters/src/lldb.rs index 4d4ecd00c1..5d46f18db0 100644 --- a/crates/dap_adapters/src/lldb.rs +++ b/crates/dap_adapters/src/lldb.rs @@ -1,25 +1,17 @@ -use std::{collections::HashMap, ffi::OsStr, path::PathBuf}; +use std::{ffi::OsStr, path::PathBuf}; use anyhow::Result; use async_trait::async_trait; use gpui::AsyncApp; -use sysinfo::{Pid, Process}; -use task::{DebugAdapterConfig, DebugRequestType}; +use task::{DebugAdapterConfig, DebugRequestType, DebugTaskDefinition}; use crate::*; -pub(crate) struct LldbDebugAdapter {} +#[derive(Default)] +pub(crate) struct LldbDebugAdapter; impl LldbDebugAdapter { - const ADAPTER_NAME: &'static str = "lldb"; - - pub(crate) fn new() -> Self { - LldbDebugAdapter {} - } - - pub fn attach_processes(processes: &HashMap) -> Vec<(&Pid, &Process)> { - processes.iter().collect::>() - } + const ADAPTER_NAME: &'static str = "LLDB"; } #[async_trait(?Send)] @@ -31,7 +23,7 @@ impl DebugAdapter for LldbDebugAdapter { async fn get_binary( &self, delegate: &dyn DapDelegate, - config: &DebugAdapterConfig, + _: &DebugAdapterConfig, user_installed_path: Option, _: &mut AsyncApp, ) -> Result { @@ -48,7 +40,7 @@ impl DebugAdapter for LldbDebugAdapter { command: lldb_dap_path, arguments: None, envs: None, - cwd: config.cwd.clone(), + cwd: None, connection: None, }) } @@ -75,21 +67,30 @@ impl DebugAdapter for LldbDebugAdapter { unimplemented!("LLDB debug adapter cannot be installed by Zed (yet)") } - fn request_args(&self, config: &DebugAdapterConfig) -> Value { - let pid = if let DebugRequestType::Attach(attach_config) = &config.request { - attach_config.process_id - } else { - None - }; - - json!({ - "program": config.program, + fn request_args(&self, config: &DebugTaskDefinition) -> Value { + let mut args = json!({ "request": match config.request { - DebugRequestType::Launch => "launch", + DebugRequestType::Launch(_) => "launch", DebugRequestType::Attach(_) => "attach", }, - "pid": pid, - "cwd": config.cwd, - }) + }); + let map = args.as_object_mut().unwrap(); + match &config.request { + DebugRequestType::Attach(attach) => { + map.insert("pid".into(), attach.process_id.into()); + } + DebugRequestType::Launch(launch) => { + map.insert("program".into(), launch.program.clone().into()); + map.insert( + "cwd".into(), + launch + .cwd + .as_ref() + .map(|s| s.to_string_lossy().into_owned()) + .into(), + ); + } + } + args } } diff --git a/crates/dap_adapters/src/php.rs b/crates/dap_adapters/src/php.rs index fa7c02694d..c576d90c26 100644 --- a/crates/dap_adapters/src/php.rs +++ b/crates/dap_adapters/src/php.rs @@ -1,27 +1,19 @@ use adapters::latest_github_release; -use dap::{adapters::TcpArguments, transport::TcpTransport}; +use anyhow::bail; +use dap::adapters::TcpArguments; use gpui::AsyncApp; -use std::{net::Ipv4Addr, path::PathBuf}; +use std::path::PathBuf; +use task::DebugTaskDefinition; use crate::*; -pub(crate) struct PhpDebugAdapter { - port: u16, - host: Ipv4Addr, - timeout: Option, -} +#[derive(Default)] +pub(crate) struct PhpDebugAdapter; impl PhpDebugAdapter { - const ADAPTER_NAME: &'static str = "vscode-php-debug"; + const ADAPTER_NAME: &'static str = "PHP"; + const ADAPTER_PACKAGE_NAME: &'static str = "vscode-php-debug"; const ADAPTER_PATH: &'static str = "extension/out/phpDebug.js"; - - pub(crate) async fn new(host: TCPHost) -> Result { - Ok(PhpDebugAdapter { - port: TcpTransport::port(&host).await?, - host: host.host(), - timeout: host.timeout, - }) - } } #[async_trait(?Send)] @@ -35,7 +27,7 @@ impl DebugAdapter for PhpDebugAdapter { delegate: &dyn DapDelegate, ) -> Result { let release = latest_github_release( - &format!("{}/{}", "xdebug", Self::ADAPTER_NAME), + &format!("{}/{}", "xdebug", Self::ADAPTER_PACKAGE_NAME), true, false, delegate.http_client(), @@ -66,7 +58,7 @@ impl DebugAdapter for PhpDebugAdapter { let adapter_path = if let Some(user_installed_path) = user_installed_path { user_installed_path } else { - let adapter_path = paths::debug_adapters_dir().join(self.name()); + let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref()); let file_name_prefix = format!("{}_", self.name()); @@ -77,6 +69,11 @@ impl DebugAdapter for PhpDebugAdapter { .ok_or_else(|| anyhow!("Couldn't find PHP dap directory"))? }; + let Some(tcp_connection) = config.tcp_connection.clone() else { + bail!("PHP Debug Adapter expects tcp connection arguments to be provided"); + }; + let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?; + Ok(DebugAdapterBinary { command: delegate .node_runtime() @@ -86,14 +83,14 @@ impl DebugAdapter for PhpDebugAdapter { .into_owned(), arguments: Some(vec![ adapter_path.join(Self::ADAPTER_PATH).into(), - format!("--server={}", self.port).into(), + format!("--server={}", port).into(), ]), connection: Some(TcpArguments { - port: self.port, - host: self.host, - timeout: self.timeout, + port, + host, + timeout, }), - cwd: config.cwd.clone(), + cwd: None, envs: None, }) } @@ -114,10 +111,18 @@ impl DebugAdapter for PhpDebugAdapter { Ok(()) } - fn request_args(&self, config: &DebugAdapterConfig) -> Value { - json!({ - "program": config.program, - "cwd": config.cwd, - }) + fn request_args(&self, config: &DebugTaskDefinition) -> Value { + match &config.request { + dap::DebugRequestType::Attach(_) => { + // php adapter does not support attaching + json!({}) + } + dap::DebugRequestType::Launch(launch_config) => { + json!({ + "program": launch_config.program, + "cwd": launch_config.cwd, + }) + } + } } } diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index 42c01da8c7..d94648b210 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -1,26 +1,18 @@ use crate::*; -use dap::transport::TcpTransport; +use anyhow::bail; +use dap::DebugRequestType; use gpui::AsyncApp; -use std::{ffi::OsStr, net::Ipv4Addr, path::PathBuf}; +use std::{ffi::OsStr, path::PathBuf}; +use task::DebugTaskDefinition; -pub(crate) struct PythonDebugAdapter { - port: u16, - host: Ipv4Addr, - timeout: Option, -} +#[derive(Default)] +pub(crate) struct PythonDebugAdapter; impl PythonDebugAdapter { - const ADAPTER_NAME: &'static str = "debugpy"; + const ADAPTER_NAME: &'static str = "Debugpy"; + const ADAPTER_PACKAGE_NAME: &'static str = "debugpy"; const ADAPTER_PATH: &'static str = "src/debugpy/adapter"; const LANGUAGE_NAME: &'static str = "Python"; - - pub(crate) async fn new(host: &TCPHost) -> Result { - Ok(PythonDebugAdapter { - port: TcpTransport::port(host).await?, - host: host.host(), - timeout: host.timeout, - }) - } } #[async_trait(?Send)] @@ -34,7 +26,7 @@ impl DebugAdapter for PythonDebugAdapter { delegate: &dyn DapDelegate, ) -> Result { let github_repo = GithubRepo { - repo_name: Self::ADAPTER_NAME.into(), + repo_name: Self::ADAPTER_PACKAGE_NAME.into(), repo_owner: "microsoft".into(), }; @@ -78,12 +70,16 @@ impl DebugAdapter for PythonDebugAdapter { cx: &mut AsyncApp, ) -> Result { const BINARY_NAMES: [&str; 3] = ["python3", "python", "py"]; + let Some(tcp_connection) = config.tcp_connection.clone() else { + bail!("Python Debug Adapter expects tcp connection arguments to be provided"); + }; + let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?; let debugpy_dir = if let Some(user_installed_path) = user_installed_path { user_installed_path } else { - let adapter_path = paths::debug_adapters_dir().join(self.name()); - let file_name_prefix = format!("{}_", self.name()); + let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref()); + let file_name_prefix = format!("{}_", Self::ADAPTER_PACKAGE_NAME); util::fs::find_file_name_in_dir(adapter_path.as_path(), |file_name| { file_name.starts_with(&file_name_prefix) @@ -118,25 +114,36 @@ impl DebugAdapter for PythonDebugAdapter { 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={}", self.port).into(), - format!("--host={}", self.host).into(), + format!("--port={}", port).into(), + format!("--host={}", host).into(), ]), connection: Some(adapters::TcpArguments { - host: self.host, - port: self.port, - timeout: self.timeout, + host, + port, + timeout, }), - cwd: config.cwd.clone(), + cwd: None, envs: None, }) } - fn request_args(&self, config: &DebugAdapterConfig) -> Value { - json!({ - "program": config.program, - "subProcess": true, - "cwd": config.cwd, - "redirectOutput": true, - }) + fn request_args(&self, config: &DebugTaskDefinition) -> Value { + match &config.request { + DebugRequestType::Launch(launch_config) => { + json!({ + "program": launch_config.program, + "subProcess": true, + "cwd": launch_config.cwd, + "redirectOutput": true, + }) + } + dap::DebugRequestType::Attach(attach_config) => { + json!({ + "subProcess": true, + "redirectOutput": true, + "processId": attach_config.process_id + }) + } + } } } diff --git a/crates/debugger_ui/Cargo.toml b/crates/debugger_ui/Cargo.toml index d3e5169232..ee9af7b266 100644 --- a/crates/debugger_ui/Cargo.toml +++ b/crates/debugger_ui/Cargo.toml @@ -8,6 +8,10 @@ license = "GPL-3.0-or-later" [lints] workspace = true +[lib] +path = "src/debugger_ui.rs" +doctest = false + [features] test-support = [ "dap/test-support", diff --git a/crates/debugger_ui/src/attach_modal.rs b/crates/debugger_ui/src/attach_modal.rs index 4756b0d693..ac2219b4c8 100644 --- a/crates/debugger_ui/src/attach_modal.rs +++ b/crates/debugger_ui/src/attach_modal.rs @@ -3,8 +3,8 @@ use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::Subscription; use gpui::{DismissEvent, Entity, EventEmitter, Focusable, Render}; use picker::{Picker, PickerDelegate}; -use project::debugger::attach_processes; +use std::cell::LazyCell; use std::sync::Arc; use sysinfo::System; use ui::{prelude::*, Context, Tooltip}; @@ -13,10 +13,10 @@ use util::debug_panic; use workspace::ModalView; #[derive(Debug, Clone)] -struct Candidate { - pid: u32, - name: String, - command: Vec, +pub(super) struct Candidate { + pub(super) pid: u32, + pub(super) name: SharedString, + pub(super) command: Vec, } pub(crate) struct AttachModalDelegate { @@ -24,16 +24,20 @@ pub(crate) struct AttachModalDelegate { matches: Vec, placeholder_text: Arc, project: Entity, - debug_config: task::DebugAdapterConfig, - candidates: Option>, + debug_config: task::DebugTaskDefinition, + candidates: Arc<[Candidate]>, } impl AttachModalDelegate { - pub fn new(project: Entity, debug_config: task::DebugAdapterConfig) -> Self { + fn new( + project: Entity, + debug_config: task::DebugTaskDefinition, + candidates: Arc<[Candidate]>, + ) -> Self { Self { project, debug_config, - candidates: None, + candidates, selected_index: 0, matches: Vec::default(), placeholder_text: Arc::from("Select the process you want to attach the debugger to"), @@ -49,12 +53,56 @@ pub struct AttachModal { impl AttachModal { pub fn new( project: Entity, - debug_config: task::DebugAdapterConfig, + debug_config: task::DebugTaskDefinition, window: &mut Window, cx: &mut Context, ) -> Self { + let mut processes: Vec<_> = System::new_all() + .processes() + .values() + .map(|process| { + let name = process.name().to_string_lossy().into_owned(); + Candidate { + name: name.into(), + pid: process.pid().as_u32(), + command: process + .cmd() + .iter() + .map(|s| s.to_string_lossy().to_string()) + .collect::>(), + } + }) + .collect(); + processes.sort_by_key(|k| k.name.clone()); + Self::with_processes(project, debug_config, processes, window, cx) + } + + pub(super) fn with_processes( + project: Entity, + debug_config: task::DebugTaskDefinition, + processes: Vec, + window: &mut Window, + cx: &mut Context, + ) -> Self { + let adapter = project + .read(cx) + .debug_adapters() + .adapter(&debug_config.adapter); + let filter = LazyCell::new(|| adapter.map(|adapter| adapter.attach_processes_filter())); + let processes = processes + .into_iter() + .filter(|process| { + filter + .as_ref() + .map_or(false, |filter| filter.is_match(&process.name)) + }) + .collect(); let picker = cx.new(|cx| { - Picker::uniform_list(AttachModalDelegate::new(project, debug_config), window, cx) + Picker::uniform_list( + AttachModalDelegate::new(project, debug_config, processes), + window, + cx, + ) }); Self { _subscription: cx.subscribe(&picker, |_, _, _, cx| { @@ -116,32 +164,7 @@ impl PickerDelegate for AttachModalDelegate { ) -> gpui::Task<()> { cx.spawn(async move |this, cx| { let Some(processes) = this - .update(cx, |this, _| { - if let Some(processes) = this.delegate.candidates.clone() { - processes - } else { - let system = System::new_all(); - - let processes = - attach_processes(&this.delegate.debug_config.kind, &system.processes()); - let candidates = processes - .into_iter() - .map(|(pid, process)| Candidate { - pid: pid.as_u32(), - name: process.name().to_string_lossy().into_owned(), - command: process - .cmd() - .iter() - .map(|s| s.to_string_lossy().to_string()) - .collect::>(), - }) - .collect::>(); - - let _ = this.delegate.candidates.insert(candidates.clone()); - - candidates - } - }) + .update(cx, |this, _| this.delegate.candidates.clone()) .ok() else { return; @@ -176,7 +199,6 @@ impl PickerDelegate for AttachModalDelegate { let delegate = &mut this.delegate; delegate.matches = matches; - delegate.candidates = Some(processes); if delegate.matches.is_empty() { delegate.selected_index = 0; @@ -195,7 +217,7 @@ impl PickerDelegate for AttachModalDelegate { .get(self.selected_index()) .and_then(|current_match| { let ix = current_match.candidate_id; - self.candidates.as_ref().map(|candidates| &candidates[ix]) + self.candidates.get(ix) }); let Some(candidate) = candidate else { @@ -206,7 +228,7 @@ impl PickerDelegate for AttachModalDelegate { DebugRequestType::Attach(config) => { config.process_id = Some(candidate.pid); } - DebugRequestType::Launch => { + DebugRequestType::Launch(_) => { debug_panic!("Debugger attach modal used on launch debug config"); return; } @@ -214,7 +236,13 @@ impl PickerDelegate for AttachModalDelegate { let config = self.debug_config.clone(); self.project - .update(cx, |project, cx| project.start_debug_session(config, cx)) + .update(cx, |project, cx| { + #[cfg(any(test, feature = "test-support"))] + let ret = project.fake_debug_session(config.request, None, false, cx); + #[cfg(not(any(test, feature = "test-support")))] + let ret = project.start_debug_session(config.into(), cx); + ret + }) .detach_and_log_err(cx); cx.emit(DismissEvent); @@ -222,7 +250,6 @@ impl PickerDelegate for AttachModalDelegate { fn dismissed(&mut self, _window: &mut Window, cx: &mut Context>) { self.selected_index = 0; - self.candidates.take(); cx.emit(DismissEvent); } @@ -234,9 +261,8 @@ impl PickerDelegate for AttachModalDelegate { _window: &mut Window, _: &mut Context>, ) -> Option { - let candidates = self.candidates.as_ref()?; let hit = &self.matches[ix]; - let candidate = &candidates.get(hit.candidate_id)?; + let candidate = self.candidates.get(hit.candidate_id)?; Some( ListItem::new(SharedString::from(format!("process-entry-{ix}"))) @@ -279,9 +305,8 @@ impl PickerDelegate for AttachModalDelegate { } } -#[allow(dead_code)] #[cfg(any(test, feature = "test-support"))] -pub(crate) fn process_names(modal: &AttachModal, cx: &mut Context) -> Vec { +pub(crate) fn _process_names(modal: &AttachModal, cx: &mut Context) -> Vec { modal.picker.update(cx, |picker, _| { picker .delegate diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index d398534ffb..15f72e5dfa 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -3,8 +3,8 @@ use anyhow::{anyhow, Result}; use collections::HashMap; use command_palette_hooks::CommandPaletteFilter; use dap::{ - client::SessionId, debugger_settings::DebuggerSettings, ContinuedEvent, DebugAdapterConfig, - LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent, + client::SessionId, debugger_settings::DebuggerSettings, ContinuedEvent, LoadedSourceEvent, + ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent, }; use futures::{channel::mpsc, SinkExt as _}; use gpui::{ @@ -19,6 +19,7 @@ use project::{ use rpc::proto::{self}; use settings::Settings; use std::{any::TypeId, path::PathBuf}; +use task::DebugTaskDefinition; use terminal_view::terminal_panel::TerminalPanel; use ui::prelude::*; use util::ResultExt; @@ -52,7 +53,7 @@ pub struct DebugPanel { project: WeakEntity, workspace: WeakEntity, _subscriptions: Vec, - pub(crate) last_inert_config: Option, + pub(crate) last_inert_config: Option, } impl DebugPanel { diff --git a/crates/debugger_ui/src/lib.rs b/crates/debugger_ui/src/debugger_ui.rs similarity index 100% rename from crates/debugger_ui/src/lib.rs rename to crates/debugger_ui/src/debugger_ui.rs diff --git a/crates/debugger_ui/src/session.rs b/crates/debugger_ui/src/session.rs index 0022cd2aa3..92f8d49814 100644 --- a/crates/debugger_ui/src/session.rs +++ b/crates/debugger_ui/src/session.rs @@ -6,7 +6,6 @@ mod starting; use std::time::Duration; use dap::client::SessionId; -use dap::DebugAdapterConfig; use failed::FailedState; use gpui::{ percentage, Animation, AnimationExt, AnyElement, App, Entity, EventEmitter, FocusHandle, @@ -19,6 +18,7 @@ use project::Project; use rpc::proto::{self, PeerId}; use running::RunningState; use starting::{StartingEvent, StartingState}; +use task::DebugTaskDefinition; use ui::{prelude::*, Indicator}; use util::ResultExt; use workspace::{ @@ -73,7 +73,7 @@ impl DebugSession { project: Entity, workspace: WeakEntity, debug_panel: WeakEntity, - config: Option, + config: Option, window: &mut Window, cx: &mut App, ) -> Entity { @@ -171,7 +171,7 @@ impl DebugSession { .flatten() .expect("worktree-less project"); let Ok((new_session_id, task)) = dap_store.update(cx, |store, cx| { - store.new_session(config, &worktree, None, cx) + store.new_session(config.into(), &worktree, None, cx) }) else { return; }; diff --git a/crates/debugger_ui/src/session/inert.rs b/crates/debugger_ui/src/session/inert.rs index d7a2f5a2ca..dcbaa13522 100644 --- a/crates/debugger_ui/src/session/inert.rs +++ b/crates/debugger_ui/src/session/inert.rs @@ -1,10 +1,10 @@ use std::path::PathBuf; -use dap::{DebugAdapterConfig, DebugAdapterKind, DebugRequestType}; +use dap::DebugRequestType; use editor::{Editor, EditorElement, EditorStyle}; use gpui::{App, AppContext, Entity, EventEmitter, FocusHandle, Focusable, TextStyle, WeakEntity}; use settings::Settings as _; -use task::TCPHost; +use task::{DebugTaskDefinition, LaunchConfig, TCPHost}; use theme::ThemeSettings; use ui::{ div, h_flex, relative, v_flex, ActiveTheme as _, ButtonCommon, ButtonLike, Clickable, Context, @@ -35,7 +35,7 @@ impl SpawnMode { impl From for SpawnMode { fn from(request: DebugRequestType) -> Self { match request { - DebugRequestType::Launch => SpawnMode::Launch, + DebugRequestType::Launch(_) => SpawnMode::Launch, DebugRequestType::Attach(_) => SpawnMode::Attach, } } @@ -55,18 +55,13 @@ impl InertState { pub(super) fn new( workspace: WeakEntity, default_cwd: &str, - debug_config: Option, + debug_config: Option, window: &mut Window, cx: &mut Context, ) -> Self { - let selected_debugger = debug_config.as_ref().and_then(|config| match config.kind { - DebugAdapterKind::Lldb => Some("LLDB".into()), - DebugAdapterKind::Go(_) => Some("Delve".into()), - DebugAdapterKind::Php(_) => Some("PHP".into()), - DebugAdapterKind::Javascript(_) => Some("JavaScript".into()), - DebugAdapterKind::Python(_) => Some("Debugpy".into()), - _ => None, - }); + let selected_debugger = debug_config + .as_ref() + .map(|config| SharedString::from(config.adapter.clone())); let spawn_mode = debug_config .as_ref() @@ -75,7 +70,10 @@ impl InertState { let program = debug_config .as_ref() - .and_then(|config| config.program.to_owned()); + .and_then(|config| match &config.request { + DebugRequestType::Attach(_) => None, + DebugRequestType::Launch(launch_config) => Some(launch_config.program.clone()), + }); let program_editor = cx.new(|cx| { let mut editor = Editor::single_line(window, cx); @@ -88,7 +86,10 @@ impl InertState { }); let cwd = debug_config - .and_then(|config| config.cwd.map(|cwd| cwd.to_owned())) + .and_then(|config| match &config.request { + DebugRequestType::Attach(_) => None, + DebugRequestType::Launch(launch_config) => launch_config.cwd.clone(), + }) .unwrap_or_else(|| PathBuf::from(default_cwd)); let cwd_editor = cx.new(|cx| { @@ -116,7 +117,7 @@ impl Focusable for InertState { } pub(crate) enum InertEvent { - Spawned { config: DebugAdapterConfig }, + Spawned { config: DebugTaskDefinition }, } impl EventEmitter for InertState {} @@ -130,6 +131,7 @@ impl Render for InertState { cx: &mut ui::Context<'_, Self>, ) -> impl ui::IntoElement { let weak = cx.weak_entity(); + let workspace = self.workspace.clone(); let disable_buttons = self.selected_debugger.is_none(); let spawn_button = ButtonLike::new_rounded_left("spawn-debug-session") .child(Label::new(self.spawn_mode.label()).size(LabelSize::Small)) @@ -137,21 +139,26 @@ impl Render for InertState { if this.spawn_mode == SpawnMode::Launch { let program = this.program_editor.read(cx).text(cx); let cwd = PathBuf::from(this.cwd_editor.read(cx).text(cx)); - let kind = - kind_for_label(this.selected_debugger.as_deref().unwrap_or_else(|| { + let kind = this + .selected_debugger + .as_deref() + .unwrap_or_else(|| { unimplemented!( "Automatic selection of a debugger based on users project" ) - })); + }) + .to_string(); + cx.emit(InertEvent::Spawned { - config: DebugAdapterConfig { + config: DebugTaskDefinition { label: "hard coded".into(), - kind, - request: DebugRequestType::Launch, - program: Some(program), - cwd: Some(cwd), + adapter: kind, + request: DebugRequestType::Launch(LaunchConfig { + program, + cwd: Some(cwd), + }), + tcp_connection: Some(TCPHost::default()), initialize_args: None, - supports_attach: false, }, }); } else { @@ -159,6 +166,7 @@ impl Render for InertState { } })) .disabled(disable_buttons); + v_flex() .track_focus(&self.focus_handle) .size_full() @@ -179,28 +187,36 @@ impl Render for InertState { .as_ref() .unwrap_or_else(|| &SELECT_DEBUGGER_LABEL) .clone(), - ContextMenu::build(window, cx, move |this, _, _| { - let setter_for_name = |name: &'static str| { + ContextMenu::build(window, cx, move |mut this, _, cx| { + let setter_for_name = |name: SharedString| { let weak = weak.clone(); move |_: &mut Window, cx: &mut App| { - let name = name; - (&weak) - .update(cx, move |this, _| { - this.selected_debugger = Some(name.into()); - }) - .ok(); + let name = name.clone(); + weak.update(cx, move |this, cx| { + this.selected_debugger = Some(name.clone()); + cx.notify(); + }) + .ok(); } }; - this.entry("GDB", None, setter_for_name("GDB")) - .entry("Delve", None, setter_for_name("Delve")) - .entry("LLDB", None, setter_for_name("LLDB")) - .entry("PHP", None, setter_for_name("PHP")) - .entry( - "JavaScript", + let available_adapters = workspace + .update(cx, |this, cx| { + this.project() + .read(cx) + .debug_adapters() + .enumerate_adapters() + }) + .ok() + .unwrap_or_default(); + + for adapter in available_adapters { + this = this.entry( + adapter.0.clone(), None, - setter_for_name("JavaScript"), - ) - .entry("Debugpy", None, setter_for_name("Debugpy")) + setter_for_name(adapter.0.clone()), + ); + } + this }), )), ), @@ -265,18 +281,6 @@ impl Render for InertState { } } -fn kind_for_label(label: &str) -> DebugAdapterKind { - match label { - "LLDB" => DebugAdapterKind::Lldb, - "Debugpy" => DebugAdapterKind::Python(TCPHost::default()), - "JavaScript" => DebugAdapterKind::Javascript(TCPHost::default()), - "PHP" => DebugAdapterKind::Php(TCPHost::default()), - "Delve" => DebugAdapterKind::Go(TCPHost::default()), - _ => { - unimplemented!() - } // Maybe we should set a toast notification here - } -} impl InertState { fn render_editor(editor: &Entity, cx: &Context) -> impl IntoElement { let settings = ThemeSettings::get_global(cx); @@ -302,19 +306,20 @@ impl InertState { } fn attach(&self, window: &mut Window, cx: &mut Context) { - let cwd = PathBuf::from(self.cwd_editor.read(cx).text(cx)); - let kind = kind_for_label(self.selected_debugger.as_deref().unwrap_or_else(|| { - unimplemented!("Automatic selection of a debugger based on users project") - })); + let kind = self + .selected_debugger + .as_deref() + .map(|s| s.to_string()) + .unwrap_or_else(|| { + unimplemented!("Automatic selection of a debugger based on users project") + }); - let config = DebugAdapterConfig { + let config = DebugTaskDefinition { label: "hard coded attach".into(), - kind, + adapter: kind, request: DebugRequestType::Attach(task::AttachConfig { process_id: None }), - program: None, - cwd: Some(cwd), initialize_args: None, - supports_attach: true, + tcp_connection: Some(TCPHost::default()), }; let _ = self.workspace.update(cx, |workspace, cx| { diff --git a/crates/debugger_ui/src/tests/attach_modal.rs b/crates/debugger_ui/src/tests/attach_modal.rs index 43812c7333..0d6c7a3bbd 100644 --- a/crates/debugger_ui/src/tests/attach_modal.rs +++ b/crates/debugger_ui/src/tests/attach_modal.rs @@ -1,11 +1,11 @@ -use crate::*; +use crate::{attach_modal::Candidate, *}; use attach_modal::AttachModal; -use dap::client::SessionId; +use dap::{client::SessionId, FakeAdapter}; use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; use menu::Confirm; use project::{FakeFs, Project}; use serde_json::json; -use task::AttachConfig; +use task::{AttachConfig, DebugTaskDefinition, TCPHost}; use tests::{init_test, init_test_workspace}; #[gpui::test] @@ -27,14 +27,12 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session( - dap::test_config( - dap::DebugRequestType::Attach(AttachConfig { - process_id: Some(10), - }), - None, - None, - ), + project.fake_debug_session( + dap::DebugRequestType::Attach(AttachConfig { + process_id: Some(10), + }), + None, + false, cx, ) }); @@ -83,13 +81,32 @@ async fn test_show_attach_modal_and_select_process( let attach_modal = workspace .update(cx, |workspace, window, cx| { workspace.toggle_modal(window, cx, |window, cx| { - AttachModal::new( + AttachModal::with_processes( project.clone(), - dap::test_config( - dap::DebugRequestType::Attach(AttachConfig { process_id: None }), - None, - None, - ), + DebugTaskDefinition { + adapter: FakeAdapter::ADAPTER_NAME.into(), + request: dap::DebugRequestType::Attach(AttachConfig::default()), + label: "attach example".into(), + initialize_args: None, + tcp_connection: Some(TCPHost::default()), + }, + vec![ + Candidate { + pid: 0, + name: "fake-binary-1".into(), + command: vec![], + }, + Candidate { + pid: 3, + name: "non-fake-binary-1".into(), + command: vec![], + }, + Candidate { + pid: 1, + name: "fake-binary-2".into(), + command: vec![], + }, + ], window, cx, ) @@ -105,10 +122,10 @@ async fn test_show_attach_modal_and_select_process( workspace .update(cx, |_, _, cx| { let names = - attach_modal.update(cx, |modal, cx| attach_modal::process_names(&modal, cx)); + attach_modal.update(cx, |modal, cx| attach_modal::_process_names(&modal, cx)); - // we filtered out all processes that are not the current process(zed itself) - assert_eq!(1, names.len()); + // we filtered out all processes that are not starting with `fake-binary` + assert_eq!(2, names.len()); }) .unwrap(); diff --git a/crates/debugger_ui/src/tests/console.rs b/crates/debugger_ui/src/tests/console.rs index eae372f792..43d5de0c60 100644 --- a/crates/debugger_ui/src/tests/console.rs +++ b/crates/debugger_ui/src/tests/console.rs @@ -3,6 +3,7 @@ use dap::requests::StackTrace; use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; use project::{FakeFs, Project}; use serde_json::json; +use task::LaunchConfig; use tests::{init_test, init_test_workspace}; #[gpui::test] @@ -29,8 +30,10 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp .unwrap(); let task = project.update(cx, |project, cx| { - project.start_debug_session( - dap::test_config(dap::DebugRequestType::Launch, None, None), + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, cx, ) }); diff --git a/crates/debugger_ui/src/tests/debugger_panel.rs b/crates/debugger_ui/src/tests/debugger_panel.rs index db5173bf17..72a082f1f1 100644 --- a/crates/debugger_ui/src/tests/debugger_panel.rs +++ b/crates/debugger_ui/src/tests/debugger_panel.rs @@ -5,8 +5,8 @@ use dap::{ Continue, Disconnect, Launch, Next, RunInTerminal, SetBreakpoints, StackTrace, StartDebugging, StepBack, StepIn, StepOut, Threads, }, - DebugRequestType, ErrorResponse, RunInTerminalRequestArguments, SourceBreakpoint, - StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest, + ErrorResponse, RunInTerminalRequestArguments, SourceBreakpoint, StartDebuggingRequestArguments, + StartDebuggingRequestArgumentsRequest, }; use editor::{ actions::{self}, @@ -25,6 +25,7 @@ use std::{ Arc, }, }; +use task::LaunchConfig; use terminal_view::{terminal_panel::TerminalPanel, TerminalView}; use tests::{active_debug_session_panel, init_test, init_test_workspace}; use util::path; @@ -49,7 +50,12 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx) + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, + cx, + ) }); let session = task.await.unwrap(); @@ -201,7 +207,12 @@ async fn test_we_can_only_have_one_panel_per_debug_session( let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx) + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, + cx, + ) }); let session = task.await.unwrap(); @@ -385,7 +396,12 @@ async fn test_handle_successful_run_in_terminal_reverse_request( let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx) + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, + cx, + ) }); let session = task.await.unwrap(); @@ -475,7 +491,12 @@ async fn test_handle_error_run_in_terminal_reverse_request( let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx) + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, + cx, + ) }); let session = task.await.unwrap(); @@ -555,7 +576,12 @@ async fn test_handle_start_debugging_reverse_request( let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx) + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, + cx, + ) }); let session = task.await.unwrap(); @@ -668,7 +694,12 @@ async fn test_shutdown_children_when_parent_session_shutdown( let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx) + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, + cx, + ) }); let parent_session = task.await.unwrap(); @@ -776,7 +807,12 @@ async fn test_shutdown_parent_session_if_all_children_are_shutdown( let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx) + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, + cx, + ) }); let parent_session = task.await.unwrap(); @@ -891,15 +927,13 @@ async fn test_debug_panel_item_thread_status_reset_on_failure( let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session( - dap::test_config( - DebugRequestType::Launch, - None, - Some(dap::Capabilities { - supports_step_back: Some(true), - ..Default::default() - }), - ), + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + Some(dap::Capabilities { + supports_step_back: Some(true), + ..Default::default() + }), + false, cx, ) }); @@ -1122,7 +1156,12 @@ async fn test_send_breakpoints_when_editor_has_been_saved( .unwrap(); let task = project.update(cx, |project, cx| { - project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx) + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, + cx, + ) }); let session = task.await.unwrap(); @@ -1347,7 +1386,12 @@ async fn test_unsetting_breakpoints_on_clear_breakpoint_action( }); let task = project.update(cx, |project, cx| { - project.start_debug_session(dap::test_config(DebugRequestType::Launch, None, None), cx) + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, + cx, + ) }); let session = task.await.unwrap(); @@ -1419,8 +1463,10 @@ async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails( let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session( - dap::test_config(DebugRequestType::Launch, Some(true), None), + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + true, cx, ) }); diff --git a/crates/debugger_ui/src/tests/module_list.rs b/crates/debugger_ui/src/tests/module_list.rs index 98574d4b9d..91bc195f9f 100644 --- a/crates/debugger_ui/src/tests/module_list.rs +++ b/crates/debugger_ui/src/tests/module_list.rs @@ -5,7 +5,7 @@ use crate::{ }; use dap::{ requests::{Modules, StackTrace, Threads}, - DebugRequestType, StoppedEvent, + StoppedEvent, }; use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; use project::{FakeFs, Project}; @@ -13,6 +13,7 @@ use std::sync::{ atomic::{AtomicBool, AtomicI32, Ordering}, Arc, }; +use task::LaunchConfig; #[gpui::test] async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext) { @@ -30,15 +31,13 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext) let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session( - dap::test_config( - DebugRequestType::Launch, - None, - Some(dap::Capabilities { - supports_modules_request: Some(true), - ..Default::default() - }), - ), + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + Some(dap::Capabilities { + supports_modules_request: Some(true), + ..Default::default() + }), + false, cx, ) }); diff --git a/crates/debugger_ui/src/tests/stack_frame_list.rs b/crates/debugger_ui/src/tests/stack_frame_list.rs index 7edfa01b26..5f3d3d601b 100644 --- a/crates/debugger_ui/src/tests/stack_frame_list.rs +++ b/crates/debugger_ui/src/tests/stack_frame_list.rs @@ -12,6 +12,7 @@ use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; use project::{FakeFs, Project}; use serde_json::json; use std::sync::Arc; +use task::LaunchConfig; use unindent::Unindent as _; use util::path; @@ -52,8 +53,10 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame( let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session( - dap::test_config(dap::DebugRequestType::Launch, None, None), + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, cx, ) }); @@ -240,8 +243,10 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session( - dap::test_config(dap::DebugRequestType::Launch, None, None), + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, cx, ) }); @@ -513,8 +518,10 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session( - dap::test_config(dap::DebugRequestType::Launch, None, None), + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, cx, ) }); diff --git a/crates/debugger_ui/src/tests/variable_list.rs b/crates/debugger_ui/src/tests/variable_list.rs index 136c48a150..958470dd10 100644 --- a/crates/debugger_ui/src/tests/variable_list.rs +++ b/crates/debugger_ui/src/tests/variable_list.rs @@ -17,6 +17,7 @@ use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; use menu::{SelectFirst, SelectNext, SelectPrevious}; use project::{FakeFs, Project}; use serde_json::json; +use task::LaunchConfig; use unindent::Unindent as _; use util::path; @@ -56,8 +57,10 @@ async fn test_basic_fetch_initial_scope_and_variables( let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session( - dap::test_config(dap::DebugRequestType::Launch, None, None), + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, cx, ) }); @@ -283,8 +286,10 @@ async fn test_fetch_variables_for_multiple_scopes( let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session( - dap::test_config(dap::DebugRequestType::Launch, None, None), + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, cx, ) }); @@ -562,8 +567,10 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session( - dap::test_config(dap::DebugRequestType::Launch, None, None), + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, cx, ) }); @@ -1362,8 +1369,10 @@ async fn test_variable_list_only_sends_requests_when_rendering( let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session( - dap::test_config(dap::DebugRequestType::Launch, None, None), + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, cx, ) }); @@ -1639,8 +1648,10 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame( let cx = &mut VisualTestContext::from_window(*workspace, cx); let task = project.update(cx, |project, cx| { - project.start_debug_session( - dap::test_config(dap::DebugRequestType::Launch, None, None), + project.fake_debug_session( + dap::DebugRequestType::Launch(LaunchConfig::default()), + None, + false, cx, ) }); diff --git a/crates/evals/Cargo.toml b/crates/evals/Cargo.toml index 9661da04eb..ba1e3c5ef8 100644 --- a/crates/evals/Cargo.toml +++ b/crates/evals/Cargo.toml @@ -19,6 +19,7 @@ clap.workspace = true client.workspace = true clock.workspace = true collections.workspace = true +dap.workspace = true env_logger.workspace = true feature_flags.workspace = true fs.workspace = true diff --git a/crates/evals/src/eval.rs b/crates/evals/src/eval.rs index 8bcced25be..2c8f05c86d 100644 --- a/crates/evals/src/eval.rs +++ b/crates/evals/src/eval.rs @@ -4,6 +4,7 @@ use clap::Parser; use client::{Client, UserStore}; use clock::RealSystemClock; use collections::BTreeMap; +use dap::DapRegistry; use feature_flags::FeatureFlagAppExt as _; use gpui::{AppContext as _, AsyncApp, BackgroundExecutor, Entity}; use http_client::{HttpClient, Method}; @@ -302,6 +303,7 @@ async fn run_evaluation( )); let language_registry = Arc::new(LanguageRegistry::new(executor.clone())); + let debug_adapters = Arc::new(DapRegistry::default()); cx.update(|cx| languages::init(language_registry.clone(), node_runtime.clone(), cx)) .unwrap(); @@ -346,6 +348,7 @@ async fn run_evaluation( node_runtime.clone(), user_store.clone(), language_registry.clone(), + debug_adapters.clone(), fs.clone(), None, cx, diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 429a982ca9..e0f667d861 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -37,7 +37,6 @@ client.workspace = true clock.workspace = true collections.workspace = true dap.workspace = true -dap_adapters.workspace = true extension.workspace = true fancy-regex.workspace = true fs.workspace = true diff --git a/crates/project/src/debugger.rs b/crates/project/src/debugger.rs index f8e9c9005e..695d5196ec 100644 --- a/crates/project/src/debugger.rs +++ b/crates/project/src/debugger.rs @@ -15,5 +15,3 @@ pub mod breakpoint_store; pub mod dap_command; pub mod dap_store; pub mod session; - -pub use dap_adapters::attach_processes; diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index f102e6182a..f75d08abd3 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -20,10 +20,9 @@ use dap::{ Completions, Evaluate, Request as _, RunInTerminal, SetExpression, SetVariable, StartDebugging, }, - Capabilities, CompletionItem, CompletionsArguments, ErrorResponse, EvaluateArguments, - EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, + Capabilities, CompletionItem, CompletionsArguments, DapRegistry, ErrorResponse, + EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, SetExpressionArguments, SetVariableArguments, Source, StartDebuggingRequestArguments, - StartDebuggingRequestArgumentsRequest, }; use fs::Fs; use futures::{ @@ -51,7 +50,7 @@ use std::{ sync::{atomic::Ordering::SeqCst, Arc}, }; use std::{collections::VecDeque, sync::atomic::AtomicU32}; -use task::{AttachConfig, DebugAdapterConfig, DebugRequestType}; +use task::{DebugAdapterConfig, DebugRequestDisposition}; use util::ResultExt as _; use worktree::Worktree; @@ -89,6 +88,7 @@ pub struct LocalDapStore { worktree_store: Entity, environment: Entity, language_registry: Arc, + debug_adapters: Arc, toolchain_store: Arc, start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>, _start_debugging_task: Task<()>, @@ -138,6 +138,7 @@ impl DapStore { node_runtime: NodeRuntime, fs: Arc, language_registry: Arc, + debug_adapters: Arc, environment: Entity, toolchain_store: Arc, breakpoint_store: Entity, @@ -178,6 +179,7 @@ impl DapStore { worktree_store, toolchain_store, language_registry, + debug_adapters, start_debugging_tx, _start_debugging_task, next_session_id: Default::default(), @@ -364,52 +366,63 @@ impl DapStore { config, local_store.start_debugging_tx.clone(), initialized_tx, + local_store.debug_adapters.clone(), cx, ); - let task = cx.spawn(async move |this, cx| { - let session = match start_client_task.await { - Ok(session) => session, - Err(error) => { - this.update(cx, |_, cx| { - cx.emit(DapStoreEvent::Notification(error.to_string())); - }) - .log_err(); + let task = create_new_session(session_id, initialized_rx, start_client_task, cx); + (session_id, task) + } + #[cfg(any(test, feature = "test-support"))] + pub fn new_fake_session( + &mut self, + config: DebugAdapterConfig, + worktree: &Entity, + parent_session: Option>, + caps: Capabilities, + fails: bool, + cx: &mut Context, + ) -> (SessionId, Task>>) { + let Some(local_store) = self.as_local() else { + unimplemented!("Starting session on remote side"); + }; - return Err(error); - } - }; + let delegate = DapAdapterDelegate::new( + local_store.fs.clone(), + worktree.read(cx).id(), + local_store.node_runtime.clone(), + local_store.http_client.clone(), + local_store.language_registry.clone(), + local_store.toolchain_store.clone(), + local_store.environment.update(cx, |env, cx| { + let worktree = worktree.read(cx); + env.get_environment(Some(worktree.id()), Some(worktree.abs_path()), cx) + }), + ); + let session_id = local_store.next_session_id(); - // we have to insert the session early, so we can handle reverse requests - // that need the session to be available - this.update(cx, |store, cx| { - store.sessions.insert(session_id, session.clone()); - cx.emit(DapStoreEvent::DebugClientStarted(session_id)); - cx.notify(); - })?; + if let Some(session) = &parent_session { + session.update(cx, |session, _| { + session.add_child_session_id(session_id); + }); + } - match session - .update(cx, |session, cx| { - session.initialize_sequence(initialized_rx, cx) - })? - .await - { - Ok(_) => {} - Err(error) => { - this.update(cx, |this, cx| { - cx.emit(DapStoreEvent::Notification(error.to_string())); + let (initialized_tx, initialized_rx) = oneshot::channel(); - this.shutdown_session(session_id, cx) - })? - .await - .log_err(); + let start_client_task = Session::fake( + self.breakpoint_store.clone(), + session_id, + parent_session, + delegate, + config, + local_store.start_debugging_tx.clone(), + initialized_tx, + caps, + fails, + cx, + ); - return Err(error); - } - } - - Ok(session) - }); + let task = create_new_session(session_id, initialized_rx, start_client_task, cx); (session_id, task) } @@ -431,7 +444,6 @@ impl DapStore { request.arguments.unwrap_or_default(), ) .expect("To parse StartDebuggingRequestArguments"); - let worktree = local_store .worktree_store .update(cx, |this, _| this.worktrees().next()) @@ -441,25 +453,30 @@ impl DapStore { unreachable!("there must be a config for local sessions"); }; - let (_, new_session_task) = self.new_session( - DebugAdapterConfig { - label: config.label, - kind: config.kind, - request: match &args.request { - StartDebuggingRequestArgumentsRequest::Launch => DebugRequestType::Launch, - StartDebuggingRequestArgumentsRequest::Attach => { - DebugRequestType::Attach(AttachConfig::default()) - } - }, - program: config.program, - cwd: config.cwd, - initialize_args: Some(args.configuration), - supports_attach: config.supports_attach, - }, - &worktree, - Some(parent_session.clone()), - cx, - ); + let debug_config = DebugAdapterConfig { + label: config.label, + adapter: config.adapter, + request: DebugRequestDisposition::ReverseRequest(args), + initialize_args: config.initialize_args.clone(), + tcp_connection: config.tcp_connection.clone(), + }; + #[cfg(any(test, feature = "test-support"))] + let new_session_task = { + let caps = parent_session.read(cx).capabilities.clone(); + self.new_fake_session( + debug_config, + &worktree, + Some(parent_session.clone()), + caps, + false, + cx, + ) + .1 + }; + #[cfg(not(any(test, feature = "test-support")))] + let new_session_task = self + .new_session(debug_config, &worktree, Some(parent_session.clone()), cx) + .1; let request_seq = request.seq; cx.spawn(async move |_, cx| { @@ -830,6 +847,58 @@ impl DapStore { } } +fn create_new_session( + session_id: SessionId, + initialized_rx: oneshot::Receiver<()>, + start_client_task: Task, anyhow::Error>>, + cx: &mut Context<'_, DapStore>, +) -> Task>> { + let task = cx.spawn(async move |this, cx| { + let session = match start_client_task.await { + Ok(session) => session, + Err(error) => { + this.update(cx, |_, cx| { + cx.emit(DapStoreEvent::Notification(error.to_string())); + }) + .log_err(); + + return Err(error); + } + }; + + // we have to insert the session early, so we can handle reverse requests + // that need the session to be available + this.update(cx, |store, cx| { + store.sessions.insert(session_id, session.clone()); + cx.emit(DapStoreEvent::DebugClientStarted(session_id)); + cx.notify(); + })?; + + match session + .update(cx, |session, cx| { + session.initialize_sequence(initialized_rx, cx) + })? + .await + { + Ok(_) => {} + Err(error) => { + this.update(cx, |this, cx| { + cx.emit(DapStoreEvent::Notification(error.to_string())); + + this.shutdown_session(session_id, cx) + })? + .await + .log_err(); + + return Err(error); + } + } + + Ok(session) + }); + task +} + #[derive(Clone)] pub struct DapAdapterDelegate { fs: Arc, diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index e8abab9849..6291e81d73 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -14,7 +14,6 @@ use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, IndexMap, IndexSet}; use dap::adapters::{DebugAdapter, DebugAdapterBinary}; use dap::messages::Response; -use dap::OutputEventCategory; use dap::{ adapters::{DapDelegate, DapStatus}, client::{DebugAdapterClient, SessionId}, @@ -22,7 +21,7 @@ use dap::{ Capabilities, ContinueArguments, EvaluateArgumentsContext, Module, Source, StackFrameId, SteppingGranularity, StoppedEvent, VariableReference, }; -use dap_adapters::build_adapter; +use dap::{DapRegistry, DebugRequestType, OutputEventCategory}; use futures::channel::oneshot; use futures::{future::Shared, FutureExt}; use gpui::{ @@ -42,7 +41,7 @@ use std::{ path::Path, sync::Arc, }; -use task::DebugAdapterConfig; +use task::{DebugAdapterConfig, DebugTaskDefinition}; use text::{PointUtf16, ToPointUtf16}; use util::{merge_json_value_into, ResultExt}; @@ -183,6 +182,7 @@ fn client_source(abs_path: &Path) -> dap::Source { impl LocalMode { fn new( + debug_adapters: Arc, session_id: SessionId, parent_session: Option>, breakpoint_store: Entity, @@ -190,9 +190,168 @@ impl LocalMode { delegate: DapAdapterDelegate, messages_tx: futures::channel::mpsc::UnboundedSender, cx: AsyncApp, + ) -> Task> { + Self::new_inner( + debug_adapters, + session_id, + parent_session, + breakpoint_store, + config, + delegate, + messages_tx, + async |_, _| {}, + cx, + ) + } + #[cfg(any(test, feature = "test-support"))] + fn new_fake( + session_id: SessionId, + parent_session: Option>, + breakpoint_store: Entity, + config: DebugAdapterConfig, + delegate: DapAdapterDelegate, + messages_tx: futures::channel::mpsc::UnboundedSender, + caps: Capabilities, + fail: bool, + cx: AsyncApp, + ) -> Task> { + use task::DebugRequestDisposition; + + let request = match config.request.clone() { + DebugRequestDisposition::UserConfigured(request) => request, + DebugRequestDisposition::ReverseRequest(reverse_request_args) => { + match reverse_request_args.request { + dap::StartDebuggingRequestArgumentsRequest::Launch => { + DebugRequestType::Launch(task::LaunchConfig { + program: "".to_owned(), + cwd: None, + }) + } + dap::StartDebuggingRequestArgumentsRequest::Attach => { + DebugRequestType::Attach(task::AttachConfig { + process_id: Some(0), + }) + } + } + } + }; + + let callback = async move |session: &mut LocalMode, cx: AsyncApp| { + session + .client + .on_request::(move |_, _| Ok(caps.clone())) + .await; + + let paths = cx + .update(|cx| session.breakpoint_store.read(cx).breakpoint_paths()) + .expect("Breakpoint store should exist in all tests that start debuggers"); + + session + .client + .on_request::(move |_, args| { + let p = Arc::from(Path::new(&args.source.path.unwrap())); + if !paths.contains(&p) { + panic!("Sent breakpoints for path without any") + } + + Ok(dap::SetBreakpointsResponse { + breakpoints: Vec::default(), + }) + }) + .await; + + match request { + dap::DebugRequestType::Launch(_) => { + if fail { + session + .client + .on_request::(move |_, _| { + Err(dap::ErrorResponse { + error: Some(dap::Message { + id: 1, + format: "error".into(), + variables: None, + send_telemetry: None, + show_user: None, + url: None, + url_label: None, + }), + }) + }) + .await; + } else { + session + .client + .on_request::(move |_, _| Ok(())) + .await; + } + } + dap::DebugRequestType::Attach(attach_config) => { + if fail { + session + .client + .on_request::(move |_, _| { + Err(dap::ErrorResponse { + error: Some(dap::Message { + id: 1, + format: "error".into(), + variables: None, + send_telemetry: None, + show_user: None, + url: None, + url_label: None, + }), + }) + }) + .await; + } else { + session + .client + .on_request::(move |_, args| { + assert_eq!( + json!({"request": "attach", "process_id": attach_config.process_id.unwrap()}), + args.raw + ); + + Ok(()) + }) + .await; + } + } + } + + session + .client + .on_request::(move |_, _| Ok(())) + .await; + session.client.fake_event(Events::Initialized(None)).await; + }; + Self::new_inner( + DapRegistry::fake().into(), + session_id, + parent_session, + breakpoint_store, + config, + delegate, + messages_tx, + callback, + cx, + ) + } + fn new_inner( + registry: Arc, + session_id: SessionId, + parent_session: Option>, + breakpoint_store: Entity, + config: DebugAdapterConfig, + delegate: DapAdapterDelegate, + messages_tx: futures::channel::mpsc::UnboundedSender, + on_initialized: impl AsyncFnOnce(&mut LocalMode, AsyncApp) + 'static, + cx: AsyncApp, ) -> Task> { cx.spawn(async move |cx| { - let (adapter, binary) = Self::get_adapter_binary(&config, &delegate, cx).await?; + let (adapter, binary) = + Self::get_adapter_binary(®istry, &config, &delegate, cx).await?; let message_handler = Box::new(move |message| { messages_tx.unbounded_send(message).ok(); @@ -219,99 +378,14 @@ impl LocalMode { ); let adapter_id = adapter.name().to_string().to_owned(); - let session = Self { + let mut session = Self { client, adapter, breakpoint_store, config: config.clone(), }; - #[cfg(any(test, feature = "test-support"))] - { - let dap::DebugAdapterKind::Fake((fail, caps)) = session.config.kind.clone() else { - panic!("Only fake debug adapter configs should be used in tests"); - }; - - session - .client - .on_request::(move |_, _| Ok(caps.clone())) - .await; - - let paths = cx.update(|cx| session.breakpoint_store.read(cx).breakpoint_paths()).expect("Breakpoint store should exist in all tests that start debuggers"); - - session.client.on_request::(move |_, args| { - let p = Arc::from(Path::new(&args.source.path.unwrap())); - if !paths.contains(&p) { - panic!("Sent breakpoints for path without any") - } - - Ok(dap::SetBreakpointsResponse { - breakpoints: Vec::default(), - }) - }).await; - - match config.request.clone() { - dap::DebugRequestType::Launch if fail => { - session - .client - .on_request::(move |_, _| { - Err(dap::ErrorResponse { - error: Some(dap::Message { - id: 1, - format: "error".into(), - variables: None, - send_telemetry: None, - show_user: None, - url: None, - url_label: None, - }), - }) - }) - .await; - } - dap::DebugRequestType::Launch => { - session - .client - .on_request::(move |_, _| Ok(())) - .await; - } - dap::DebugRequestType::Attach(_) if fail => { - session - .client - .on_request::(move |_, _| { - Err(dap::ErrorResponse { - error: Some(dap::Message { - id: 1, - format: "error".into(), - variables: None, - send_telemetry: None, - show_user: None, - url: None, - url_label: None, - }), - }) - }) - .await; - } - dap::DebugRequestType::Attach(attach_config) => { - session - .client - .on_request::(move |_, args| { - assert_eq!( - json!({"request": "attach", "process_id": attach_config.process_id.unwrap()}), - args.raw - ); - - Ok(()) - }) - .await; - } - } - - session.client.on_request::(move |_, _| Ok(())).await; - session.client.fake_event(Events::Initialized(None)).await; - } - + on_initialized(&mut session, cx.clone()).await; let capabilities = session .request(Initialize { adapter_id }, cx.background_executor().clone()) .await?; @@ -420,11 +494,14 @@ impl LocalMode { } async fn get_adapter_binary( + registry: &Arc, config: &DebugAdapterConfig, delegate: &DapAdapterDelegate, cx: &mut AsyncApp, ) -> Result<(Arc, DebugAdapterBinary)> { - let adapter = build_adapter(&config.kind).await?; + let adapter = registry + .adapter(&config.adapter) + .ok_or_else(|| anyhow!("Debug adapter with name `{}` was not found", config.adapter))?; let binary = cx.update(|cx| { ProjectSettings::get_global(cx) @@ -465,20 +542,36 @@ impl LocalMode { initialized_rx: oneshot::Receiver<()>, cx: &App, ) -> Task> { - let mut raw = self.adapter.request_args(&self.config); + let (mut raw, is_launch) = match &self.config.request { + task::DebugRequestDisposition::UserConfigured(_) => { + let Ok(raw) = DebugTaskDefinition::try_from(self.config.clone()) else { + debug_assert!(false, "This part of code should be unreachable in practice"); + return Task::ready(Err(anyhow!( + "Expected debug config conversion to succeed" + ))); + }; + let is_launch = matches!(raw.request, DebugRequestType::Launch(_)); + let raw = self.adapter.request_args(&raw); + (raw, is_launch) + } + task::DebugRequestDisposition::ReverseRequest(start_debugging_request_arguments) => ( + start_debugging_request_arguments.configuration.clone(), + matches!( + start_debugging_request_arguments.request, + dap::StartDebuggingRequestArgumentsRequest::Launch + ), + ), + }; + merge_json_value_into( self.config.initialize_args.clone().unwrap_or(json!({})), &mut raw, ); - // Of relevance: https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522 - let launch = match &self.config.request { - dap::DebugRequestType::Launch => { - self.request(Launch { raw }, cx.background_executor().clone()) - } - dap::DebugRequestType::Attach(_) => { - self.request(Attach { raw }, cx.background_executor().clone()) - } + let launch = if is_launch { + self.request(Launch { raw }, cx.background_executor().clone()) + } else { + self.request(Attach { raw }, cx.background_executor().clone()) }; let configuration_done_supported = ConfigurationDone::is_supported(capabilities); @@ -745,12 +838,14 @@ impl Session { config: DebugAdapterConfig, start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>, initialized_tx: oneshot::Sender<()>, + debug_adapters: Arc, cx: &mut App, ) -> Task>> { - let (message_tx, mut message_rx) = futures::channel::mpsc::unbounded(); + let (message_tx, message_rx) = futures::channel::mpsc::unbounded(); cx.spawn(async move |cx| { let (mode, capabilities) = LocalMode::new( + debug_adapters, session_id, parent_session.clone(), breakpoint_store.clone(), @@ -762,74 +857,62 @@ impl Session { .await?; cx.new(|cx| { - let _background_tasks = vec![cx.spawn(async move |this: WeakEntity, cx| { - let mut initialized_tx = Some(initialized_tx); - while let Some(message) = message_rx.next().await { - if let Message::Event(event) = message { - if let Events::Initialized(_) = *event { - if let Some(tx) = initialized_tx.take() { - tx.send(()).ok(); - } - } else { - let Ok(_) = this.update(cx, |session, cx| { - session.handle_dap_event(event, cx); - }) else { - break; - }; - } - } else { - let Ok(_) = - start_debugging_requests_tx.unbounded_send((session_id, message)) - else { - break; - }; - } - } - })]; - - cx.subscribe(&breakpoint_store, |this, _, event, cx| match event { - BreakpointStoreEvent::BreakpointsUpdated(path, reason) => { - if let Some(local) = (!this.ignore_breakpoints) - .then(|| this.as_local_mut()) - .flatten() - { - local - .send_breakpoints_from_path(path.clone(), *reason, cx) - .detach(); - }; - } - BreakpointStoreEvent::BreakpointsCleared(paths) => { - if let Some(local) = (!this.ignore_breakpoints) - .then(|| this.as_local_mut()) - .flatten() - { - local.unset_breakpoints_from_paths(paths, cx).detach(); - } - } - BreakpointStoreEvent::ActiveDebugLineChanged => {} - }) - .detach(); - - Self { - mode: Mode::Local(mode), - id: session_id, - child_session_ids: HashSet::default(), - parent_id: parent_session.map(|session| session.read(cx).id), - variables: Default::default(), + create_local_session( + breakpoint_store, + session_id, + parent_session, + start_debugging_requests_tx, + initialized_tx, + message_rx, + mode, capabilities, - thread_states: ThreadStates::default(), - output_token: OutputToken(0), - ignore_breakpoints: false, - output: circular_buffer::CircularBuffer::boxed(), - requests: HashMap::default(), - modules: Vec::default(), - loaded_sources: Vec::default(), - threads: IndexMap::default(), - stack_frames: IndexMap::default(), - locations: Default::default(), - _background_tasks, - is_session_terminated: false, - } + cx, + ) + }) + }) + } + + #[cfg(any(test, feature = "test-support"))] + pub(crate) fn fake( + breakpoint_store: Entity, + session_id: SessionId, + parent_session: Option>, + delegate: DapAdapterDelegate, + config: DebugAdapterConfig, + start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>, + initialized_tx: oneshot::Sender<()>, + caps: Capabilities, + fails: bool, + cx: &mut App, + ) -> Task>> { + let (message_tx, message_rx) = futures::channel::mpsc::unbounded(); + + cx.spawn(async move |cx| { + let (mode, capabilities) = LocalMode::new_fake( + session_id, + parent_session.clone(), + breakpoint_store.clone(), + config.clone(), + delegate, + message_tx, + caps, + fails, + cx.clone(), + ) + .await?; + + cx.new(|cx| { + create_local_session( + breakpoint_store, + session_id, + parent_session, + start_debugging_requests_tx, + initialized_tx, + message_rx, + mode, + capabilities, + cx, + ) }) }) } @@ -1838,3 +1921,83 @@ impl Session { } } } + +fn create_local_session( + breakpoint_store: Entity, + session_id: SessionId, + parent_session: Option>, + start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>, + initialized_tx: oneshot::Sender<()>, + mut message_rx: futures::channel::mpsc::UnboundedReceiver, + mode: LocalMode, + capabilities: Capabilities, + cx: &mut Context<'_, Session>, +) -> Session { + let _background_tasks = vec![cx.spawn(async move |this: WeakEntity, cx| { + let mut initialized_tx = Some(initialized_tx); + while let Some(message) = message_rx.next().await { + if let Message::Event(event) = message { + if let Events::Initialized(_) = *event { + if let Some(tx) = initialized_tx.take() { + tx.send(()).ok(); + } + } else { + let Ok(_) = this.update(cx, |session, cx| { + session.handle_dap_event(event, cx); + }) else { + break; + }; + } + } else { + let Ok(_) = start_debugging_requests_tx.unbounded_send((session_id, message)) + else { + break; + }; + } + } + })]; + + cx.subscribe(&breakpoint_store, |this, _, event, cx| match event { + BreakpointStoreEvent::BreakpointsUpdated(path, reason) => { + if let Some(local) = (!this.ignore_breakpoints) + .then(|| this.as_local_mut()) + .flatten() + { + local + .send_breakpoints_from_path(path.clone(), *reason, cx) + .detach(); + }; + } + BreakpointStoreEvent::BreakpointsCleared(paths) => { + if let Some(local) = (!this.ignore_breakpoints) + .then(|| this.as_local_mut()) + .flatten() + { + local.unset_breakpoints_from_paths(paths, cx).detach(); + } + } + BreakpointStoreEvent::ActiveDebugLineChanged => {} + }) + .detach(); + + Session { + mode: Mode::Local(mode), + id: session_id, + child_session_ids: HashSet::default(), + parent_id: parent_session.map(|session| session.read(cx).id), + variables: Default::default(), + capabilities, + thread_states: ThreadStates::default(), + output_token: OutputToken(0), + ignore_breakpoints: false, + output: circular_buffer::CircularBuffer::boxed(), + requests: HashMap::default(), + modules: Vec::default(), + loaded_sources: Vec::default(), + threads: IndexMap::default(), + stack_frames: IndexMap::default(), + locations: Default::default(), + _background_tasks, + is_session_terminated: false, + } +} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 62aae83e07..6cd942c032 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -38,7 +38,7 @@ use client::{ }; use clock::ReplicaId; -use dap::{client::DebugAdapterClient, DebugAdapterConfig}; +use dap::{client::DebugAdapterClient, DapRegistry, DebugAdapterConfig}; use collections::{BTreeSet, HashMap, HashSet}; use debounced_delay::DebouncedDelay; @@ -163,6 +163,7 @@ pub struct Project { active_entry: Option, buffer_ordered_messages_tx: mpsc::UnboundedSender, languages: Arc, + debug_adapters: Arc, dap_store: Entity, breakpoint_store: Entity, client: Arc, @@ -818,6 +819,7 @@ impl Project { node: NodeRuntime, user_store: Entity, languages: Arc, + debug_adapters: Arc, fs: Arc, env: Option>, cx: &mut App, @@ -854,6 +856,7 @@ impl Project { node.clone(), fs.clone(), languages.clone(), + debug_adapters.clone(), environment.clone(), toolchain_store.read(cx).as_language_toolchain_store(), breakpoint_store.clone(), @@ -940,6 +943,7 @@ impl Project { active_entry: None, snippets, languages, + debug_adapters, client, task_store, user_store, @@ -1102,6 +1106,7 @@ impl Project { active_entry: None, snippets, languages, + debug_adapters: Arc::new(DapRegistry::default()), client, task_store, user_store, @@ -1239,7 +1244,6 @@ 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()) })?; @@ -1326,6 +1330,7 @@ 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, @@ -1450,13 +1455,7 @@ impl Project { config: DebugAdapterConfig, cx: &mut Context, ) -> Task>> { - let worktree = maybe!({ - if let Some(cwd) = &config.cwd { - Some(self.find_worktree(cwd.as_path(), cx)?.0) - } else { - self.worktrees(cx).next() - } - }); + let worktree = maybe!({ self.worktrees(cx).next() }); let Some(worktree) = &worktree else { return Task::ready(Err(anyhow!("Failed to find a worktree"))); @@ -1469,6 +1468,40 @@ impl Project { .1 } + #[cfg(any(test, feature = "test-support"))] + pub fn fake_debug_session( + &mut self, + request: task::DebugRequestType, + caps: Option, + fails: bool, + cx: &mut Context, + ) -> Task>> { + use dap::{Capabilities, FakeAdapter}; + use task::DebugRequestDisposition; + + let worktree = maybe!({ self.worktrees(cx).next() }); + + let Some(worktree) = &worktree else { + return Task::ready(Err(anyhow!("Failed to find a worktree"))); + }; + let config = DebugAdapterConfig { + label: "test config".into(), + adapter: FakeAdapter::ADAPTER_NAME.into(), + request: DebugRequestDisposition::UserConfigured(request), + initialize_args: None, + tcp_connection: None, + }; + let caps = caps.unwrap_or(Capabilities { + supports_step_back: Some(false), + ..Default::default() + }); + self.dap_store + .update(cx, |dap_store, cx| { + dap_store.new_fake_session(config, worktree, None, caps, fails, cx) + }) + .1 + } + #[cfg(any(test, feature = "test-support"))] pub async fn example( root_paths: impl IntoIterator, @@ -1478,6 +1511,7 @@ 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 @@ -1491,6 +1525,7 @@ impl Project { node_runtime::NodeRuntime::unavailable(), user_store, Arc::new(languages), + debug_adapters, fs, None, cx, @@ -1521,6 +1556,7 @@ 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)); @@ -1531,6 +1567,7 @@ impl Project { node_runtime::NodeRuntime::unavailable(), user_store, Arc::new(languages), + Arc::new(debug_adapters), fs, None, cx, @@ -1574,6 +1611,10 @@ 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/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index 68a1b70061..79eb506ca9 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -557,6 +557,7 @@ 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 7396909299..d50a5c8339 100644 --- a/crates/remote_server/Cargo.toml +++ b/crates/remote_server/Cargo.toml @@ -28,6 +28,7 @@ backtrace = "0.3" chrono.workspace = true clap.workspace = true client.workspace = true +dap.workspace = true env_logger.workspace = true extension.workspace = true extension_host.workspace = true @@ -69,6 +70,7 @@ libc.workspace = true [dev-dependencies] client = { workspace = true, features = ["test-support"] } clock = { workspace = true, features = ["test-support"] } +dap = { workspace = true, features = ["test-support"] } fs = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] } http_client = { workspace = true, features = ["test-support"] } diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 3802d08c8c..b1c2a7ec12 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -1,5 +1,6 @@ use ::proto::{FromProto, ToProto}; use anyhow::{anyhow, Result}; +use dap::DapRegistry; use extension::ExtensionHostProxy; use extension_host::headless_host::HeadlessExtensionStore; use fs::Fs; @@ -52,6 +53,7 @@ pub struct HeadlessAppState { pub http_client: Arc, pub node_runtime: NodeRuntime, pub languages: Arc, + pub debug_adapters: Arc, pub extension_host_proxy: Arc, } @@ -69,6 +71,7 @@ impl HeadlessProject { http_client, node_runtime, languages, + debug_adapters, extension_host_proxy: proxy, }: HeadlessAppState, cx: &mut Context, @@ -108,6 +111,7 @@ impl HeadlessProject { node_runtime.clone(), fs.clone(), languages.clone(), + debug_adapters.clone(), environment.clone(), toolchain_store.read(cx).as_language_toolchain_store(), breakpoint_store.clone(), diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index 5a72a946cf..438ac5c07c 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/crates/remote_server/src/remote_editing_tests.rs @@ -4,6 +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}; @@ -1445,6 +1446,7 @@ 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| { @@ -1457,6 +1459,7 @@ 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 5a585cee69..2dc427110e 100644 --- a/crates/remote_server/src/unix.rs +++ b/crates/remote_server/src/unix.rs @@ -3,6 +3,7 @@ use crate::HeadlessProject; use anyhow::{anyhow, Context as _, Result}; use chrono::Utc; use client::{telemetry, ProxySettings}; +use dap::DapRegistry; use extension::ExtensionHostProxy; use fs::{Fs, RealFs}; use futures::channel::mpsc; @@ -471,6 +472,7 @@ 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 { @@ -479,6 +481,7 @@ pub fn execute_run( http_client, node_runtime, languages, + debug_adapters, extension_host_proxy, }, cx, diff --git a/crates/task/src/debug_format.rs b/crates/task/src/debug_format.rs index 2a600c24da..b4554ee3f0 100644 --- a/crates/task/src/debug_format.rs +++ b/crates/task/src/debug_format.rs @@ -1,6 +1,6 @@ +use dap_types::StartDebuggingRequestArguments; use schemars::{gen::SchemaSettings, JsonSchema}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::net::Ipv4Addr; use std::path::PathBuf; use util::ResultExt; @@ -45,97 +45,121 @@ pub struct AttachConfig { 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 { + /// The program that you trying to debug + pub program: String, + /// The current working directory of your project + pub cwd: Option, +} + /// Represents the type that will determine which request to call on the debug adapter -#[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)] -#[serde(rename_all = "lowercase")] +#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)] +#[serde(rename_all = "lowercase", untagged)] pub enum DebugRequestType { /// Call the `launch` request on the debug adapter - #[default] - Launch, + Launch(LaunchConfig), /// Call the `attach` request on the debug adapter Attach(AttachConfig), } -/// The Debug adapter to use -#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)] -#[serde(rename_all = "lowercase", tag = "adapter")] -pub enum DebugAdapterKind { - /// Manually setup starting a debug adapter - /// The argument within is used to start the DAP - Custom(CustomArgs), - /// Use debugpy - Python(TCPHost), - /// Use vscode-php-debug - Php(TCPHost), - /// Use vscode-js-debug - Javascript(TCPHost), - /// Use delve - Go(TCPHost), - /// Use lldb - Lldb, - /// Use GDB's built-in DAP support - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - Gdb, - /// Used for integration tests - #[cfg(any(test, feature = "test-support"))] - #[serde(skip)] - Fake((bool, dap_types::Capabilities)), +/// 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 DebugAdapterKind { - /// Returns the display name for the adapter kind - pub fn display_name(&self) -> &str { +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::Custom(_) => "Custom", - Self::Python(_) => "Python", - Self::Php(_) => "PHP", - Self::Javascript(_) => "JavaScript", - Self::Lldb => "LLDB", - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - Self::Gdb => "GDB", - Self::Go(_) => "Go", - #[cfg(any(test, feature = "test-support"))] - Self::Fake(_) => "Fake", + Self::UserConfigured(DebugRequestType::Launch(launch_config)) => { + launch_config.cwd.clone() + } + _ => None, } } } - -/// Custom arguments used to setup a custom debugger -#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)] -pub struct CustomArgs { - /// The connection that a custom debugger should use - #[serde(flatten)] - pub connection: DebugConnectionType, - /// The cli command used to start the debug adapter e.g. `python3`, `node` or the adapter binary - pub command: String, - /// The cli arguments used to start the debug adapter - pub args: Option>, - /// The cli envs used to start the debug adapter - pub envs: Option>, -} - /// Represents the configuration for the debug adapter -#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)] -#[serde(rename_all = "snake_case")] +#[derive(PartialEq, Eq, Clone, Debug)] pub struct DebugAdapterConfig { /// Name of the debug task pub label: String, /// The type of adapter you want to use - #[serde(flatten)] - pub kind: DebugAdapterKind, + pub adapter: String, /// The type of request that should be called on the debug adapter - #[serde(default)] - pub request: DebugRequestType, - /// The program that you trying to debug - pub program: Option, - /// The current working directory of your project - pub cwd: Option, + pub request: DebugRequestDisposition, /// Additional initialization arguments to be sent on DAP initialization pub initialize_args: Option, - /// Whether the debug adapter supports attaching to a running process. - pub supports_attach: bool, + /// Optional TCP connection information + /// + /// 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, } +impl From for DebugAdapterConfig { + fn from(def: DebugTaskDefinition) -> Self { + Self { + label: def.label, + adapter: def.adapter, + request: DebugRequestDisposition::UserConfigured(def.request), + initialize_args: def.initialize_args, + tcp_connection: def.tcp_connection, + } + } +} + +impl TryFrom for DebugTaskDefinition { + type Error = (); + fn try_from(def: DebugAdapterConfig) -> Result { + let request = match def.request { + DebugRequestDisposition::UserConfigured(debug_request_type) => debug_request_type, + DebugRequestDisposition::ReverseRequest(_) => return Err(()), + }; + + Ok(Self { + label: def.label, + adapter: def.adapter, + request, + initialize_args: def.initialize_args, + tcp_connection: def.tcp_connection, + }) + } +} + +impl DebugTaskDefinition { + /// Translate from debug definition to a task template + pub fn to_zed_format(self) -> anyhow::Result { + let command = "".to_string(); + + let cwd = if let DebugRequestType::Launch(ref launch) = self.request { + launch + .cwd + .as_ref() + .map(|path| path.to_string_lossy().into_owned()) + } else { + None + }; + let label = self.label.clone(); + let task_type = TaskType::Debug(self); + + Ok(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")] @@ -151,48 +175,20 @@ pub enum DebugConnectionType { #[serde(rename_all = "snake_case")] pub struct DebugTaskDefinition { /// The adapter to run - #[serde(flatten)] - kind: DebugAdapterKind, + pub adapter: String, /// The type of request that should be called on the debug adapter - #[serde(default)] - request: DebugRequestType, + #[serde(flatten)] + pub request: DebugRequestType, /// Name of the debug task - label: String, - /// Program to run the debugger on - program: Option, - /// The current working directory of your project - cwd: Option, + pub label: String, /// Additional initialization arguments to be sent on DAP initialization - initialize_args: Option, -} - -impl DebugTaskDefinition { - /// Translate from debug definition to a task template - pub fn to_zed_format(self) -> anyhow::Result { - let command = "".to_string(); - let cwd = self.cwd.clone().map(PathBuf::from).take_if(|p| p.exists()); - - let task_type = TaskType::Debug(DebugAdapterConfig { - label: self.label.clone(), - kind: self.kind, - request: self.request, - program: self.program, - cwd: cwd.clone(), - initialize_args: self.initialize_args, - supports_attach: true, - }); - - let args: Vec = Vec::new(); - - Ok(TaskTemplate { - label: self.label, - command, - args, - task_type, - cwd: self.cwd, - ..Default::default() - }) - } + pub initialize_args: Option, + /// Optional TCP connection information + /// + /// 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, } /// A group of Debug Tasks defined in a JSON file. diff --git a/crates/task/src/lib.rs b/crates/task/src/lib.rs index 573558edf7..5716bbbf4b 100644 --- a/crates/task/src/lib.rs +++ b/crates/task/src/lib.rs @@ -15,8 +15,8 @@ use std::path::PathBuf; use std::str::FromStr; pub use debug_format::{ - AttachConfig, CustomArgs, DebugAdapterConfig, DebugAdapterKind, DebugConnectionType, - DebugRequestType, DebugTaskDefinition, DebugTaskFile, TCPHost, + AttachConfig, DebugAdapterConfig, DebugConnectionType, DebugRequestDisposition, + DebugRequestType, DebugTaskDefinition, DebugTaskFile, LaunchConfig, TCPHost, }; pub use task_template::{ HideStrategy, RevealStrategy, TaskModal, TaskTemplate, TaskTemplates, TaskType, @@ -104,14 +104,20 @@ 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::Script => None, TaskType::Debug(mut adapter_config) => { if let Some(resolved) = &self.resolved { adapter_config.label = resolved.label.clone(); - adapter_config.program = resolved.program.clone().or(adapter_config.program); - adapter_config.cwd = resolved.cwd.clone().or(adapter_config.cwd); + if let DebugRequestType::Launch(ref mut launch) = adapter_config.request { + if let Some(program) = resolved.program.clone() { + launch.program = program; + } + if let Some(cwd) = resolved.cwd.clone() { + launch.cwd = Some(cwd); + } + } } Some(adapter_config) diff --git a/crates/task/src/task_template.rs b/crates/task/src/task_template.rs index aeba2ac098..7c423db1f4 100644 --- a/crates/task/src/task_template.rs +++ b/crates/task/src/task_template.rs @@ -9,7 +9,7 @@ use sha2::{Digest, Sha256}; use util::{truncate_and_remove_front, ResultExt}; use crate::{ - debug_format::DebugAdapterConfig, ResolvedTask, RevealTarget, Shell, SpawnInTerminal, + DebugRequestType, DebugTaskDefinition, ResolvedTask, RevealTarget, Shell, SpawnInTerminal, TaskContext, TaskId, VariableName, ZED_VARIABLE_NAME_PREFIX, }; @@ -78,18 +78,18 @@ pub struct TaskTemplate { /// Represents the type of task that is being ran #[derive(Default, Deserialize, Serialize, Eq, PartialEq, JsonSchema, Clone, Debug)] #[serde(rename_all = "snake_case", tag = "type")] -#[expect(clippy::large_enum_variant)] +#[allow(clippy::large_enum_variant)] pub enum TaskType { /// Act like a typically task that runs commands #[default] Script, /// This task starts the debugger for a language - Debug(DebugAdapterConfig), + Debug(DebugTaskDefinition), } #[cfg(test)] mod deserialization_tests { - use crate::{DebugAdapterKind, TCPHost}; + use crate::LaunchConfig; use super::*; use serde_json::json; @@ -105,19 +105,20 @@ mod deserialization_tests { #[test] fn deserialize_task_type_debug() { - let adapter_config = DebugAdapterConfig { + let adapter_config = DebugTaskDefinition { label: "test config".into(), - kind: DebugAdapterKind::Python(TCPHost::default()), - request: crate::DebugRequestType::Launch, - program: Some("main".to_string()), - supports_attach: false, - cwd: None, + adapter: "Debugpy".into(), + request: crate::DebugRequestType::Launch(LaunchConfig { + program: "main".to_string(), + cwd: None, + }), initialize_args: None, + tcp_connection: None, }; let json = json!({ "label": "test config", "type": "debug", - "adapter": "python", + "adapter": "Debugpy", "program": "main", "supports_attach": false, }); @@ -272,9 +273,9 @@ impl TaskTemplate { let program = match &self.task_type { TaskType::Script => None, TaskType::Debug(adapter_config) => { - if let Some(program) = &adapter_config.program { + if let DebugRequestType::Launch(ref launch) = &adapter_config.request { Some(substitute_all_template_variables_in_str( - program, + &launch.program, &task_variables, &variable_names, &mut substituted_variables, diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index b11d2e7be9..7831456ba7 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -349,7 +349,7 @@ impl PickerDelegate for TasksModalDelegate { _ => { project.update(cx, |project, cx| { project - .start_debug_session(config, cx) + .start_debug_session(config.into(), cx) .detach_and_log_err(cx); }); } @@ -521,7 +521,10 @@ impl PickerDelegate for TasksModalDelegate { // This would allow users to access to debug history and other issues TaskType::Debug(_) => workspace.project().update(cx, |project, cx| { project - .start_debug_session(task.resolved_debug_adapter_config().unwrap(), cx) + .start_debug_session( + task.resolved_debug_adapter_config().unwrap().into(), + cx, + ) .detach_and_log_err(cx); }), }; diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 217662206e..9853a06501 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -35,6 +35,7 @@ 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 @@ -68,6 +69,7 @@ zed_actions.workspace = true [dev-dependencies] call = { workspace = true, features = ["test-support"] } client = { workspace = true, features = ["test-support"] } +dap = { workspace = true, features = ["test-support"] } db = { workspace = true, features = ["test-support"] } env_logger.workspace = true fs = { workspace = true, features = ["test-support"] } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index b4ef0ea1f1..99160b1701 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -14,6 +14,7 @@ mod toast_layer; mod toolbar; mod workspace_settings; +use dap::DapRegistry; pub use toast_layer::{RunAction, ToastAction, ToastLayer, ToastView}; use anyhow::{anyhow, Context as _, Result}; @@ -637,6 +638,7 @@ 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, @@ -688,6 +690,7 @@ 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); @@ -703,6 +706,7 @@ impl AppState { client, fs, languages, + debug_adapters, user_store, workspace_store, node_runtime: NodeRuntime::unavailable(), @@ -1197,6 +1201,7 @@ 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, @@ -5025,6 +5030,7 @@ 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, diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 905b13f51e..ae9759c167 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -41,6 +41,8 @@ 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 db.workspace = true @@ -149,6 +151,7 @@ ashpd.workspace = true [dev-dependencies] call = { workspace = true, features = ["test-support"] } +dap = { workspace = true, features = ["test-support"] } editor = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] } image_viewer = { workspace = true, features = ["test-support"] } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f07d8817f2..0c4e870842 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -11,6 +11,7 @@ use cli::FORCE_CLI_MODE_ENV_VAR_NAME; use client::{parse_zed_link, Client, ProxySettings, UserStore}; 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; @@ -422,6 +423,7 @@ 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(), @@ -433,6 +435,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()); auto_update_ui::init(cx); reliability::init( client.http_client(),