diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index 5f1083d0d6..12a814b5ab 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -3,13 +3,13 @@ use anyhow::{Context as _, Ok, Result, anyhow}; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use async_trait::async_trait; +use dap_types::StartDebuggingRequestArguments; use futures::io::BufReader; use gpui::{AsyncApp, SharedString}; pub use http_client::{HttpClient, github::latest_github_release}; use language::LanguageToolchainStore; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; -use serde_json::Value; use settings::WorktreeId; use smol::{self, fs::File, lock::Mutex}; use std::{ @@ -22,7 +22,7 @@ use std::{ path::PathBuf, sync::Arc, }; -use task::{DebugAdapterConfig, DebugTaskDefinition}; +use task::DebugTaskDefinition; use util::ResultExt; #[derive(Clone, Debug, PartialEq, Eq)] @@ -93,13 +93,15 @@ pub struct TcpArguments { pub port: u16, pub timeout: Option, } -#[derive(Default, Debug, Clone)] +#[derive(Debug, Clone)] pub struct DebugAdapterBinary { + pub adapter_name: DebugAdapterName, pub command: String, pub arguments: Option>, pub envs: Option>, pub cwd: Option, pub connection: Option, + pub request_args: StartDebuggingRequestArguments, } #[derive(Debug)] @@ -220,7 +222,7 @@ pub trait DebugAdapter: 'static + Send + Sync { async fn get_binary( &self, delegate: &dyn DapDelegate, - config: &DebugAdapterConfig, + config: &DebugTaskDefinition, user_installed_path: Option, cx: &mut AsyncApp, ) -> Result { @@ -284,13 +286,10 @@ pub trait DebugAdapter: 'static + Send + Sync { async fn get_installed_binary( &self, delegate: &dyn DapDelegate, - config: &DebugAdapterConfig, + config: &DebugTaskDefinition, user_installed_path: Option, cx: &mut AsyncApp, ) -> Result; - - /// Should return base configuration to make the debug adapter work - fn request_args(&self, config: &DebugTaskDefinition) -> Value; } #[cfg(any(test, feature = "test-support"))] pub struct FakeAdapter {} @@ -302,6 +301,31 @@ impl FakeAdapter { pub fn new() -> Self { Self {} } + + fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments { + use serde_json::json; + use task::DebugRequestType; + + let value = json!({ + "request": match config.request { + DebugRequestType::Launch(_) => "launch", + DebugRequestType::Attach(_) => "attach", + }, + "process_id": if let DebugRequestType::Attach(attach_config) = &config.request { + attach_config.process_id + } else { + None + }, + }); + let request = match config.request { + DebugRequestType::Launch(_) => dap_types::StartDebuggingRequestArgumentsRequest::Launch, + DebugRequestType::Attach(_) => dap_types::StartDebuggingRequestArgumentsRequest::Attach, + }; + StartDebuggingRequestArguments { + configuration: value, + request, + } + } } #[cfg(any(test, feature = "test-support"))] @@ -314,16 +338,18 @@ impl DebugAdapter for FakeAdapter { async fn get_binary( &self, _: &dyn DapDelegate, - _: &DebugAdapterConfig, + config: &DebugTaskDefinition, _: Option, _: &mut AsyncApp, ) -> Result { Ok(DebugAdapterBinary { + adapter_name: Self::ADAPTER_NAME.into(), command: "command".into(), arguments: None, connection: None, envs: None, cwd: None, + request_args: self.request_args(config), }) } @@ -345,27 +371,10 @@ impl DebugAdapter for FakeAdapter { async fn get_installed_binary( &self, _: &dyn DapDelegate, - _: &DebugAdapterConfig, + _: &DebugTaskDefinition, _: Option, _: &mut AsyncApp, ) -> Result { unimplemented!("get installed binary"); } - - fn request_args(&self, config: &DebugTaskDefinition) -> Value { - use serde_json::json; - use task::DebugRequestType; - - json!({ - "request": match config.request { - DebugRequestType::Launch(_) => "launch", - DebugRequestType::Attach(_) => "attach", - }, - "process_id": if let DebugRequestType::Attach(attach_config) = &config.request { - attach_config.process_id - } else { - None - }, - }) - } } diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs index c681b64e55..a5eebc73df 100644 --- a/crates/dap/src/client.rs +++ b/crates/dap/src/client.rs @@ -39,7 +39,6 @@ impl SessionId { /// Represents a connection to the debug adapter process, either via stdout/stdin or a socket. pub struct DebugAdapterClient { id: SessionId, - name: DebugAdapterName, sequence_count: AtomicU64, binary: DebugAdapterBinary, executor: BackgroundExecutor, @@ -51,7 +50,6 @@ pub type DapMessageHandler = Box; impl DebugAdapterClient { pub async fn start( id: SessionId, - name: DebugAdapterName, binary: DebugAdapterBinary, message_handler: DapMessageHandler, cx: AsyncApp, @@ -60,7 +58,6 @@ impl DebugAdapterClient { TransportDelegate::start(&binary, cx.clone()).await?; let this = Self { id, - name, binary, transport_delegate, sequence_count: AtomicU64::new(1), @@ -91,6 +88,7 @@ impl DebugAdapterClient { ) -> Result { let binary = match self.transport_delegate.transport() { crate::transport::Transport::Tcp(tcp_transport) => DebugAdapterBinary { + adapter_name: binary.adapter_name, command: binary.command, arguments: binary.arguments, envs: binary.envs, @@ -100,11 +98,12 @@ impl DebugAdapterClient { port: tcp_transport.port, timeout: Some(tcp_transport.timeout), }), + request_args: binary.request_args, }, _ => self.binary.clone(), }; - Self::start(session_id, self.name(), binary, message_handler, cx).await + Self::start(session_id, binary, message_handler, cx).await } async fn handle_receive_messages( @@ -189,7 +188,17 @@ impl DebugAdapterClient { let response = response??; match response.success { - true => Ok(serde_json::from_value(response.body.unwrap_or_default())?), + true => { + if let Some(json) = response.body { + Ok(serde_json::from_value(json)?) + // Note: dap types configure themselves to return `None` when an empty object is received, + // which then fails here... + } else if let Ok(result) = serde_json::from_value(serde_json::Value::Object(Default::default())) { + Ok(result) + } else { + Ok(serde_json::from_value(Default::default())?) + } + } false => Err(anyhow!("Request failed: {}", response.message.unwrap_or_default())), } } @@ -211,7 +220,7 @@ impl DebugAdapterClient { } pub fn name(&self) -> DebugAdapterName { - self.name.clone() + self.binary.adapter_name.clone() } pub fn binary(&self) -> &DebugAdapterBinary { &self.binary @@ -238,14 +247,14 @@ impl DebugAdapterClient { } #[cfg(any(test, feature = "test-support"))] - pub async fn on_request(&self, handler: F) + pub fn on_request(&self, handler: F) where F: 'static + Send + FnMut(u64, R::Arguments) -> Result, { let transport = self.transport_delegate.transport().as_fake(); - transport.on_request::(handler).await; + transport.on_request::(handler); } #[cfg(any(test, feature = "test-support"))] @@ -282,7 +291,7 @@ mod tests { use crate::{client::DebugAdapterClient, debugger_settings::DebuggerSettings}; use dap_types::{ Capabilities, InitializeRequestArguments, InitializeRequestArgumentsPathFormat, - RunInTerminalRequestArguments, + RunInTerminalRequestArguments, StartDebuggingRequestArguments, messages::Events, requests::{Initialize, Request, RunInTerminal}, }; @@ -312,13 +321,17 @@ mod tests { let client = DebugAdapterClient::start( crate::client::SessionId(1), - DebugAdapterName("adapter".into()), DebugAdapterBinary { + adapter_name: "adapter".into(), command: "command".into(), arguments: Default::default(), envs: Default::default(), connection: None, cwd: None, + request_args: StartDebuggingRequestArguments { + configuration: serde_json::Value::Null, + request: dap_types::StartDebuggingRequestArgumentsRequest::Launch, + }, }, Box::new(|_| panic!("Did not expect to hit this code path")), cx.to_async(), @@ -326,14 +339,12 @@ mod tests { .await .unwrap(); - client - .on_request::(move |_, _| { - Ok(dap_types::Capabilities { - supports_configuration_done_request: Some(true), - ..Default::default() - }) + client.on_request::(move |_, _| { + Ok(dap_types::Capabilities { + supports_configuration_done_request: Some(true), + ..Default::default() }) - .await; + }); cx.run_until_parked(); @@ -381,13 +392,17 @@ mod tests { let client = DebugAdapterClient::start( crate::client::SessionId(1), - DebugAdapterName("adapter".into()), DebugAdapterBinary { + adapter_name: "adapter".into(), command: "command".into(), arguments: Default::default(), envs: Default::default(), connection: None, cwd: None, + request_args: StartDebuggingRequestArguments { + configuration: serde_json::Value::Null, + request: dap_types::StartDebuggingRequestArgumentsRequest::Launch, + }, }, Box::new({ let called_event_handler = called_event_handler.clone(); @@ -431,13 +446,17 @@ mod tests { let client = DebugAdapterClient::start( crate::client::SessionId(1), - DebugAdapterName("test-adapter".into()), DebugAdapterBinary { + adapter_name: "test-adapter".into(), command: "command".into(), arguments: Default::default(), envs: Default::default(), connection: None, cwd: None, + request_args: dap_types::StartDebuggingRequestArguments { + configuration: serde_json::Value::Null, + request: dap_types::StartDebuggingRequestArgumentsRequest::Launch, + }, }, Box::new({ let called_event_handler = called_event_handler.clone(); diff --git a/crates/dap/src/transport.rs b/crates/dap/src/transport.rs index 727cc1c00d..c7857e20dd 100644 --- a/crates/dap/src/transport.rs +++ b/crates/dap/src/transport.rs @@ -699,14 +699,8 @@ impl StdioTransport { } #[cfg(any(test, feature = "test-support"))] -type RequestHandler = Box< - dyn Send - + FnMut( - u64, - serde_json::Value, - Arc>, - ) -> std::pin::Pin + Send>>, ->; +type RequestHandler = + Box dap_types::messages::Response>; #[cfg(any(test, feature = "test-support"))] type ResponseHandler = Box; @@ -714,45 +708,41 @@ type ResponseHandler = Box; #[cfg(any(test, feature = "test-support"))] pub struct FakeTransport { // for sending fake response back from adapter side - request_handlers: Arc>>, + request_handlers: Arc>>, // for reverse request responses - response_handlers: Arc>>, + response_handlers: Arc>>, } #[cfg(any(test, feature = "test-support"))] impl FakeTransport { - pub async fn on_request(&self, mut handler: F) + pub fn on_request(&self, mut handler: F) where F: 'static + Send + FnMut(u64, R::Arguments) -> Result, { - self.request_handlers.lock().await.insert( + self.request_handlers.lock().insert( R::COMMAND, - Box::new( - move |seq, args, writer: Arc>| { - let response = handler(seq, serde_json::from_value(args).unwrap()); - - let message = serde_json::to_string(&Message::Response(Response { + Box::new(move |seq, args| { + let result = handler(seq, serde_json::from_value(args).unwrap()); + let response = match result { + Ok(response) => Response { seq: seq + 1, request_seq: seq, - success: response.as_ref().is_ok(), + success: true, command: R::COMMAND.into(), - body: util::maybe!({ serde_json::to_value(response.ok()?).ok() }), + body: Some(serde_json::to_value(response).unwrap()), message: None, - })) - .unwrap(); - - let writer = writer.clone(); - - Box::pin(async move { - let mut writer = writer.lock().await; - writer - .write_all(TransportDelegate::build_rpc_message(message).as_bytes()) - .await - .unwrap(); - writer.flush().await.unwrap(); - }) - }, - ), + }, + Err(response) => Response { + seq: seq + 1, + request_seq: seq, + success: false, + command: R::COMMAND.into(), + body: Some(serde_json::to_value(response).unwrap()), + message: None, + }, + }; + response + }), ); } @@ -762,14 +752,13 @@ impl FakeTransport { { self.response_handlers .lock() - .await .insert(R::COMMAND, Box::new(handler)); } async fn start(cx: AsyncApp) -> Result<(TransportPipe, Self)> { let this = Self { - request_handlers: Arc::new(Mutex::new(HashMap::default())), - response_handlers: Arc::new(Mutex::new(HashMap::default())), + request_handlers: Arc::new(parking_lot::Mutex::new(HashMap::default())), + response_handlers: Arc::new(parking_lot::Mutex::new(HashMap::default())), }; use dap_types::requests::{Request, RunInTerminal, StartDebugging}; use serde_json::json; @@ -816,23 +805,31 @@ impl FakeTransport { .unwrap(); writer.flush().await.unwrap(); } else { - if let Some(handle) = request_handlers + let response = if let Some(handle) = request_handlers .lock() - .await .get_mut(request.command.as_str()) { handle( request.seq, request.arguments.unwrap_or(json!({})), - stdout_writer.clone(), ) - .await; } else { - log::error!( - "No request handler for {}", - request.command - ); - } + panic!("No request handler for {}", request.command); + }; + let message = + serde_json::to_string(&Message::Response(response)) + .unwrap(); + + let mut writer = stdout_writer.lock().await; + + writer + .write_all( + TransportDelegate::build_rpc_message(message) + .as_bytes(), + ) + .await + .unwrap(); + writer.flush().await.unwrap(); } } Message::Event(event) => { @@ -850,10 +847,8 @@ impl FakeTransport { writer.flush().await.unwrap(); } Message::Response(response) => { - if let Some(handle) = response_handlers - .lock() - .await - .get(response.command.as_str()) + if let Some(handle) = + response_handlers.lock().get(response.command.as_str()) { handle(response); } else { diff --git a/crates/dap_adapters/src/codelldb.rs b/crates/dap_adapters/src/codelldb.rs index 08dc03707a..8fe2497738 100644 --- a/crates/dap_adapters/src/codelldb.rs +++ b/crates/dap_adapters/src/codelldb.rs @@ -4,7 +4,7 @@ use anyhow::{Result, bail}; use async_trait::async_trait; use dap::adapters::latest_github_release; use gpui::AsyncApp; -use task::{DebugAdapterConfig, DebugRequestType, DebugTaskDefinition}; +use task::{DebugRequestType, DebugTaskDefinition}; use crate::*; @@ -15,6 +15,42 @@ pub(crate) struct CodeLldbDebugAdapter { impl CodeLldbDebugAdapter { const ADAPTER_NAME: &'static str = "CodeLLDB"; + + fn request_args(&self, config: &DebugTaskDefinition) -> dap::StartDebuggingRequestArguments { + let mut configuration = json!({ + "request": match config.request { + DebugRequestType::Launch(_) => "launch", + DebugRequestType::Attach(_) => "attach", + }, + }); + let map = configuration.as_object_mut().unwrap(); + // CodeLLDB uses `name` for a terminal label. + map.insert("name".into(), Value::String(config.label.clone())); + let request = config.request.to_dap(); + match &config.request { + DebugRequestType::Attach(attach) => { + map.insert("pid".into(), attach.process_id.into()); + } + DebugRequestType::Launch(launch) => { + map.insert("program".into(), launch.program.clone().into()); + + if !launch.args.is_empty() { + map.insert("args".into(), launch.args.clone().into()); + } + + if let Some(stop_on_entry) = config.stop_on_entry { + map.insert("stopOnEntry".into(), stop_on_entry.into()); + } + if let Some(cwd) = launch.cwd.as_ref() { + map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into()); + } + } + } + dap::StartDebuggingRequestArguments { + request, + configuration, + } + } } #[async_trait(?Send)] @@ -86,7 +122,7 @@ impl DebugAdapter for CodeLldbDebugAdapter { async fn get_installed_binary( &self, _: &dyn DapDelegate, - _: &DebugAdapterConfig, + config: &DebugTaskDefinition, _: Option, _: &mut AsyncApp, ) -> Result { @@ -111,39 +147,10 @@ impl DebugAdapter for CodeLldbDebugAdapter { .to_string() .into(), ]), - ..Default::default() + request_args: self.request_args(config), + adapter_name: "test".into(), + envs: None, + connection: None, }) } - - fn request_args(&self, config: &DebugTaskDefinition) -> Value { - let mut args = json!({ - "request": match config.request { - DebugRequestType::Launch(_) => "launch", - DebugRequestType::Attach(_) => "attach", - }, - }); - let map = args.as_object_mut().unwrap(); - // CodeLLDB uses `name` for a terminal label. - map.insert("name".into(), Value::String(config.label.clone())); - match &config.request { - DebugRequestType::Attach(attach) => { - map.insert("pid".into(), attach.process_id.into()); - } - DebugRequestType::Launch(launch) => { - map.insert("program".into(), launch.program.clone().into()); - - if !launch.args.is_empty() { - map.insert("args".into(), launch.args.clone().into()); - } - - if let Some(stop_on_entry) = config.stop_on_entry { - map.insert("stopOnEntry".into(), stop_on_entry.into()); - } - if let Some(cwd) = launch.cwd.as_ref() { - map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into()); - } - } - } - args - } } diff --git a/crates/dap_adapters/src/dap_adapters.rs b/crates/dap_adapters/src/dap_adapters.rs index f6c6f7844c..de9c2b86d6 100644 --- a/crates/dap_adapters/src/dap_adapters.rs +++ b/crates/dap_adapters/src/dap_adapters.rs @@ -12,7 +12,7 @@ use anyhow::{Result, anyhow}; use async_trait::async_trait; use codelldb::CodeLldbDebugAdapter; use dap::{ - DapRegistry, + DapRegistry, DebugRequestType, adapters::{ self, AdapterVersion, DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName, GithubRepo, @@ -25,7 +25,7 @@ use lldb::LldbDebugAdapter; use php::PhpDebugAdapter; use python::PythonDebugAdapter; use serde_json::{Value, json}; -use task::{DebugAdapterConfig, TCPHost}; +use task::TCPHost; pub fn init(registry: Arc) { registry.add_adapter(Arc::from(CodeLldbDebugAdapter::default())); @@ -51,3 +51,16 @@ pub(crate) async fn configure_tcp_connection( Ok((host, port, timeout)) } + +trait ToDap { + fn to_dap(&self) -> dap::StartDebuggingRequestArgumentsRequest; +} + +impl ToDap for DebugRequestType { + fn to_dap(&self) -> dap::StartDebuggingRequestArgumentsRequest { + match self { + Self::Launch(_) => dap::StartDebuggingRequestArgumentsRequest::Launch, + Self::Attach(_) => dap::StartDebuggingRequestArgumentsRequest::Attach, + } + } +} diff --git a/crates/dap_adapters/src/gdb.rs b/crates/dap_adapters/src/gdb.rs index e67556b6ae..1510d1a90c 100644 --- a/crates/dap_adapters/src/gdb.rs +++ b/crates/dap_adapters/src/gdb.rs @@ -2,8 +2,9 @@ use std::ffi::OsStr; use anyhow::{Result, bail}; use async_trait::async_trait; +use dap::StartDebuggingRequestArguments; use gpui::AsyncApp; -use task::{DebugAdapterConfig, DebugRequestType, DebugTaskDefinition}; +use task::{DebugRequestType, DebugTaskDefinition}; use crate::*; @@ -12,68 +13,8 @@ pub(crate) struct GdbDebugAdapter; impl GdbDebugAdapter { const ADAPTER_NAME: &'static str = "GDB"; -} -#[async_trait(?Send)] -impl DebugAdapter for GdbDebugAdapter { - fn name(&self) -> DebugAdapterName { - DebugAdapterName(Self::ADAPTER_NAME.into()) - } - - async fn get_binary( - &self, - delegate: &dyn DapDelegate, - _: &DebugAdapterConfig, - user_installed_path: Option, - _: &mut AsyncApp, - ) -> Result { - let user_setting_path = user_installed_path - .filter(|p| p.exists()) - .and_then(|p| p.to_str().map(|s| s.to_string())); - - let gdb_path = delegate - .which(OsStr::new("gdb")) - .and_then(|p| p.to_str().map(|s| s.to_string())) - .ok_or(anyhow!("Could not find gdb in path")); - - if gdb_path.is_err() && user_setting_path.is_none() { - bail!("Could not find gdb path or it's not installed"); - } - - let gdb_path = user_setting_path.unwrap_or(gdb_path?); - - Ok(DebugAdapterBinary { - command: gdb_path, - arguments: Some(vec!["-i=dap".into()]), - envs: None, - cwd: None, - connection: None, - }) - } - - async fn install_binary( - &self, - _version: AdapterVersion, - _delegate: &dyn DapDelegate, - ) -> Result<()> { - unimplemented!("GDB debug adapter cannot be installed by Zed (yet)") - } - - async fn fetch_latest_adapter_version(&self, _: &dyn DapDelegate) -> Result { - unimplemented!("Fetch latest GDB version not implemented (yet)") - } - - async fn get_installed_binary( - &self, - _: &dyn DapDelegate, - _: &DebugAdapterConfig, - _: Option, - _: &mut AsyncApp, - ) -> Result { - unimplemented!("GDB cannot be installed by Zed (yet)") - } - - fn request_args(&self, config: &DebugTaskDefinition) -> Value { + fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments { let mut args = json!({ "request": match config.request { DebugRequestType::Launch(_) => "launch", @@ -105,6 +46,71 @@ impl DebugAdapter for GdbDebugAdapter { } } } - args + StartDebuggingRequestArguments { + configuration: args, + request: config.request.to_dap(), + } + } +} + +#[async_trait(?Send)] +impl DebugAdapter for GdbDebugAdapter { + fn name(&self) -> DebugAdapterName { + DebugAdapterName(Self::ADAPTER_NAME.into()) + } + + async fn get_binary( + &self, + delegate: &dyn DapDelegate, + config: &DebugTaskDefinition, + user_installed_path: Option, + _: &mut AsyncApp, + ) -> Result { + let user_setting_path = user_installed_path + .filter(|p| p.exists()) + .and_then(|p| p.to_str().map(|s| s.to_string())); + + let gdb_path = delegate + .which(OsStr::new("gdb")) + .and_then(|p| p.to_str().map(|s| s.to_string())) + .ok_or(anyhow!("Could not find gdb in path")); + + if gdb_path.is_err() && user_setting_path.is_none() { + bail!("Could not find gdb path or it's not installed"); + } + + let gdb_path = user_setting_path.unwrap_or(gdb_path?); + + Ok(DebugAdapterBinary { + adapter_name: Self::ADAPTER_NAME.into(), + command: gdb_path, + arguments: Some(vec!["-i=dap".into()]), + envs: None, + cwd: None, + connection: None, + request_args: self.request_args(config), + }) + } + + async fn install_binary( + &self, + _version: AdapterVersion, + _delegate: &dyn DapDelegate, + ) -> Result<()> { + unimplemented!("GDB debug adapter cannot be installed by Zed (yet)") + } + + async fn fetch_latest_adapter_version(&self, _: &dyn DapDelegate) -> Result { + unimplemented!("Fetch latest GDB version not implemented (yet)") + } + + async fn get_installed_binary( + &self, + _: &dyn DapDelegate, + _: &DebugTaskDefinition, + _: Option, + _: &mut AsyncApp, + ) -> Result { + unimplemented!("GDB cannot be installed by Zed (yet)") } } diff --git a/crates/dap_adapters/src/go.rs b/crates/dap_adapters/src/go.rs index ecb1408ca0..535e0b59e3 100644 --- a/crates/dap_adapters/src/go.rs +++ b/crates/dap_adapters/src/go.rs @@ -1,3 +1,4 @@ +use dap::StartDebuggingRequestArguments; use gpui::AsyncApp; use std::{ffi::OsStr, path::PathBuf}; use task::DebugTaskDefinition; @@ -9,6 +10,31 @@ pub(crate) struct GoDebugAdapter; impl GoDebugAdapter { const ADAPTER_NAME: &'static str = "Delve"; + fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments { + let mut args = match &config.request { + dap::DebugRequestType::Attach(attach_config) => { + json!({ + "processId": attach_config.process_id, + }) + } + dap::DebugRequestType::Launch(launch_config) => json!({ + "program": launch_config.program, + "cwd": launch_config.cwd, + "args": launch_config.args + }), + }; + + let map = args.as_object_mut().unwrap(); + + if let Some(stop_on_entry) = config.stop_on_entry { + map.insert("stopOnEntry".into(), stop_on_entry.into()); + } + + StartDebuggingRequestArguments { + configuration: args, + request: config.request.to_dap(), + } + } } #[async_trait(?Send)] @@ -20,7 +46,7 @@ impl DebugAdapter for GoDebugAdapter { async fn get_binary( &self, delegate: &dyn DapDelegate, - config: &DebugAdapterConfig, + config: &DebugTaskDefinition, user_installed_path: Option, cx: &mut AsyncApp, ) -> Result { @@ -53,7 +79,7 @@ impl DebugAdapter for GoDebugAdapter { async fn get_installed_binary( &self, delegate: &dyn DapDelegate, - config: &DebugAdapterConfig, + config: &DebugTaskDefinition, _: Option, _: &mut AsyncApp, ) -> Result { @@ -66,6 +92,7 @@ impl DebugAdapter for GoDebugAdapter { let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?; Ok(DebugAdapterBinary { + adapter_name: self.name(), command: delve_path, arguments: Some(vec![ "dap".into(), @@ -79,29 +106,7 @@ impl DebugAdapter for GoDebugAdapter { port, timeout, }), + request_args: self.request_args(config), }) } - - fn request_args(&self, config: &DebugTaskDefinition) -> Value { - let mut args = match &config.request { - dap::DebugRequestType::Attach(attach_config) => { - json!({ - "processId": attach_config.process_id, - }) - } - dap::DebugRequestType::Launch(launch_config) => json!({ - "program": launch_config.program, - "cwd": launch_config.cwd, - "args": launch_config.args - }), - }; - - let map = args.as_object_mut().unwrap(); - - if let Some(stop_on_entry) = config.stop_on_entry { - map.insert("stopOnEntry".into(), stop_on_entry.into()); - } - - args - } } diff --git a/crates/dap_adapters/src/javascript.rs b/crates/dap_adapters/src/javascript.rs index 11dee971b1..aa6c052d3f 100644 --- a/crates/dap_adapters/src/javascript.rs +++ b/crates/dap_adapters/src/javascript.rs @@ -1,4 +1,5 @@ use adapters::latest_github_release; +use dap::StartDebuggingRequestArguments; use gpui::AsyncApp; use std::path::PathBuf; use task::{DebugRequestType, DebugTaskDefinition}; @@ -12,6 +13,40 @@ impl JsDebugAdapter { const ADAPTER_NAME: &'static str = "JavaScript"; const ADAPTER_NPM_NAME: &'static str = "vscode-js-debug"; const ADAPTER_PATH: &'static str = "js-debug/src/dapDebugServer.js"; + + fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments { + let mut args = json!({ + "type": "pwa-node", + "request": match config.request { + DebugRequestType::Launch(_) => "launch", + DebugRequestType::Attach(_) => "attach", + }, + }); + let map = args.as_object_mut().unwrap(); + match &config.request { + DebugRequestType::Attach(attach) => { + map.insert("processId".into(), attach.process_id.into()); + } + DebugRequestType::Launch(launch) => { + map.insert("program".into(), launch.program.clone().into()); + + if !launch.args.is_empty() { + map.insert("args".into(), launch.args.clone().into()); + } + + if let Some(stop_on_entry) = config.stop_on_entry { + map.insert("stopOnEntry".into(), stop_on_entry.into()); + } + if let Some(cwd) = launch.cwd.as_ref() { + map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into()); + } + } + } + StartDebuggingRequestArguments { + configuration: args, + request: config.request.to_dap(), + } + } } #[async_trait(?Send)] @@ -49,7 +84,7 @@ impl DebugAdapter for JsDebugAdapter { async fn get_installed_binary( &self, delegate: &dyn DapDelegate, - config: &DebugAdapterConfig, + config: &DebugTaskDefinition, user_installed_path: Option, _: &mut AsyncApp, ) -> Result { @@ -71,6 +106,7 @@ impl DebugAdapter for JsDebugAdapter { let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?; Ok(DebugAdapterBinary { + adapter_name: self.name(), command: delegate .node_runtime() .binary_path() @@ -89,6 +125,7 @@ impl DebugAdapter for JsDebugAdapter { port, timeout, }), + request_args: self.request_args(config), }) } @@ -107,35 +144,4 @@ impl DebugAdapter for JsDebugAdapter { return Ok(()); } - - fn request_args(&self, config: &DebugTaskDefinition) -> Value { - let mut args = json!({ - "type": "pwa-node", - "request": match config.request { - DebugRequestType::Launch(_) => "launch", - DebugRequestType::Attach(_) => "attach", - }, - }); - let map = args.as_object_mut().unwrap(); - match &config.request { - DebugRequestType::Attach(attach) => { - map.insert("processId".into(), attach.process_id.into()); - } - DebugRequestType::Launch(launch) => { - map.insert("program".into(), launch.program.clone().into()); - - if !launch.args.is_empty() { - map.insert("args".into(), launch.args.clone().into()); - } - - if let Some(stop_on_entry) = config.stop_on_entry { - map.insert("stopOnEntry".into(), stop_on_entry.into()); - } - if let Some(cwd) = launch.cwd.as_ref() { - map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into()); - } - } - } - args - } } diff --git a/crates/dap_adapters/src/lldb.rs b/crates/dap_adapters/src/lldb.rs index bb3f28c210..763d783d57 100644 --- a/crates/dap_adapters/src/lldb.rs +++ b/crates/dap_adapters/src/lldb.rs @@ -3,7 +3,7 @@ use std::{ffi::OsStr, path::PathBuf}; use anyhow::Result; use async_trait::async_trait; use gpui::AsyncApp; -use task::{DebugAdapterConfig, DebugRequestType, DebugTaskDefinition}; +use task::{DebugRequestType, DebugTaskDefinition}; use crate::*; @@ -12,62 +12,9 @@ pub(crate) struct LldbDebugAdapter; impl LldbDebugAdapter { const ADAPTER_NAME: &'static str = "LLDB"; -} -#[async_trait(?Send)] -impl DebugAdapter for LldbDebugAdapter { - fn name(&self) -> DebugAdapterName { - DebugAdapterName(Self::ADAPTER_NAME.into()) - } - - async fn get_binary( - &self, - delegate: &dyn DapDelegate, - _: &DebugAdapterConfig, - user_installed_path: Option, - _: &mut AsyncApp, - ) -> Result { - let lldb_dap_path = if let Some(user_installed_path) = user_installed_path { - user_installed_path.to_string_lossy().into() - } else { - delegate - .which(OsStr::new("lldb-dap")) - .and_then(|p| p.to_str().map(|s| s.to_string())) - .ok_or(anyhow!("Could not find lldb-dap in path"))? - }; - - Ok(DebugAdapterBinary { - command: lldb_dap_path, - arguments: None, - envs: None, - cwd: None, - connection: None, - }) - } - - async fn install_binary( - &self, - _version: AdapterVersion, - _delegate: &dyn DapDelegate, - ) -> Result<()> { - unimplemented!("LLDB debug adapter cannot be installed by Zed (yet)") - } - - async fn fetch_latest_adapter_version(&self, _: &dyn DapDelegate) -> Result { - unimplemented!("Fetch latest adapter version not implemented for lldb (yet)") - } - - async fn get_installed_binary( - &self, - _: &dyn DapDelegate, - _: &DebugAdapterConfig, - _: Option, - _: &mut AsyncApp, - ) -> Result { - unimplemented!("LLDB debug adapter cannot be installed by Zed (yet)") - } - - fn request_args(&self, config: &DebugTaskDefinition) -> Value { + fn request_args(&self, config: &DebugTaskDefinition) -> dap::StartDebuggingRequestArguments { + let request = config.request.to_dap(); let mut args = json!({ "request": match config.request { DebugRequestType::Launch(_) => "launch", @@ -94,6 +41,65 @@ impl DebugAdapter for LldbDebugAdapter { } } } - args + dap::StartDebuggingRequestArguments { + request, + configuration: args, + } + } +} + +#[async_trait(?Send)] +impl DebugAdapter for LldbDebugAdapter { + fn name(&self) -> DebugAdapterName { + DebugAdapterName(Self::ADAPTER_NAME.into()) + } + + async fn get_binary( + &self, + delegate: &dyn DapDelegate, + config: &DebugTaskDefinition, + user_installed_path: Option, + _: &mut AsyncApp, + ) -> Result { + let lldb_dap_path = if let Some(user_installed_path) = user_installed_path { + user_installed_path.to_string_lossy().into() + } else { + delegate + .which(OsStr::new("lldb-dap")) + .and_then(|p| p.to_str().map(|s| s.to_string())) + .ok_or(anyhow!("Could not find lldb-dap in path"))? + }; + + Ok(DebugAdapterBinary { + adapter_name: Self::ADAPTER_NAME.into(), + command: lldb_dap_path, + arguments: None, + envs: None, + cwd: None, + connection: None, + request_args: self.request_args(config), + }) + } + + async fn install_binary( + &self, + _version: AdapterVersion, + _delegate: &dyn DapDelegate, + ) -> Result<()> { + unimplemented!("LLDB debug adapter cannot be installed by Zed (yet)") + } + + async fn fetch_latest_adapter_version(&self, _: &dyn DapDelegate) -> Result { + unimplemented!("Fetch latest adapter version not implemented for lldb (yet)") + } + + async fn get_installed_binary( + &self, + _: &dyn DapDelegate, + _: &DebugTaskDefinition, + _: Option, + _: &mut AsyncApp, + ) -> Result { + unimplemented!("LLDB debug adapter cannot be installed by Zed (yet)") } } diff --git a/crates/dap_adapters/src/php.rs b/crates/dap_adapters/src/php.rs index 76134a818f..895d8c52b2 100644 --- a/crates/dap_adapters/src/php.rs +++ b/crates/dap_adapters/src/php.rs @@ -13,6 +13,28 @@ impl PhpDebugAdapter { const ADAPTER_NAME: &'static str = "PHP"; const ADAPTER_PACKAGE_NAME: &'static str = "vscode-php-debug"; const ADAPTER_PATH: &'static str = "extension/out/phpDebug.js"; + + fn request_args( + &self, + config: &DebugTaskDefinition, + ) -> Result { + match &config.request { + dap::DebugRequestType::Attach(_) => { + anyhow::bail!("php adapter does not support attaching") + } + dap::DebugRequestType::Launch(launch_config) => { + Ok(dap::StartDebuggingRequestArguments { + configuration: json!({ + "program": launch_config.program, + "cwd": launch_config.cwd, + "args": launch_config.args, + "stopOnEntry": config.stop_on_entry.unwrap_or_default(), + }), + request: config.request.to_dap(), + }) + } + } + } } #[async_trait(?Send)] @@ -50,7 +72,7 @@ impl DebugAdapter for PhpDebugAdapter { async fn get_installed_binary( &self, delegate: &dyn DapDelegate, - config: &DebugAdapterConfig, + config: &DebugTaskDefinition, user_installed_path: Option, _: &mut AsyncApp, ) -> Result { @@ -72,6 +94,7 @@ impl DebugAdapter for PhpDebugAdapter { let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?; Ok(DebugAdapterBinary { + adapter_name: self.name(), command: delegate .node_runtime() .binary_path() @@ -89,6 +112,7 @@ impl DebugAdapter for PhpDebugAdapter { }), cwd: None, envs: None, + request_args: self.request_args(config)?, }) } @@ -107,21 +131,4 @@ impl DebugAdapter for PhpDebugAdapter { Ok(()) } - - fn request_args(&self, config: &DebugTaskDefinition) -> Value { - match &config.request { - dap::DebugRequestType::Attach(_) => { - // php adapter does not support attaching - json!({}) - } - dap::DebugRequestType::Launch(launch_config) => { - json!({ - "program": launch_config.program, - "cwd": launch_config.cwd, - "args": launch_config.args, - "stopOnEntry": config.stop_on_entry.unwrap_or_default(), - }) - } - } - } } diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index daed07a653..01b008f371 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -1,5 +1,5 @@ use crate::*; -use dap::DebugRequestType; +use dap::{DebugRequestType, StartDebuggingRequestArguments}; use gpui::AsyncApp; use std::{ffi::OsStr, path::PathBuf}; use task::DebugTaskDefinition; @@ -12,6 +12,38 @@ impl PythonDebugAdapter { const ADAPTER_PACKAGE_NAME: &'static str = "debugpy"; const ADAPTER_PATH: &'static str = "src/debugpy/adapter"; const LANGUAGE_NAME: &'static str = "Python"; + + fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments { + let mut args = json!({ + "request": match config.request { + DebugRequestType::Launch(_) => "launch", + DebugRequestType::Attach(_) => "attach", + }, + "subProcess": true, + "redirectOutput": true, + }); + let map = args.as_object_mut().unwrap(); + match &config.request { + DebugRequestType::Attach(attach) => { + map.insert("processId".into(), attach.process_id.into()); + } + DebugRequestType::Launch(launch) => { + map.insert("program".into(), launch.program.clone().into()); + map.insert("args".into(), launch.args.clone().into()); + + if let Some(stop_on_entry) = config.stop_on_entry { + map.insert("stopOnEntry".into(), stop_on_entry.into()); + } + if let Some(cwd) = launch.cwd.as_ref() { + map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into()); + } + } + } + StartDebuggingRequestArguments { + configuration: args, + request: config.request.to_dap(), + } + } } #[async_trait(?Send)] @@ -64,7 +96,7 @@ impl DebugAdapter for PythonDebugAdapter { async fn get_installed_binary( &self, delegate: &dyn DapDelegate, - config: &DebugAdapterConfig, + config: &DebugTaskDefinition, user_installed_path: Option, cx: &mut AsyncApp, ) -> Result { @@ -109,6 +141,7 @@ impl DebugAdapter for PythonDebugAdapter { }; Ok(DebugAdapterBinary { + adapter_name: self.name(), command: python_path.ok_or(anyhow!("failed to find binary path for python"))?, arguments: Some(vec![ debugpy_dir.join(Self::ADAPTER_PATH).into(), @@ -122,35 +155,7 @@ impl DebugAdapter for PythonDebugAdapter { }), cwd: None, envs: None, + request_args: self.request_args(config), }) } - - fn request_args(&self, config: &DebugTaskDefinition) -> Value { - let mut args = json!({ - "request": match config.request { - DebugRequestType::Launch(_) => "launch", - DebugRequestType::Attach(_) => "attach", - }, - "subProcess": true, - "redirectOutput": true, - }); - let map = args.as_object_mut().unwrap(); - match &config.request { - DebugRequestType::Attach(attach) => { - map.insert("processId".into(), attach.process_id.into()); - } - DebugRequestType::Launch(launch) => { - map.insert("program".into(), launch.program.clone().into()); - map.insert("args".into(), launch.args.clone().into()); - - if let Some(stop_on_entry) = config.stop_on_entry { - map.insert("stopOnEntry".into(), stop_on_entry.into()); - } - if let Some(cwd) = launch.cwd.as_ref() { - map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into()); - } - } - } - args - } } diff --git a/crates/debugger_ui/src/attach_modal.rs b/crates/debugger_ui/src/attach_modal.rs index 870d09aa3f..e6a327e56c 100644 --- a/crates/debugger_ui/src/attach_modal.rs +++ b/crates/debugger_ui/src/attach_modal.rs @@ -228,10 +228,7 @@ impl PickerDelegate for AttachModalDelegate { let config = self.debug_config.clone(); self.project .update(cx, |project, cx| { - #[cfg(any(test, feature = "test-support"))] - let ret = project.fake_debug_session(config.request, None, false, cx); - #[cfg(not(any(test, feature = "test-support")))] - let ret = project.start_debug_session(config.into(), cx); + let ret = project.start_debug_session(config, cx); ret }) .detach_and_log_err(cx); diff --git a/crates/debugger_ui/src/new_session_modal.rs b/crates/debugger_ui/src/new_session_modal.rs index c6f10bae25..9f5c53a4f1 100644 --- a/crates/debugger_ui/src/new_session_modal.rs +++ b/crates/debugger_ui/src/new_session_modal.rs @@ -146,7 +146,7 @@ impl NewSessionModal { { this.start_debug_session(debug_config, cx) } else { - this.start_debug_session(config.into(), cx) + this.start_debug_session(config, cx) } })?; let spawn_result = task.await; diff --git a/crates/debugger_ui/src/tests/attach_modal.rs b/crates/debugger_ui/src/tests/attach_modal.rs index 0c7465ca26..34c67b8690 100644 --- a/crates/debugger_ui/src/tests/attach_modal.rs +++ b/crates/debugger_ui/src/tests/attach_modal.rs @@ -26,18 +26,30 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Attach(AttachConfig { + let session = debugger::test::start_debug_session_with( + &project, + cx, + DebugTaskDefinition { + adapter: "fake-adapter".to_string(), + request: dap::DebugRequestType::Attach(AttachConfig { process_id: Some(10), }), - None, - false, - cx, - ) - }); + label: "label".to_string(), + initialize_args: None, + tcp_connection: None, + locator: None, + stop_on_entry: None, + }, + |client| { + client.on_request::(move |_, args| { + assert_eq!(json!({"request": "attach", "process_id": 10}), args.raw); - let session = task.await.unwrap(); + Ok(()) + }); + }, + ) + .await + .unwrap(); cx.run_until_parked(); @@ -77,7 +89,15 @@ async fn test_show_attach_modal_and_select_process( let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + // Set up handlers for sessions spawned via modal. + let _initialize_subscription = + project::debugger::test::intercept_debug_sessions(cx, |client| { + client.on_request::(move |_, args| { + assert_eq!(json!({"request": "attach", "process_id": 1}), args.raw); + Ok(()) + }); + }); let attach_modal = workspace .update(cx, |workspace, window, cx| { workspace.toggle_modal(window, cx, |window, cx| { diff --git a/crates/debugger_ui/src/tests/console.rs b/crates/debugger_ui/src/tests/console.rs index b665473e5a..1fe57b2906 100644 --- a/crates/debugger_ui/src/tests/console.rs +++ b/crates/debugger_ui/src/tests/console.rs @@ -3,7 +3,6 @@ use dap::requests::StackTrace; use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; use project::{FakeFs, Project}; use serde_json::json; -use task::LaunchConfig; use tests::{init_test, init_test_workspace}; #[gpui::test] @@ -29,26 +28,17 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp }) .unwrap(); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let session = task.await.unwrap(); + let session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); - client - .on_request::(move |_, _| { - Ok(dap::StackTraceResponse { - stack_frames: Vec::default(), - total_frames: None, - }) + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: Vec::default(), + total_frames: None, }) - .await; + }); client .fake_event(dap::messages::Events::Output(dap::OutputEvent { diff --git a/crates/debugger_ui/src/tests/debugger_panel.rs b/crates/debugger_ui/src/tests/debugger_panel.rs index a19d852a85..4985fccb15 100644 --- a/crates/debugger_ui/src/tests/debugger_panel.rs +++ b/crates/debugger_ui/src/tests/debugger_panel.rs @@ -1,7 +1,7 @@ use crate::*; use dap::{ - ErrorResponse, RunInTerminalRequestArguments, SourceBreakpoint, StartDebuggingRequestArguments, - StartDebuggingRequestArgumentsRequest, + ErrorResponse, Message, RunInTerminalRequestArguments, SourceBreakpoint, + StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest, client::SessionId, requests::{ Continue, Disconnect, Launch, Next, RunInTerminal, SetBreakpoints, StackTrace, @@ -25,7 +25,6 @@ use std::{ atomic::{AtomicBool, Ordering}, }, }; -use task::LaunchConfig; use terminal_view::{TerminalView, terminal_panel::TerminalPanel}; use tests::{active_debug_session_panel, init_test, init_test_workspace}; use util::path; @@ -49,37 +48,26 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let session = task.await.unwrap(); + let session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); - client - .on_request::(move |_, _| { - Ok(dap::ThreadsResponse { - threads: vec![dap::Thread { - id: 1, - name: "Thread 1".into(), - }], - }) + client.on_request::(move |_, _| { + Ok(dap::ThreadsResponse { + threads: vec![dap::Thread { + id: 1, + name: "Thread 1".into(), + }], }) - .await; + }); - client - .on_request::(move |_, _| { - Ok(dap::StackTraceResponse { - stack_frames: Vec::default(), - total_frames: None, - }) + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: Vec::default(), + total_frames: None, }) - .await; + }); cx.run_until_parked(); @@ -199,37 +187,26 @@ async fn test_we_can_only_have_one_panel_per_debug_session( let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let session = task.await.unwrap(); + let session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); - client - .on_request::(move |_, _| { - Ok(dap::ThreadsResponse { - threads: vec![dap::Thread { - id: 1, - name: "Thread 1".into(), - }], - }) + client.on_request::(move |_, _| { + Ok(dap::ThreadsResponse { + threads: vec![dap::Thread { + id: 1, + name: "Thread 1".into(), + }], }) - .await; + }); - client - .on_request::(move |_, _| { - Ok(dap::StackTraceResponse { - stack_frames: Vec::default(), - total_frames: None, - }) + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: Vec::default(), + total_frames: None, }) - .await; + }); cx.run_until_parked(); @@ -377,16 +354,9 @@ async fn test_handle_successful_run_in_terminal_reverse_request( let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let session = task.await.unwrap(); + let session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); client @@ -474,16 +444,9 @@ async fn test_handle_error_run_in_terminal_reverse_request( let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let session = task.await.unwrap(); + let session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); client @@ -559,28 +522,19 @@ async fn test_handle_start_debugging_reverse_request( let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let session = task.await.unwrap(); + let session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); - client - .on_request::(move |_, _| { - Ok(dap::ThreadsResponse { - threads: vec![dap::Thread { - id: 1, - name: "Thread 1".into(), - }], - }) + client.on_request::(move |_, _| { + Ok(dap::ThreadsResponse { + threads: vec![dap::Thread { + id: 1, + name: "Thread 1".into(), + }], }) - .await; + }); client .on_response::({ @@ -593,7 +547,9 @@ async fn test_handle_start_debugging_reverse_request( } }) .await; - + // Set up handlers for sessions spawned with reverse request too. + let _reverse_request_subscription = + project::debugger::test::intercept_debug_sessions(cx, |_| {}); client .fake_reverse_request::(StartDebuggingRequestArguments { configuration: json!({}), @@ -612,20 +568,16 @@ async fn test_handle_start_debugging_reverse_request( }); let child_client = child_session.update(cx, |session, _| session.adapter_client().unwrap()); - child_client - .on_request::(move |_, _| { - Ok(dap::ThreadsResponse { - threads: vec![dap::Thread { - id: 1, - name: "Thread 1".into(), - }], - }) + child_client.on_request::(move |_, _| { + Ok(dap::ThreadsResponse { + threads: vec![dap::Thread { + id: 1, + name: "Thread 1".into(), + }], }) - .await; + }); - child_client - .on_request::(move |_, _| Ok(())) - .await; + child_client.on_request::(move |_, _| Ok(())); child_client .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { @@ -677,31 +629,24 @@ async fn test_shutdown_children_when_parent_session_shutdown( let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let parent_session = task.await.unwrap(); + let parent_session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap()); - client - .on_request::(move |_, _| { - Ok(dap::ThreadsResponse { - threads: vec![dap::Thread { - id: 1, - name: "Thread 1".into(), - }], - }) + client.on_request::(move |_, _| { + Ok(dap::ThreadsResponse { + threads: vec![dap::Thread { + id: 1, + name: "Thread 1".into(), + }], }) - .await; + }); client.on_response::(move |_| {}).await; - + // Set up handlers for sessions spawned with reverse request too. + let _reverse_request_subscription = + project::debugger::test::intercept_debug_sessions(cx, |_| {}); // start first child session client .fake_reverse_request::(StartDebuggingRequestArguments { @@ -729,9 +674,7 @@ async fn test_shutdown_children_when_parent_session_shutdown( let first_child_client = first_child_session.update(cx, |session, _| session.adapter_client().unwrap()); - first_child_client - .on_request::(move |_, _| Ok(())) - .await; + first_child_client.on_request::(move |_, _| Ok(())); // configure second child session let second_child_session = dap_store.read_with(cx, |dap_store, _| { @@ -740,9 +683,7 @@ async fn test_shutdown_children_when_parent_session_shutdown( let second_child_client = second_child_session.update(cx, |session, _| session.adapter_client().unwrap()); - second_child_client - .on_request::(move |_, _| Ok(())) - .await; + second_child_client.on_request::(move |_, _| Ok(())); cx.run_until_parked(); @@ -796,20 +737,15 @@ async fn test_shutdown_parent_session_if_all_children_are_shutdown( let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let parent_session = task.await.unwrap(); + let parent_session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap()); client.on_response::(move |_| {}).await; - + // Set up handlers for sessions spawned with reverse request too. + let _reverse_request_subscription = + project::debugger::test::intercept_debug_sessions(cx, |_| {}); // start first child session client .fake_reverse_request::(StartDebuggingRequestArguments { @@ -837,9 +773,7 @@ async fn test_shutdown_parent_session_if_all_children_are_shutdown( let first_child_client = first_child_session.update(cx, |session, _| session.adapter_client().unwrap()); - first_child_client - .on_request::(move |_, _| Ok(())) - .await; + first_child_client.on_request::(move |_, _| Ok(())); // configure second child session let second_child_session = dap_store.read_with(cx, |dap_store, _| { @@ -848,9 +782,7 @@ async fn test_shutdown_parent_session_if_all_children_are_shutdown( let second_child_client = second_child_session.update(cx, |session, _| session.adapter_client().unwrap()); - second_child_client - .on_request::(move |_, _| Ok(())) - .await; + second_child_client.on_request::(move |_, _| Ok(())); cx.run_until_parked(); @@ -926,123 +858,107 @@ async fn test_debug_panel_item_thread_status_reset_on_failure( let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - Some(dap::Capabilities { + let session = debugger::test::start_debug_session(&project, cx, |client| { + client.on_request::(move |_, _| { + Ok(dap::Capabilities { supports_step_back: Some(true), ..Default::default() - }), - false, - cx, - ) - }); + }) + }); + }) + .await + .unwrap(); - let session = task.await.unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); const THREAD_ID_NUM: u64 = 1; - client - .on_request::(move |_, _| { - Ok(dap::ThreadsResponse { - threads: vec![dap::Thread { - id: THREAD_ID_NUM, - name: "Thread 1".into(), - }], - }) + client.on_request::(move |_, _| { + Ok(dap::ThreadsResponse { + threads: vec![dap::Thread { + id: THREAD_ID_NUM, + name: "Thread 1".into(), + }], }) - .await; + }); - client.on_request::(move |_, _| Ok(())).await; + client.on_request::(move |_, _| Ok(())); - client - .on_request::(move |_, _| { - Ok(dap::StackTraceResponse { - stack_frames: Vec::default(), - total_frames: None, - }) + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: Vec::default(), + total_frames: None, }) - .await; + }); - client - .on_request::(move |_, _| { - Err(ErrorResponse { - error: Some(dap::Message { - id: 1, - format: "error".into(), - variables: None, - send_telemetry: None, - show_user: None, - url: None, - url_label: None, - }), - }) + client.on_request::(move |_, _| { + Err(ErrorResponse { + error: Some(dap::Message { + id: 1, + format: "error".into(), + variables: None, + send_telemetry: None, + show_user: None, + url: None, + url_label: None, + }), }) - .await; + }); - client - .on_request::(move |_, _| { - Err(ErrorResponse { - error: Some(dap::Message { - id: 1, - format: "error".into(), - variables: None, - send_telemetry: None, - show_user: None, - url: None, - url_label: None, - }), - }) + client.on_request::(move |_, _| { + Err(ErrorResponse { + error: Some(dap::Message { + id: 1, + format: "error".into(), + variables: None, + send_telemetry: None, + show_user: None, + url: None, + url_label: None, + }), }) - .await; + }); - client - .on_request::(move |_, _| { - Err(ErrorResponse { - error: Some(dap::Message { - id: 1, - format: "error".into(), - variables: None, - send_telemetry: None, - show_user: None, - url: None, - url_label: None, - }), - }) + client.on_request::(move |_, _| { + Err(ErrorResponse { + error: Some(dap::Message { + id: 1, + format: "error".into(), + variables: None, + send_telemetry: None, + show_user: None, + url: None, + url_label: None, + }), }) - .await; + }); - client - .on_request::(move |_, _| { - Err(ErrorResponse { - error: Some(dap::Message { - id: 1, - format: "error".into(), - variables: None, - send_telemetry: None, - show_user: None, - url: None, - url_label: None, - }), - }) + client.on_request::(move |_, _| { + Err(ErrorResponse { + error: Some(dap::Message { + id: 1, + format: "error".into(), + variables: None, + send_telemetry: None, + show_user: None, + url: None, + url_label: None, + }), }) - .await; + }); - client - .on_request::(move |_, _| { - Err(ErrorResponse { - error: Some(dap::Message { - id: 1, - format: "error".into(), - variables: None, - send_telemetry: None, - show_user: None, - url: None, - url_label: None, - }), - }) + client.on_request::(move |_, _| { + Err(ErrorResponse { + error: Some(dap::Message { + id: 1, + format: "error".into(), + variables: None, + send_telemetry: None, + show_user: None, + url: None, + url_label: None, + }), }) - .await; + }); client .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { @@ -1157,16 +1073,9 @@ async fn test_send_breakpoints_when_editor_has_been_saved( .update(cx, |_, _, cx| worktree.read(cx).id()) .unwrap(); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let session = task.await.unwrap(); + let session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); let buffer = project @@ -1186,16 +1095,14 @@ async fn test_send_breakpoints_when_editor_has_been_saved( ) }); - client.on_request::(move |_, _| Ok(())).await; + client.on_request::(move |_, _| Ok(())); - client - .on_request::(move |_, _| { - Ok(dap::StackTraceResponse { - stack_frames: Vec::default(), - total_frames: None, - }) + client.on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: Vec::default(), + total_frames: None, }) - .await; + }); client .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { @@ -1210,32 +1117,30 @@ async fn test_send_breakpoints_when_editor_has_been_saved( .await; let called_set_breakpoints = Arc::new(AtomicBool::new(false)); - client - .on_request::({ - let called_set_breakpoints = called_set_breakpoints.clone(); - move |_, args| { - assert_eq!(path!("/project/main.rs"), args.source.path.unwrap()); - assert_eq!( - vec![SourceBreakpoint { - line: 2, - column: None, - condition: None, - hit_condition: None, - log_message: None, - mode: None - }], - args.breakpoints.unwrap() - ); - assert!(!args.source_modified.unwrap()); + client.on_request::({ + let called_set_breakpoints = called_set_breakpoints.clone(); + move |_, args| { + assert_eq!(path!("/project/main.rs"), args.source.path.unwrap()); + assert_eq!( + vec![SourceBreakpoint { + line: 2, + column: None, + condition: None, + hit_condition: None, + log_message: None, + mode: None + }], + args.breakpoints.unwrap() + ); + assert!(!args.source_modified.unwrap()); - called_set_breakpoints.store(true, Ordering::SeqCst); + called_set_breakpoints.store(true, Ordering::SeqCst); - Ok(dap::SetBreakpointsResponse { - breakpoints: Vec::default(), - }) - } - }) - .await; + Ok(dap::SetBreakpointsResponse { + breakpoints: Vec::default(), + }) + } + }); editor.update_in(cx, |editor, window, cx| { editor.move_down(&actions::MoveDown, window, cx); @@ -1250,32 +1155,30 @@ async fn test_send_breakpoints_when_editor_has_been_saved( ); let called_set_breakpoints = Arc::new(AtomicBool::new(false)); - client - .on_request::({ - let called_set_breakpoints = called_set_breakpoints.clone(); - move |_, args| { - assert_eq!(path!("/project/main.rs"), args.source.path.unwrap()); - assert_eq!( - vec![SourceBreakpoint { - line: 3, - column: None, - condition: None, - hit_condition: None, - log_message: None, - mode: None - }], - args.breakpoints.unwrap() - ); - assert!(args.source_modified.unwrap()); + client.on_request::({ + let called_set_breakpoints = called_set_breakpoints.clone(); + move |_, args| { + assert_eq!(path!("/project/main.rs"), args.source.path.unwrap()); + assert_eq!( + vec![SourceBreakpoint { + line: 3, + column: None, + condition: None, + hit_condition: None, + log_message: None, + mode: None + }], + args.breakpoints.unwrap() + ); + assert!(args.source_modified.unwrap()); - called_set_breakpoints.store(true, Ordering::SeqCst); + called_set_breakpoints.store(true, Ordering::SeqCst); - Ok(dap::SetBreakpointsResponse { - breakpoints: Vec::default(), - }) - } - }) - .await; + Ok(dap::SetBreakpointsResponse { + breakpoints: Vec::default(), + }) + } + }); editor.update_in(cx, |editor, window, cx| { editor.move_up(&actions::MoveUp, window, cx); @@ -1387,49 +1290,40 @@ async fn test_unsetting_breakpoints_on_clear_breakpoint_action( editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx); }); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let session = task.await.unwrap(); + let session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); let called_set_breakpoints = Arc::new(AtomicBool::new(false)); - client - .on_request::({ - let called_set_breakpoints = called_set_breakpoints.clone(); - move |_, args| { - assert!( - args.breakpoints.is_none_or(|bps| bps.is_empty()), - "Send empty breakpoint sets to clear them from DAP servers" - ); + client.on_request::({ + let called_set_breakpoints = called_set_breakpoints.clone(); + move |_, args| { + assert!( + args.breakpoints.is_none_or(|bps| bps.is_empty()), + "Send empty breakpoint sets to clear them from DAP servers" + ); - match args - .source - .path - .expect("We should always send a breakpoint's path") - .as_str() - { - "/project/main.rs" | "/project/second.rs" => {} - _ => { - panic!("Unset breakpoints for path that doesn't have any") - } + match args + .source + .path + .expect("We should always send a breakpoint's path") + .as_str() + { + "/project/main.rs" | "/project/second.rs" => {} + _ => { + panic!("Unset breakpoints for path that doesn't have any") } - - called_set_breakpoints.store(true, Ordering::SeqCst); - - Ok(dap::SetBreakpointsResponse { - breakpoints: Vec::default(), - }) } - }) - .await; + + called_set_breakpoints.store(true, Ordering::SeqCst); + + Ok(dap::SetBreakpointsResponse { + breakpoints: Vec::default(), + }) + } + }); cx.dispatch_action(crate::ClearAllBreakpoints); cx.run_until_parked(); @@ -1464,13 +1358,20 @@ async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails( let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - true, - cx, - ) + let task = project::debugger::test::start_debug_session(&project, cx, |client| { + client.on_request::(|_, _| { + Err(ErrorResponse { + error: Some(Message { + format: "failed to launch".to_string(), + id: 1, + variables: None, + send_telemetry: None, + show_user: None, + url: None, + url_label: None, + }), + }) + }); }); assert!( diff --git a/crates/debugger_ui/src/tests/module_list.rs b/crates/debugger_ui/src/tests/module_list.rs index f408850771..30c25e58b4 100644 --- a/crates/debugger_ui/src/tests/module_list.rs +++ b/crates/debugger_ui/src/tests/module_list.rs @@ -4,15 +4,17 @@ use crate::{ }; use dap::{ StoppedEvent, - requests::{Modules, StackTrace, Threads}, + requests::{Initialize, Modules}, }; use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; -use project::{FakeFs, Project}; +use project::{ + FakeFs, Project, + debugger::{self}, +}; use std::sync::{ Arc, atomic::{AtomicBool, AtomicI32, Ordering}, }; -use task::LaunchConfig; #[gpui::test] async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext) { @@ -29,30 +31,18 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext) .unwrap(); let cx = &mut VisualTestContext::from_window(*workspace, cx); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - Some(dap::Capabilities { + let session = debugger::test::start_debug_session(&project, cx, |client| { + client.on_request::(move |_, _| { + Ok(dap::Capabilities { supports_modules_request: Some(true), ..Default::default() - }), - false, - cx, - ) - }); - - let session = task.await.unwrap(); - let client = session.update(cx, |session, _| session.adapter_client().unwrap()); - - client - .on_request::(move |_, args| { - assert!(args.thread_id == 1); - Ok(dap::StackTraceResponse { - stack_frames: Vec::default(), - total_frames: None, }) - }) - .await; + }); + }) + .await + .unwrap(); + + let client = session.update(cx, |session, _| session.adapter_client().unwrap()); let called_modules = Arc::new(AtomicBool::new(false)); let modules = vec![ @@ -82,38 +72,25 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext) }, ]; - client - .on_request::(move |_, _| { - Ok(dap::ThreadsResponse { - threads: vec![dap::Thread { - id: 1, - name: "Thread 1".into(), - }], + client.on_request::({ + let called_modules = called_modules.clone(); + let modules_request_count = AtomicI32::new(0); + let modules = modules.clone(); + move |_, _| { + modules_request_count.fetch_add(1, Ordering::SeqCst); + assert_eq!( + 1, + modules_request_count.load(Ordering::SeqCst), + "This request should only be called once from the host" + ); + called_modules.store(true, Ordering::SeqCst); + + Ok(dap::ModulesResponse { + modules: modules.clone(), + total_modules: Some(2u64), }) - }) - .await; - - client - .on_request::({ - let called_modules = called_modules.clone(); - let modules_request_count = AtomicI32::new(0); - let modules = modules.clone(); - move |_, _| { - modules_request_count.fetch_add(1, Ordering::SeqCst); - assert_eq!( - 1, - modules_request_count.load(Ordering::SeqCst), - "This request should only be called once from the host" - ); - called_modules.store(true, Ordering::SeqCst); - - Ok(dap::ModulesResponse { - modules: modules.clone(), - total_modules: Some(2u64), - }) - } - }) - .await; + } + }); client .fake_event(dap::messages::Events::Stopped(StoppedEvent { diff --git a/crates/debugger_ui/src/tests/stack_frame_list.rs b/crates/debugger_ui/src/tests/stack_frame_list.rs index 0cabd3df8b..8f21e1d5b0 100644 --- a/crates/debugger_ui/src/tests/stack_frame_list.rs +++ b/crates/debugger_ui/src/tests/stack_frame_list.rs @@ -5,14 +5,13 @@ use crate::{ }; use dap::{ StackFrame, - requests::{StackTrace, Threads}, + requests::{Scopes, StackTrace, Threads}, }; use editor::{Editor, ToPoint as _}; use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; -use project::{FakeFs, Project}; +use project::{FakeFs, Project, debugger}; use serde_json::json; use std::sync::Arc; -use task::LaunchConfig; use unindent::Unindent as _; use util::path; @@ -51,29 +50,20 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame( let project = Project::test(fs, [path!("/project").as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let session = task.await.unwrap(); + let session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); + client.on_request::(move |_, _| Ok(dap::ScopesResponse { scopes: vec![] })); - client - .on_request::(move |_, _| { - Ok(dap::ThreadsResponse { - threads: vec![dap::Thread { - id: 1, - name: "Thread 1".into(), - }], - }) + client.on_request::(move |_, _| { + Ok(dap::ThreadsResponse { + threads: vec![dap::Thread { + id: 1, + name: "Thread 1".into(), + }], }) - .await; + }); let stack_frames = vec![ StackFrame { @@ -122,19 +112,17 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame( }, ]; - client - .on_request::({ - let stack_frames = Arc::new(stack_frames.clone()); - move |_, args| { - assert_eq!(1, args.thread_id); + client.on_request::({ + let stack_frames = Arc::new(stack_frames.clone()); + move |_, args| { + assert_eq!(1, args.thread_id); - Ok(dap::StackTraceResponse { - stack_frames: (*stack_frames).clone(), - total_frames: None, - }) - } - }) - .await; + Ok(dap::StackTraceResponse { + stack_frames: (*stack_frames).clone(), + total_frames: None, + }) + } + }); client .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { @@ -241,29 +229,21 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC }); let cx = &mut VisualTestContext::from_window(*workspace, cx); - - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let session = task.await.unwrap(); + let session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); - client - .on_request::(move |_, _| { - Ok(dap::ThreadsResponse { - threads: vec![dap::Thread { - id: 1, - name: "Thread 1".into(), - }], - }) + client.on_request::(move |_, _| { + Ok(dap::ThreadsResponse { + threads: vec![dap::Thread { + id: 1, + name: "Thread 1".into(), + }], }) - .await; + }); + + client.on_request::(move |_, _| Ok(dap::ScopesResponse { scopes: vec![] })); let stack_frames = vec![ StackFrame { @@ -312,19 +292,17 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC }, ]; - client - .on_request::({ - let stack_frames = Arc::new(stack_frames.clone()); - move |_, args| { - assert_eq!(1, args.thread_id); + client.on_request::({ + let stack_frames = Arc::new(stack_frames.clone()); + move |_, args| { + assert_eq!(1, args.thread_id); - Ok(dap::StackTraceResponse { - stack_frames: (*stack_frames).clone(), - total_frames: None, - }) - } - }) - .await; + Ok(dap::StackTraceResponse { + stack_frames: (*stack_frames).clone(), + total_frames: None, + }) + } + }); client .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { @@ -517,28 +495,21 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let session = task.await.unwrap(); + let session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); - client - .on_request::(move |_, _| { - Ok(dap::ThreadsResponse { - threads: vec![dap::Thread { - id: 1, - name: "Thread 1".into(), - }], - }) + client.on_request::(move |_, _| { + Ok(dap::ThreadsResponse { + threads: vec![dap::Thread { + id: 1, + name: "Thread 1".into(), + }], }) - .await; + }); + + client.on_request::(move |_, _| Ok(dap::ScopesResponse { scopes: vec![] })); let stack_frames = vec![ StackFrame { @@ -697,19 +668,17 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo }, ]; - client - .on_request::({ - let stack_frames = Arc::new(stack_frames.clone()); - move |_, args| { - assert_eq!(1, args.thread_id); + client.on_request::({ + let stack_frames = Arc::new(stack_frames.clone()); + move |_, args| { + assert_eq!(1, args.thread_id); - Ok(dap::StackTraceResponse { - stack_frames: (*stack_frames).clone(), - total_frames: None, - }) - } - }) - .await; + Ok(dap::StackTraceResponse { + stack_frames: (*stack_frames).clone(), + total_frames: None, + }) + } + }); client .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { diff --git a/crates/debugger_ui/src/tests/variable_list.rs b/crates/debugger_ui/src/tests/variable_list.rs index c4001cc5e2..fa539babd8 100644 --- a/crates/debugger_ui/src/tests/variable_list.rs +++ b/crates/debugger_ui/src/tests/variable_list.rs @@ -15,9 +15,8 @@ use dap::{ }; use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; use menu::{SelectFirst, SelectNext, SelectPrevious}; -use project::{FakeFs, Project}; +use project::{FakeFs, Project, debugger}; use serde_json::json; -use task::LaunchConfig; use unindent::Unindent as _; use util::path; @@ -55,29 +54,19 @@ async fn test_basic_fetch_initial_scope_and_variables( }) .unwrap(); let cx = &mut VisualTestContext::from_window(*workspace, cx); - - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let session = task.await.unwrap(); + let session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); - client - .on_request::(move |_, _| { - Ok(dap::ThreadsResponse { - threads: vec![dap::Thread { - id: 1, - name: "Thread 1".into(), - }], - }) + client.on_request::(move |_, _| { + Ok(dap::ThreadsResponse { + threads: vec![dap::Thread { + id: 1, + name: "Thread 1".into(), + }], }) - .await; + }); let stack_frames = vec![StackFrame { id: 1, @@ -102,19 +91,17 @@ async fn test_basic_fetch_initial_scope_and_variables( presentation_hint: None, }]; - client - .on_request::({ - let stack_frames = Arc::new(stack_frames.clone()); - move |_, args| { - assert_eq!(1, args.thread_id); + client.on_request::({ + let stack_frames = Arc::new(stack_frames.clone()); + move |_, args| { + assert_eq!(1, args.thread_id); - Ok(dap::StackTraceResponse { - stack_frames: (*stack_frames).clone(), - total_frames: None, - }) - } - }) - .await; + Ok(dap::StackTraceResponse { + stack_frames: (*stack_frames).clone(), + total_frames: None, + }) + } + }); let scopes = vec![Scope { name: "Scope 1".into(), @@ -130,18 +117,16 @@ async fn test_basic_fetch_initial_scope_and_variables( end_column: None, }]; - client - .on_request::({ - let scopes = Arc::new(scopes.clone()); - move |_, args| { - assert_eq!(1, args.frame_id); + client.on_request::({ + let scopes = Arc::new(scopes.clone()); + move |_, args| { + assert_eq!(1, args.frame_id); - Ok(dap::ScopesResponse { - scopes: (*scopes).clone(), - }) - } - }) - .await; + Ok(dap::ScopesResponse { + scopes: (*scopes).clone(), + }) + } + }); let variables = vec![ Variable { @@ -172,18 +157,16 @@ async fn test_basic_fetch_initial_scope_and_variables( }, ]; - client - .on_request::({ - let variables = Arc::new(variables.clone()); - move |_, args| { - assert_eq!(2, args.variables_reference); + client.on_request::({ + let variables = Arc::new(variables.clone()); + move |_, args| { + assert_eq!(2, args.variables_reference); - Ok(dap::VariablesResponse { - variables: (*variables).clone(), - }) - } - }) - .await; + Ok(dap::VariablesResponse { + variables: (*variables).clone(), + }) + } + }); client .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { @@ -283,39 +266,28 @@ async fn test_fetch_variables_for_multiple_scopes( .unwrap(); let cx = &mut VisualTestContext::from_window(*workspace, cx); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let session = task.await.unwrap(); + let session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); - client - .on_request::(move |_, _| { - Ok(dap::ThreadsResponse { - threads: vec![dap::Thread { - id: 1, - name: "Thread 1".into(), - }], - }) + client.on_request::(move |_, _| { + Ok(dap::ThreadsResponse { + threads: vec![dap::Thread { + id: 1, + name: "Thread 1".into(), + }], }) - .await; + }); - client - .on_request::(move |_, _| { - Ok(dap::Capabilities { - supports_step_back: Some(false), - ..Default::default() - }) + client.on_request::(move |_, _| { + Ok(dap::Capabilities { + supports_step_back: Some(false), + ..Default::default() }) - .await; + }); - client.on_request::(move |_, _| Ok(())).await; + client.on_request::(move |_, _| Ok(())); let stack_frames = vec![StackFrame { id: 1, @@ -340,19 +312,17 @@ async fn test_fetch_variables_for_multiple_scopes( presentation_hint: None, }]; - client - .on_request::({ - let stack_frames = Arc::new(stack_frames.clone()); - move |_, args| { - assert_eq!(1, args.thread_id); + client.on_request::({ + let stack_frames = Arc::new(stack_frames.clone()); + move |_, args| { + assert_eq!(1, args.thread_id); - Ok(dap::StackTraceResponse { - stack_frames: (*stack_frames).clone(), - total_frames: None, - }) - } - }) - .await; + Ok(dap::StackTraceResponse { + stack_frames: (*stack_frames).clone(), + total_frames: None, + }) + } + }); let scopes = vec![ Scope { @@ -383,18 +353,16 @@ async fn test_fetch_variables_for_multiple_scopes( }, ]; - client - .on_request::({ - let scopes = Arc::new(scopes.clone()); - move |_, args| { - assert_eq!(1, args.frame_id); + client.on_request::({ + let scopes = Arc::new(scopes.clone()); + move |_, args| { + assert_eq!(1, args.frame_id); - Ok(dap::ScopesResponse { - scopes: (*scopes).clone(), - }) - } - }) - .await; + Ok(dap::ScopesResponse { + scopes: (*scopes).clone(), + }) + } + }); let mut variables = HashMap::default(); variables.insert( @@ -445,16 +413,14 @@ async fn test_fetch_variables_for_multiple_scopes( }], ); - client - .on_request::({ - let variables = Arc::new(variables.clone()); - move |_, args| { - Ok(dap::VariablesResponse { - variables: variables.get(&args.variables_reference).unwrap().clone(), - }) - } - }) - .await; + client.on_request::({ + let variables = Arc::new(variables.clone()); + move |_, args| { + Ok(dap::VariablesResponse { + variables: variables.get(&args.variables_reference).unwrap().clone(), + }) + } + }); client .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { @@ -562,40 +528,28 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp }) .unwrap(); let cx = &mut VisualTestContext::from_window(*workspace, cx); - - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let session = task.await.unwrap(); + let session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); - client - .on_request::(move |_, _| { - Ok(dap::ThreadsResponse { - threads: vec![dap::Thread { - id: 1, - name: "Thread 1".into(), - }], - }) + client.on_request::(move |_, _| { + Ok(dap::ThreadsResponse { + threads: vec![dap::Thread { + id: 1, + name: "Thread 1".into(), + }], }) - .await; + }); - client - .on_request::(move |_, _| { - Ok(dap::Capabilities { - supports_step_back: Some(false), - ..Default::default() - }) + client.on_request::(move |_, _| { + Ok(dap::Capabilities { + supports_step_back: Some(false), + ..Default::default() }) - .await; + }); - client.on_request::(move |_, _| Ok(())).await; + client.on_request::(move |_, _| Ok(())); let stack_frames = vec![StackFrame { id: 1, @@ -620,19 +574,17 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp presentation_hint: None, }]; - client - .on_request::({ - let stack_frames = Arc::new(stack_frames.clone()); - move |_, args| { - assert_eq!(1, args.thread_id); + client.on_request::({ + let stack_frames = Arc::new(stack_frames.clone()); + move |_, args| { + assert_eq!(1, args.thread_id); - Ok(dap::StackTraceResponse { - stack_frames: (*stack_frames).clone(), - total_frames: None, - }) - } - }) - .await; + Ok(dap::StackTraceResponse { + stack_frames: (*stack_frames).clone(), + total_frames: None, + }) + } + }); let scopes = vec![ Scope { @@ -663,18 +615,16 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp }, ]; - client - .on_request::({ - let scopes = Arc::new(scopes.clone()); - move |_, args| { - assert_eq!(1, args.frame_id); + client.on_request::({ + let scopes = Arc::new(scopes.clone()); + move |_, args| { + assert_eq!(1, args.frame_id); - Ok(dap::ScopesResponse { - scopes: (*scopes).clone(), - }) - } - }) - .await; + Ok(dap::ScopesResponse { + scopes: (*scopes).clone(), + }) + } + }); let scope1_variables = vec![ Variable { @@ -748,25 +698,23 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp value_location_reference: None, }]; - client - .on_request::({ - let scope1_variables = Arc::new(scope1_variables.clone()); - let nested_variables = Arc::new(nested_variables.clone()); - let scope2_variables = Arc::new(scope2_variables.clone()); - move |_, args| match args.variables_reference { - 4 => Ok(dap::VariablesResponse { - variables: (*scope2_variables).clone(), - }), - 3 => Ok(dap::VariablesResponse { - variables: (*nested_variables).clone(), - }), - 2 => Ok(dap::VariablesResponse { - variables: (*scope1_variables).clone(), - }), - id => unreachable!("unexpected variables reference {id}"), - } - }) - .await; + client.on_request::({ + let scope1_variables = Arc::new(scope1_variables.clone()); + let nested_variables = Arc::new(nested_variables.clone()); + let scope2_variables = Arc::new(scope2_variables.clone()); + move |_, args| match args.variables_reference { + 4 => Ok(dap::VariablesResponse { + variables: (*scope2_variables).clone(), + }), + 3 => Ok(dap::VariablesResponse { + variables: (*nested_variables).clone(), + }), + 2 => Ok(dap::VariablesResponse { + variables: (*scope1_variables).clone(), + }), + id => unreachable!("unexpected variables reference {id}"), + } + }); client .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { @@ -1365,39 +1313,28 @@ async fn test_variable_list_only_sends_requests_when_rendering( let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let session = task.await.unwrap(); + let session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); - client - .on_request::(move |_, _| { - Ok(dap::ThreadsResponse { - threads: vec![dap::Thread { - id: 1, - name: "Thread 1".into(), - }], - }) + client.on_request::(move |_, _| { + Ok(dap::ThreadsResponse { + threads: vec![dap::Thread { + id: 1, + name: "Thread 1".into(), + }], }) - .await; + }); - client - .on_request::(move |_, _| { - Ok(dap::Capabilities { - supports_step_back: Some(false), - ..Default::default() - }) + client.on_request::(move |_, _| { + Ok(dap::Capabilities { + supports_step_back: Some(false), + ..Default::default() }) - .await; + }); - client.on_request::(move |_, _| Ok(())).await; + client.on_request::(move |_, _| Ok(())); let stack_frames = vec![ StackFrame { @@ -1446,19 +1383,17 @@ async fn test_variable_list_only_sends_requests_when_rendering( }, ]; - client - .on_request::({ - let stack_frames = Arc::new(stack_frames.clone()); - move |_, args| { - assert_eq!(1, args.thread_id); + client.on_request::({ + let stack_frames = Arc::new(stack_frames.clone()); + move |_, args| { + assert_eq!(1, args.thread_id); - Ok(dap::StackTraceResponse { - stack_frames: (*stack_frames).clone(), - total_frames: None, - }) - } - }) - .await; + Ok(dap::StackTraceResponse { + stack_frames: (*stack_frames).clone(), + total_frames: None, + }) + } + }); let frame_1_scopes = vec![Scope { name: "Frame 1 Scope 1".into(), @@ -1476,25 +1411,23 @@ async fn test_variable_list_only_sends_requests_when_rendering( let made_scopes_request = Arc::new(AtomicBool::new(false)); - client - .on_request::({ - let frame_1_scopes = Arc::new(frame_1_scopes.clone()); - let made_scopes_request = made_scopes_request.clone(); - move |_, args| { - assert_eq!(1, args.frame_id); - assert!( - !made_scopes_request.load(Ordering::SeqCst), - "We should be caching the scope request" - ); + client.on_request::({ + let frame_1_scopes = Arc::new(frame_1_scopes.clone()); + let made_scopes_request = made_scopes_request.clone(); + move |_, args| { + assert_eq!(1, args.frame_id); + assert!( + !made_scopes_request.load(Ordering::SeqCst), + "We should be caching the scope request" + ); - made_scopes_request.store(true, Ordering::SeqCst); + made_scopes_request.store(true, Ordering::SeqCst); - Ok(dap::ScopesResponse { - scopes: (*frame_1_scopes).clone(), - }) - } - }) - .await; + Ok(dap::ScopesResponse { + scopes: (*frame_1_scopes).clone(), + }) + } + }); let frame_1_variables = vec![ Variable { @@ -1525,18 +1458,16 @@ async fn test_variable_list_only_sends_requests_when_rendering( }, ]; - client - .on_request::({ - let frame_1_variables = Arc::new(frame_1_variables.clone()); - move |_, args| { - assert_eq!(2, args.variables_reference); + client.on_request::({ + let frame_1_variables = Arc::new(frame_1_variables.clone()); + move |_, args| { + assert_eq!(2, args.variables_reference); - Ok(dap::VariablesResponse { - variables: (*frame_1_variables).clone(), - }) - } - }) - .await; + Ok(dap::VariablesResponse { + variables: (*frame_1_variables).clone(), + }) + } + }); cx.run_until_parked(); @@ -1629,39 +1560,28 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame( .unwrap(); let cx = &mut VisualTestContext::from_window(*workspace, cx); - let task = project.update(cx, |project, cx| { - project.fake_debug_session( - dap::DebugRequestType::Launch(LaunchConfig::default()), - None, - false, - cx, - ) - }); - - let session = task.await.unwrap(); + let session = debugger::test::start_debug_session(&project, cx, |_| {}) + .await + .unwrap(); let client = session.update(cx, |session, _| session.adapter_client().unwrap()); - client - .on_request::(move |_, _| { - Ok(dap::ThreadsResponse { - threads: vec![dap::Thread { - id: 1, - name: "Thread 1".into(), - }], - }) + client.on_request::(move |_, _| { + Ok(dap::ThreadsResponse { + threads: vec![dap::Thread { + id: 1, + name: "Thread 1".into(), + }], }) - .await; + }); - client - .on_request::(move |_, _| { - Ok(dap::Capabilities { - supports_step_back: Some(false), - ..Default::default() - }) + client.on_request::(move |_, _| { + Ok(dap::Capabilities { + supports_step_back: Some(false), + ..Default::default() }) - .await; + }); - client.on_request::(move |_, _| Ok(())).await; + client.on_request::(move |_, _| Ok(())); let stack_frames = vec![ StackFrame { @@ -1710,19 +1630,17 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame( }, ]; - client - .on_request::({ - let stack_frames = Arc::new(stack_frames.clone()); - move |_, args| { - assert_eq!(1, args.thread_id); + client.on_request::({ + let stack_frames = Arc::new(stack_frames.clone()); + move |_, args| { + assert_eq!(1, args.thread_id); - Ok(dap::StackTraceResponse { - stack_frames: (*stack_frames).clone(), - total_frames: None, - }) - } - }) - .await; + Ok(dap::StackTraceResponse { + stack_frames: (*stack_frames).clone(), + total_frames: None, + }) + } + }); let frame_1_scopes = vec![Scope { name: "Frame 1 Scope 1".into(), @@ -1757,30 +1675,28 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame( let called_second_stack_frame = Arc::new(AtomicBool::new(false)); let called_first_stack_frame = Arc::new(AtomicBool::new(false)); - client - .on_request::({ - let frame_1_scopes = Arc::new(frame_1_scopes.clone()); - let frame_2_scopes = Arc::new(frame_2_scopes.clone()); - let called_first_stack_frame = called_first_stack_frame.clone(); - let called_second_stack_frame = called_second_stack_frame.clone(); - move |_, args| match args.frame_id { - 1 => { - called_first_stack_frame.store(true, Ordering::SeqCst); - Ok(dap::ScopesResponse { - scopes: (*frame_1_scopes).clone(), - }) - } - 2 => { - called_second_stack_frame.store(true, Ordering::SeqCst); - - Ok(dap::ScopesResponse { - scopes: (*frame_2_scopes).clone(), - }) - } - _ => panic!("Made a scopes request with an invalid frame id"), + client.on_request::({ + let frame_1_scopes = Arc::new(frame_1_scopes.clone()); + let frame_2_scopes = Arc::new(frame_2_scopes.clone()); + let called_first_stack_frame = called_first_stack_frame.clone(); + let called_second_stack_frame = called_second_stack_frame.clone(); + move |_, args| match args.frame_id { + 1 => { + called_first_stack_frame.store(true, Ordering::SeqCst); + Ok(dap::ScopesResponse { + scopes: (*frame_1_scopes).clone(), + }) } - }) - .await; + 2 => { + called_second_stack_frame.store(true, Ordering::SeqCst); + + Ok(dap::ScopesResponse { + scopes: (*frame_2_scopes).clone(), + }) + } + _ => panic!("Made a scopes request with an invalid frame id"), + } + }); let frame_1_variables = vec![ Variable { @@ -1840,18 +1756,16 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame( }, ]; - client - .on_request::({ - let frame_1_variables = Arc::new(frame_1_variables.clone()); - move |_, args| { - assert_eq!(2, args.variables_reference); + client.on_request::({ + let frame_1_variables = Arc::new(frame_1_variables.clone()); + move |_, args| { + assert_eq!(2, args.variables_reference); - Ok(dap::VariablesResponse { - variables: (*frame_1_variables).clone(), - }) - } - }) - .await; + Ok(dap::VariablesResponse { + variables: (*frame_1_variables).clone(), + }) + } + }); client .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { @@ -1907,18 +1821,16 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame( assert_eq!(frame_1_variables, variables); }); - client - .on_request::({ - let frame_2_variables = Arc::new(frame_2_variables.clone()); - move |_, args| { - assert_eq!(3, args.variables_reference); + client.on_request::({ + let frame_2_variables = Arc::new(frame_2_variables.clone()); + move |_, args| { + assert_eq!(3, args.variables_reference); - Ok(dap::VariablesResponse { - variables: (*frame_2_variables).clone(), - }) - } - }) - .await; + Ok(dap::VariablesResponse { + variables: (*frame_2_variables).clone(), + }) + } + }); running_state .update_in(cx, |running_state, window, cx| { diff --git a/crates/project/src/debugger.rs b/crates/project/src/debugger.rs index a01f63606a..4312d56c1d 100644 --- a/crates/project/src/debugger.rs +++ b/crates/project/src/debugger.rs @@ -16,3 +16,6 @@ pub mod dap_command; pub mod dap_store; mod locator_store; pub mod session; + +#[cfg(any(feature = "test-support", test))] +pub mod test; diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index fcc557f809..d0891bd30a 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -3,15 +3,15 @@ use super::{ locator_store::LocatorStore, session::{self, Session, SessionStateEvent}, }; -use crate::{ProjectEnvironment, debugger, worktree_store::WorktreeStore}; +use crate::{ProjectEnvironment, debugger}; use anyhow::{Result, anyhow}; use async_trait::async_trait; use collections::HashMap; use dap::{ - Capabilities, CompletionItem, CompletionsArguments, DapRegistry, ErrorResponse, - EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, - Source, StartDebuggingRequestArguments, - adapters::{DapStatus, DebugAdapterName}, + Capabilities, CompletionItem, CompletionsArguments, ErrorResponse, EvaluateArguments, + EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, Source, + StartDebuggingRequestArguments, + adapters::{DapStatus, DebugAdapterBinary, DebugAdapterName}, client::SessionId, messages::Message, requests::{Completions, Evaluate, Request as _, RunInTerminal, StartDebugging}, @@ -42,7 +42,7 @@ use std::{ sync::{Arc, atomic::Ordering::SeqCst}, }; use std::{collections::VecDeque, sync::atomic::AtomicU32}; -use task::{DebugAdapterConfig, DebugRequestDisposition}; +use task::DebugTaskDefinition; use util::ResultExt as _; use worktree::Worktree; @@ -78,10 +78,8 @@ pub struct LocalDapStore { node_runtime: NodeRuntime, next_session_id: AtomicU32, http_client: Arc, - worktree_store: Entity, environment: Entity, language_registry: Arc, - debug_adapters: Arc, toolchain_store: Arc, locator_store: Arc, start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>, @@ -132,11 +130,9 @@ impl DapStore { node_runtime: NodeRuntime, fs: Arc, language_registry: Arc, - debug_adapters: Arc, environment: Entity, toolchain_store: Arc, breakpoint_store: Entity, - worktree_store: Entity, cx: &mut Context, ) -> Self { cx.on_app_quit(Self::shutdown_sessions).detach(); @@ -170,10 +166,8 @@ impl DapStore { environment, http_client, node_runtime, - worktree_store, toolchain_store, language_registry, - debug_adapters, start_debugging_tx, _start_debugging_task, locator_store: Arc::from(LocatorStore::new()), @@ -320,18 +314,12 @@ impl DapStore { Ok(()) } - pub fn new_session( - &mut self, - mut config: DebugAdapterConfig, - worktree: &Entity, - parent_session: Option>, - cx: &mut Context, - ) -> (SessionId, Task>>) { + pub fn delegate(&self, worktree: &Entity, cx: &mut App) -> DapAdapterDelegate { let Some(local_store) = self.as_local() else { unimplemented!("Starting session on remote side"); }; - let delegate = DapAdapterDelegate::new( + DapAdapterDelegate::new( local_store.fs.clone(), worktree.read(cx).id(), local_store.node_runtime.clone(), @@ -341,7 +329,20 @@ impl DapStore { local_store.environment.update(cx, |env, cx| { env.get_worktree_environment(worktree.clone(), cx) }), - ); + ) + } + + pub fn new_session( + &mut self, + binary: DebugAdapterBinary, + mut config: DebugTaskDefinition, + parent_session: Option>, + cx: &mut Context, + ) -> (SessionId, Task>>) { + let Some(local_store) = self.as_local() else { + unimplemented!("Starting session on remote side"); + }; + let session_id = local_store.next_session_id(); if let Some(session) = &parent_session { @@ -352,7 +353,6 @@ impl DapStore { let (initialized_tx, initialized_rx) = oneshot::channel(); let locator_store = local_store.locator_store.clone(); - let debug_adapters = local_store.debug_adapters.clone(); let start_debugging_tx = local_store.start_debugging_tx.clone(); @@ -373,86 +373,31 @@ impl DapStore { this.breakpoint_store.clone(), session_id, parent_session, - delegate, + binary, config, start_debugging_tx.clone(), initialized_tx, - debug_adapters, cx, ) })?; - this.update(cx, |_, cx| { - create_new_session(session_id, initialized_rx, start_client_task, cx) - })? - .await + let ret = this + .update(cx, |_, cx| { + create_new_session(session_id, initialized_rx, start_client_task, cx) + })? + .await; + ret }); (session_id, task) } - #[cfg(any(test, feature = "test-support"))] - pub fn new_fake_session( - &mut self, - config: DebugAdapterConfig, - worktree: &Entity, - 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"); - }; - - let delegate = DapAdapterDelegate::new( - local_store.fs.clone(), - worktree.read(cx).id(), - local_store.node_runtime.clone(), - local_store.http_client.clone(), - local_store.language_registry.clone(), - local_store.toolchain_store.clone(), - local_store.environment.update(cx, |env, cx| { - env.get_worktree_environment(worktree.clone(), cx) - }), - ); - let session_id = local_store.next_session_id(); - - if let Some(session) = &parent_session { - session.update(cx, |session, _| { - session.add_child_session_id(session_id); - }); - } - - let (initialized_tx, initialized_rx) = oneshot::channel(); - - let start_client_task = Session::fake( - self.breakpoint_store.clone(), - session_id, - parent_session, - delegate, - config, - local_store.start_debugging_tx.clone(), - initialized_tx, - caps, - fails, - cx, - ); - - let task = create_new_session(session_id, initialized_rx, start_client_task, cx); - (session_id, task) - } - fn handle_start_debugging_request( &mut self, session_id: SessionId, request: dap::messages::Request, cx: &mut Context, ) -> Task> { - let Some(local_store) = self.as_local() else { - unreachable!("Cannot response for non-local session"); - }; - let Some(parent_session) = self.session_by_id(session_id) else { return Task::ready(Err(anyhow!("Session not found"))); }; @@ -461,41 +406,12 @@ impl DapStore { request.arguments.unwrap_or_default(), ) .expect("To parse StartDebuggingRequestArguments"); - let worktree = local_store - .worktree_store - .update(cx, |this, _| this.worktrees().next()) - .expect("worktree-less project"); + let mut binary = parent_session.read(cx).binary().clone(); + let config = parent_session.read(cx).configuration().unwrap().clone(); + binary.request_args = args; - let Some(config) = parent_session.read(cx).configuration() else { - unreachable!("there must be a config for local sessions"); - }; - - let debug_config = DebugAdapterConfig { - label: config.label, - adapter: config.adapter, - request: DebugRequestDisposition::ReverseRequest(args), - initialize_args: config.initialize_args.clone(), - tcp_connection: config.tcp_connection.clone(), - locator: None, - stop_on_entry: config.stop_on_entry, - }; - - #[cfg(any(test, feature = "test-support"))] - let new_session_task = { - let caps = parent_session.read(cx).capabilities.clone(); - self.new_fake_session( - debug_config, - &worktree, - Some(parent_session.clone()), - caps, - false, - cx, - ) - .1 - }; - #[cfg(not(any(test, feature = "test-support")))] let new_session_task = self - .new_session(debug_config, &worktree, Some(parent_session.clone()), cx) + .new_session(binary, config, Some(parent_session.clone()), cx) .1; let request_seq = request.seq; @@ -607,13 +523,7 @@ impl DapStore { .map(Arc::from) .or_else(|| { self.session_by_id(session_id) - .and_then(|session| { - session - .read(cx) - .configuration() - .and_then(|config| config.request.cwd()) - }) - .map(Arc::from) + .and_then(|session| session.read(cx).binary().cwd.as_deref().map(Arc::from)) }); cx.emit(DapStoreEvent::RunInTerminal { session_id, @@ -852,7 +762,7 @@ fn create_new_session( cx.emit(DapStoreEvent::DebugClientStarted(session_id)); cx.notify(); })?; - let seq_result = { + let seq_result = async || { session .update(cx, |session, cx| session.request_initialize(cx))? .await?; @@ -863,12 +773,11 @@ fn create_new_session( })? .await }; - match seq_result { + match seq_result().await { Ok(_) => {} Err(error) => { this.update(cx, |this, cx| { cx.emit(DapStoreEvent::Notification(error.to_string())); - this.shutdown_session(session_id, cx) })? .await diff --git a/crates/project/src/debugger/locator_store.rs b/crates/project/src/debugger/locator_store.rs index d951cebfe9..f599b95615 100644 --- a/crates/project/src/debugger/locator_store.rs +++ b/crates/project/src/debugger/locator_store.rs @@ -1,9 +1,9 @@ use anyhow::{Result, anyhow}; use cargo::CargoLocator; use collections::HashMap; -use dap::DebugAdapterConfig; use gpui::SharedString; use locators::DapLocator; +use task::DebugTaskDefinition; mod cargo; mod locators; @@ -23,7 +23,7 @@ impl LocatorStore { pub(super) async fn resolve_debug_config( &self, - debug_config: &mut DebugAdapterConfig, + debug_config: &mut DebugTaskDefinition, ) -> Result<()> { let Some(locator_name) = &debug_config.locator else { log::debug!("Attempted to resolve debug config without a locator field"); diff --git a/crates/project/src/debugger/locator_store/cargo.rs b/crates/project/src/debugger/locator_store/cargo.rs index 7ec1898b3a..ee7a60337e 100644 --- a/crates/project/src/debugger/locator_store/cargo.rs +++ b/crates/project/src/debugger/locator_store/cargo.rs @@ -1,12 +1,12 @@ use super::DapLocator; use anyhow::{Result, anyhow}; use async_trait::async_trait; -use dap::DebugAdapterConfig; use serde_json::{Value, json}; use smol::{ io::AsyncReadExt, process::{Command, Stdio}, }; +use task::DebugTaskDefinition; use util::maybe; pub(super) struct CargoLocator; @@ -38,11 +38,9 @@ async fn find_best_executable(executables: &[String], test_name: &str) -> Option } #[async_trait] impl DapLocator for CargoLocator { - async fn run_locator(&self, debug_config: &mut DebugAdapterConfig) -> Result<()> { + async fn run_locator(&self, debug_config: &mut DebugTaskDefinition) -> Result<()> { let Some(launch_config) = (match &mut debug_config.request { - task::DebugRequestDisposition::UserConfigured(task::DebugRequestType::Launch( - launch_config, - )) => Some(launch_config), + task::DebugRequestType::Launch(launch_config) => Some(launch_config), _ => None, }) else { return Err(anyhow!("Couldn't get launch config in locator")); diff --git a/crates/project/src/debugger/locator_store/locators.rs b/crates/project/src/debugger/locator_store/locators.rs index c9b6663875..e360d31019 100644 --- a/crates/project/src/debugger/locator_store/locators.rs +++ b/crates/project/src/debugger/locator_store/locators.rs @@ -1,8 +1,8 @@ use anyhow::Result; use async_trait::async_trait; -use dap::DebugAdapterConfig; +use task::DebugTaskDefinition; #[async_trait] pub(super) trait DapLocator: Send + Sync { - async fn run_locator(&self, debug_config: &mut DebugAdapterConfig) -> Result<()>; + async fn run_locator(&self, debug_config: &mut DebugTaskDefinition) -> Result<()>; } diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index 401494ddcc..8d3ae43590 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -1,5 +1,3 @@ -use crate::project_settings::ProjectSettings; - use super::breakpoint_store::{ BreakpointStore, BreakpointStoreEvent, BreakpointUpdatedReason, SourceBreakpoint, }; @@ -11,22 +9,17 @@ use super::dap_command::{ StepBackCommand, StepCommand, StepInCommand, StepOutCommand, TerminateCommand, TerminateThreadsCommand, ThreadsCommand, VariablesCommand, }; -use super::dap_store::DapAdapterDelegate; use anyhow::{Context as _, Result, anyhow}; use collections::{HashMap, HashSet, IndexMap, IndexSet}; -use dap::adapters::{DebugAdapter, DebugAdapterBinary}; +use dap::adapters::DebugAdapterBinary; use dap::messages::Response; use dap::{ Capabilities, ContinueArguments, EvaluateArgumentsContext, Module, Source, StackFrameId, SteppingGranularity, StoppedEvent, VariableReference, - adapters::{DapDelegate, DapStatus}, client::{DebugAdapterClient, SessionId}, messages::{Events, Message}, }; -use dap::{ - DapRegistry, DebugRequestType, ExceptionBreakpointsFilter, ExceptionFilterOptions, - OutputEventCategory, -}; +use dap::{ExceptionBreakpointsFilter, ExceptionFilterOptions, OutputEventCategory}; use futures::channel::oneshot; use futures::{FutureExt, future::Shared}; use gpui::{ @@ -35,11 +28,9 @@ use gpui::{ }; use rpc::AnyProtoClient; use serde_json::{Value, json}; -use settings::Settings; use smol::stream::StreamExt; use std::any::TypeId; use std::collections::BTreeMap; -use std::path::PathBuf; use std::u64; use std::{ any::Any, @@ -48,7 +39,7 @@ use std::{ path::Path, sync::Arc, }; -use task::{DebugAdapterConfig, DebugTaskDefinition}; +use task::DebugTaskDefinition; use text::{PointUtf16, ToPointUtf16}; use util::{ResultExt, merge_json_value_into}; @@ -168,9 +159,9 @@ enum Mode { #[derive(Clone)] pub struct LocalMode { client: Arc, - config: DebugAdapterConfig, - adapter: Arc, - breakpoint_store: Entity, + definition: DebugTaskDefinition, + binary: DebugAdapterBinary, + pub(crate) breakpoint_store: Entity, tmp_breakpoint: Option, } @@ -191,185 +182,37 @@ fn client_source(abs_path: &Path) -> dap::Source { impl LocalMode { fn new( - debug_adapters: Arc, session_id: SessionId, parent_session: Option>, breakpoint_store: Entity, - config: DebugAdapterConfig, - delegate: DapAdapterDelegate, + config: DebugTaskDefinition, + binary: DebugAdapterBinary, messages_tx: futures::channel::mpsc::UnboundedSender, cx: AsyncApp, ) -> Task> { Self::new_inner( - debug_adapters, session_id, parent_session, breakpoint_store, config, - delegate, + binary, messages_tx, async |_, _| {}, cx, ) } - #[cfg(any(test, feature = "test-support"))] - fn new_fake( - session_id: SessionId, - parent_session: Option>, - 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, - args: Default::default(), - }) - } - dap::StartDebuggingRequestArgumentsRequest::Attach => { - DebugRequestType::Attach(task::AttachConfig { - process_id: Some(0), - }) - } - } - } - }; - - let callback = async move |session: &mut LocalMode, cx: AsyncApp| { - session - .client - .on_request::(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(dap::SetExceptionBreakpointsResponse { breakpoints: None }) - }) - .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, + config: DebugTaskDefinition, + binary: DebugAdapterBinary, 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(®istry, &config, &delegate, cx).await?; - let message_handler = Box::new(move |message| { messages_tx.unbounded_send(message).ok(); }); @@ -380,13 +223,12 @@ impl LocalMode { .flatten() { client - .reconnect(session_id, binary, message_handler, cx.clone()) + .reconnect(session_id, binary.clone(), message_handler, cx.clone()) .await? } else { DebugAdapterClient::start( session_id, - adapter.name(), - binary, + binary.clone(), message_handler, cx.clone(), ) @@ -397,10 +239,10 @@ impl LocalMode { let mut session = Self { client, - adapter, breakpoint_store, tmp_breakpoint: None, - config: config.clone(), + definition: config, + binary, }; on_initialized(&mut session, cx.clone()).await; @@ -533,55 +375,12 @@ impl LocalMode { }) } - async fn get_adapter_binary( - registry: &Arc, - config: &DebugAdapterConfig, - delegate: &DapAdapterDelegate, - cx: &mut AsyncApp, - ) -> Result<(Arc, DebugAdapterBinary)> { - let adapter = registry - .adapter(&config.adapter) - .ok_or_else(|| anyhow!("Debug adapter with name `{}` was not found", config.adapter))?; - - let binary = cx.update(|cx| { - ProjectSettings::get_global(cx) - .dap - .get(&adapter.name()) - .and_then(|s| s.binary.as_ref().map(PathBuf::from)) - })?; - - let binary = match adapter.get_binary(delegate, &config, binary, cx).await { - Err(error) => { - delegate.update_status( - adapter.name(), - DapStatus::Failed { - error: error.to_string(), - }, - ); - - return Err(error); - } - Ok(mut binary) => { - delegate.update_status(adapter.name(), DapStatus::None); - - let shell_env = delegate.shell_env().await; - let mut envs = binary.envs.unwrap_or_default(); - envs.extend(shell_env); - binary.envs = Some(envs); - - binary - } - }; - - Ok((adapter, binary)) - } - pub fn label(&self) -> String { - self.config.label.clone() + self.definition.label.clone() } fn request_initialization(&self, cx: &App) -> Task> { - let adapter_id = self.adapter.name().to_string(); + let adapter_id = self.binary.adapter_name.to_string(); self.request(Initialize { adapter_id }, cx.background_executor().clone()) } @@ -592,36 +391,26 @@ impl LocalMode { initialized_rx: oneshot::Receiver<()>, cx: &App, ) -> Task> { - let (mut raw, is_launch) = match &self.config.request { - task::DebugRequestDisposition::UserConfigured(_) => { - let Ok(raw) = DebugTaskDefinition::try_from(self.config.clone()) else { - debug_assert!(false, "This part of code should be unreachable in practice"); - return Task::ready(Err(anyhow!( - "Expected debug config conversion to succeed" - ))); - }; - let is_launch = matches!(raw.request, DebugRequestType::Launch(_)); - let raw = self.adapter.request_args(&raw); - (raw, is_launch) - } - task::DebugRequestDisposition::ReverseRequest(start_debugging_request_arguments) => ( - start_debugging_request_arguments.configuration.clone(), - matches!( - start_debugging_request_arguments.request, - dap::StartDebuggingRequestArgumentsRequest::Launch - ), - ), - }; + let mut raw = self.binary.request_args.clone(); merge_json_value_into( - self.config.initialize_args.clone().unwrap_or(json!({})), - &mut raw, + self.definition.initialize_args.clone().unwrap_or(json!({})), + &mut raw.configuration, ); // Of relevance: https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522 - let launch = if is_launch { - self.request(Launch { raw }, cx.background_executor().clone()) - } else { - self.request(Attach { raw }, cx.background_executor().clone()) + let launch = match raw.request { + dap::StartDebuggingRequestArgumentsRequest::Launch => self.request( + Launch { + raw: raw.configuration, + }, + cx.background_executor().clone(), + ), + dap::StartDebuggingRequestArgumentsRequest::Attach => self.request( + Attach { + raw: raw.configuration, + }, + cx.background_executor().clone(), + ), }; let configuration_done_supported = ConfigurationDone::is_supported(capabilities); @@ -656,12 +445,13 @@ impl LocalMode { })? .await .ok(); - if configuration_done_supported { + let ret = if configuration_done_supported { this.request(ConfigurationDone {}, cx.background_executor().clone()) } else { Task::ready(Ok(())) } - .await + .await; + ret } }); @@ -909,23 +699,21 @@ impl Session { breakpoint_store: Entity, session_id: SessionId, parent_session: Option>, - delegate: DapAdapterDelegate, - config: DebugAdapterConfig, + binary: DebugAdapterBinary, + config: DebugTaskDefinition, start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>, initialized_tx: oneshot::Sender<()>, - debug_adapters: Arc, cx: &mut App, ) -> Task>> { let (message_tx, message_rx) = futures::channel::mpsc::unbounded(); cx.spawn(async move |cx| { let mode = LocalMode::new( - debug_adapters, session_id, parent_session.clone(), breakpoint_store.clone(), config.clone(), - delegate, + binary, message_tx, cx.clone(), ) @@ -946,50 +734,6 @@ impl Session { }) } - #[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 = 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, - cx, - ) - }) - }) - } - pub(crate) fn remote( session_id: SessionId, client: AnyProtoClient, @@ -1047,16 +791,23 @@ impl Session { &self.capabilities } + pub fn binary(&self) -> &DebugAdapterBinary { + let Mode::Local(local_mode) = &self.mode else { + panic!("Session is not local"); + }; + &local_mode.binary + } + pub fn adapter_name(&self) -> SharedString { match &self.mode { - Mode::Local(local_mode) => local_mode.adapter.name().into(), + Mode::Local(local_mode) => local_mode.definition.adapter.clone().into(), Mode::Remote(remote_mode) => remote_mode._adapter_name.clone(), } } - pub fn configuration(&self) -> Option { + pub fn configuration(&self) -> Option { if let Mode::Local(local_mode) = &self.mode { - Some(local_mode.config.clone()) + Some(local_mode.definition.clone()) } else { None } diff --git a/crates/project/src/debugger/test.rs b/crates/project/src/debugger/test.rs new file mode 100644 index 0000000000..d4319061b2 --- /dev/null +++ b/crates/project/src/debugger/test.rs @@ -0,0 +1,98 @@ +use std::{path::Path, sync::Arc}; + +use anyhow::Result; +use dap::{DebugRequestType, client::DebugAdapterClient}; +use gpui::{App, AppContext, Entity, Subscription, Task}; +use task::DebugTaskDefinition; + +use crate::Project; + +use super::session::Session; + +pub fn intercept_debug_sessions) + 'static>( + cx: &mut gpui::TestAppContext, + configure: T, +) -> Subscription { + cx.update(|cx| { + cx.observe_new::(move |session, _, cx| { + let client = session.adapter_client().unwrap(); + register_default_handlers(session, &client, cx); + configure(&client); + cx.background_spawn(async move { + client + .fake_event(dap::messages::Events::Initialized(Some(Default::default()))) + .await + }) + .detach(); + }) + }) +} + +pub fn start_debug_session_with) + 'static>( + project: &Entity, + cx: &mut gpui::TestAppContext, + config: DebugTaskDefinition, + configure: T, +) -> Task>> { + let subscription = intercept_debug_sessions(cx, configure); + let task = project.update(cx, |project, cx| project.start_debug_session(config, cx)); + cx.spawn(async move |_| { + let result = task.await; + drop(subscription); + result + }) +} + +pub fn start_debug_session) + 'static>( + project: &Entity, + cx: &mut gpui::TestAppContext, + configure: T, +) -> Task>> { + start_debug_session_with( + project, + cx, + DebugTaskDefinition { + adapter: "fake-adapter".to_string(), + request: DebugRequestType::Launch(Default::default()), + label: "test".to_string(), + initialize_args: None, + tcp_connection: None, + locator: None, + stop_on_entry: None, + }, + configure, + ) +} + +fn register_default_handlers(session: &Session, client: &Arc, cx: &mut App) { + client.on_request::(move |_, _| Ok(Default::default())); + let paths = session + .as_local() + .unwrap() + .breakpoint_store + .read(cx) + .breakpoint_paths(); + + 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(), + }) + }); + + client.on_request::(move |_, _| Ok(())); + + client.on_request::(move |_, _| { + Ok(dap::SetExceptionBreakpointsResponse { breakpoints: None }) + }); + + client.on_request::(move |_, _| Ok(())); + + client.on_request::(move |_, _| { + Ok(dap::ThreadsResponse { threads: vec![] }) + }); +} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index d27ffc070c..083dcfe8a2 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -25,6 +25,7 @@ mod environment; use buffer_diff::BufferDiff; pub use environment::{EnvironmentErrorMessage, ProjectEnvironmentEvent}; use git_store::{Repository, RepositoryId}; +use task::DebugTaskDefinition; pub mod search_history; mod yarn; @@ -38,7 +39,7 @@ use client::{ }; use clock::ReplicaId; -use dap::{DapRegistry, DebugAdapterConfig, client::DebugAdapterClient}; +use dap::{DapRegistry, client::DebugAdapterClient}; use collections::{BTreeSet, HashMap, HashSet}; use debounced_delay::DebouncedDelay; @@ -106,7 +107,7 @@ use terminals::Terminals; use text::{Anchor, BufferId}; use toolchain_store::EmptyToolchainStore; use util::{ - ResultExt as _, maybe, + ResultExt as _, paths::{SanitizedPath, compare_paths}, }; use worktree::{CreatedEntry, Snapshot, Traversal}; @@ -870,11 +871,9 @@ impl Project { node.clone(), fs.clone(), languages.clone(), - debug_adapters.clone(), environment.clone(), toolchain_store.read(cx).as_language_toolchain_store(), breakpoint_store.clone(), - worktree_store.clone(), cx, ) }); @@ -1458,64 +1457,46 @@ impl Project { } } - pub fn queue_debug_session(&mut self, config: DebugAdapterConfig, cx: &mut Context) { - if config.locator.is_none() { - self.start_debug_session(config, cx).detach_and_log_err(cx); - } - } - pub fn start_debug_session( &mut self, - config: DebugAdapterConfig, + config: DebugTaskDefinition, cx: &mut Context, ) -> Task>> { - let worktree = maybe!({ self.worktrees(cx).next() }); - - let Some(worktree) = &worktree else { + let Some(worktree) = self.worktrees(cx).next() else { return Task::ready(Err(anyhow!("Failed to find a worktree"))); }; - self.dap_store - .update(cx, |dap_store, cx| { - dap_store.new_session(config, worktree, None, cx) - }) - .1 - } - - #[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 Some(adapter) = self.debug_adapters.adapter(&config.adapter) else { + return Task::ready(Err(anyhow!("Failed to find a debug adapter"))); }; - let config = DebugAdapterConfig { - label: "test config".into(), - adapter: FakeAdapter::ADAPTER_NAME.into(), - request: DebugRequestDisposition::UserConfigured(request), - initialize_args: None, - tcp_connection: None, - locator: None, - stop_on_entry: None, - }; - let caps = caps.unwrap_or(Capabilities { - supports_step_back: Some(false), - ..Default::default() + + let user_installed_path = ProjectSettings::get_global(cx) + .dap + .get(&adapter.name()) + .and_then(|s| s.binary.as_ref().map(PathBuf::from)); + + let result = cx.spawn(async move |this, cx| { + let delegate = this.update(cx, |project, cx| { + project + .dap_store + .update(cx, |dap_store, cx| dap_store.delegate(&worktree, cx)) + })?; + + let binary = adapter + .get_binary(&delegate, &config, user_installed_path, cx) + .await?; + + let ret = this + .update(cx, |project, cx| { + project.dap_store.update(cx, |dap_store, cx| { + dap_store.new_session(binary, config, None, cx) + }) + })? + .1 + .await; + ret }); - self.dap_store - .update(cx, |dap_store, cx| { - dap_store.new_fake_session(config, worktree, None, caps, fails, cx) - }) - .1 + result } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index d981696a08..a8870c4e45 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -39,7 +39,7 @@ use std::{env, mem, num::NonZeroU32, ops::Range, str::FromStr, sync::OnceLock, t use task::{ResolvedTask, TaskContext}; use unindent::Unindent as _; use util::{ - TryFutureExt as _, assert_set_eq, path, + TryFutureExt as _, assert_set_eq, maybe, path, paths::PathMatcher, separator, test::{TempTree, marked_text_offsets}, diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index e546cf25a3..155534a8fd 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -72,7 +72,7 @@ impl HeadlessProject { http_client, node_runtime, languages, - debug_adapters, + debug_adapters: _debug_adapters, extension_host_proxy: proxy, }: HeadlessAppState, cx: &mut Context, @@ -112,11 +112,9 @@ impl HeadlessProject { node_runtime.clone(), fs.clone(), languages.clone(), - debug_adapters.clone(), environment.clone(), toolchain_store.read(cx).as_language_toolchain_store(), breakpoint_store.clone(), - worktree_store.clone(), cx, ) }); diff --git a/crates/task/src/lib.rs b/crates/task/src/lib.rs index 27c1cb6e39..0d92f063b0 100644 --- a/crates/task/src/lib.rs +++ b/crates/task/src/lib.rs @@ -104,7 +104,7 @@ impl ResolvedTask { } /// Get the configuration for the debug adapter that should be used for this task. - pub fn resolved_debug_adapter_config(&self) -> Option { + pub fn resolved_debug_adapter_config(&self) -> Option { match self.original_task.task_type.clone() { TaskType::Debug(debug_args) if self.resolved.is_some() => { let resolved = self @@ -127,10 +127,10 @@ impl ResolvedTask { }) .collect(); - Some(DebugAdapterConfig { + Some(DebugTaskDefinition { label: resolved.label.clone(), adapter: debug_args.adapter.clone(), - request: DebugRequestDisposition::UserConfigured(match debug_args.request { + request: match debug_args.request { crate::task_template::DebugArgsRequest::Launch => { DebugRequestType::Launch(LaunchConfig { program: resolved.command.clone(), @@ -141,7 +141,7 @@ impl ResolvedTask { crate::task_template::DebugArgsRequest::Attach(attach_config) => { DebugRequestType::Attach(attach_config) } - }), + }, initialize_args: debug_args.initialize_args, tcp_connection: debug_args.tcp_connection, locator: debug_args.locator.clone(), diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index f483ee8e27..537209b02f 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -361,9 +361,8 @@ impl PickerDelegate for TasksModalDelegate { match task.task_type() { TaskType::Debug(config) if config.locator.is_none() => { - let Some(config): Option = task - .resolved_debug_adapter_config() - .and_then(|config| config.try_into().ok()) + let Some(config): Option = + task.resolved_debug_adapter_config() else { return; }; @@ -382,7 +381,7 @@ impl PickerDelegate for TasksModalDelegate { .update(cx, |workspace, cx| { workspace.project().update(cx, |project, cx| { project - .start_debug_session(config.into(), cx) + .start_debug_session(config, cx) .detach_and_log_err(cx); }); }) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6c06489c44..61f5251cc2 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -96,7 +96,7 @@ use std::{ sync::{Arc, LazyLock, Weak, atomic::AtomicUsize}, time::Duration, }; -use task::{DebugAdapterConfig, SpawnInTerminal, TaskId}; +use task::{DebugTaskDefinition, SpawnInTerminal, TaskId}; use theme::{ActiveTheme, SystemAppearance, ThemeSettings}; pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; pub use ui; @@ -858,7 +858,7 @@ pub struct Workspace { serialized_ssh_project: Option, _items_serializer: Task>, session_id: Option, - debug_task_queue: HashMap, + debug_task_queue: HashMap, } impl EventEmitter for Workspace {}