diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 66b3c4b625..29114afaf2 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -429,7 +429,6 @@ impl Server { .add_message_handler( broadcast_project_message_from_host::, ) - .add_message_handler(broadcast_project_message_from_host::) .add_message_handler(broadcast_project_message_from_host::) .add_message_handler( broadcast_project_message_from_host::, diff --git a/crates/collab/src/tests/debug_panel_tests.rs b/crates/collab/src/tests/debug_panel_tests.rs index 2da39f55e7..5b54ef14a2 100644 --- a/crates/collab/src/tests/debug_panel_tests.rs +++ b/crates/collab/src/tests/debug_panel_tests.rs @@ -5,7 +5,7 @@ use dap::{ SourceBreakpoint, StackFrame, }; use debugger_ui::debugger_panel::DebugPanel; -use debugger_ui::debugger_panel_item::DebugPanelItem; +use debugger_ui::session::DebugSession; use editor::Editor; use gpui::{Entity, TestAppContext, VisualTestContext}; use project::{Project, ProjectPath, WorktreeId}; @@ -52,7 +52,7 @@ async fn add_debugger_panel(workspace: &Entity, cx: &mut VisualTestCo pub fn active_debug_panel_item( workspace: Entity, cx: &mut VisualTestContext, -) -> Entity { +) -> Entity { workspace.update_in(cx, |workspace, _window, cx| { let debug_panel = workspace.panel::(cx).unwrap(); debug_panel @@ -1251,9 +1251,7 @@ async fn test_module_list( debug_panel_item.update(cx, |item, cx| { assert_eq!( true, - item.capabilities(cx) - .and_then(|caps| caps.supports_modules_request) - .unwrap(), + item.capabilities(cx).supports_modules_request.unwrap(), "Local supports modules request should be true" ); @@ -1281,9 +1279,7 @@ async fn test_module_list( debug_panel_item.update(cx, |item, cx| { assert_eq!( true, - item.capabilities(cx) - .and_then(|caps| caps.supports_modules_request) - .unwrap(), + item.capabilities(cx).supports_modules_request.unwrap(), "Remote capabilities supports modules request should be true" ); let remote_module_list = item.module_list().update(cx, |list, cx| list.modules(cx)); @@ -1314,9 +1310,7 @@ async fn test_module_list( debug_panel_item.update(cx, |item, cx| { assert_eq!( true, - item.capabilities(cx) - .and_then(|caps| caps.supports_modules_request) - .unwrap(), + item.capabilities(cx).supports_modules_request.unwrap(), "Remote (mid session join) capabilities supports modules request should be true" ); let remote_module_list = item.module_list().update(cx, |list, cx| list.modules(cx)); diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index 4dcf37693b..4758640698 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -1,6 +1,5 @@ #[cfg(any(test, feature = "test-support"))] use crate::transport::FakeTransport; -use crate::transport::Transport; use ::fs::Fs; use anyhow::{anyhow, Context as _, Ok, Result}; use async_compression::futures::bufread::GzipDecoder; @@ -19,9 +18,11 @@ use std::{ collections::{HashMap, HashSet}, ffi::{OsStr, OsString}, fmt::Debug, + net::Ipv4Addr, ops::Deref, path::{Path, PathBuf}, sync::Arc, + time::Duration, }; use sysinfo::{Pid, Process}; use task::DebugAdapterConfig; @@ -89,12 +90,19 @@ impl<'a> From<&'a str> for DebugAdapterName { } } +#[derive(Debug, Clone)] +pub struct TcpArguments { + pub host: Ipv4Addr, + pub port: Option, + pub timeout: Option, +} #[derive(Debug, Clone)] pub struct DebugAdapterBinary { pub command: String, pub arguments: Option>, pub envs: Option>, pub cwd: Option, + pub connection: Option, } pub struct AdapterVersion { @@ -261,8 +269,6 @@ pub trait DebugAdapter: 'static + Send + Sync { .await } - fn transport(&self) -> Arc; - async fn fetch_latest_adapter_version( &self, delegate: &dyn DapDelegate, @@ -288,12 +294,6 @@ pub trait DebugAdapter: 'static + Send + Sync { /// Should return base configuration to make the debug adapter work fn request_args(&self, config: &DebugAdapterConfig) -> Value; - /// Whether the adapter supports `attach` request, - /// if not support and the request is selected we will show an error message - fn supports_attach(&self) -> bool { - false - } - /// Filters out the processes that the adapter can attach to for debugging fn attach_processes<'a>( &self, @@ -322,10 +322,6 @@ impl DebugAdapter for FakeAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } - fn transport(&self) -> Arc { - Arc::new(FakeTransport::new()) - } - async fn get_binary( &self, _: &dyn DapDelegate, @@ -336,6 +332,11 @@ impl DebugAdapter for FakeAdapter { Ok(DebugAdapterBinary { command: "command".into(), arguments: None, + connection: Some(TcpArguments { + host: Ipv4Addr::LOCALHOST, + port: None, + timeout: None, + }), envs: None, cwd: None, }) @@ -383,10 +384,6 @@ impl DebugAdapter for FakeAdapter { }) } - fn supports_attach(&self) -> bool { - true - } - fn attach_processes<'a>( &self, processes: &'a HashMap, diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs index 9499a83c20..4cf340c2d4 100644 --- a/crates/dap/src/client.rs +++ b/crates/dap/src/client.rs @@ -27,9 +27,9 @@ const DAP_REQUEST_TIMEOUT: Duration = Duration::from_secs(12); #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] -pub struct DebugAdapterClientId(pub u32); +pub struct SessionId(pub u32); -impl DebugAdapterClientId { +impl SessionId { pub fn from_proto(client_id: u64) -> Self { Self(client_id as u32) } @@ -41,69 +41,35 @@ impl DebugAdapterClientId { /// Represents a connection to the debug adapter process, either via stdout/stdin or a socket. pub struct DebugAdapterClient { - id: DebugAdapterClientId, + id: SessionId, sequence_count: AtomicU64, binary: DebugAdapterBinary, executor: BackgroundExecutor, - adapter: Arc, transport_delegate: TransportDelegate, } impl DebugAdapterClient { - pub fn new( - id: DebugAdapterClientId, - adapter: Arc, + pub async fn start( + id: SessionId, binary: DebugAdapterBinary, - cx: &AsyncApp, - ) -> Self { - let transport_delegate = TransportDelegate::new(adapter.transport()); - - Self { + message_handler: F, + cx: AsyncApp, + ) -> Result + where + F: FnMut(Message, &mut App) + 'static + Send + Sync + Clone, + { + let ((server_rx, server_tx), transport_delegate) = + TransportDelegate::start(&binary, cx.clone()).await?; + let this = Self { id, binary, - adapter, transport_delegate, sequence_count: AtomicU64::new(1), executor: cx.background_executor().clone(), - } - } - - pub async fn reconnect(&mut self, message_handler: F, cx: &mut AsyncApp) -> Result<()> - where - F: FnMut(Message, &mut App) + 'static + Send + Sync + Clone, - { - let (server_rx, server_tx) = self.transport_delegate.reconnect(cx).await?; - log::info!("Successfully reconnected to debug adapter"); - - let client_id = self.id; - - // start handling events/reverse requests - cx.update(|cx| { - cx.spawn({ - let server_tx = server_tx.clone(); - |mut cx| async move { - Self::handle_receive_messages( - client_id, - server_rx, - server_tx, - message_handler, - &mut cx, - ) - .await - } - }) - .detach_and_log_err(cx); - }) - } - - pub async fn start(&mut self, message_handler: F, cx: &mut AsyncApp) -> Result<()> - where - F: FnMut(Message, &mut App) + 'static + Send + Sync + Clone, - { - let (server_rx, server_tx) = self.transport_delegate.start(&self.binary, cx).await?; + }; log::info!("Successfully connected to debug adapter"); - let client_id = self.id; + let client_id = this.id; // start handling events/reverse requests cx.update(|cx| { @@ -121,11 +87,13 @@ impl DebugAdapterClient { } }) .detach_and_log_err(cx); + + this }) } async fn handle_receive_messages( - client_id: DebugAdapterClientId, + client_id: SessionId, server_rx: Receiver, client_tx: Sender, mut event_handler: F, @@ -225,22 +193,14 @@ impl DebugAdapterClient { self.transport_delegate.send_message(message).await } - pub fn id(&self) -> DebugAdapterClientId { + pub fn id(&self) -> SessionId { self.id } - pub fn adapter(&self) -> &Arc { - &self.adapter - } - pub fn binary(&self) -> &DebugAdapterBinary { &self.binary } - pub fn adapter_id(&self) -> String { - self.adapter.name().to_string() - } - /// Get the next sequence id to be used in a request pub fn next_sequence_id(&self) -> u64 { self.sequence_count.fetch_add(1, Ordering::Relaxed) @@ -335,15 +295,17 @@ mod tests { pub async fn test_initialize_client(cx: &mut TestAppContext) { init_test(cx); - let adapter = Arc::new(FakeAdapter::new()); - let mut client = DebugAdapterClient::new( - crate::client::DebugAdapterClientId(1), - adapter, + crate::client::SessionId(1), DebugAdapterBinary { command: "command".into(), arguments: Default::default(), envs: Default::default(), + connection: Some(TcpArguments { + host: Ipv4Addr::LOCALHOST, + port: None, + timeout: None, + }), cwd: None, }, &mut cx.to_async(), @@ -411,12 +373,16 @@ mod tests { let called_event_handler = Arc::new(AtomicBool::new(false)); let mut client = DebugAdapterClient::new( - crate::client::DebugAdapterClientId(1), - adapter, + crate::client::SessionId(1), DebugAdapterBinary { command: "command".into(), arguments: Default::default(), envs: Default::default(), + connection: Some(TCPArguments { + host: Ipv4Addr::LOCALHOST, + port: None, + path: None, + }), cwd: None, }, &mut cx.to_async(), @@ -466,12 +432,17 @@ mod tests { let called_event_handler = Arc::new(AtomicBool::new(false)); let mut client = DebugAdapterClient::new( - crate::client::DebugAdapterClientId(1), - adapter, + crate::client::SessionId(1), DebugAdapterBinary { command: "command".into(), arguments: Default::default(), envs: Default::default(), + + connection: Some(TCPArguments { + host: Ipv4Addr::LOCALHOST, + port: None, + path: None, + }), cwd: None, }, &mut cx.to_async(), diff --git a/crates/dap/src/proto_conversions.rs b/crates/dap/src/proto_conversions.rs index 153247dfc9..41af22cc7d 100644 --- a/crates/dap/src/proto_conversions.rs +++ b/crates/dap/src/proto_conversions.rs @@ -357,11 +357,9 @@ pub fn capabilities_to_proto( capabilities: &Capabilities, project_id: u64, session_id: u64, - client_id: u64, ) -> SetDebugClientCapabilities { SetDebugClientCapabilities { session_id, - client_id, project_id, supports_loaded_sources_request: capabilities .supports_loaded_sources_request diff --git a/crates/dap/src/transport.rs b/crates/dap/src/transport.rs index 2e95f639a9..96f6d1c3bf 100644 --- a/crates/dap/src/transport.rs +++ b/crates/dap/src/transport.rs @@ -23,7 +23,7 @@ use std::{ sync::Arc, time::Duration, }; -use task::TCPHost; +use task::{DebugAdapterKind, TCPHost}; use util::ResultExt as _; use crate::{adapters::DebugAdapterBinary, debugger_settings::DebuggerSettings}; @@ -68,18 +68,80 @@ impl TransportPipe { type Requests = Arc>>>>; type LogHandlers = Arc>>; +enum Transport { + Stdio(StdioTransport), + Tcp(TcpTransport), + #[cfg(any(test, feature = "test-support"))] + Fake(FakeTransport), +} + +impl Transport { + async fn start(binary: &DebugAdapterBinary, cx: AsyncApp) -> Result<(TransportPipe, Self)> { + #[cfg(any(test, feature = "test-support"))] + if binary.kind == DebugAdapterKind::Fake { + return FakeTransport::start(cx) + .await + .map(|(transports, fake)| (transports, Self::Fake(fake))); + } + + if binary.connection.is_some() { + TcpTransport::start(binary, cx) + .await + .map(|(transports, tcp)| (transports, Self::Tcp(tcp))) + } else { + StdioTransport::start(binary, cx) + .await + .map(|(transports, stdio)| (transports, Self::Stdio(stdio))) + } + } + + fn has_adapter_logs(&self) -> bool { + match self { + Transport::Stdio(stdio_transport) => stdio_transport.has_adapter_logs(), + Transport::Tcp(tcp_transport) => tcp_transport.has_adapter_logs(), + #[cfg(any(test, feature = "test-support"))] + Transport::Fake(fake_transport) => fake_transport.has_adapter_logs(), + } + } + + async fn kill(&self) -> Result<()> { + match self { + Transport::Stdio(stdio_transport) => stdio_transport.kill().await, + Transport::Tcp(tcp_transport) => tcp_transport.kill().await, + #[cfg(any(test, feature = "test-support"))] + Transport::Fake(fake_transport) => fake_transport.kill().await, + } + } + + #[cfg(any(test, feature = "test-support"))] + fn as_fake(&self) -> &FakeTransport { + match self { + Transport::Fake(fake_transport) => fake_transport, + _ => panic!("Not a fake transport layer"), + } + } +} + pub(crate) struct TransportDelegate { log_handlers: LogHandlers, current_requests: Requests, pending_requests: Requests, - transport: Arc, + transport: Transport, server_tx: Arc>>>, } +impl Transport { + #[cfg(any(test, feature = "test-support"))] + fn fake(args: DebugAdapterBinary) -> Self { + let this = Self::Fake(FakeTransport::new()); + } +} + impl TransportDelegate { - pub fn new(transport: Arc) -> Self { + #[cfg(any(test, feature = "test-support"))] + pub fn fake(args: DebugAdapterBinary) -> Self { Self { - transport, + transport: Transport::fake(args), server_tx: Default::default(), log_handlers: Default::default(), current_requests: Default::default(), @@ -87,27 +149,26 @@ impl TransportDelegate { } } - pub(crate) async fn reconnect( - &mut self, - cx: &mut AsyncApp, - ) -> Result<(Receiver, Sender)> { - self.start_handlers(self.transport.reconnect(cx).await?, cx) - .await - } - pub(crate) async fn start( - &mut self, binary: &DebugAdapterBinary, - cx: &mut AsyncApp, - ) -> Result<(Receiver, Sender)> { - self.start_handlers(self.transport.start(binary, cx).await?, cx) - .await + cx: AsyncApp, + ) -> Result<((Receiver, Sender), Self)> { + let (transport_pipes, transport) = Transport::start(binary, cx.clone()).await?; + let mut this = Self { + transport, + server_tx: Default::default(), + log_handlers: Default::default(), + current_requests: Default::default(), + pending_requests: Default::default(), + }; + let messages = this.start_handlers(transport_pipes, cx).await?; + Ok((messages, this)) } async fn start_handlers( &mut self, mut params: TransportPipe, - cx: &mut AsyncApp, + cx: AsyncApp, ) -> Result<(Receiver, Sender)> { let (client_tx, server_rx) = unbounded::(); let (server_tx, client_rx) = unbounded::(); @@ -475,41 +536,14 @@ impl TransportDelegate { } } -#[async_trait(?Send)] -pub trait Transport: 'static + Send + Sync + Any { - async fn start(&self, binary: &DebugAdapterBinary, cx: &mut AsyncApp) -> Result; - - fn has_adapter_logs(&self) -> bool; - - async fn kill(&self) -> Result<()>; - - async fn reconnect(&self, _: &mut AsyncApp) -> Result { - bail!("Cannot reconnect to adapter") - } - - #[cfg(any(test, feature = "test-support"))] - fn as_fake(&self) -> &FakeTransport { - panic!("Called as_fake on a real adapter"); - } -} - pub struct TcpTransport { port: u16, host: Ipv4Addr, - timeout: Option, - process: Arc>>, + timeout: u64, + process: Mutex, } impl TcpTransport { - pub fn new(host: Ipv4Addr, port: u16, timeout: Option) -> Self { - Self { - port, - host, - timeout, - process: Arc::new(Mutex::new(None)), - } - } - /// Get an open port to use with the tcp client when not supplied by debug config pub async fn port(host: &TCPHost) -> Result { if let Some(port) = host.port { @@ -521,49 +555,33 @@ impl TcpTransport { .port()) } } -} - -#[async_trait(?Send)] -impl Transport for TcpTransport { - async fn reconnect(&self, cx: &mut AsyncApp) -> Result { - let address = SocketAddrV4::new(self.host, self.port); - - let timeout = self.timeout.unwrap_or_else(|| { - cx.update(|cx| DebuggerSettings::get_global(cx).timeout) - .unwrap_or(2000u64) - }); - - let (rx, tx) = select! { - _ = cx.background_executor().timer(Duration::from_millis(timeout)).fuse() => { - return Err(anyhow!(format!("Reconnect to TCP DAP timeout {}:{}", self.host, self.port))) - }, - result = cx.spawn(|cx| async move { - loop { - match TcpStream::connect(address).await { - Ok(stream) => return stream.split(), - Err(_) => { - cx.background_executor().timer(Duration::from_millis(100)).await; - } - } - } - }).fuse() => result - }; - - log::info!( - "Debug adapter has reconnected to TCP server {}:{}", - self.host, - self.port - ); - - Ok(TransportPipe::new( - Box::new(tx), - Box::new(BufReader::new(rx)), - None, - None, - )) + async fn port_for_host(host: Ipv4Addr) -> Result { + Ok(TcpListener::bind(SocketAddrV4::new(host, 0)) + .await? + .local_addr()? + .port()) } - async fn start(&self, binary: &DebugAdapterBinary, cx: &mut AsyncApp) -> Result { + async fn start(binary: &DebugAdapterBinary, cx: AsyncApp) -> Result<(TransportPipe, Self)> { + let Some(connection_args) = binary.connection.as_ref() else { + return Err(anyhow!("No connection arguments provided")); + }; + let host = connection_args.host; + let port = if let Some(port) = connection_args.port { + port + } else { + TcpListener::bind(SocketAddrV4::new(host, 0)) + .await + .with_context(|| { + format!( + "Failed to connect to debug adapter over tcp. host: {}", + host + ) + })? + .local_addr()? + .port() + }; + let mut command = util::command::new_smol_command(&binary.command); if let Some(cwd) = &binary.cwd { @@ -588,16 +606,16 @@ impl Transport for TcpTransport { .spawn() .with_context(|| "failed to start debug adapter.")?; - let address = SocketAddrV4::new(self.host, self.port); + let address = SocketAddrV4::new(host, port); - let timeout = self.timeout.unwrap_or_else(|| { + let timeout = connection_args.timeout.unwrap_or_else(|| { cx.update(|cx| DebuggerSettings::get_global(cx).timeout) .unwrap_or(2000u64) }); let (rx, tx) = select! { _ = cx.background_executor().timer(Duration::from_millis(timeout)).fuse() => { - return Err(anyhow!(format!("Connection to TCP DAP timeout {}:{}", self.host, self.port))) + return Err(anyhow!(format!("Connection to TCP DAP timeout {}:{}", host, port))) }, result = cx.spawn(|cx| async move { loop { @@ -612,23 +630,27 @@ impl Transport for TcpTransport { }; log::info!( "Debug adapter has connected to TCP server {}:{}", - self.host, - self.port + host, + port ); - let stdout = process.stdout.take(); let stderr = process.stderr.take(); - { - *self.process.lock().await = Some(process); - } + let this = Self { + port, + host, + process: Mutex::new(process), + timeout, + }; - Ok(TransportPipe::new( + let pipe = TransportPipe::new( Box::new(tx), Box::new(BufReader::new(rx)), stdout.map(|s| Box::new(s) as Box), stderr.map(|s| Box::new(s) as Box), - )) + ); + + Ok((pipe, this)) } fn has_adapter_logs(&self) -> bool { @@ -636,29 +658,18 @@ impl Transport for TcpTransport { } async fn kill(&self) -> Result<()> { - if let Some(mut process) = self.process.lock().await.take() { - process.kill()?; - } + self.process.lock().await.kill()?; Ok(()) } } pub struct StdioTransport { - process: Arc>>, + process: Mutex, } impl StdioTransport { - pub fn new() -> Self { - Self { - process: Arc::new(Mutex::new(None)), - } - } -} - -#[async_trait(?Send)] -impl Transport for StdioTransport { - async fn start(&self, binary: &DebugAdapterBinary, _: &mut AsyncApp) -> Result { + async fn start(binary: &DebugAdapterBinary, _: AsyncApp) -> Result<(TransportPipe, Self)> { let mut command = util::command::new_smol_command(&binary.command); if let Some(cwd) = &binary.cwd { @@ -705,15 +716,16 @@ impl Transport for StdioTransport { log::info!("Debug adapter has connected to stdio adapter"); - { - *self.process.lock().await = Some(process); - } + let process = Mutex::new(process); - Ok(TransportPipe::new( - Box::new(stdin), - Box::new(BufReader::new(stdout)), - None, - stderr, + Ok(( + TransportPipe::new( + Box::new(stdin), + Box::new(BufReader::new(stdout)), + None, + stderr, + ), + Self { process }, )) } @@ -721,11 +733,12 @@ impl Transport for StdioTransport { false } - async fn kill(&self) -> Result<()> { - if let Some(mut process) = self.process.lock().await.take() { - process.kill()?; - } + async fn reconnect(&self, _: AsyncApp) -> Result { + bail!("Cannot reconnect to adapter") + } + async fn kill(&self) -> Result<()> { + self.process.lock().await.kill()?; Ok(()) } } @@ -753,13 +766,6 @@ pub struct FakeTransport { #[cfg(any(test, feature = "test-support"))] impl FakeTransport { - pub fn new() -> Self { - Self { - request_handlers: Arc::new(Mutex::new(HashMap::default())), - response_handlers: Arc::new(Mutex::new(HashMap::default())), - } - } - pub async fn on_request(&self, mut handler: F) where F: 'static + Send + FnMut(u64, R::Arguments) -> Result, @@ -803,37 +809,34 @@ impl FakeTransport { .await .insert(R::COMMAND, Box::new(handler)); } -} -#[cfg(any(test, feature = "test-support"))] -#[async_trait(?Send)] -impl Transport for FakeTransport { - async fn reconnect(&self, cx: &mut AsyncApp) -> Result { + async fn reconnect(&self, cx: AsyncApp) -> Result { self.start( &DebugAdapterBinary { command: "command".into(), arguments: None, envs: None, cwd: None, + connection: TcpTransport::new(None, None), }, cx, ) .await } - async fn start( - &self, - _binary: &DebugAdapterBinary, - cx: &mut AsyncApp, - ) -> Result { + async fn start(cx: AsyncApp) -> Result { + let this = Self { + request_handlers: Arc::new(Mutex::new(HashMap::default())), + response_handlers: Arc::new(Mutex::new(HashMap::default())), + }; use dap_types::requests::{Request, RunInTerminal, StartDebugging}; use serde_json::json; let (stdin_writer, stdin_reader) = async_pipe::pipe(); let (stdout_writer, stdout_reader) = async_pipe::pipe(); - let request_handlers = self.request_handlers.clone(); - let response_handlers = self.response_handlers.clone(); + let request_handlers = this.request_handlers.clone(); + let response_handlers = this.response_handlers.clone(); let stdout_writer = Arc::new(Mutex::new(stdout_writer)); cx.background_executor() diff --git a/crates/dap_adapters/src/custom.rs b/crates/dap_adapters/src/custom.rs index 6abec9407f..a761dfb541 100644 --- a/crates/dap_adapters/src/custom.rs +++ b/crates/dap_adapters/src/custom.rs @@ -1,6 +1,6 @@ use std::{ffi::OsString, path::PathBuf, sync::Arc}; -use dap::transport::{StdioTransport, TcpTransport, Transport}; +use dap::transport::{StdioTransport, TcpTransport}; use gpui::AsyncApp; use serde_json::Value; use task::DebugAdapterConfig; @@ -9,24 +9,13 @@ use crate::*; pub(crate) struct CustomDebugAdapter { custom_args: CustomArgs, - transport: Arc, } impl CustomDebugAdapter { const ADAPTER_NAME: &'static str = "custom_dap"; pub(crate) async fn new(custom_args: CustomArgs) -> Result { - Ok(CustomDebugAdapter { - transport: match &custom_args.connection { - DebugConnectionType::TCP(host) => Arc::new(TcpTransport::new( - host.host(), - TcpTransport::port(&host).await?, - host.timeout, - )), - DebugConnectionType::STDIO => Arc::new(StdioTransport::new()), - }, - custom_args, - }) + Ok(CustomDebugAdapter { custom_args }) } } @@ -36,10 +25,6 @@ impl DebugAdapter for CustomDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } - fn transport(&self) -> Arc { - self.transport.clone() - } - async fn get_binary( &self, _: &dyn DapDelegate, @@ -47,7 +32,17 @@ impl DebugAdapter for CustomDebugAdapter { _: Option, _: &mut AsyncApp, ) -> Result { - Ok(DebugAdapterBinary { + let connection = if let DebugConnectionType::TCP(connection) = &self.custom_args.connection + { + Some(adapters::TcpArguments { + host: connection.host(), + port: connection.port, + timeout: connection.timeout, + }) + } else { + None + }; + let ret = DebugAdapterBinary { command: self.custom_args.command.clone(), arguments: self .custom_args @@ -56,7 +51,9 @@ impl DebugAdapter for CustomDebugAdapter { .map(|args| args.iter().map(OsString::from).collect()), cwd: config.cwd.clone(), envs: self.custom_args.envs.clone(), - }) + connection, + }; + Ok(ret) } async fn fetch_latest_adapter_version(&self, _: &dyn DapDelegate) -> Result { diff --git a/crates/dap_adapters/src/go.rs b/crates/dap_adapters/src/go.rs index d19a3f2290..95e7d453b2 100644 --- a/crates/dap_adapters/src/go.rs +++ b/crates/dap_adapters/src/go.rs @@ -1,4 +1,4 @@ -use dap::transport::{TcpTransport, Transport}; +use dap::transport::TcpTransport; use gpui::AsyncApp; use std::{ffi::OsStr, net::Ipv4Addr, path::PathBuf, sync::Arc}; @@ -28,10 +28,6 @@ impl DebugAdapter for GoDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } - fn transport(&self) -> Arc { - Arc::new(TcpTransport::new(self.host, self.port, self.timeout)) - } - async fn get_binary( &self, delegate: &dyn DapDelegate, @@ -86,6 +82,7 @@ impl DebugAdapter for GoDebugAdapter { ]), cwd: config.cwd.clone(), envs: None, + connection: None, }) } diff --git a/crates/dap_adapters/src/javascript.rs b/crates/dap_adapters/src/javascript.rs index a68afc28f1..61e1f8bfe1 100644 --- a/crates/dap_adapters/src/javascript.rs +++ b/crates/dap_adapters/src/javascript.rs @@ -1,5 +1,5 @@ use adapters::latest_github_release; -use dap::transport::{TcpTransport, Transport}; +use dap::transport::TcpTransport; use gpui::AsyncApp; use regex::Regex; use std::{collections::HashMap, net::Ipv4Addr, path::PathBuf, sync::Arc}; @@ -33,10 +33,6 @@ impl DebugAdapter for JsDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } - fn transport(&self) -> Arc { - Arc::new(TcpTransport::new(self.host, self.port, self.timeout)) - } - async fn fetch_latest_adapter_version( &self, delegate: &dyn DapDelegate, @@ -98,6 +94,11 @@ impl DebugAdapter for JsDebugAdapter { ]), cwd: config.cwd.clone(), envs: None, + connection: Some(adapters::TcpArguments { + host: self.host, + port: Some(self.port), + timeout: self.timeout, + }), }) } @@ -136,10 +137,6 @@ impl DebugAdapter for JsDebugAdapter { }) } - fn supports_attach(&self) -> bool { - true - } - fn attach_processes<'a>( &self, processes: &'a HashMap, diff --git a/crates/dap_adapters/src/lldb.rs b/crates/dap_adapters/src/lldb.rs index ec274bd05d..0015bf0b64 100644 --- a/crates/dap_adapters/src/lldb.rs +++ b/crates/dap_adapters/src/lldb.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, ffi::OsStr, path::PathBuf, sync::Arc}; use anyhow::Result; use async_trait::async_trait; -use dap::transport::{StdioTransport, Transport}; +use dap::transport::StdioTransport; use gpui::AsyncApp; use sysinfo::{Pid, Process}; use task::{DebugAdapterConfig, DebugRequestType}; @@ -25,10 +25,6 @@ impl DebugAdapter for LldbDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } - fn transport(&self) -> Arc { - Arc::new(StdioTransport::new()) - } - async fn get_binary( &self, delegate: &dyn DapDelegate, @@ -59,6 +55,7 @@ impl DebugAdapter for LldbDebugAdapter { arguments: None, envs: None, cwd: config.cwd.clone(), + connection: None, }) } @@ -102,10 +99,6 @@ impl DebugAdapter for LldbDebugAdapter { }) } - fn supports_attach(&self) -> bool { - true - } - fn attach_processes<'a>( &self, processes: &'a HashMap, diff --git a/crates/dap_adapters/src/php.rs b/crates/dap_adapters/src/php.rs index 23aa2c30ef..98d7474e6f 100644 --- a/crates/dap_adapters/src/php.rs +++ b/crates/dap_adapters/src/php.rs @@ -1,5 +1,5 @@ use adapters::latest_github_release; -use dap::transport::{TcpTransport, Transport}; +use dap::{adapters::TcpArguments, transport::TcpTransport}; use gpui::AsyncApp; use std::{net::Ipv4Addr, path::PathBuf, sync::Arc}; @@ -30,10 +30,6 @@ impl DebugAdapter for PhpDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } - fn transport(&self) -> Arc { - Arc::new(TcpTransport::new(self.host, self.port, self.timeout)) - } - async fn fetch_latest_adapter_version( &self, delegate: &dyn DapDelegate, @@ -92,6 +88,11 @@ impl DebugAdapter for PhpDebugAdapter { adapter_path.join(Self::ADAPTER_PATH).into(), format!("--server={}", self.port).into(), ]), + connection: Some(TcpArguments { + port: Some(self.port), + host: Ipv4Addr::LOCALHOST, + timeout: None, + }), cwd: config.cwd.clone(), envs: None, }) diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index e44cadd22e..bce937a6c3 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -1,5 +1,5 @@ use crate::*; -use dap::transport::{TcpTransport, Transport}; +use dap::transport::TcpTransport; use gpui::AsyncApp; use std::{ffi::OsStr, net::Ipv4Addr, path::PathBuf, sync::Arc}; @@ -29,10 +29,6 @@ impl DebugAdapter for PythonDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } - fn transport(&self) -> Arc { - Arc::new(TcpTransport::new(self.host, self.port, self.timeout)) - } - async fn fetch_latest_adapter_version( &self, delegate: &dyn DapDelegate, @@ -125,6 +121,11 @@ impl DebugAdapter for PythonDebugAdapter { format!("--port={}", self.port).into(), format!("--host={}", self.host).into(), ]), + connection: Some(adapters::TcpArguments { + host: self.host, + port: Some(self.port), + timeout: self.timeout, + }), cwd: config.cwd.clone(), envs: None, }) diff --git a/crates/debugger_tools/src/dap_log.rs b/crates/debugger_tools/src/dap_log.rs index 14f20ffd4b..b34e812876 100644 --- a/crates/debugger_tools/src/dap_log.rs +++ b/crates/debugger_tools/src/dap_log.rs @@ -1,5 +1,5 @@ use dap::{ - client::{DebugAdapterClient, DebugAdapterClientId}, + client::{DebugAdapterClient, SessionId}, debugger_settings::DebuggerSettings, transport::{IoKind, LogKind}, }; @@ -12,11 +12,7 @@ use gpui::{ actions, div, App, AppContext, Context, Empty, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, WeakEntity, Window, }; -use project::{ - debugger::dap_session::{DebugSession, DebugSessionId}, - search::SearchQuery, - Project, -}; +use project::{debugger::session::Session, search::SearchQuery, Project}; use settings::Settings as _; use std::{ borrow::Cow, @@ -36,16 +32,16 @@ struct DapLogView { focus_handle: FocusHandle, log_store: Entity, editor_subscriptions: Vec, - current_view: Option<(DebugAdapterClientId, LogKind)>, + current_view: Option<(SessionId, LogKind)>, project: Entity, _subscriptions: Vec, } struct LogStore { projects: HashMap, ProjectState>, - debug_clients: HashMap, - rpc_tx: UnboundedSender<(DebugAdapterClientId, IoKind, String)>, - adapter_log_tx: UnboundedSender<(DebugAdapterClientId, IoKind, String)>, + debug_clients: HashMap, + rpc_tx: UnboundedSender<(SessionId, IoKind, String)>, + adapter_log_tx: UnboundedSender<(SessionId, IoKind, String)>, } struct ProjectState { @@ -102,7 +98,7 @@ impl DebugAdapterState { impl LogStore { fn new(cx: &Context) -> Self { - let (rpc_tx, mut rpc_rx) = unbounded::<(DebugAdapterClientId, IoKind, String)>(); + let (rpc_tx, mut rpc_rx) = unbounded::<(SessionId, IoKind, String)>(); cx.spawn(|this, mut cx| async move { while let Some((client_id, io_kind, message)) = rpc_rx.next().await { if let Some(this) = this.upgrade() { @@ -117,8 +113,7 @@ impl LogStore { }) .detach_and_log_err(cx); - let (adapter_log_tx, mut adapter_log_rx) = - unbounded::<(DebugAdapterClientId, IoKind, String)>(); + let (adapter_log_tx, mut adapter_log_rx) = unbounded::<(SessionId, IoKind, String)>(); cx.spawn(|this, mut cx| async move { while let Some((client_id, io_kind, message)) = adapter_log_rx.next().await { if let Some(this) = this.upgrade() { @@ -142,7 +137,7 @@ impl LogStore { fn on_rpc_log( &mut self, - client_id: DebugAdapterClientId, + client_id: SessionId, io_kind: IoKind, message: &str, cx: &mut Context, @@ -152,7 +147,7 @@ impl LogStore { fn on_adapter_log( &mut self, - client_id: DebugAdapterClientId, + client_id: SessionId, io_kind: IoKind, message: &str, cx: &mut Context, @@ -170,19 +165,17 @@ impl LogStore { this.projects.remove(&weak_project); }), cx.subscribe(project, |this, project, event, cx| match event { - project::Event::DebugClientStarted((_, client_id)) => { - this.add_debug_client( - *client_id, - project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.client_by_id(client_id, cx).and_then( - |(session, client)| { - Some((session, client.read(cx).adapter_client()?)) - }, - ) - }) - }), - ); + project::Event::DebugClientStarted(client_id) => { + let client = project.update(cx, |project, cx| { + project.dap_store().update(cx, |store, cx| { + store + .session_by_id(client_id) + .and_then(|client| Some(client)) + }) + }); + if let Some(client) = client { + this.add_debug_client(*client_id, client, cx); + } } project::Event::DebugClientShutdown(client_id) => { this.remove_debug_client(*client_id, cx); @@ -195,16 +188,13 @@ impl LogStore { ); } - fn get_debug_adapter_state( - &mut self, - id: DebugAdapterClientId, - ) -> Option<&mut DebugAdapterState> { + fn get_debug_adapter_state(&mut self, id: SessionId) -> Option<&mut DebugAdapterState> { self.debug_clients.get_mut(&id) } fn add_debug_client_message( &mut self, - id: DebugAdapterClientId, + id: SessionId, io_kind: IoKind, message: String, cx: &mut Context, @@ -236,7 +226,7 @@ impl LogStore { fn add_debug_client_log( &mut self, - id: DebugAdapterClientId, + id: SessionId, io_kind: IoKind, message: String, cx: &mut Context, @@ -266,7 +256,7 @@ impl LogStore { fn add_debug_client_entry( log_lines: &mut VecDeque, - id: DebugAdapterClientId, + id: SessionId, message: String, kind: LogKind, cx: &mut Context, @@ -295,56 +285,50 @@ impl LogStore { fn add_debug_client( &mut self, - client_id: DebugAdapterClientId, - session_and_client: Option<(Entity, Arc)>, + client_id: SessionId, + client: Entity, + cx: &App, ) -> Option<&mut DebugAdapterState> { let client_state = self .debug_clients .entry(client_id) .or_insert_with(DebugAdapterState::new); - if let Some((_, client)) = session_and_client { - let io_tx = self.rpc_tx.clone(); + let io_tx = self.rpc_tx.clone(); - client.add_log_handler( - move |io_kind, message| { - io_tx - .unbounded_send((client_id, io_kind, message.to_string())) - .ok(); - }, - LogKind::Rpc, - ); + let client = client.read(cx).adapter_client()?; + client.add_log_handler( + move |io_kind, message| { + io_tx + .unbounded_send((client_id, io_kind, message.to_string())) + .ok(); + }, + LogKind::Rpc, + ); - let log_io_tx = self.adapter_log_tx.clone(); - client.add_log_handler( - move |io_kind, message| { - log_io_tx - .unbounded_send((client_id, io_kind, message.to_string())) - .ok(); - }, - LogKind::Adapter, - ); - } + let log_io_tx = self.adapter_log_tx.clone(); + client.add_log_handler( + move |io_kind, message| { + log_io_tx + .unbounded_send((client_id, io_kind, message.to_string())) + .ok(); + }, + LogKind::Adapter, + ); Some(client_state) } - fn remove_debug_client(&mut self, client_id: DebugAdapterClientId, cx: &mut Context) { + fn remove_debug_client(&mut self, client_id: SessionId, cx: &mut Context) { self.debug_clients.remove(&client_id); cx.notify(); } - fn log_messages_for_client( - &mut self, - client_id: DebugAdapterClientId, - ) -> Option<&mut VecDeque> { + fn log_messages_for_client(&mut self, client_id: SessionId) -> Option<&mut VecDeque> { Some(&mut self.debug_clients.get_mut(&client_id)?.log_messages) } - fn rpc_messages_for_client( - &mut self, - client_id: DebugAdapterClientId, - ) -> Option<&mut VecDeque> { + fn rpc_messages_for_client(&mut self, client_id: SessionId) -> Option<&mut VecDeque> { Some( &mut self .debug_clients @@ -379,11 +363,9 @@ impl Render for DapLogToolbarItemView { }); let current_client = current_client_id.and_then(|current_client_id| { - menu_rows.iter().find_map(|row| { - row.clients - .iter() - .find(|sub_item| sub_item.client_id == current_client_id) - }) + menu_rows + .iter() + .find(|row| row.client_id == current_client_id) }); let dap_menu: PopoverMenu<_> = PopoverMenu::new("DapLogView") @@ -409,56 +391,48 @@ impl Render for DapLogToolbarItemView { let menu_rows = menu_rows.clone(); ContextMenu::build(&mut window, cx, move |mut menu, window, _cx| { for row in menu_rows.into_iter() { - menu = menu.header(format!("{}. {}", row.session_id.0, row.session_name)); - - for sub_item in row.clients.into_iter() { - menu = menu.custom_row(move |_window, _cx| { - div() - .w_full() - .pl_2() - .child( - Label::new(format!( - "{}. {}", - sub_item.client_id.0, sub_item.client_name, - )) - .color(workspace::ui::Color::Muted), + menu = menu.custom_row(move |_window, _cx| { + div() + .w_full() + .pl_2() + .child( + Label::new( + format!("{}. {}", row.client_id.0, row.client_name,), ) - .into_any_element() - }); - - if sub_item.has_adapter_logs { - menu = menu.custom_entry( - move |_window, _cx| { - div() - .w_full() - .pl_4() - .child(Label::new(ADAPTER_LOGS)) - .into_any_element() - }, - window.handler_for(&log_view, move |view, window, cx| { - view.show_log_messages_for_adapter( - sub_item.client_id, - window, - cx, - ); - }), - ); - } + .color(workspace::ui::Color::Muted), + ) + .into_any_element() + }); + if row.has_adapter_logs { menu = menu.custom_entry( move |_window, _cx| { div() .w_full() .pl_4() - .child(Label::new(RPC_MESSAGES)) + .child(Label::new(ADAPTER_LOGS)) .into_any_element() }, window.handler_for(&log_view, move |view, window, cx| { - view.show_rpc_trace_for_server(sub_item.client_id, window, cx); + view.show_log_messages_for_adapter(row.client_id, window, cx); }), ); } + + menu = menu.custom_entry( + move |_window, _cx| { + div() + .w_full() + .pl_4() + .child(Label::new(RPC_MESSAGES)) + .into_any_element() + }, + window.handler_for(&log_view, move |view, window, cx| { + view.show_rpc_trace_for_server(row.client_id, window, cx); + }), + ); } + menu }) .into() @@ -585,36 +559,23 @@ impl DapLogView { .dap_store() .read(cx) .sessions() - .filter_map(|session| { + .filter_map(|client| { + let client = client.read(cx).adapter_client()?; Some(DapMenuItem { - session_id: session.read(cx).id(), - session_name: session.read(cx).name(), - clients: { - let mut clients = session - .read_with(cx, |session, cx| session.clients(cx)) - .iter() - .map(|client| DapMenuSubItem { - client_id: client.id(), - client_name: client.adapter_id(), - has_adapter_logs: client.has_adapter_logs(), - selected_entry: self - .current_view - .map_or(LogKind::Adapter, |(_, kind)| kind), - }) - .collect::>(); - clients.sort_by_key(|item| item.client_id.0); - clients - }, + client_id: client.id(), + client_name: unimplemented!(), + has_adapter_logs: client.has_adapter_logs(), + selected_entry: self.current_view.map_or(LogKind::Adapter, |(_, kind)| kind), }) }) .collect::>(); - menu_items.sort_by_key(|item| item.session_id.0); + menu_items.sort_by_key(|item| item.client_id.0); Some(menu_items) } fn show_rpc_trace_for_server( &mut self, - client_id: DebugAdapterClientId, + client_id: SessionId, window: &mut Window, cx: &mut Context, ) { @@ -656,7 +617,7 @@ impl DapLogView { fn show_log_messages_for_adapter( &mut self, - client_id: DebugAdapterClientId, + client_id: SessionId, window: &mut Window, cx: &mut Context, ) { @@ -697,14 +658,7 @@ fn log_contents(lines: &VecDeque) -> String { #[derive(Clone, PartialEq)] pub(crate) struct DapMenuItem { - pub session_id: DebugSessionId, - pub session_name: String, - pub clients: Vec, -} - -#[derive(Clone, PartialEq)] -pub(crate) struct DapMenuSubItem { - pub client_id: DebugAdapterClientId, + pub client_id: SessionId, pub client_name: String, pub has_adapter_logs: bool, pub selected_entry: LogKind, @@ -870,7 +824,7 @@ impl Focusable for DapLogView { pub enum Event { NewLogEntry { - id: DebugAdapterClientId, + id: SessionId, entry: String, kind: LogKind, }, diff --git a/crates/debugger_ui/src/attach_modal.rs b/crates/debugger_ui/src/attach_modal.rs index 98fe4c91be..37d2d04b11 100644 --- a/crates/debugger_ui/src/attach_modal.rs +++ b/crates/debugger_ui/src/attach_modal.rs @@ -1,9 +1,9 @@ -use dap::client::DebugAdapterClientId; +use dap::client::SessionId; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::Subscription; use gpui::{DismissEvent, Entity, EventEmitter, Focusable, Render}; use picker::{Picker, PickerDelegate}; -use project::debugger::{dap_session::DebugSessionId, dap_store::DapStore}; +use project::debugger::dap_store::DapStore; use std::sync::Arc; use sysinfo::System; use ui::{prelude::*, Context, Tooltip}; @@ -20,19 +20,15 @@ struct Candidate { pub(crate) struct AttachModalDelegate { selected_index: usize, matches: Vec, - session_id: DebugSessionId, + session_id: SessionId, placeholder_text: Arc, dap_store: Entity, - client_id: DebugAdapterClientId, + client_id: SessionId, candidates: Option>, } impl AttachModalDelegate { - pub fn new( - session_id: DebugSessionId, - client_id: DebugAdapterClientId, - dap_store: Entity, - ) -> Self { + pub fn new(session_id: SessionId, client_id: SessionId, dap_store: Entity) -> Self { Self { client_id, dap_store, @@ -52,8 +48,8 @@ pub(crate) struct AttachModal { impl AttachModal { pub fn new( - session_id: &DebugSessionId, - client_id: DebugAdapterClientId, + session_id: &SessionId, + client_id: SessionId, dap_store: Entity, window: &mut Window, cx: &mut Context, @@ -132,18 +128,15 @@ impl PickerDelegate for AttachModalDelegate { } else { let Some(client) = this.delegate.dap_store.update(cx, |store, cx| { store - .client_by_id(&this.delegate.client_id, cx) - .and_then(|(_, client)| client.read(cx).adapter_client()) + .session_by_id(&this.delegate.client_id) + .and_then(|client| client.read(cx).adapter_client()) }) else { return Vec::new(); }; let system = System::new_all(); - let Some(processes) = - client.adapter().attach_processes(&system.processes()) - else { - return Vec::new(); - }; + todo!("client.adapter().attach_processes(&system.processes())"); + let processes: Vec<(&sysinfo::Pid, &sysinfo::Process)> = vec![]; let processes = processes .into_iter() @@ -156,7 +149,7 @@ impl PickerDelegate for AttachModalDelegate { .map(|s| s.to_string_lossy().to_string()) .collect::>(), }) - .collect::>(); + .collect::>(); let _ = this.delegate.candidates.insert(processes.clone()); @@ -224,7 +217,7 @@ impl PickerDelegate for AttachModalDelegate { self.dap_store.update(cx, |store, cx| { store - .attach(&self.session_id, self.client_id, candidate.pid, cx) + .attach(self.client_id, candidate.pid, cx) .detach_and_log_err(cx); }); diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 7ba512592b..52f5d6b834 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -1,9 +1,9 @@ -use crate::{attach_modal::AttachModal, debugger_panel_item::DebugPanelItem}; +use crate::{attach_modal::AttachModal, session::DebugSession}; use anyhow::Result; use collections::{BTreeMap, HashMap}; use command_palette_hooks::CommandPaletteFilter; use dap::{ - client::DebugAdapterClientId, + client::SessionId, debugger_settings::DebuggerSettings, messages::{Events, Message}, requests::{Request, RunInTerminal, StartDebugging}, @@ -17,12 +17,12 @@ use gpui::{ }; use project::{ debugger::{ - dap_session::{DebugSessionId, ThreadId}, dap_store::{DapStore, DapStoreEvent}, + session::ThreadId, }, terminals::TerminalKind, }; -use rpc::proto::{self, SetDebuggerPanelItem, UpdateDebugAdapter}; +use rpc::proto::{self, UpdateDebugAdapter}; use serde_json::Value; use settings::Settings; use std::{any::TypeId, collections::VecDeque, path::PathBuf, u64}; @@ -37,90 +37,29 @@ use workspace::{ }; pub enum DebugPanelEvent { - Exited(DebugAdapterClientId), - Terminated(DebugAdapterClientId), + Exited(SessionId), + Terminated(SessionId), Stopped { - client_id: DebugAdapterClientId, + client_id: SessionId, event: StoppedEvent, go_to_stack_frame: bool, }, - Thread((DebugAdapterClientId, ThreadEvent)), - Continued((DebugAdapterClientId, ContinuedEvent)), - Output((DebugAdapterClientId, OutputEvent)), - Module((DebugAdapterClientId, ModuleEvent)), - LoadedSource((DebugAdapterClientId, LoadedSourceEvent)), - ClientShutdown(DebugAdapterClientId), - CapabilitiesChanged(DebugAdapterClientId), + Thread((SessionId, ThreadEvent)), + Continued((SessionId, ContinuedEvent)), + Output((SessionId, OutputEvent)), + Module((SessionId, ModuleEvent)), + LoadedSource((SessionId, LoadedSourceEvent)), + ClientShutdown(SessionId), + CapabilitiesChanged(SessionId), } actions!(debug_panel, [ToggleFocus]); - -#[derive(Debug, Default, Clone)] -pub struct ThreadState { - pub status: ThreadStatus, - // we update this value only once we stopped, - // we will use this to indicated if we should show a warning when debugger thread was exited - pub stopped: bool, -} - -impl ThreadState { - pub fn from_proto(thread_state: proto::DebuggerThreadState) -> Self { - let status = ThreadStatus::from_proto(thread_state.thread_status()); - - Self { - status, - stopped: thread_state.stopped, - } - } - - pub fn to_proto(&self) -> proto::DebuggerThreadState { - let status = self.status.to_proto(); - - proto::DebuggerThreadState { - thread_status: status, - stopped: self.stopped, - } - } -} - -#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum ThreadStatus { - #[default] - Running, - Stopped, - Exited, - Ended, -} - -impl ThreadStatus { - pub fn from_proto(status: proto::DebuggerThreadStatus) -> Self { - match status { - proto::DebuggerThreadStatus::Running => Self::Running, - proto::DebuggerThreadStatus::Stopped => Self::Stopped, - proto::DebuggerThreadStatus::Exited => Self::Exited, - proto::DebuggerThreadStatus::Ended => Self::Ended, - } - } - - pub fn to_proto(&self) -> i32 { - match self { - Self::Running => proto::DebuggerThreadStatus::Running.into(), - Self::Stopped => proto::DebuggerThreadStatus::Stopped.into(), - Self::Exited => proto::DebuggerThreadStatus::Exited.into(), - Self::Ended => proto::DebuggerThreadStatus::Ended.into(), - } - } -} - pub struct DebugPanel { size: Pixels, pane: Entity, - focus_handle: FocusHandle, - dap_store: Entity, + workspace: WeakEntity, _subscriptions: Vec, - message_queue: HashMap>, - thread_states: BTreeMap<(DebugAdapterClientId, ThreadId), Entity>, } impl DebugPanel { @@ -144,102 +83,55 @@ impl DebugPanel { pane.set_can_navigate(true, cx); pane.display_nav_history_buttons(None); pane.set_should_display_tab_bar(|_window, _cx| true); - pane.set_close_pane_if_empty(false, cx); - + pane.set_close_pane_if_empty(true, cx); + pane.set_render_tab_bar_buttons(cx, |_, _, cx| { + ( + None, + Some( + h_flex() + .child( + IconButton::new("new-debug-session", IconName::Plus) + .icon_size(IconSize::Small) + .on_click(cx.listener(|pane, _, window, cx| { + pane.add_item( + Box::new(DebugSession::inert(cx)), + false, + false, + None, + window, + cx, + ); + })), + ) + .into_any_element(), + ), + ) + }); + pane.add_item( + Box::new(DebugSession::inert(cx)), + false, + false, + None, + window, + cx, + ); pane }); let project = workspace.project().clone(); - let dap_store = project.read(cx).dap_store(); let _subscriptions = vec![ cx.observe(&pane, |_, _, cx| cx.notify()), cx.subscribe_in(&pane, window, Self::handle_pane_event), - cx.subscribe_in(&dap_store, window, Self::on_dap_store_event), - cx.subscribe_in(&project, window, { - move |this: &mut Self, _, event, window, cx| match event { - project::Event::DebugClientStarted((session_id, client_id)) => { - this.handle_debug_client_started(session_id, *client_id, window, cx); - } - project::Event::DebugClientEvent { - session_id, - client_id, - message, - } => match message { - Message::Event(event) => { - this.handle_debug_client_events( - session_id, *client_id, event, window, cx, - ); - } - Message::Request(request) => { - if StartDebugging::COMMAND == request.command { - this.handle_start_debugging_request( - session_id, - *client_id, - request.seq, - request.arguments.clone(), - cx, - ); - } else if RunInTerminal::COMMAND == request.command { - this.handle_run_in_terminal_request( - session_id, - *client_id, - request.seq, - request.arguments.clone(), - window, - cx, - ); - } else { - debug_assert!(false, "Encountered unexpected command type"); - } - } - _ => unreachable!(), - }, - project::Event::DebugClientShutdown(client_id) => { - cx.emit(DebugPanelEvent::ClientShutdown(*client_id)); - - this.message_queue.remove(client_id); - this.thread_states - .retain(|&(client_id_, _), _| client_id_ != *client_id); - - cx.notify(); - } - project::Event::SetDebugClient(set_debug_client) => { - this.handle_set_debug_panel_item(set_debug_client, window, cx); - } - _ => {} - } - }), ]; - let dap_store = project.read(cx).dap_store(); - - let mut debug_panel = Self { + let debug_panel = Self { pane, size: px(300.), _subscriptions, - focus_handle: cx.focus_handle(), - thread_states: Default::default(), - message_queue: Default::default(), workspace: workspace.weak_handle(), - dap_store: dap_store.clone(), }; - if let Some(mut dap_event_queue) = debug_panel - .dap_store - .clone() - .update(cx, |this, _| this.remote_event_queue()) - { - while let Some(dap_event) = dap_event_queue.pop_front() { - debug_panel.on_dap_store_event( - &debug_panel.dap_store.clone(), - &dap_event, - window, - cx, - ); - } - } - debug_panel }) } @@ -256,15 +148,7 @@ impl DebugPanel { let (has_active_session, support_step_back) = debug_panel.update(cx, |this, cx| { this.active_debug_panel_item(cx) - .map(|item| { - ( - true, - item.update(cx, |this, cx| this.capabilities(cx)) - .map(|caps| caps.supports_step_back) - .flatten() - .unwrap_or(false), - ) - }) + .map(|item| (true, false)) .unwrap_or((false, false)) }); @@ -305,7 +189,7 @@ impl DebugPanel { } #[cfg(any(test, feature = "test-support"))] - pub fn message_queue(&self) -> &HashMap> { + pub fn message_queue(&self) -> &HashMap> { &self.message_queue } @@ -314,41 +198,40 @@ impl DebugPanel { self.dap_store.clone() } - pub fn active_debug_panel_item(&self, cx: &Context) -> Option> { + pub fn active_debug_panel_item(&self, cx: &Context) -> Option> { self.pane .read(cx) .active_item() - .and_then(|panel| panel.downcast::()) + .and_then(|panel| panel.downcast::()) } pub fn debug_panel_items_by_client( &self, - client_id: &DebugAdapterClientId, + client_id: &SessionId, cx: &Context, - ) -> Vec> { + ) -> Vec> { self.pane .read(cx) .items() - .filter_map(|item| item.downcast::()) - .filter(|item| &item.read(cx).client_id() == client_id) + .filter_map(|item| item.downcast::()) + .filter(|item| item.read(cx).session_id(cx) == Some(*client_id)) .map(|item| item.clone()) .collect() } pub fn debug_panel_item_by_client( &self, - client_id: DebugAdapterClientId, - thread_id: ThreadId, + client_id: SessionId, cx: &mut Context, - ) -> Option> { + ) -> Option> { self.pane .read(cx) .items() - .filter_map(|item| item.downcast::()) + .filter_map(|item| item.downcast::()) .find(|item| { let item = item.read(cx); - item.client_id() == client_id && item.thread_id() == thread_id + item.session_id(cx) == Some(client_id) }) } @@ -360,29 +243,6 @@ impl DebugPanel { cx: &mut Context, ) { match event { - pane::Event::RemovedItem { item } => { - let thread_panel = item.downcast::().unwrap(); - - let thread_id = thread_panel.read(cx).thread_id(); - let client_id = thread_panel.read(cx).client_id(); - - self.thread_states.remove(&(client_id, thread_id)); - - cx.notify(); - - let Some(client_state) = thread_panel - .read(cx) - .session() - .read(cx) - .client_state(client_id) - else { - return; - }; - - client_state.update(cx, |state, cx| { - state.terminate_threads(Some(vec![thread_id; 1]), cx); - }); - } pane::Event::Remove { .. } => cx.emit(PanelEvent::Close), pane::Event::ZoomIn => cx.emit(PanelEvent::ZoomIn), pane::Event::ZoomOut => cx.emit(PanelEvent::ZoomOut), @@ -393,763 +253,9 @@ impl DebugPanel { }) .ok(); } - pane::Event::ActivateItem { local, .. } => { - if !local { - return; - } - - if let Some(active_item) = self.pane.read(cx).active_item() { - if let Some(debug_item) = active_item.downcast::() { - debug_item.update(cx, |panel, cx| { - panel.go_to_current_stack_frame(window, cx); - }); - } - } - } _ => {} } } - - fn handle_start_debugging_request( - &mut self, - session_id: &DebugSessionId, - client_id: DebugAdapterClientId, - seq: u64, - request_args: Option, - cx: &mut Context, - ) { - let args = if let Some(args) = request_args { - serde_json::from_value(args.clone()).ok() - } else { - None - }; - - self.dap_store.update(cx, |store, cx| { - store - .respond_to_start_debugging(session_id, client_id, seq, args, cx) - .detach_and_log_err(cx); - }); - } - - fn handle_run_in_terminal_request( - &mut self, - session_id: &DebugSessionId, - client_id: DebugAdapterClientId, - seq: u64, - request_args: Option, - window: &mut Window, - cx: &mut Context, - ) { - let request_args = request_args.and_then(|request_args| { - serde_json::from_value::(request_args).ok() - }); - let Some(request_args) = request_args else { - self.dap_store.update(cx, |store, cx| { - store - .respond_to_run_in_terminal( - session_id, - client_id, - false, - seq, - serde_json::to_value(ErrorResponse { - error: Some(dap::Message { - id: seq, - format: - "Request arguments must be provided when spawnng debug terminal" - .into(), - variables: None, - send_telemetry: None, - show_user: None, - url: None, - url_label: None, - }), - }) - .ok(), - cx, - ) - .detach_and_log_err(cx); - }); - return; - }; - - let mut envs: HashMap = Default::default(); - if let Some(Value::Object(env)) = request_args.env { - for (key, value) in env { - let value_str = match (key.as_str(), value) { - (_, Value::String(value)) => value, - _ => continue, - }; - - envs.insert(key, value_str); - } - } - - let terminal_task = self.workspace.update(cx, |workspace, cx| { - let terminal_panel = workspace.panel::(cx).unwrap(); - - terminal_panel.update(cx, |terminal_panel, cx| { - let mut args = request_args.args.clone(); - - // Handle special case for NodeJS debug adapter - // If only the Node binary path is provided, we set the command to None - // This prevents the NodeJS REPL from appearing, which is not the desired behavior - // The expected usage is for users to provide their own Node command, e.g., `node test.js` - // This allows the NodeJS debug client to attach correctly - let command = if args.len() > 1 { - Some(args.remove(0)) - } else { - None - }; - - let terminal_task = terminal_panel.add_terminal( - TerminalKind::Debug { - command, - args, - envs, - cwd: PathBuf::from(request_args.cwd), - title: request_args.title, - }, - task::RevealStrategy::Always, - window, - cx, - ); - - cx.spawn(|_, mut cx| async move { - let pid_task = async move { - let terminal = terminal_task.await?; - - terminal.read_with(&mut cx, |terminal, _| terminal.pty_info.pid()) - }; - - pid_task.await - }) - }) - }); - - let session_id = *session_id; - - cx.spawn(|this, mut cx| async move { - // Ensure a response is always sent, even in error cases, - // to maintain proper communication with the debug adapter - let (success, body) = match terminal_task { - Ok(pid_task) => match pid_task.await { - Ok(pid) => ( - true, - serde_json::to_value(RunInTerminalResponse { - process_id: None, - shell_process_id: pid.map(|pid| pid.as_u32() as u64), - }) - .ok(), - ), - Err(error) => { - this.update(&mut cx, |this, cx| { - this.dap_store.update(cx, |_, cx| { - cx.emit(DapStoreEvent::Notification(error.to_string())); - }) - }) - .log_err(); - - ( - false, - serde_json::to_value(ErrorResponse { - error: Some(dap::Message { - id: seq, - format: error.to_string(), - variables: None, - send_telemetry: None, - show_user: None, - url: None, - url_label: None, - }), - }) - .ok(), - ) - } - }, - Err(error) => ( - false, - serde_json::to_value(ErrorResponse { - error: Some(dap::Message { - id: seq, - format: error.to_string(), - variables: None, - send_telemetry: None, - show_user: None, - url: None, - url_label: None, - }), - }) - .ok(), - ), - }; - - let respond_task = this.update(&mut cx, |this, cx| { - this.dap_store.update(cx, |store, cx| { - store.respond_to_run_in_terminal(&session_id, client_id, success, seq, body, cx) - }) - }); - - respond_task?.await - }) - .detach_and_log_err(cx); - } - - fn handle_debug_client_started( - &self, - session_id: &DebugSessionId, - client_id: DebugAdapterClientId, - window: &mut Window, - cx: &mut Context, - ) { - let Some(session) = self - .dap_store - .read(cx) - .session_by_id(session_id) - .and_then(|session| session.read(cx).as_local()) - else { - return; - }; - - let session_id = *session_id; - let workspace = self.workspace.clone(); - let request_type = session.configuration().request.clone(); - cx.spawn_in(window, |this, mut cx| async move { - let task = this.update(&mut cx, |this, cx| { - this.dap_store - .update(cx, |store, cx| store.initialize(&session_id, client_id, cx)) - })?; - - task.await?; - - let result = match request_type { - DebugRequestType::Launch => { - let task = this.update(&mut cx, |this, cx| { - this.dap_store - .update(cx, |store, cx| store.launch(&session_id, client_id, cx)) - }); - - task?.await - } - DebugRequestType::Attach(config) => { - if let Some(process_id) = config.process_id { - let task = this.update(&mut cx, |this, cx| { - this.dap_store.update(cx, |store, cx| { - store.attach(&session_id, client_id, process_id, cx) - }) - })?; - - task.await - } else { - this.update_in(&mut cx, |this, window, cx| { - workspace.update(cx, |workspace, cx| { - workspace.toggle_modal(window, cx, |window, cx| { - AttachModal::new( - &session_id, - client_id, - this.dap_store.clone(), - window, - cx, - ) - }) - }) - })? - } - } - }; - - if result.is_err() { - this.update(&mut cx, |debug_panel, cx| { - debug_panel.dap_store.update(cx, |store, cx| { - cx.emit(DapStoreEvent::Notification( - "Failed to start debug session".into(), - )); - - store - .shutdown_session(&session_id, cx) - .detach_and_log_err(cx); - }); - })?; - } - - result - }) - .detach_and_log_err(cx); - } - - fn handle_debug_client_events( - &mut self, - session_id: &DebugSessionId, - client_id: DebugAdapterClientId, - event: &Events, - window: &mut Window, - cx: &mut Context, - ) { - match event { - Events::Initialized(event) => { - self.handle_initialized_event(&session_id, client_id, event, cx) - } - Events::Stopped(event) => { - self.handle_stopped_event(&session_id, client_id, event, window, cx) - } - Events::Continued(event) => self.handle_continued_event(client_id, event, cx), - Events::Exited(event) => self.handle_exited_event(client_id, event, cx), - Events::Terminated(event) => { - self.handle_terminated_event(&session_id, client_id, event, cx) - } - Events::Thread(event) => self.handle_thread_event(client_id, event, cx), - Events::Output(event) => self.handle_output_event(client_id, event, cx), - Events::Breakpoint(_) => {} - Events::Module(event) => self.handle_module_event(client_id, event, cx), - Events::LoadedSource(event) => self.handle_loaded_source_event(client_id, event, cx), - Events::Capabilities(event) => { - self.handle_capabilities_changed_event(session_id, client_id, event, cx); - } - Events::Memory(_) => {} - Events::Process(_) => {} - Events::ProgressEnd(_) => {} - Events::ProgressStart(_) => {} - Events::ProgressUpdate(_) => {} - Events::Invalidated(_) => {} - Events::Other(_) => {} - } - } - - fn handle_initialized_event( - &mut self, - session_id: &DebugSessionId, - client_id: DebugAdapterClientId, - capabilities: &Option, - cx: &mut Context, - ) { - if let Some(capabilities) = capabilities { - self.dap_store.update(cx, |store, cx| { - store.update_capabilities_for_client(&session_id, client_id, capabilities, cx); - }); - - cx.emit(DebugPanelEvent::CapabilitiesChanged(client_id)); - } - - let session_id = *session_id; - - cx.spawn(|this, mut cx| async move { - this.update(&mut cx, |debug_panel, cx| { - debug_panel.workspace.update(cx, |workspace, cx| { - workspace.project().update(cx, |project, cx| { - project.initial_send_breakpoints(&session_id, client_id, cx) - }) - }) - })?? - .await; - - this.update(&mut cx, |debug_panel, cx| { - debug_panel - .dap_store - .update(cx, |store, cx| store.configuration_done(client_id, cx)) - })? - .await - }) - .detach_and_log_err(cx); - } - - fn handle_continued_event( - &mut self, - client_id: DebugAdapterClientId, - event: &ContinuedEvent, - cx: &mut Context, - ) { - cx.emit(DebugPanelEvent::Continued((client_id, event.clone()))); - } - - fn handle_stopped_event( - &mut self, - session_id: &DebugSessionId, - client_id: DebugAdapterClientId, - event: &StoppedEvent, - window: &mut Window, - cx: &mut Context, - ) { - let Some(thread_id) = event.thread_id.map(|thread_id| ThreadId(thread_id)) else { - return; - }; - - let Some(session) = self - .dap_store - .read(cx) - .session_by_id(session_id) - .map(|session| session) - else { - return; // this can/should never happen - }; - - cx.spawn_in(window, { - let event = event.clone(); - |this, mut cx| async move { - let workspace = this.update_in(&mut cx, |this, window, cx| { - let thread_state = this - .thread_states - .entry((client_id, thread_id)) - .or_insert(cx.new(|_| ThreadState::default())) - .clone(); - - thread_state.update(cx, |thread_state, _| { - thread_state.stopped = true; - thread_state.status = ThreadStatus::Stopped; - }); - - let existing_item = this.debug_panel_item_by_client(client_id, thread_id, cx); - if existing_item.is_none() { - let debug_panel = cx.entity(); - this.pane.update(cx, |pane, cx| { - let tab = cx.new(|cx| { - DebugPanelItem::new( - session, - client_id, - thread_id, - thread_state, - &debug_panel, - this.workspace.clone(), - window, - cx, - ) - }); - - pane.add_item(Box::new(tab), true, true, None, window, cx); - }); - - if let Some(message_queue) = this.message_queue.get(&client_id) { - for output in message_queue.iter() { - cx.emit(DebugPanelEvent::Output((client_id, output.clone()))); - } - } - } - - let go_to_stack_frame = if let Some(item) = this.pane.read(cx).active_item() { - item.downcast::().map_or(false, |pane| { - let pane = pane.read(cx); - pane.thread_id() == thread_id && pane.client_id() == client_id - }) - } else { - true - }; - - cx.emit(DebugPanelEvent::Stopped { - client_id, - event, - go_to_stack_frame, - }); - - this.workspace.clone() - })?; - - cx.update(|window, cx| { - workspace.update(cx, |workspace, cx| { - workspace.focus_panel::(window, cx); - }) - }) - } - }) - .detach_and_log_err(cx); - } - - fn handle_thread_event( - &mut self, - client_id: DebugAdapterClientId, - event: &ThreadEvent, - cx: &mut Context, - ) { - let thread_id = ThreadId(event.thread_id); - - if let Some(thread_state) = self.thread_states.get(&(client_id, thread_id)) { - if !thread_state.read(cx).stopped && event.reason == ThreadEventReason::Exited { - const MESSAGE: &'static str = "Debug session exited without hitting breakpoints\n\nTry adding a breakpoint, or define the correct path mapping for your debugger."; - - self.dap_store.update(cx, |_, cx| { - cx.emit(DapStoreEvent::Notification(MESSAGE.into())); - }); - }; - } - - if event.reason == ThreadEventReason::Started { - self.thread_states - .insert((client_id, thread_id), cx.new(|_| ThreadState::default())); - } - - cx.emit(DebugPanelEvent::Thread((client_id, event.clone()))); - cx.notify(); - } - - fn handle_exited_event( - &mut self, - client_id: DebugAdapterClientId, - _: &ExitedEvent, - cx: &mut Context, - ) { - cx.emit(DebugPanelEvent::Exited(client_id)); - } - - fn handle_terminated_event( - &mut self, - session_id: &DebugSessionId, - client_id: DebugAdapterClientId, - event: &Option, - cx: &mut Context, - ) { - let restart_args = event.clone().and_then(|e| e.restart); - - for (_, thread_state) in self - .thread_states - .range_mut(&(client_id, ThreadId::MIN)..&(client_id, ThreadId::MAX)) - { - thread_state.update(cx, |thread_state, cx| { - thread_state.status = ThreadStatus::Ended; - - cx.notify(); - }); - } - - self.dap_store.update(cx, |store, cx| { - if restart_args - .as_ref() - .is_some_and(|v| v.as_bool().unwrap_or(true)) - { - let Some(session) = store.session_by_client_id(client_id) else { - return; - }; - - let Some(client_state) = session.read(cx).client_state(client_id) else { - return; - }; - - client_state.update(cx, |state, cx| { - state.restart(restart_args, cx); - }); - } else { - store - .shutdown_session(&session_id, cx) - .detach_and_log_err(cx); - } - }); - - cx.emit(DebugPanelEvent::Terminated(client_id)); - } - - fn handle_output_event( - &mut self, - client_id: DebugAdapterClientId, - event: &OutputEvent, - cx: &mut Context, - ) { - self.message_queue - .entry(client_id) - .or_default() - .push_back(event.clone()); - - cx.emit(DebugPanelEvent::Output((client_id, event.clone()))); - } - - fn on_dap_store_event( - &mut self, - _: &Entity, - event: &DapStoreEvent, - window: &mut Window, - cx: &mut Context, - ) { - match event { - DapStoreEvent::SetDebugPanelItem(set_debug_panel_item) => { - self.handle_set_debug_panel_item(set_debug_panel_item, window, cx); - } - DapStoreEvent::UpdateDebugAdapter(debug_adapter_update) => { - self.handle_debug_adapter_update(debug_adapter_update, window, cx); - } - DapStoreEvent::UpdateThreadStatus(thread_status_update) => { - self.handle_thread_status_update(thread_status_update, cx); - } - DapStoreEvent::RemoteHasInitialized => self.handle_remote_has_initialized(window, cx), - _ => {} - } - } - - pub(crate) fn handle_thread_status_update( - &mut self, - update: &proto::UpdateThreadStatus, - cx: &mut Context, - ) { - if let Some(thread_state) = self.thread_states.get_mut(&( - DebugAdapterClientId::from_proto(update.client_id), - ThreadId(update.thread_id), - )) { - thread_state.update(cx, |thread_state, _| { - thread_state.status = ThreadStatus::from_proto(update.status()); - }); - - cx.notify(); - } - } - - pub(crate) fn handle_debug_adapter_update( - &mut self, - update: &UpdateDebugAdapter, - _window: &mut Window, - cx: &mut Context, - ) { - let client_id = DebugAdapterClientId::from_proto(update.client_id); - let thread_id = update.thread_id.map(|thread_id| ThreadId(thread_id)); - - let active_item = self - .pane - .read(cx) - .active_item() - .and_then(|item| item.downcast::()); - - let _search = self - .pane - .read(cx) - .items() - .filter_map(|item| item.downcast::()) - .find_map(|item_view| { - let item = item_view.read(cx); - - if item.client_id() == client_id - && thread_id.map(|id| id == item.thread_id()).unwrap_or(true) - { - Some(( - item_view.clone(), - active_item - .as_ref() - .map_or(false, |this| this == &item_view), - )) - } else { - None - } - }); - - // if let Some((debug_panel_item, is_active_item)) = search { - // TODO(debugger): make this work again - // debug_panel_item.update(cx, |this, cx| { - // this.update_adapter(update, window, cx); - - // if is_active_item { - // this.go_to_current_stack_frame(window, cx); - // } - // }); - // } - } - - fn handle_remote_has_initialized(&mut self, window: &mut Window, cx: &mut Context) { - if let Some(mut dap_event_queue) = self - .dap_store - .clone() - .update(cx, |this, _| this.remote_event_queue()) - { - while let Some(dap_event) = dap_event_queue.pop_front() { - self.on_dap_store_event(&self.dap_store.clone(), &dap_event, window, cx); - } - } - } - - pub(crate) fn handle_set_debug_panel_item( - &mut self, - payload: &SetDebuggerPanelItem, - window: &mut Window, - cx: &mut Context, - ) { - let session_id = DebugSessionId::from_proto(payload.session_id); - let Some(session) = self.dap_store.read(cx).session_by_id(&session_id) else { - return; - }; - - let client_id = DebugAdapterClientId::from_proto(payload.client_id); - let thread_id = ThreadId(payload.thread_id); - let thread_state = payload.thread_state.clone().unwrap(); - let thread_state = cx.new(|_| ThreadState::from_proto(thread_state)); - - let mut existing_item = self - .pane - .read(cx) - .items() - .filter_map(|item| item.downcast::()) - .find(|item| { - let item = item.read(cx); - - item.client_id() == client_id && item.thread_id() == thread_id - }); - - let debug_panel_item = existing_item.get_or_insert_with(|| { - self.thread_states - .insert((client_id, thread_id), thread_state.clone()); - - let debug_panel = cx.entity(); - let debug_panel_item = self.pane.update(cx, |pane, cx| { - let debug_panel_item = cx.new(|cx| { - DebugPanelItem::new( - session, - client_id, - thread_id, - thread_state, - &debug_panel, - self.workspace.clone(), - window, - cx, - ) - }); - - self.dap_store.update(cx, |dap_store, cx| { - dap_store.add_remote_session(session_id, None, cx); - dap_store.add_client_to_session(session_id, client_id); - }); - - pane.add_item( - Box::new(debug_panel_item.clone()), - true, - true, - None, - window, - cx, - ); - debug_panel_item - }); - - debug_panel_item - }); - - debug_panel_item.update(cx, |this, cx| { - this.from_proto(payload, cx); - }); - - cx.notify(); - } - - fn handle_module_event( - &mut self, - client_id: DebugAdapterClientId, - event: &ModuleEvent, - cx: &mut Context, - ) { - cx.emit(DebugPanelEvent::Module((client_id, event.clone()))); - } - - fn handle_loaded_source_event( - &mut self, - client_id: DebugAdapterClientId, - event: &LoadedSourceEvent, - cx: &mut Context, - ) { - cx.emit(DebugPanelEvent::LoadedSource((client_id, event.clone()))); - } - - fn handle_capabilities_changed_event( - &mut self, - session_id: &DebugSessionId, - client_id: DebugAdapterClientId, - event: &CapabilitiesEvent, - cx: &mut Context, - ) { - self.dap_store.update(cx, |store, cx| { - store.update_capabilities_for_client(session_id, client_id, &event.capabilities, cx); - }); - - cx.emit(DebugPanelEvent::CapabilitiesChanged(client_id)); - } } impl EventEmitter for DebugPanel {} @@ -1157,8 +263,8 @@ impl EventEmitter for DebugPanel {} impl EventEmitter for DebugPanel {} impl Focusable for DebugPanel { - fn focus_handle(&self, _cx: &App) -> FocusHandle { - self.focus_handle.clone() + fn focus_handle(&self, cx: &App) -> FocusHandle { + self.pane.focus_handle(cx) } } @@ -1218,46 +324,30 @@ impl Panel for DebugPanel { fn activation_priority(&self) -> u32 { 9 } + fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context) { + if active && self.pane.read(cx).items_len() == 0 { + // todo: We need to revisit it when we start adding stopped items to pane (as that'll cause us to add two items). + self.pane.update(cx, |this, cx| { + this.add_item( + Box::new(DebugSession::inert(cx)), + false, + false, + None, + window, + cx, + ); + }); + } + } } impl Render for DebugPanel { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { v_flex() .key_context("DebugPanel") - .track_focus(&self.focus_handle) + .track_focus(&self.focus_handle(cx)) .size_full() - .map(|this| { - if self.pane.read(cx).items_len() == 0 { - this.child( - h_flex().size_full().items_center().justify_center().child( - v_flex() - .gap_2() - .rounded_md() - .max_w_64() - .items_start() - .child( - Label::new("You can create a debug task by creating a new task and setting the `type` key to `debug`") - .size(LabelSize::Small) - .color(Color::Muted), - ) - .child( - h_flex().w_full().justify_end().child( - Button::new( - "start-debugger", - "Choose a debugger", - ) - .label_size(LabelSize::Small) - .on_click(move |_, window, cx| { - window.dispatch_action(Box::new(Start), cx); - }) - ), - ), - ), - ) - } else { - this.child(self.pane.clone()) - } - }) + .child(self.pane.clone()) .into_any() } } diff --git a/crates/debugger_ui/src/lib.rs b/crates/debugger_ui/src/lib.rs index e79233a2e0..7d4dc52538 100644 --- a/crates/debugger_ui/src/lib.rs +++ b/crates/debugger_ui/src/lib.rs @@ -1,7 +1,7 @@ use dap::debugger_settings::DebuggerSettings; use debugger_panel::{DebugPanel, ToggleFocus}; -use debugger_panel_item::DebugPanelItem; use gpui::App; +use session::DebugSession; use settings::Settings; use workspace::{ Continue, Pause, Restart, ShutdownDebugAdapters, Start, StepBack, StepInto, StepOut, StepOver, @@ -9,20 +9,15 @@ use workspace::{ }; pub mod attach_modal; -pub mod console; pub mod debugger_panel; -pub mod debugger_panel_item; -pub mod loaded_source_list; -pub mod module_list; -pub mod stack_frame_list; -pub mod variable_list; +pub mod session; #[cfg(test)] mod tests; pub fn init(cx: &mut App) { DebuggerSettings::register(cx); - workspace::FollowableViewRegistry::register::(cx); + workspace::FollowableViewRegistry::register::(cx); cx.observe_new(|workspace: &mut Workspace, window, _cx| { let Some(_) = window else { @@ -45,108 +40,7 @@ pub fn init(cx: &mut App) { }) }) }, - ) - .register_action(|workspace: &mut Workspace, _: &Stop, _window, cx| { - let debug_panel = workspace.panel::(cx).unwrap(); - - debug_panel.update(cx, |panel, cx| { - let Some(active_item) = panel.active_debug_panel_item(cx) else { - return; - }; - - active_item.update(cx, |item, cx| item.stop_thread(cx)) - }); - }) - .register_action(|workspace: &mut Workspace, _: &Continue, _window, cx| { - let debug_panel = workspace.panel::(cx).unwrap(); - - debug_panel.update(cx, |panel, cx| { - let Some(active_item) = panel.active_debug_panel_item(cx) else { - return; - }; - - active_item.update(cx, |item, cx| item.continue_thread(cx)) - }); - }) - .register_action(|workspace: &mut Workspace, _: &StepInto, _window, cx| { - let debug_panel = workspace.panel::(cx).unwrap(); - - debug_panel.update(cx, |panel, cx| { - let Some(active_item) = panel.active_debug_panel_item(cx) else { - return; - }; - - active_item.update(cx, |item, cx| item.step_in(cx)) - }); - }) - .register_action(|workspace: &mut Workspace, _: &StepBack, _window, cx| { - let debug_panel = workspace.panel::(cx).unwrap(); - - debug_panel.update(cx, |panel, cx| { - let Some(active_item) = panel.active_debug_panel_item(cx) else { - return; - }; - - active_item.update(cx, |item, cx| item.step_back(cx)) - }); - }) - .register_action(|workspace: &mut Workspace, _: &StepOut, _window, cx| { - let debug_panel = workspace.panel::(cx).unwrap(); - - debug_panel.update(cx, |panel, cx| { - let Some(active_item) = panel.active_debug_panel_item(cx) else { - return; - }; - - active_item.update(cx, |item, cx| item.step_out(cx)) - }); - }) - .register_action(|workspace: &mut Workspace, _: &StepOver, _window, cx| { - let debug_panel = workspace.panel::(cx).unwrap(); - - debug_panel.update(cx, |panel, cx| { - let Some(active_item) = panel.active_debug_panel_item(cx) else { - return; - }; - - active_item.update(cx, |item, cx| item.step_over(cx)) - }); - }) - .register_action(|workspace: &mut Workspace, _: &Restart, _window, cx| { - let debug_panel = workspace.panel::(cx).unwrap(); - - debug_panel.update(cx, |panel, cx| { - let Some(active_item) = panel.active_debug_panel_item(cx) else { - return; - }; - - active_item.update(cx, |item, cx| item.restart_client(cx)) - }); - }) - .register_action( - |workspace: &mut Workspace, _: &ToggleIgnoreBreakpoints, _window, cx| { - let debug_panel = workspace.panel::(cx).unwrap(); - - debug_panel.update(cx, |panel, cx| { - let Some(active_item) = panel.active_debug_panel_item(cx) else { - return; - }; - - active_item.update(cx, |item, cx| item.toggle_ignore_breakpoints(cx)) - }); - }, - ) - .register_action(|workspace: &mut Workspace, _: &Pause, _window, cx| { - let debug_panel = workspace.panel::(cx).unwrap(); - - debug_panel.update(cx, |panel, cx| { - let Some(active_item) = panel.active_debug_panel_item(cx) else { - return; - }; - - active_item.update(cx, |item, cx| item.pause_thread(cx)) - }); - }); + ); }) .detach(); } diff --git a/crates/debugger_ui/src/session.rs b/crates/debugger_ui/src/session.rs new file mode 100644 index 0000000000..5bb0d3f76e --- /dev/null +++ b/crates/debugger_ui/src/session.rs @@ -0,0 +1,190 @@ +mod inert; +mod running; +mod starting; + +use crate::debugger_panel::{DebugPanel, DebugPanelEvent}; + +use dap::{ + client::SessionId, debugger_settings::DebuggerSettings, Capabilities, ContinuedEvent, + LoadedSourceEvent, ModuleEvent, OutputEvent, OutputEventCategory, StoppedEvent, ThreadEvent, +}; +use gpui::{ + AnyElement, App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity, +}; +use inert::InertState; +use project::debugger::session::Session; +use project::debugger::session::{ThreadId, ThreadStatus}; + +use rpc::proto::{self, PeerId}; +use settings::Settings; +use starting::StartingState; +use ui::{prelude::*, ContextMenu, DropdownMenu, Indicator, Tooltip}; +use workspace::{ + item::{self, Item, ItemEvent}, + FollowableItem, ViewId, Workspace, +}; + +enum DebugSessionState { + Inert(Entity), + Starting(Entity), + Running(Entity), +} + +pub struct DebugSession { + remote_id: Option, + mode: DebugSessionState, +} +#[derive(Debug)] +pub enum DebugPanelItemEvent { + Close, + Stopped { go_to_stack_frame: bool }, +} + +#[derive(Clone, PartialEq, Eq)] +pub enum ThreadItem { + Console, + LoadedSource, + Modules, + Variables, +} + +impl ThreadItem { + fn _to_proto(&self) -> proto::DebuggerThreadItem { + match self { + ThreadItem::Console => proto::DebuggerThreadItem::Console, + ThreadItem::LoadedSource => proto::DebuggerThreadItem::LoadedSource, + ThreadItem::Modules => proto::DebuggerThreadItem::Modules, + ThreadItem::Variables => proto::DebuggerThreadItem::Variables, + } + } + + fn from_proto(active_thread_item: proto::DebuggerThreadItem) -> Self { + match active_thread_item { + proto::DebuggerThreadItem::Console => ThreadItem::Console, + proto::DebuggerThreadItem::LoadedSource => ThreadItem::LoadedSource, + proto::DebuggerThreadItem::Modules => ThreadItem::Modules, + proto::DebuggerThreadItem::Variables => ThreadItem::Variables, + } + } +} + +impl DebugSession { + pub(super) fn inert(cx: &mut App) -> Entity { + cx.new(|cx| Self { + remote_id: None, + mode: DebugSessionState::Inert(cx.new(|cx| InertState::new(cx))), + }) + } + pub(crate) fn session_id(&self, cx: &App) -> Option { + match &self.mode { + DebugSessionState::Inert(_) => None, + DebugSessionState::Starting(_entity) => unimplemented!(), + DebugSessionState::Running(entity) => Some(entity.read(cx).client_id()), + } + } +} +impl EventEmitter for DebugSession {} + +impl Focusable for DebugSession { + fn focus_handle(&self, cx: &App) -> FocusHandle { + match &self.mode { + DebugSessionState::Inert(inert_state) => inert_state.focus_handle(cx), + DebugSessionState::Starting(starting_state) => starting_state.focus_handle(cx), + DebugSessionState::Running(running_state) => running_state.focus_handle(cx), + } + } +} + +impl Item for DebugSession { + type Event = DebugPanelItemEvent; + fn tab_content(&self, _: item::TabContentParams, _: &Window, _: &App) -> AnyElement { + let label = match &self.mode { + DebugSessionState::Inert(_) => "New Session", + DebugSessionState::Starting(_) => "Starting", + DebugSessionState::Running(_) => "Running", + }; + div().child(Label::new(label)).into_any_element() + } +} + +impl FollowableItem for DebugSession { + fn remote_id(&self) -> Option { + self.remote_id + } + + fn to_state_proto(&self, _window: &Window, _cx: &App) -> Option { + None + } + + fn from_state_proto( + _workspace: Entity, + _remote_id: ViewId, + _state: &mut Option, + _window: &mut Window, + _cx: &mut App, + ) -> Option>>> { + None + } + + fn add_event_to_update_proto( + &self, + _event: &Self::Event, + _update: &mut Option, + _window: &Window, + _cx: &App, + ) -> bool { + // update.get_or_insert_with(|| proto::update_view::Variant::DebugPanel(Default::default())); + + true + } + + fn apply_update_proto( + &mut self, + _project: &Entity, + _message: proto::update_view::Variant, + _window: &mut Window, + _cx: &mut Context, + ) -> gpui::Task> { + Task::ready(Ok(())) + } + + fn set_leader_peer_id( + &mut self, + _leader_peer_id: Option, + _window: &mut Window, + _cx: &mut Context, + ) { + } + + fn to_follow_event(_event: &Self::Event) -> Option { + None + } + + fn dedup(&self, existing: &Self, _window: &Window, cx: &App) -> Option { + if existing.session_id(cx) == self.session_id(cx) { + Some(item::Dedup::KeepExisting) + } else { + None + } + } + + fn is_project_item(&self, _window: &Window, _cx: &App) -> bool { + true + } +} + +impl Render for DebugSession { + fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement { + match &self.mode { + DebugSessionState::Inert(inert_state) => { + inert_state.update(cx, |this, cx| this.render(window, cx).into_any_element()) + } + DebugSessionState::Starting(starting_state) => { + starting_state.update(cx, |this, cx| this.render(window, cx).into_any_element()) + } + DebugSessionState::Running(running_state) => { + running_state.update(cx, |this, cx| this.render(window, cx).into_any_element()) + } + } + } +} diff --git a/crates/debugger_ui/src/session/inert.rs b/crates/debugger_ui/src/session/inert.rs new file mode 100644 index 0000000000..12738320b4 --- /dev/null +++ b/crates/debugger_ui/src/session/inert.rs @@ -0,0 +1,51 @@ +use gpui::{App, FocusHandle, Focusable}; +use ui::{ + div, h_flex, v_flex, Button, ButtonCommon, ButtonStyle, Context, ContextMenu, DropdownMenu, + Element, InteractiveElement, ParentElement, Render, Styled, +}; + +pub(super) struct InertState { + focus_handle: FocusHandle, +} + +impl InertState { + pub(super) fn new(cx: &mut Context) -> Self { + Self { + focus_handle: cx.focus_handle(), + } + } +} +impl Focusable for InertState { + fn focus_handle(&self, cx: &App) -> FocusHandle { + self.focus_handle.clone() + } +} + +impl Render for InertState { + fn render( + &mut self, + window: &mut ui::Window, + cx: &mut ui::Context<'_, Self>, + ) -> impl ui::IntoElement { + v_flex() + .track_focus(&self.focus_handle) + .size_full() + .gap_1() + .p_1() + .child(h_flex().child(DropdownMenu::new( + "dap-adapter-picker", + "Select Debug Adapter", + ContextMenu::build(window, cx, |this, _, _| { + this.entry("GDB", None, |_, _| {}) + .entry("Delve", None, |_, _| {}) + .entry("LLDB", None, |_, _| {}) + }), + ))) + .child( + h_flex() + .gap_1() + .child(Button::new("launch-dap", "Launch").style(ButtonStyle::Filled)) + .child(Button::new("attach-dap", "Attach").style(ButtonStyle::Filled)), + ) + } +} diff --git a/crates/debugger_ui/src/debugger_panel_item.rs b/crates/debugger_ui/src/session/running.rs similarity index 58% rename from crates/debugger_ui/src/debugger_panel_item.rs rename to crates/debugger_ui/src/session/running.rs index 9983b2e6c7..e083213652 100644 --- a/crates/debugger_ui/src/debugger_panel_item.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -1,86 +1,326 @@ -use crate::console::Console; -use crate::debugger_panel::{DebugPanel, DebugPanelEvent, ThreadState, ThreadStatus}; -use crate::loaded_source_list::LoadedSourceList; -use crate::module_list::ModuleList; -use crate::stack_frame_list::{StackFrameList, StackFrameListEvent}; -use crate::variable_list::VariableList; +mod console; +mod loaded_source_list; +mod module_list; +mod stack_frame_list; +mod variable_list; -use dap::{ - client::DebugAdapterClientId, debugger_settings::DebuggerSettings, Capabilities, - ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, OutputEventCategory, StoppedEvent, - ThreadEvent, -}; +use console::Console; +use dap::{client::SessionId, debugger_settings::DebuggerSettings, Capabilities, ContinuedEvent}; use gpui::{ - AnyElement, App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity, + AppContext, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity, }; -use project::debugger::dap_session::{DebugSession, ThreadId}; -use rpc::proto::{self, DebuggerThreadStatus, PeerId, SetDebuggerPanelItem}; +use loaded_source_list::LoadedSourceList; +use module_list::ModuleList; +use project::debugger::session::{Session, ThreadId, ThreadStatus}; +use rpc::proto::{self, ViewId}; use settings::Settings; -use ui::{prelude::*, ContextMenu, DropdownMenu, Indicator, PopoverMenu, Tooltip}; +use stack_frame_list::{StackFrameList, StackFrameListEvent}; +use ui::{ + div, h_flex, v_flex, ActiveTheme, AnyElement, App, Button, ButtonCommon, Clickable, Color, + Context, ContextMenu, Disableable, DropdownMenu, Element, FluentBuilder, IconButton, IconName, + IconSize, Indicator, InteractiveElement, IntoElement, Label, LabelCommon, ParentElement, + Render, SharedString, StatefulInteractiveElement, Styled, Tooltip, Window, +}; +use variable_list::VariableList; use workspace::{ - item::{self, Item, ItemEvent}, - FollowableItem, ViewId, Workspace, + item::{self, ItemEvent}, + FollowableItem, Item, Workspace, }; -#[derive(Debug)] -pub enum DebugPanelItemEvent { - Close, - Stopped { go_to_stack_frame: bool }, -} +use crate::debugger_panel::{DebugPanel, DebugPanelEvent}; -#[derive(Clone, PartialEq, Eq)] -pub enum ThreadItem { - Console, - LoadedSource, - Modules, - Variables, -} +use super::{DebugPanelItemEvent, ThreadItem}; -impl ThreadItem { - fn _to_proto(&self) -> proto::DebuggerThreadItem { - match self { - ThreadItem::Console => proto::DebuggerThreadItem::Console, - ThreadItem::LoadedSource => proto::DebuggerThreadItem::LoadedSource, - ThreadItem::Modules => proto::DebuggerThreadItem::Modules, - ThreadItem::Variables => proto::DebuggerThreadItem::Variables, - } - } - - fn from_proto(active_thread_item: proto::DebuggerThreadItem) -> Self { - match active_thread_item { - proto::DebuggerThreadItem::Console => ThreadItem::Console, - proto::DebuggerThreadItem::LoadedSource => ThreadItem::LoadedSource, - proto::DebuggerThreadItem::Modules => ThreadItem::Modules, - proto::DebuggerThreadItem::Variables => ThreadItem::Variables, - } - } -} - -pub struct DebugPanelItem { +pub struct RunningState { + session: Entity, thread_id: ThreadId, - console: Entity, + console: Entity, focus_handle: FocusHandle, remote_id: Option, - session: Entity, show_console_indicator: bool, - module_list: Entity, + module_list: Entity, active_thread_item: ThreadItem, _workspace: WeakEntity, - client_id: DebugAdapterClientId, - thread_state: Entity, - variable_list: Entity, + client_id: SessionId, + variable_list: Entity, _subscriptions: Vec, - stack_frame_list: Entity, - loaded_source_list: Entity, + stack_frame_list: Entity, + loaded_source_list: Entity, } -impl DebugPanelItem { +impl Render for RunningState { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + let thread_status = ThreadStatus::Running; + let active_thread_item = &self.active_thread_item; + + let capabilities = self.capabilities(cx); + + h_flex() + .key_context("DebugPanelItem") + .track_focus(&self.focus_handle(cx)) + .size_full() + .items_start() + .child( + v_flex() + .size_full() + .items_start() + .child( + h_flex() + .w_full() + .border_b_1() + .border_color(cx.theme().colors().border_variant) + .justify_between() + .child( + h_flex() + .p_1() + .w_full() + .gap_2() + .map(|this| { + if thread_status == ThreadStatus::Running { + this.child( + IconButton::new( + "debug-pause", + IconName::DebugPause, + ) + .icon_size(IconSize::Small) + .on_click(cx.listener(|this, _, _window, cx| { + this.pause_thread(cx); + })) + .tooltip(move |window, cx| { + Tooltip::text("Pause program")(window, cx) + }), + ) + } else { + this.child( + IconButton::new( + "debug-continue", + IconName::DebugContinue, + ) + .icon_size(IconSize::Small) + .on_click(cx.listener(|this, _, _window, cx| { + this.continue_thread(cx) + })) + .disabled(thread_status != ThreadStatus::Stopped) + .tooltip(move |window, cx| { + Tooltip::text("Continue program")(window, cx) + }), + ) + } + }) + .when( + capabilities.supports_step_back.unwrap_or(false), + |this| { + this.child( + IconButton::new( + "debug-step-back", + IconName::DebugStepBack, + ) + .icon_size(IconSize::Small) + .on_click(cx.listener(|this, _, _window, cx| { + this.step_back(cx); + })) + .disabled(thread_status != ThreadStatus::Stopped) + .tooltip(move |window, cx| { + Tooltip::text("Step back")(window, cx) + }), + ) + }, + ) + .child( + IconButton::new("debug-step-over", IconName::DebugStepOver) + .icon_size(IconSize::Small) + .on_click(cx.listener(|this, _, _window, cx| { + this.step_over(cx); + })) + .disabled(thread_status != ThreadStatus::Stopped) + .tooltip(move |window, cx| { + Tooltip::text("Step over")(window, cx) + }), + ) + .child( + IconButton::new("debug-step-in", IconName::DebugStepInto) + .icon_size(IconSize::Small) + .on_click(cx.listener(|this, _, _window, cx| { + this.step_in(cx); + })) + .disabled(thread_status != ThreadStatus::Stopped) + .tooltip(move |window, cx| { + Tooltip::text("Step in")(window, cx) + }), + ) + .child( + IconButton::new("debug-step-out", IconName::DebugStepOut) + .icon_size(IconSize::Small) + .on_click(cx.listener(|this, _, _window, cx| { + this.step_out(cx); + })) + .disabled(thread_status != ThreadStatus::Stopped) + .tooltip(move |window, cx| { + Tooltip::text("Step out")(window, cx) + }), + ) + .child( + IconButton::new("debug-restart", IconName::DebugRestart) + .icon_size(IconSize::Small) + .on_click(cx.listener(|this, _, _window, cx| { + this.restart_client(cx); + })) + .disabled( + !capabilities + .supports_restart_request + .unwrap_or_default(), + ) + .tooltip(move |window, cx| { + Tooltip::text("Restart")(window, cx) + }), + ) + .child( + IconButton::new("debug-stop", IconName::DebugStop) + .icon_size(IconSize::Small) + .on_click(cx.listener(|this, _, _window, cx| { + this.stop_thread(cx); + })) + .disabled( + thread_status != ThreadStatus::Stopped + && thread_status != ThreadStatus::Running, + ) + .tooltip(move |window, cx| { + Tooltip::text("Stop")(window, cx) + }), + ) + .child( + IconButton::new( + "debug-disconnect", + IconName::DebugDisconnect, + ) + .icon_size(IconSize::Small) + .on_click(cx.listener(|this, _, _window, cx| { + this.disconnect_client(cx); + })) + .disabled( + thread_status == ThreadStatus::Exited + || thread_status == ThreadStatus::Ended, + ) + .tooltip( + move |window, cx| { + Tooltip::text("Disconnect")(window, cx) + }, + ), + ) + .child( + IconButton::new( + "debug-ignore-breakpoints", + if self.session.read(cx).breakpoints_enabled() { + IconName::DebugBreakpoint + } else { + IconName::DebugIgnoreBreakpoints + }, + ) + .icon_size(IconSize::Small) + .on_click(cx.listener(|this, _, _window, cx| { + this.toggle_ignore_breakpoints(cx); + })) + .disabled( + thread_status == ThreadStatus::Exited + || thread_status == ThreadStatus::Ended, + ) + .tooltip( + move |window, cx| { + Tooltip::text("Ignore breakpoints")(window, cx) + }, + ), + ), + ) + //.child(h_flex()) + .child(h_flex().p_1().mx_2().w_3_4().justify_end().child( + DropdownMenu::new( + "thread-list", + "Threads", + ContextMenu::build(window, cx, |this, _, _| { + this.entry("Thread 1", None, |_, _| {}).entry( + "Thread 2", + None, + |_, _| {}, + ) + }), + ), + )), + ) + .child( + h_flex() + .size_full() + .items_start() + .p_1() + .gap_4() + .child(self.stack_frame_list.clone()), + ), + ) + .child( + v_flex() + .border_l_1() + .border_color(cx.theme().colors().border_variant) + .size_full() + .items_start() + .child( + h_flex() + .border_b_1() + .w_full() + .border_color(cx.theme().colors().border_variant) + .child(self.render_entry_button( + &SharedString::from("Variables"), + ThreadItem::Variables, + cx, + )) + .when( + capabilities.supports_modules_request.unwrap_or_default(), + |this| { + this.child(self.render_entry_button( + &SharedString::from("Modules"), + ThreadItem::Modules, + cx, + )) + }, + ) + .when( + capabilities + .supports_loaded_sources_request + .unwrap_or_default(), + |this| { + this.child(self.render_entry_button( + &SharedString::from("Loaded Sources"), + ThreadItem::LoadedSource, + cx, + )) + }, + ) + .child(self.render_entry_button( + &SharedString::from("Console"), + ThreadItem::Console, + cx, + )), + ) + .when(*active_thread_item == ThreadItem::Variables, |this| { + this.size_full().child(self.variable_list.clone()) + }) + .when(*active_thread_item == ThreadItem::Modules, |this| { + this.size_full().child(self.module_list.clone()) + }) + .when(*active_thread_item == ThreadItem::LoadedSource, |this| { + this.size_full().child(self.loaded_source_list.clone()) + }) + .when(*active_thread_item == ThreadItem::Console, |this| { + this.child(self.console.clone()) + }), + ) + } +} + +impl RunningState { #[allow(clippy::too_many_arguments)] pub fn new( - session: Entity, - client_id: DebugAdapterClientId, + session: Entity, + client_id: SessionId, thread_id: ThreadId, - thread_state: Entity, debug_panel: &Entity, workspace: WeakEntity, window: &mut Window, @@ -89,14 +329,7 @@ impl DebugPanelItem { let focus_handle = cx.focus_handle(); let stack_frame_list = cx.new(|cx| { - StackFrameList::new( - workspace.clone(), - session.clone(), - client_id, - thread_id, - window, - cx, - ) + StackFrameList::new(workspace.clone(), session.clone(), thread_id, window, cx) }); let variable_list = cx.new(|cx| { @@ -126,51 +359,13 @@ impl DebugPanelItem { cx.observe(&module_list, |_, _, cx| cx.notify()).detach(); - let _subscriptions = vec![ - cx.subscribe_in(debug_panel, window, { - move |this: &mut Self, _, event: &DebugPanelEvent, window, cx| { - match event { - DebugPanelEvent::Stopped { - client_id, - event, - go_to_stack_frame, - } => this.handle_stopped_event(client_id, event, *go_to_stack_frame, cx), - DebugPanelEvent::Thread((client_id, event)) => { - this.handle_thread_event(client_id, event, cx) - } - DebugPanelEvent::Output((client_id, event)) => { - this.handle_output_event(client_id, event, window, cx) - } - DebugPanelEvent::Module((client_id, event)) => { - this.handle_module_event(client_id, event, cx) - } - DebugPanelEvent::LoadedSource((client_id, event)) => { - this.handle_loaded_source_event(client_id, event, cx) - } - DebugPanelEvent::ClientShutdown(client_id) => { - this.handle_client_shutdown_event(client_id, cx) - } - DebugPanelEvent::Continued((client_id, event)) => { - this.handle_thread_continued_event(client_id, event, cx); - } - DebugPanelEvent::Exited(client_id) - | DebugPanelEvent::Terminated(client_id) => { - this.handle_client_exited_and_terminated_event(client_id, cx); - } - DebugPanelEvent::CapabilitiesChanged(client_id) => { - this.handle_capabilities_changed_event(client_id, cx); - } - }; - } - }), - cx.subscribe( - &stack_frame_list, - move |this: &mut Self, _, event: &StackFrameListEvent, cx| match event { - StackFrameListEvent::SelectedStackFrameChanged(_) - | StackFrameListEvent::StackFramesUpdated => this.clear_highlights(cx), - }, - ), - ]; + let _subscriptions = vec![cx.subscribe( + &stack_frame_list, + move |this: &mut Self, _, event: &StackFrameListEvent, cx| match event { + StackFrameListEvent::SelectedStackFrameChanged(_) + | StackFrameListEvent::StackFramesUpdated => this.clear_highlights(cx), + }, + )]; Self { session, @@ -178,7 +373,7 @@ impl DebugPanelItem { thread_id, _workspace: workspace, module_list, - thread_state, + focus_handle, variable_list, _subscriptions, @@ -191,201 +386,6 @@ impl DebugPanelItem { } } - pub(crate) fn from_proto(&mut self, state: &SetDebuggerPanelItem, cx: &mut Context) { - self.thread_state.update(cx, |thread_state, _| { - let (status, stopped) = state - .thread_state - .as_ref() - .map_or((DebuggerThreadStatus::Stopped, true), |thread_state| { - (thread_state.thread_status(), true) - }); - - thread_state.status = ThreadStatus::from_proto(status); - thread_state.stopped = stopped; - }); - - self.active_thread_item = ThreadItem::from_proto(state.active_thread_item()); - - if let Some(variable_list_state) = state.variable_list.as_ref() { - self.variable_list - .update(cx, |this, cx| this.set_from_proto(variable_list_state, cx)); - } - - cx.notify(); - } - - pub fn update_thread_state_status(&mut self, status: ThreadStatus, cx: &mut Context) { - self.thread_state.update(cx, |thread_state, cx| { - thread_state.status = status; - - cx.notify(); - }); - - if status == ThreadStatus::Exited - || status == ThreadStatus::Ended - || status == ThreadStatus::Stopped - { - self.clear_highlights(cx); - } - } - - fn should_skip_event(&self, client_id: &DebugAdapterClientId, thread_id: ThreadId) -> bool { - thread_id != self.thread_id || *client_id != self.client_id - } - - fn handle_thread_continued_event( - &mut self, - client_id: &DebugAdapterClientId, - event: &ContinuedEvent, - cx: &mut Context, - ) { - if self.should_skip_event(client_id, ThreadId(event.thread_id)) { - return; - } - - self.update_thread_state_status(ThreadStatus::Running, cx); - } - - fn handle_stopped_event( - &mut self, - client_id: &DebugAdapterClientId, - event: &StoppedEvent, - go_to_stack_frame: bool, - cx: &mut Context, - ) { - if self.should_skip_event( - client_id, - event.thread_id.map(ThreadId).unwrap_or(self.thread_id), - ) { - return; - } - - if let Some(client_state) = self.session.read(cx).client_state(*client_id) { - client_state.update(cx, |state, cx| { - state.invalidate(cx); - }); - } - - cx.emit(DebugPanelItemEvent::Stopped { go_to_stack_frame }); - } - - fn handle_thread_event( - &mut self, - client_id: &DebugAdapterClientId, - event: &ThreadEvent, - cx: &mut Context, - ) { - if self.should_skip_event(client_id, ThreadId(event.thread_id)) { - return; - } - - self.update_thread_state_status(ThreadStatus::Running, cx); - } - - fn handle_output_event( - &mut self, - client_id: &DebugAdapterClientId, - event: &OutputEvent, - window: &mut Window, - cx: &mut Context, - ) { - if self.should_skip_event(client_id, self.thread_id) { - return; - } - - // skip telemetry output as it pollutes the users output view - let output_category = event - .category - .as_ref() - .unwrap_or(&OutputEventCategory::Console); - - // skip telemetry output as it pollutes the users output view - if output_category == &OutputEventCategory::Telemetry { - return; - } - - self.console.update(cx, |console, cx| { - console.add_message(event.clone(), window, cx); - }); - self.show_console_indicator = true; - cx.notify(); - } - - fn handle_module_event( - &mut self, - client_id: &DebugAdapterClientId, - event: &ModuleEvent, - cx: &mut Context, - ) { - if self.should_skip_event(client_id, self.thread_id) { - return; - } - - self.module_list.update(cx, |module_list, cx| { - module_list.on_module_event(event, cx); - }); - } - - fn handle_loaded_source_event( - &mut self, - client_id: &DebugAdapterClientId, - event: &LoadedSourceEvent, - cx: &mut Context, - ) { - if self.should_skip_event(client_id, self.thread_id) { - return; - } - - if let Some(state) = self.session.read(cx).client_state(self.client_id) { - state.update(cx, |state, cx| state.handle_loaded_source_event(event, cx)); - } - } - - fn handle_client_shutdown_event( - &mut self, - client_id: &DebugAdapterClientId, - cx: &mut Context, - ) { - if self.should_skip_event(client_id, self.thread_id) { - return; - } - - self.update_thread_state_status(ThreadStatus::Stopped, cx); - - // TODO(debugger): make this work again - // self.dap_store.update(cx, |store, cx| { - // store.remove_active_debug_line_for_client(client_id, cx); - // }); - - cx.emit(DebugPanelItemEvent::Close); - } - - fn handle_client_exited_and_terminated_event( - &mut self, - client_id: &DebugAdapterClientId, - cx: &mut Context, - ) { - if Self::should_skip_event(self, client_id, self.thread_id) { - return; - } - - self.update_thread_state_status(ThreadStatus::Exited, cx); - - cx.emit(DebugPanelItemEvent::Close); - } - - fn handle_capabilities_changed_event( - &mut self, - client_id: &DebugAdapterClientId, - cx: &mut Context, - ) { - if Self::should_skip_event(self, client_id, self.thread_id) { - return; - } - - cx.notify(); - } - // pub(crate) fn update_adapter( // &mut self, // update: &UpdateDebugAdapter, @@ -420,11 +420,11 @@ impl DebugPanelItem { // } // } - pub fn session(&self) -> &Entity { + pub fn session(&self) -> &Entity { &self.session } - pub fn client_id(&self) -> DebugAdapterClientId { + pub fn client_id(&self) -> SessionId { self.client_id } @@ -458,21 +458,13 @@ impl DebugPanelItem { &self.variable_list } - #[cfg(any(test, feature = "test-support"))] - pub fn thread_state(&self) -> &Entity { - &self.thread_state - } - #[cfg(any(test, feature = "test-support"))] pub fn are_breakpoints_ignored(&self, cx: &App) -> bool { self.session.read(cx).ignore_breakpoints() } - pub fn capabilities(&self, cx: &mut Context) -> Option { - self.session() - .read(cx) - .client_state(self.client_id) - .map(|state| state.read(cx).capabilities().clone()) + pub fn capabilities(&self, cx: &mut Context) -> Capabilities { + self.session().read(cx).capabilities().clone() } fn clear_highlights(&self, _cx: &mut Context) { @@ -547,128 +539,83 @@ impl DebugPanelItem { } pub fn continue_thread(&mut self, cx: &mut Context) { - self.session() - .read(cx) - .client_state(self.client_id) - .map(|entity| { - entity.update(cx, |state, cx| { - state.continue_thread(self.thread_id, cx); - }); - }); + self.session().update(cx, |state, cx| { + state.continue_thread(self.thread_id, cx); + }); } pub fn step_over(&mut self, cx: &mut Context) { let granularity = DebuggerSettings::get_global(cx).stepping_granularity; - self.session() - .read(cx) - .client_state(self.client_id) - .map(|entity| { - entity.update(cx, |state, cx| { - state.step_over(self.thread_id, granularity, cx); - }); - }); + self.session().update(cx, |state, cx| { + state.step_over(self.thread_id, granularity, cx); + }); } pub fn step_in(&mut self, cx: &mut Context) { let granularity = DebuggerSettings::get_global(cx).stepping_granularity; - self.session() - .read(cx) - .client_state(self.client_id) - .map(|entity| { - entity.update(cx, |state, cx| { - state.step_in(self.thread_id, granularity, cx); - }); - }); + self.session().update(cx, |state, cx| { + state.step_in(self.thread_id, granularity, cx); + }); } pub fn step_out(&mut self, cx: &mut Context) { let granularity = DebuggerSettings::get_global(cx).stepping_granularity; - self.session() - .read(cx) - .client_state(self.client_id) - .map(|entity| { - entity.update(cx, |state, cx| { - state.step_out(self.thread_id, granularity, cx); - }); - }); + self.session().update(cx, |state, cx| { + state.step_out(self.thread_id, granularity, cx); + }); } pub fn step_back(&mut self, cx: &mut Context) { let granularity = DebuggerSettings::get_global(cx).stepping_granularity; - self.session() - .read(cx) - .client_state(self.client_id) - .map(|entity| { - entity.update(cx, |state, cx| { - state.step_back(self.thread_id, granularity, cx); - }); - }); + self.session().update(cx, |state, cx| { + state.step_back(self.thread_id, granularity, cx); + }); } pub fn restart_client(&self, cx: &mut Context) { - self.session() - .read(cx) - .client_state(self.client_id) - .map(|entity| { - entity.update(cx, |state, cx| { - state.restart(None, cx); - }); - }); + self.session().update(cx, |state, cx| { + state.restart(None, cx); + }); } pub fn pause_thread(&self, cx: &mut Context) { - self.session() - .read(cx) - .client_state(self.client_id) - .map(|entity| { - entity.update(cx, |state, cx| { - state.pause_thread(self.thread_id, cx); - }); - }); + self.session().update(cx, |state, cx| { + state.pause_thread(self.thread_id, cx); + }); } pub fn stop_thread(&self, cx: &mut Context) { - self.session() - .read(cx) - .client_state(self.client_id) - .map(|entity| { - entity.update(cx, |state, cx| { - state.terminate_threads(Some(vec![self.thread_id; 1]), cx); - }); - }); + self.session().update(cx, |state, cx| { + state.terminate_threads(Some(vec![self.thread_id; 1]), cx); + }); } pub fn disconnect_client(&self, cx: &mut Context) { - self.session() - .read(cx) - .client_state(self.client_id) - .map(|entity| { - entity.update(cx, |state, cx| { - state.disconnect_client(cx); - }); - }); + self.session().update(cx, |state, cx| { + state.disconnect_client(cx); + }); } pub fn toggle_ignore_breakpoints(&mut self, cx: &mut Context) { self.session.update(cx, |session, cx| { - session.set_ignore_breakpoints(!session.ignore_breakpoints(), cx); + session.set_ignore_breakpoints(!session.breakpoints_enabled()); }); } } -impl EventEmitter for DebugPanelItem {} +impl EventEmitter for RunningState {} -impl Focusable for DebugPanelItem { +impl Focusable for RunningState { fn focus_handle(&self, _: &App) -> FocusHandle { self.focus_handle.clone() } } -impl Item for DebugPanelItem { +impl Item for RunningState { type Event = DebugPanelItemEvent; fn tab_content( @@ -677,25 +624,21 @@ impl Item for DebugPanelItem { _window: &Window, cx: &App, ) -> AnyElement { - Label::new(format!( - "{} - Thread {}", - self.session.read(cx).name(), - self.thread_id.0 - )) - .color(if params.selected { - Color::Default - } else { - Color::Muted - }) - .into_any_element() + Label::new(format!("{} - Thread {}", todo!(), self.thread_id.0)) + .color(if params.selected { + Color::Default + } else { + Color::Muted + }) + .into_any_element() } fn tab_tooltip_text(&self, cx: &App) -> Option { Some(SharedString::from(format!( "{} Thread {} - {:?}", - self.session.read(cx).name(), + todo!(), self.thread_id.0, - self.thread_state.read(cx).status, + todo!("thread state"), ))) } @@ -706,354 +649,3 @@ impl Item for DebugPanelItem { } } } - -impl FollowableItem for DebugPanelItem { - fn remote_id(&self) -> Option { - self.remote_id - } - - fn to_state_proto(&self, _window: &Window, _cx: &App) -> Option { - None - } - - fn from_state_proto( - _workspace: Entity, - _remote_id: ViewId, - _state: &mut Option, - _window: &mut Window, - _cx: &mut App, - ) -> Option>>> { - None - } - - fn add_event_to_update_proto( - &self, - _event: &Self::Event, - _update: &mut Option, - _window: &Window, - _cx: &App, - ) -> bool { - // update.get_or_insert_with(|| proto::update_view::Variant::DebugPanel(Default::default())); - - true - } - - fn apply_update_proto( - &mut self, - _project: &Entity, - _message: proto::update_view::Variant, - _window: &mut Window, - _cx: &mut Context, - ) -> gpui::Task> { - Task::ready(Ok(())) - } - - fn set_leader_peer_id( - &mut self, - _leader_peer_id: Option, - _window: &mut Window, - _cx: &mut Context, - ) { - } - - fn to_follow_event(_event: &Self::Event) -> Option { - None - } - - fn dedup( - &self, - existing: &Self, - _window: &Window, - _cx: &App, - ) -> Option { - if existing.client_id == self.client_id && existing.thread_id == self.thread_id { - Some(item::Dedup::KeepExisting) - } else { - None - } - } - - fn is_project_item(&self, _window: &Window, _cx: &App) -> bool { - true - } -} - -impl Render for DebugPanelItem { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - let thread_status = self.thread_state.read(cx).status; - let active_thread_item = &self.active_thread_item; - - let capabilities = self.capabilities(cx); - - h_flex() - .key_context("DebugPanelItem") - .track_focus(&self.focus_handle(cx)) - .size_full() - .items_start() - .child( - v_flex() - .size_full() - .items_start() - .child( - h_flex() - .w_full() - .border_b_1() - .border_color(cx.theme().colors().border_variant) - .justify_between() - .child( - h_flex() - .p_1() - .w_full() - .gap_2() - .map(|this| { - if thread_status == ThreadStatus::Running { - this.child( - IconButton::new( - "debug-pause", - IconName::DebugPause, - ) - .icon_size(IconSize::Small) - .on_click(cx.listener(|this, _, _window, cx| { - this.pause_thread(cx); - })) - .tooltip(move |window, cx| { - Tooltip::text("Pause program")(window, cx) - }), - ) - } else { - this.child( - IconButton::new( - "debug-continue", - IconName::DebugContinue, - ) - .icon_size(IconSize::Small) - .on_click(cx.listener(|this, _, _window, cx| { - this.continue_thread(cx) - })) - .disabled(thread_status != ThreadStatus::Stopped) - .tooltip(move |window, cx| { - Tooltip::text("Continue program")(window, cx) - }), - ) - } - }) - .when( - capabilities - .as_ref() - .map(|caps| caps.supports_step_back) - .flatten() - .unwrap_or(false), - |this| { - this.child( - IconButton::new( - "debug-step-back", - IconName::DebugStepBack, - ) - .icon_size(IconSize::Small) - .on_click(cx.listener(|this, _, _window, cx| { - this.step_back(cx); - })) - .disabled(thread_status != ThreadStatus::Stopped) - .tooltip(move |window, cx| { - Tooltip::text("Step back")(window, cx) - }), - ) - }, - ) - .child( - IconButton::new("debug-step-over", IconName::DebugStepOver) - .icon_size(IconSize::Small) - .on_click(cx.listener(|this, _, _window, cx| { - this.step_over(cx); - })) - .disabled(thread_status != ThreadStatus::Stopped) - .tooltip(move |window, cx| { - Tooltip::text("Step over")(window, cx) - }), - ) - .child( - IconButton::new("debug-step-in", IconName::DebugStepInto) - .icon_size(IconSize::Small) - .on_click(cx.listener(|this, _, _window, cx| { - this.step_in(cx); - })) - .disabled(thread_status != ThreadStatus::Stopped) - .tooltip(move |window, cx| { - Tooltip::text("Step in")(window, cx) - }), - ) - .child( - IconButton::new("debug-step-out", IconName::DebugStepOut) - .icon_size(IconSize::Small) - .on_click(cx.listener(|this, _, _window, cx| { - this.step_out(cx); - })) - .disabled(thread_status != ThreadStatus::Stopped) - .tooltip(move |window, cx| { - Tooltip::text("Step out")(window, cx) - }), - ) - .child( - IconButton::new("debug-restart", IconName::DebugRestart) - .icon_size(IconSize::Small) - .on_click(cx.listener(|this, _, _window, cx| { - this.restart_client(cx); - })) - .disabled( - !capabilities - .as_ref() - .map(|caps| caps.supports_restart_request) - .flatten() - .unwrap_or_default(), - ) - .tooltip(move |window, cx| { - Tooltip::text("Restart")(window, cx) - }), - ) - .child( - IconButton::new("debug-stop", IconName::DebugStop) - .icon_size(IconSize::Small) - .on_click(cx.listener(|this, _, _window, cx| { - this.stop_thread(cx); - })) - .disabled( - thread_status != ThreadStatus::Stopped - && thread_status != ThreadStatus::Running, - ) - .tooltip(move |window, cx| { - Tooltip::text("Stop")(window, cx) - }), - ) - .child( - IconButton::new( - "debug-disconnect", - IconName::DebugDisconnect, - ) - .icon_size(IconSize::Small) - .on_click(cx.listener(|this, _, _window, cx| { - this.disconnect_client(cx); - })) - .disabled( - thread_status == ThreadStatus::Exited - || thread_status == ThreadStatus::Ended, - ) - .tooltip( - move |window, cx| { - Tooltip::text("Disconnect")(window, cx) - }, - ), - ) - .child( - IconButton::new( - "debug-ignore-breakpoints", - if self.session.read(cx).ignore_breakpoints() { - IconName::DebugIgnoreBreakpoints - } else { - IconName::DebugBreakpoint - }, - ) - .icon_size(IconSize::Small) - .on_click(cx.listener(|this, _, _window, cx| { - this.toggle_ignore_breakpoints(cx); - })) - .disabled( - thread_status == ThreadStatus::Exited - || thread_status == ThreadStatus::Ended, - ) - .tooltip( - move |window, cx| { - Tooltip::text("Ignore breakpoints")(window, cx) - }, - ), - ), - ) - //.child(h_flex()) - .child(h_flex().p_1().mx_2().w_3_4().justify_end().child( - DropdownMenu::new( - "thread-list", - "Threads", - ContextMenu::build(window, cx, |this, _, _| { - this.entry("Thread 1", None, |_, _| {}).entry( - "Thread 2", - None, - |_, _| {}, - ) - }), - ), - )), - ) - .child( - h_flex() - .size_full() - .items_start() - .p_1() - .gap_4() - .child(self.stack_frame_list.clone()), - ), - ) - .child( - v_flex() - .border_l_1() - .border_color(cx.theme().colors().border_variant) - .size_full() - .items_start() - .child( - h_flex() - .border_b_1() - .w_full() - .border_color(cx.theme().colors().border_variant) - .child(self.render_entry_button( - &SharedString::from("Variables"), - ThreadItem::Variables, - cx, - )) - .when( - capabilities - .as_ref() - .map(|caps| caps.supports_modules_request) - .flatten() - .unwrap_or_default(), - |this| { - this.child(self.render_entry_button( - &SharedString::from("Modules"), - ThreadItem::Modules, - cx, - )) - }, - ) - .when( - capabilities - .as_ref() - .map(|caps| caps.supports_loaded_sources_request) - .flatten() - .unwrap_or_default(), - |this| { - this.child(self.render_entry_button( - &SharedString::from("Loaded Sources"), - ThreadItem::LoadedSource, - cx, - )) - }, - ) - .child(self.render_entry_button( - &SharedString::from("Console"), - ThreadItem::Console, - cx, - )), - ) - .when(*active_thread_item == ThreadItem::Variables, |this| { - this.size_full().child(self.variable_list.clone()) - }) - .when(*active_thread_item == ThreadItem::Modules, |this| { - this.size_full().child(self.module_list.clone()) - }) - .when(*active_thread_item == ThreadItem::LoadedSource, |this| { - this.size_full().child(self.loaded_source_list.clone()) - }) - .when(*active_thread_item == ThreadItem::Console, |this| { - this.child(self.console.clone()) - }), - ) - .into_any() - } -} diff --git a/crates/debugger_ui/src/console.rs b/crates/debugger_ui/src/session/running/console.rs similarity index 93% rename from crates/debugger_ui/src/console.rs rename to crates/debugger_ui/src/session/running/console.rs index c0d2275fd7..e06745bbae 100644 --- a/crates/debugger_ui/src/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -1,9 +1,9 @@ -use crate::{ +use super::{ stack_frame_list::{StackFrameList, StackFrameListEvent}, variable_list::VariableList, }; use anyhow::anyhow; -use dap::{client::DebugAdapterClientId, OutputEvent, OutputEventGroup}; +use dap::{client::SessionId, OutputEvent, OutputEventGroup}; use editor::{ display_map::{Crease, CreaseId}, Anchor, CompletionProvider, Editor, EditorElement, EditorStyle, FoldPlaceholder, @@ -13,7 +13,7 @@ use gpui::{Context, Entity, Render, Subscription, Task, TextStyle, WeakEntity}; use language::{Buffer, CodeLabel, LanguageServerId}; use menu::Confirm; use project::{ - debugger::dap_session::{CompletionsQuery, DebugSession}, + debugger::session::{CompletionsQuery, Session}, Completion, }; use settings::Settings; @@ -33,8 +33,8 @@ pub struct Console { groups: Vec, console: Entity, query_bar: Entity, - session: Entity, - client_id: DebugAdapterClientId, + session: Entity, + client_id: SessionId, _subscriptions: Vec, variable_list: Entity, stack_frame_list: Entity, @@ -42,8 +42,8 @@ pub struct Console { impl Console { pub fn new( - session: Entity, - client_id: DebugAdapterClientId, + session: Entity, + client_id: SessionId, stack_frame_list: Entity, variable_list: Entity, window: &mut Window, @@ -234,11 +234,7 @@ impl Console { expression }); - let Some(client_state) = self.session.read(cx).client_state(self.client_id) else { - return; - }; - - client_state.update(cx, |state, cx| { + self.session.update(cx, |state, cx| { state.evaluate( expression, Some(dap::EvaluateArgumentsContext::Variables), @@ -369,10 +365,8 @@ impl CompletionProvider for ConsoleQueryBarCompletionProvider { .read(cx) .session .read(cx) - .client_state(console.read(cx).client_id) - .map(|state| state.read(cx).capabilities()) - .map(|caps| caps.supports_completions_request) - .flatten() + .capabilities() + .supports_completions_request .unwrap_or_default(); if support_completions { @@ -498,21 +492,15 @@ impl ConsoleQueryBarCompletionProvider { buffer_position: language::Anchor, cx: &mut Context, ) -> gpui::Task>> { - let client_id = console.read(cx).client_id; - let completion_task = console.update(cx, |console, cx| { - if let Some(client_state) = console.session.read(cx).client_state(client_id) { - client_state.update(cx, |state, cx| { - let frame_id = Some(console.stack_frame_list.read(cx).current_stack_frame_id()); + console.session.update(cx, |state, cx| { + let frame_id = Some(console.stack_frame_list.read(cx).current_stack_frame_id()); - state.completions( - CompletionsQuery::new(buffer.read(cx), buffer_position, frame_id), - cx, - ) - }) - } else { - Task::ready(Err(anyhow!("failed to fetch completions"))) - } + state.completions( + CompletionsQuery::new(buffer.read(cx), buffer_position, frame_id), + cx, + ) + }) }); cx.background_executor().spawn(async move { diff --git a/crates/debugger_ui/src/loaded_source_list.rs b/crates/debugger_ui/src/session/running/loaded_source_list.rs similarity index 79% rename from crates/debugger_ui/src/loaded_source_list.rs rename to crates/debugger_ui/src/session/running/loaded_source_list.rs index 84296c728d..589e1f2653 100644 --- a/crates/debugger_ui/src/loaded_source_list.rs +++ b/crates/debugger_ui/src/session/running/loaded_source_list.rs @@ -1,6 +1,6 @@ -use dap::client::DebugAdapterClientId; +use dap::client::SessionId; use gpui::{list, AnyElement, Empty, Entity, FocusHandle, Focusable, ListState, Subscription}; -use project::debugger::dap_session::DebugSession; +use project::debugger::session::Session; use ui::prelude::*; use util::maybe; @@ -8,14 +8,14 @@ pub struct LoadedSourceList { list: ListState, focus_handle: FocusHandle, _subscription: Subscription, - session: Entity, - client_id: DebugAdapterClientId, + session: Entity, + client_id: SessionId, } impl LoadedSourceList { pub fn new( - session: Entity, - client_id: DebugAdapterClientId, + session: Entity, + client_id: SessionId, cx: &mut Context, ) -> Self { let weak_entity = cx.weak_entity(); @@ -35,8 +35,7 @@ impl LoadedSourceList { }, ); - let client_state = session.read(cx).client_state(client_id).unwrap(); - let _subscription = cx.observe(&client_state, |loaded_source_list, state, cx| { + let _subscription = cx.observe(&session, |loaded_source_list, state, cx| { let len = state.update(cx, |state, cx| state.loaded_sources(cx).len()); loaded_source_list.list.reset(len); @@ -55,8 +54,6 @@ impl LoadedSourceList { fn render_entry(&mut self, ix: usize, cx: &mut Context) -> AnyElement { let Some(source) = maybe!({ self.session - .read(cx) - .client_state(self.client_id)? .update(cx, |state, cx| state.loaded_sources(cx).get(ix).cloned()) }) else { return Empty.into_any(); @@ -92,11 +89,9 @@ impl Focusable for LoadedSourceList { impl Render for LoadedSourceList { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - if let Some(state) = self.session.read(cx).client_state(self.client_id) { - state.update(cx, |state, cx| { - state.loaded_sources(cx); - }); - } + self.session.update(cx, |state, cx| { + state.loaded_sources(cx); + }); div() .track_focus(&self.focus_handle) diff --git a/crates/debugger_ui/src/module_list.rs b/crates/debugger_ui/src/session/running/module_list.rs similarity index 77% rename from crates/debugger_ui/src/module_list.rs rename to crates/debugger_ui/src/session/running/module_list.rs index 00484d2e89..198fb0454f 100644 --- a/crates/debugger_ui/src/module_list.rs +++ b/crates/debugger_ui/src/session/running/module_list.rs @@ -1,20 +1,20 @@ -use dap::{client::DebugAdapterClientId, ModuleEvent}; +use dap::{client::SessionId, ModuleEvent}; use gpui::{list, AnyElement, Empty, Entity, FocusHandle, Focusable, ListState, Subscription}; -use project::debugger::dap_session::DebugSession; +use project::debugger::session::Session; use ui::prelude::*; pub struct ModuleList { list: ListState, focus_handle: FocusHandle, _subscription: Subscription, - session: Entity, - client_id: DebugAdapterClientId, + session: Entity, + client_id: SessionId, } impl ModuleList { pub fn new( - session: Entity, - client_id: DebugAdapterClientId, + session: Entity, + client_id: SessionId, cx: &mut Context, ) -> Self { let weak_entity = cx.weak_entity(); @@ -32,9 +32,7 @@ impl ModuleList { }, ); - let client_state = session.read(cx).client_state(client_id).unwrap(); - - let _subscription = cx.observe(&client_state, |module_list, state, cx| { + let _subscription = cx.observe(&session, |module_list, state, cx| { let modules_len = state.update(cx, |state, cx| state.modules(cx).len()); module_list.list.reset(modules_len); @@ -51,16 +49,13 @@ impl ModuleList { } pub fn on_module_event(&mut self, event: &ModuleEvent, cx: &mut Context) { - if let Some(state) = self.session.read(cx).client_state(self.client_id) { - state.update(cx, |state, cx| state.handle_module_event(event, cx)); - } + self.session + .update(cx, |state, cx| state.handle_module_event(event, cx)); } fn render_entry(&mut self, ix: usize, cx: &mut Context) -> AnyElement { let Some(module) = maybe!({ self.session - .read(cx) - .client_state(self.client_id)? .update(cx, |state, cx| state.modules(cx).get(ix).cloned()) }) else { return Empty.into_any(); @@ -91,11 +86,9 @@ impl Focusable for ModuleList { impl Render for ModuleList { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - if let Some(state) = self.session.read(cx).client_state(self.client_id) { - state.update(cx, |state, cx| { - state.modules(cx); - }); - } + self.session.update(cx, |state, cx| { + state.modules(cx); + }); div() .track_focus(&self.focus_handle) diff --git a/crates/debugger_ui/src/stack_frame_list.rs b/crates/debugger_ui/src/session/running/stack_frame_list.rs similarity index 93% rename from crates/debugger_ui/src/stack_frame_list.rs rename to crates/debugger_ui/src/session/running/stack_frame_list.rs index d9af8caa4b..790009bc7d 100644 --- a/crates/debugger_ui/src/stack_frame_list.rs +++ b/crates/debugger_ui/src/session/running/stack_frame_list.rs @@ -1,12 +1,13 @@ use std::path::Path; use anyhow::{anyhow, Result}; -use dap::client::DebugAdapterClientId; +use dap::client::SessionId; use gpui::{ list, AnyElement, Entity, EventEmitter, FocusHandle, Focusable, ListState, Subscription, Task, WeakEntity, }; -use project::debugger::dap_session::{DebugSession, StackFrame, ThreadId}; + +use project::debugger::session::{Session, StackFrame, ThreadId}; use project::ProjectPath; use ui::{prelude::*, Tooltip}; use workspace::Workspace; @@ -24,10 +25,9 @@ pub struct StackFrameList { thread_id: ThreadId, focus_handle: FocusHandle, _subscription: Subscription, - session: Entity, + session: Entity, entries: Vec, workspace: WeakEntity, - client_id: DebugAdapterClientId, current_stack_frame_id: StackFrameId, _fetch_stack_frames_task: Option>>, } @@ -41,8 +41,7 @@ pub enum StackFrameEntry { impl StackFrameList { pub fn new( workspace: WeakEntity, - session: Entity, - client_id: DebugAdapterClientId, + session: Entity, thread_id: ThreadId, _window: &Window, cx: &mut Context, @@ -64,9 +63,7 @@ impl StackFrameList { }, ); - let client_state = session.read(cx).client_state(client_id).unwrap(); - - let _subscription = cx.observe(&client_state, |stack_frame_list, state, cx| { + let _subscription = cx.observe(&session, |stack_frame_list, state, cx| { let _frame_len = state.update(cx, |state, cx| { state.stack_frames(stack_frame_list.thread_id, cx).len() }); @@ -79,7 +76,7 @@ impl StackFrameList { session, workspace, thread_id, - client_id, + focus_handle, _subscription, entries: Default::default(), @@ -99,10 +96,7 @@ impl StackFrameList { pub fn stack_frames(&self, cx: &mut App) -> Vec { self.session - .read(cx) - .client_state(self.client_id) - .map(|state| state.update(cx, |client, cx| client.stack_frames(self.thread_id, cx))) - .unwrap_or_default() + .update(cx, |this, cx| this.stack_frames(self.thread_id, cx)) } #[cfg(any(test, feature = "test-support"))] @@ -267,11 +261,9 @@ impl StackFrameList { } pub fn restart_stack_frame(&mut self, stack_frame_id: u64, cx: &mut Context) { - if let Some(client_state) = self.session.read(cx).client_state(self.client_id) { - client_state.update(cx, |state, cx| { - state.restart_stack_frame(stack_frame_id, cx) - }); - } + self.session.update(cx, |state, cx| { + state.restart_stack_frame(stack_frame_id, cx) + }); } fn render_normal_entry( @@ -291,8 +283,8 @@ impl StackFrameList { let supports_frame_restart = self .session .read(cx) - .client_state(self.client_id) - .and_then(|client| client.read(cx).capabilities().supports_restart_frame) + .capabilities() + .supports_restart_frame .unwrap_or_default(); let origin = stack_frame diff --git a/crates/debugger_ui/src/variable_list.rs b/crates/debugger_ui/src/session/running/variable_list.rs similarity index 97% rename from crates/debugger_ui/src/variable_list.rs rename to crates/debugger_ui/src/session/running/variable_list.rs index 38677c1176..56c1174abc 100644 --- a/crates/debugger_ui/src/variable_list.rs +++ b/crates/debugger_ui/src/session/running/variable_list.rs @@ -1,8 +1,7 @@ -use crate::stack_frame_list::{StackFrameId, StackFrameList, StackFrameListEvent}; +use super::stack_frame_list::{StackFrameId, StackFrameList, StackFrameListEvent}; use anyhow::{anyhow, Result}; use dap::{ - client::DebugAdapterClientId, proto_conversions::ProtoConversion, Scope, ScopePresentationHint, - Variable, + client::SessionId, proto_conversions::ProtoConversion, Scope, ScopePresentationHint, Variable, }; use editor::{actions::SelectAll, Editor, EditorEvent}; use gpui::{ @@ -10,7 +9,7 @@ use gpui::{ FocusHandle, Focusable, Hsla, ListOffset, ListState, MouseDownEvent, Point, Subscription, Task, }; use menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev}; -use project::debugger::dap_session::DebugSession; +use project::debugger::session::Session; use rpc::proto::{ self, DebuggerScopeVariableIndex, DebuggerVariableContainer, VariableListScopes, VariableListVariables, @@ -330,8 +329,8 @@ pub struct VariableList { list: ListState, focus_handle: FocusHandle, open_entries: Vec, - session: Entity, - client_id: DebugAdapterClientId, + session: Entity, + client_id: SessionId, _subscriptions: Vec, set_variable_editor: Entity, selection: Option, @@ -346,8 +345,8 @@ pub struct VariableList { impl VariableList { pub fn new( - session: Entity, - client_id: DebugAdapterClientId, + session: Entity, + client_id: SessionId, stack_frame_list: Entity, window: &mut Window, cx: &mut Context, @@ -842,20 +841,11 @@ impl VariableList { window: &mut Window, cx: &mut Context, ) { - let Some((support_set_variable, support_clipboard_context)) = self - .session - .read(cx) - .client_state(self.client_id) - .map(|state| state.read(cx).capabilities()) - .map(|caps| { - ( - caps.supports_set_variable.unwrap_or_default(), - caps.supports_clipboard_context.unwrap_or_default(), - ) - }) - else { - return; - }; + let caps = self.session.read(cx).capabilities(); + let (support_set_variable, support_clipboard_context) = ( + caps.supports_set_variable.unwrap_or_default(), + caps.supports_clipboard_context.unwrap_or_default(), + ); let this = cx.entity(); @@ -874,12 +864,7 @@ impl VariableList { window.handler_for(&this.clone(), move |this, _window, cx| { if support_clipboard_context { - let Some(client_state) = this.session.read(cx).client_state(this.client_id) - else { - return; - }; - - client_state.update(cx, |state, cx| { + this.session.update(cx, |state, cx| { state.evaluate( evaluate_name.clone().unwrap_or(variable_name.clone()), Some(dap::EvaluateArgumentsContext::Clipboard), @@ -984,11 +969,7 @@ impl VariableList { return cx.notify(); } - let Some(client_state) = self.session.read(cx).client_state(self.client_id) else { - return; - }; - - client_state.update(cx, |state, cx| { + self.session.update(cx, |state, cx| { state.set_variable_value( set_variable_state.parent_variables_reference, set_variable_state.name, diff --git a/crates/debugger_ui/src/session/starting.rs b/crates/debugger_ui/src/session/starting.rs new file mode 100644 index 0000000000..d928a2b3f0 --- /dev/null +++ b/crates/debugger_ui/src/session/starting.rs @@ -0,0 +1,22 @@ +use gpui::{FocusHandle, Focusable}; +use ui::{div, Element, ParentElement, Render, Styled}; + +pub(super) struct StartingState { + focus_handle: FocusHandle, +} + +impl Focusable for StartingState { + fn focus_handle(&self, cx: &ui::App) -> FocusHandle { + self.focus_handle.clone() + } +} + +impl Render for StartingState { + fn render( + &mut self, + window: &mut ui::Window, + cx: &mut ui::Context<'_, Self>, + ) -> impl ui::IntoElement { + div().size_full().child("Starting a debug adapter") + } +} diff --git a/crates/debugger_ui/src/tests.rs b/crates/debugger_ui/src/tests.rs index 616cd97198..4a0d158ea0 100644 --- a/crates/debugger_ui/src/tests.rs +++ b/crates/debugger_ui/src/tests.rs @@ -4,7 +4,7 @@ use settings::SettingsStore; use terminal_view::terminal_panel::TerminalPanel; use workspace::Workspace; -use crate::{debugger_panel::DebugPanel, debugger_panel_item::DebugPanelItem}; +use crate::{debugger_panel::DebugPanel, session::DebugSession}; mod attach_modal; mod console; @@ -62,7 +62,7 @@ pub async fn init_test_workspace( pub fn active_debug_panel_item( workspace: WindowHandle, cx: &mut TestAppContext, -) -> Entity { +) -> Entity { workspace .update(cx, |workspace, _window, cx| { let debug_panel = workspace.panel::(cx).unwrap(); diff --git a/crates/debugger_ui/src/tests/debugger_panel.rs b/crates/debugger_ui/src/tests/debugger_panel.rs index 35ae2f39c6..e1477a8c46 100644 --- a/crates/debugger_ui/src/tests/debugger_panel.rs +++ b/crates/debugger_ui/src/tests/debugger_panel.rs @@ -1,6 +1,6 @@ use crate::*; use dap::{ - client::DebugAdapterClientId, + client::SessionId, requests::{ Continue, Disconnect, Initialize, Launch, Next, RunInTerminal, SetBreakpoints, StackTrace, StartDebugging, StepBack, StepIn, StepOut, @@ -759,7 +759,7 @@ async fn test_handle_start_debugging_reverse_request( let second_client = project.update(cx, |_, cx| { session .read(cx) - .client_state(DebugAdapterClientId(1)) + .client_state(SessionId(1)) .unwrap() .read(cx) .adapter_client() diff --git a/crates/debugger_ui/src/tests/module_list.rs b/crates/debugger_ui/src/tests/module_list.rs index 3e6fe3fbb9..13da9e154c 100644 --- a/crates/debugger_ui/src/tests/module_list.rs +++ b/crates/debugger_ui/src/tests/module_list.rs @@ -1,5 +1,5 @@ use crate::{ - debugger_panel_item::ThreadItem, + session::ThreadItem, tests::{active_debug_panel_item, init_test, init_test_workspace}, }; use dap::{ diff --git a/crates/project/src/debugger.rs b/crates/project/src/debugger.rs index 9cd45bf4cb..695d5196ec 100644 --- a/crates/project/src/debugger.rs +++ b/crates/project/src/debugger.rs @@ -13,5 +13,5 @@ pub mod breakpoint_store; pub mod dap_command; -pub mod dap_session; pub mod dap_store; +pub mod session; diff --git a/crates/project/src/debugger/dap_command.rs b/crates/project/src/debugger/dap_command.rs index ef8c6c6eb2..e069e37036 100644 --- a/crates/project/src/debugger/dap_command.rs +++ b/crates/project/src/debugger/dap_command.rs @@ -2,46 +2,23 @@ use std::sync::Arc; use anyhow::{Ok, Result}; use dap::{ - client::DebugAdapterClientId, + client::SessionId, proto_conversions::ProtoConversion, requests::{Continue, Next}, - Capabilities, ContinueArguments, NextArguments, SetVariableResponse, StepInArguments, + Capabilities, ContinueArguments, InitializeRequestArguments, + InitializeRequestArgumentsPathFormat, NextArguments, SetVariableResponse, StepInArguments, StepOutArguments, SteppingGranularity, ValueFormat, Variable, VariablesArgumentsFilter, }; use rpc::proto; use util::ResultExt; - -use super::dap_session::DebugSessionId; - -pub(crate) trait DapCommand: 'static + Send + Sync + std::fmt::Debug { +pub(crate) trait LocalDapCommand: 'static + Send + Sync + std::fmt::Debug { type Response: 'static + Send + std::fmt::Debug; type DapRequest: 'static + Send + dap::requests::Request; - type ProtoRequest: 'static + Send + proto::RequestMessage; fn is_supported(_capabilities: &Capabilities) -> bool { true } - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId; - - fn from_proto(request: &Self::ProtoRequest) -> Self; - - fn to_proto( - &self, - debug_client_id: DebugAdapterClientId, - upstream_project_id: u64, - ) -> Self::ProtoRequest; - - fn response_to_proto( - debug_client_id: DebugAdapterClientId, - message: Self::Response, - ) -> ::Response; - - fn response_from_proto( - &self, - message: ::Response, - ) -> Result; - fn to_dap(&self) -> ::Arguments; fn response_from_dap( @@ -50,43 +27,32 @@ pub(crate) trait DapCommand: 'static + Send + Sync + std::fmt::Debug { ) -> Result; } -impl DapCommand for Arc { - type Response = T::Response; - type DapRequest = T::DapRequest; - type ProtoRequest = T::ProtoRequest; +pub(crate) trait DapCommand: LocalDapCommand { + type ProtoRequest: 'static + Send + proto::RequestMessage; - fn is_supported(capabilities: &Capabilities) -> bool { - T::is_supported(capabilities) - } + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId; - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - T::client_id_from_proto(request) - } + fn from_proto(request: &Self::ProtoRequest) -> Self; - fn from_proto(request: &Self::ProtoRequest) -> Self { - Arc::new(T::from_proto(request)) - } - - fn to_proto( - &self, - debug_client_id: DebugAdapterClientId, - upstream_project_id: u64, - ) -> Self::ProtoRequest { - T::to_proto(self, debug_client_id, upstream_project_id) - } + fn to_proto(&self, debug_client_id: SessionId, upstream_project_id: u64) -> Self::ProtoRequest; fn response_to_proto( - debug_client_id: DebugAdapterClientId, + debug_client_id: SessionId, message: Self::Response, - ) -> ::Response { - T::response_to_proto(debug_client_id, message) - } + ) -> ::Response; fn response_from_proto( &self, message: ::Response, - ) -> Result { - T::response_from_proto(self, message) + ) -> Result; +} + +impl LocalDapCommand for Arc { + type Response = T::Response; + type DapRequest = T::DapRequest; + + fn is_supported(capabilities: &Capabilities) -> bool { + T::is_supported(capabilities) } fn to_dap(&self) -> ::Arguments { @@ -101,6 +67,36 @@ impl DapCommand for Arc { } } +impl DapCommand for Arc { + type ProtoRequest = T::ProtoRequest; + + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + T::client_id_from_proto(request) + } + + fn from_proto(request: &Self::ProtoRequest) -> Self { + Arc::new(T::from_proto(request)) + } + + fn to_proto(&self, debug_client_id: SessionId, upstream_project_id: u64) -> Self::ProtoRequest { + T::to_proto(self, debug_client_id, upstream_project_id) + } + + fn response_to_proto( + debug_client_id: SessionId, + message: Self::Response, + ) -> ::Response { + T::response_to_proto(debug_client_id, message) + } + + fn response_from_proto( + &self, + message: ::Response, + ) -> Result { + T::response_from_proto(self, message) + } +} + #[derive(Debug, Hash, PartialEq, Eq)] pub struct StepCommand { pub thread_id: u64, @@ -132,13 +128,30 @@ pub(crate) struct NextCommand { pub inner: StepCommand, } -impl DapCommand for NextCommand { +impl LocalDapCommand for NextCommand { type Response = ::Response; type DapRequest = Next; + + fn to_dap(&self) -> ::Arguments { + NextArguments { + thread_id: self.inner.thread_id, + single_thread: self.inner.single_thread, + granularity: self.inner.granularity, + } + } + fn response_from_dap( + &self, + _message: ::Response, + ) -> Result { + Ok(()) + } +} + +impl DapCommand for NextCommand { type ProtoRequest = proto::DapNextRequest; - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) } fn from_proto(request: &Self::ProtoRequest) -> Self { @@ -148,7 +161,7 @@ impl DapCommand for NextCommand { } fn response_to_proto( - _debug_client_id: DebugAdapterClientId, + _debug_client_id: SessionId, _message: Self::Response, ) -> ::Response { proto::Ack {} @@ -156,7 +169,7 @@ impl DapCommand for NextCommand { fn to_proto( &self, - debug_client_id: DebugAdapterClientId, + debug_client_id: SessionId, upstream_project_id: u64, ) -> proto::DapNextRequest { proto::DapNextRequest { @@ -168,21 +181,6 @@ impl DapCommand for NextCommand { } } - fn to_dap(&self) -> ::Arguments { - NextArguments { - thread_id: self.inner.thread_id, - single_thread: self.inner.single_thread, - granularity: self.inner.granularity, - } - } - - fn response_from_dap( - &self, - _message: ::Response, - ) -> Result { - Ok(()) - } - fn response_from_proto( &self, _message: ::Response, @@ -196,48 +194,9 @@ pub(crate) struct StepInCommand { pub inner: StepCommand, } -impl DapCommand for StepInCommand { +impl LocalDapCommand for StepInCommand { type Response = ::Response; type DapRequest = dap::requests::StepIn; - type ProtoRequest = proto::DapStepInRequest; - - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) - } - - fn from_proto(request: &Self::ProtoRequest) -> Self { - Self { - inner: StepCommand::from_proto(proto::DapNextRequest { - project_id: request.project_id, - client_id: request.client_id, - thread_id: request.thread_id, - single_thread: request.single_thread, - granularity: request.granularity, - }), - } - } - - fn response_to_proto( - _debug_client_id: DebugAdapterClientId, - _message: Self::Response, - ) -> ::Response { - proto::Ack {} - } - - fn to_proto( - &self, - debug_client_id: DebugAdapterClientId, - upstream_project_id: u64, - ) -> proto::DapStepInRequest { - proto::DapStepInRequest { - project_id: upstream_project_id, - client_id: debug_client_id.to_proto(), - thread_id: self.inner.thread_id, - single_thread: self.inner.single_thread, - granularity: self.inner.granularity.map(|gran| gran.to_proto() as i32), - target_id: None, - } - } fn to_dap(&self) -> ::Arguments { StepInArguments { @@ -254,27 +213,13 @@ impl DapCommand for StepInCommand { ) -> Result { Ok(()) } - - fn response_from_proto( - &self, - _message: ::Response, - ) -> Result { - Ok(()) - } } -#[derive(Debug, Hash, PartialEq, Eq)] -pub(crate) struct StepOutCommand { - pub inner: StepCommand, -} +impl DapCommand for StepInCommand { + type ProtoRequest = proto::DapStepInRequest; -impl DapCommand for StepOutCommand { - type Response = ::Response; - type DapRequest = dap::requests::StepOut; - type ProtoRequest = proto::DapStepOutRequest; - - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) } fn from_proto(request: &Self::ProtoRequest) -> Self { @@ -290,7 +235,7 @@ impl DapCommand for StepOutCommand { } fn response_to_proto( - _debug_client_id: DebugAdapterClientId, + _debug_client_id: SessionId, _message: Self::Response, ) -> ::Response { proto::Ack {} @@ -298,18 +243,36 @@ impl DapCommand for StepOutCommand { fn to_proto( &self, - debug_client_id: DebugAdapterClientId, + debug_client_id: SessionId, upstream_project_id: u64, - ) -> proto::DapStepOutRequest { - proto::DapStepOutRequest { + ) -> proto::DapStepInRequest { + proto::DapStepInRequest { project_id: upstream_project_id, client_id: debug_client_id.to_proto(), thread_id: self.inner.thread_id, single_thread: self.inner.single_thread, granularity: self.inner.granularity.map(|gran| gran.to_proto() as i32), + target_id: None, } } + fn response_from_proto( + &self, + _message: ::Response, + ) -> Result { + Ok(()) + } +} + +#[derive(Debug, Hash, PartialEq, Eq)] +pub(crate) struct StepOutCommand { + pub inner: StepCommand, +} + +impl LocalDapCommand for StepOutCommand { + type Response = ::Response; + type DapRequest = dap::requests::StepOut; + fn to_dap(&self) -> ::Arguments { StepOutArguments { thread_id: self.inner.thread_id, @@ -324,31 +287,13 @@ impl DapCommand for StepOutCommand { ) -> Result { Ok(()) } - - fn response_from_proto( - &self, - _message: ::Response, - ) -> Result { - Ok(()) - } } -#[derive(Debug, Hash, PartialEq, Eq)] -pub(crate) struct StepBackCommand { - pub inner: StepCommand, -} +impl DapCommand for StepOutCommand { + type ProtoRequest = proto::DapStepOutRequest; -impl DapCommand for StepBackCommand { - type Response = ::Response; - type DapRequest = dap::requests::StepBack; - type ProtoRequest = proto::DapStepBackRequest; - - fn is_supported(capabilities: &Capabilities) -> bool { - capabilities.supports_step_back.unwrap_or_default() - } - - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) } fn from_proto(request: &Self::ProtoRequest) -> Self { @@ -364,7 +309,7 @@ impl DapCommand for StepBackCommand { } fn response_to_proto( - _debug_client_id: DebugAdapterClientId, + _debug_client_id: SessionId, _message: Self::Response, ) -> ::Response { proto::Ack {} @@ -372,10 +317,10 @@ impl DapCommand for StepBackCommand { fn to_proto( &self, - debug_client_id: DebugAdapterClientId, + debug_client_id: SessionId, upstream_project_id: u64, - ) -> proto::DapStepBackRequest { - proto::DapStepBackRequest { + ) -> proto::DapStepOutRequest { + proto::DapStepOutRequest { project_id: upstream_project_id, client_id: debug_client_id.to_proto(), thread_id: self.inner.thread_id, @@ -384,6 +329,26 @@ impl DapCommand for StepBackCommand { } } + fn response_from_proto( + &self, + _message: ::Response, + ) -> Result { + Ok(()) + } +} + +#[derive(Debug, Hash, PartialEq, Eq)] +pub(crate) struct StepBackCommand { + pub inner: StepCommand, +} +impl LocalDapCommand for StepBackCommand { + type Response = ::Response; + type DapRequest = dap::requests::StepBack; + + fn is_supported(capabilities: &Capabilities) -> bool { + capabilities.supports_step_back.unwrap_or_default() + } + fn to_dap(&self) -> ::Arguments { dap::StepBackArguments { thread_id: self.inner.thread_id, @@ -398,6 +363,47 @@ impl DapCommand for StepBackCommand { ) -> Result { Ok(()) } +} + +impl DapCommand for StepBackCommand { + type ProtoRequest = proto::DapStepBackRequest; + + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) + } + + fn from_proto(request: &Self::ProtoRequest) -> Self { + Self { + inner: StepCommand::from_proto(proto::DapNextRequest { + project_id: request.project_id, + client_id: request.client_id, + thread_id: request.thread_id, + single_thread: request.single_thread, + granularity: request.granularity, + }), + } + } + + fn response_to_proto( + _debug_client_id: SessionId, + _message: Self::Response, + ) -> ::Response { + proto::Ack {} + } + + fn to_proto( + &self, + debug_client_id: SessionId, + upstream_project_id: u64, + ) -> proto::DapStepBackRequest { + proto::DapStepBackRequest { + project_id: upstream_project_id, + client_id: debug_client_id.to_proto(), + thread_id: self.inner.thread_id, + single_thread: self.inner.single_thread, + granularity: self.inner.granularity.map(|gran| gran.to_proto() as i32), + } + } fn response_from_proto( &self, @@ -412,18 +418,32 @@ pub(crate) struct ContinueCommand { pub args: ContinueArguments, } -impl DapCommand for ContinueCommand { +impl LocalDapCommand for ContinueCommand { type Response = ::Response; type DapRequest = Continue; + + fn to_dap(&self) -> ::Arguments { + self.args.clone() + } + + fn response_from_dap( + &self, + message: ::Response, + ) -> Result { + Ok(message) + } +} + +impl DapCommand for ContinueCommand { type ProtoRequest = proto::DapContinueRequest; - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) } fn to_proto( &self, - debug_client_id: DebugAdapterClientId, + debug_client_id: SessionId, upstream_project_id: u64, ) -> proto::DapContinueRequest { proto::DapContinueRequest { @@ -443,17 +463,6 @@ impl DapCommand for ContinueCommand { } } - fn to_dap(&self) -> ::Arguments { - self.args.clone() - } - - fn response_from_dap( - &self, - message: ::Response, - ) -> Result { - Ok(message) - } - fn response_from_proto( &self, message: ::Response, @@ -464,7 +473,7 @@ impl DapCommand for ContinueCommand { } fn response_to_proto( - debug_client_id: DebugAdapterClientId, + debug_client_id: SessionId, message: Self::Response, ) -> ::Response { proto::DapContinueResponse { @@ -479,40 +488,9 @@ pub(crate) struct PauseCommand { pub thread_id: u64, } -impl DapCommand for PauseCommand { +impl LocalDapCommand for PauseCommand { type Response = ::Response; type DapRequest = dap::requests::Pause; - type ProtoRequest = proto::DapPauseRequest; - - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) - } - - fn from_proto(request: &Self::ProtoRequest) -> Self { - Self { - thread_id: request.thread_id, - } - } - - fn to_proto( - &self, - debug_client_id: DebugAdapterClientId, - upstream_project_id: u64, - ) -> proto::DapPauseRequest { - proto::DapPauseRequest { - project_id: upstream_project_id, - client_id: debug_client_id.to_proto(), - thread_id: self.thread_id, - } - } - - fn response_to_proto( - _debug_client_id: DebugAdapterClientId, - _message: Self::Response, - ) -> ::Response { - proto::Ack {} - } - fn to_dap(&self) -> ::Arguments { dap::PauseArguments { thread_id: self.thread_id, @@ -525,6 +503,39 @@ impl DapCommand for PauseCommand { ) -> Result { Ok(()) } +} + +impl DapCommand for PauseCommand { + type ProtoRequest = proto::DapPauseRequest; + + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) + } + + fn from_proto(request: &Self::ProtoRequest) -> Self { + Self { + thread_id: request.thread_id, + } + } + + fn to_proto( + &self, + debug_client_id: SessionId, + upstream_project_id: u64, + ) -> proto::DapPauseRequest { + proto::DapPauseRequest { + project_id: upstream_project_id, + client_id: debug_client_id.to_proto(), + thread_id: self.thread_id, + } + } + + fn response_to_proto( + _debug_client_id: SessionId, + _message: Self::Response, + ) -> ::Response { + proto::Ack {} + } fn response_from_proto( &self, @@ -541,43 +552,9 @@ pub(crate) struct DisconnectCommand { pub suspend_debuggee: Option, } -impl DapCommand for DisconnectCommand { +impl LocalDapCommand for DisconnectCommand { type Response = ::Response; type DapRequest = dap::requests::Disconnect; - type ProtoRequest = proto::DapDisconnectRequest; - - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) - } - - fn from_proto(request: &Self::ProtoRequest) -> Self { - Self { - restart: request.restart, - terminate_debuggee: request.terminate_debuggee, - suspend_debuggee: request.suspend_debuggee, - } - } - - fn to_proto( - &self, - debug_client_id: DebugAdapterClientId, - upstream_project_id: u64, - ) -> proto::DapDisconnectRequest { - proto::DapDisconnectRequest { - project_id: upstream_project_id, - client_id: debug_client_id.to_proto(), - restart: self.restart, - terminate_debuggee: self.terminate_debuggee, - suspend_debuggee: self.suspend_debuggee, - } - } - - fn response_to_proto( - _debug_client_id: DebugAdapterClientId, - _message: Self::Response, - ) -> ::Response { - proto::Ack {} - } fn to_dap(&self) -> ::Arguments { dap::DisconnectArguments { @@ -593,6 +570,43 @@ impl DapCommand for DisconnectCommand { ) -> Result { Ok(()) } +} + +impl DapCommand for DisconnectCommand { + type ProtoRequest = proto::DapDisconnectRequest; + + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) + } + + fn from_proto(request: &Self::ProtoRequest) -> Self { + Self { + restart: request.restart, + terminate_debuggee: request.terminate_debuggee, + suspend_debuggee: request.suspend_debuggee, + } + } + + fn to_proto( + &self, + debug_client_id: SessionId, + upstream_project_id: u64, + ) -> proto::DapDisconnectRequest { + proto::DapDisconnectRequest { + project_id: upstream_project_id, + client_id: debug_client_id.to_proto(), + restart: self.restart, + terminate_debuggee: self.terminate_debuggee, + suspend_debuggee: self.suspend_debuggee, + } + } + + fn response_to_proto( + _debug_client_id: SessionId, + _message: Self::Response, + ) -> ::Response { + proto::Ack {} + } fn response_from_proto( &self, @@ -607,10 +621,9 @@ pub(crate) struct TerminateThreadsCommand { pub thread_ids: Option>, } -impl DapCommand for TerminateThreadsCommand { +impl LocalDapCommand for TerminateThreadsCommand { type Response = ::Response; type DapRequest = dap::requests::TerminateThreads; - type ProtoRequest = proto::DapTerminateThreadsRequest; fn is_supported(capabilities: &Capabilities) -> bool { capabilities @@ -618,8 +631,25 @@ impl DapCommand for TerminateThreadsCommand { .unwrap_or_default() } - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) + fn to_dap(&self) -> ::Arguments { + dap::TerminateThreadsArguments { + thread_ids: self.thread_ids.clone(), + } + } + + fn response_from_dap( + &self, + _message: ::Response, + ) -> Result { + Ok(()) + } +} + +impl DapCommand for TerminateThreadsCommand { + type ProtoRequest = proto::DapTerminateThreadsRequest; + + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) } fn from_proto(request: &Self::ProtoRequest) -> Self { @@ -634,7 +664,7 @@ impl DapCommand for TerminateThreadsCommand { fn to_proto( &self, - debug_client_id: DebugAdapterClientId, + debug_client_id: SessionId, upstream_project_id: u64, ) -> proto::DapTerminateThreadsRequest { proto::DapTerminateThreadsRequest { @@ -645,25 +675,12 @@ impl DapCommand for TerminateThreadsCommand { } fn response_to_proto( - _debug_client_id: DebugAdapterClientId, + _debug_client_id: SessionId, _message: Self::Response, ) -> ::Response { proto::Ack {} } - fn to_dap(&self) -> ::Arguments { - dap::TerminateThreadsArguments { - thread_ids: self.thread_ids.clone(), - } - } - - fn response_from_dap( - &self, - _message: ::Response, - ) -> Result { - Ok(()) - } - fn response_from_proto( &self, _message: ::Response, @@ -677,44 +694,13 @@ pub(crate) struct TerminateCommand { pub restart: Option, } -impl DapCommand for TerminateCommand { +impl LocalDapCommand for TerminateCommand { type Response = ::Response; type DapRequest = dap::requests::Terminate; - type ProtoRequest = proto::DapTerminateRequest; fn is_supported(capabilities: &Capabilities) -> bool { capabilities.supports_terminate_request.unwrap_or_default() } - - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) - } - - fn from_proto(request: &Self::ProtoRequest) -> Self { - Self { - restart: request.restart, - } - } - - fn to_proto( - &self, - debug_client_id: DebugAdapterClientId, - upstream_project_id: u64, - ) -> proto::DapTerminateRequest { - proto::DapTerminateRequest { - project_id: upstream_project_id, - client_id: debug_client_id.to_proto(), - restart: self.restart, - } - } - - fn response_to_proto( - _debug_client_id: DebugAdapterClientId, - _message: Self::Response, - ) -> ::Response { - proto::Ack {} - } - fn to_dap(&self) -> ::Arguments { dap::TerminateArguments { restart: self.restart, @@ -727,6 +713,39 @@ impl DapCommand for TerminateCommand { ) -> Result { Ok(()) } +} + +impl DapCommand for TerminateCommand { + type ProtoRequest = proto::DapTerminateRequest; + + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) + } + + fn from_proto(request: &Self::ProtoRequest) -> Self { + Self { + restart: request.restart, + } + } + + fn to_proto( + &self, + debug_client_id: SessionId, + upstream_project_id: u64, + ) -> proto::DapTerminateRequest { + proto::DapTerminateRequest { + project_id: upstream_project_id, + client_id: debug_client_id.to_proto(), + restart: self.restart, + } + } + + fn response_to_proto( + _debug_client_id: SessionId, + _message: Self::Response, + ) -> ::Response { + proto::Ack {} + } fn response_from_proto( &self, @@ -741,48 +760,14 @@ pub(crate) struct RestartCommand { pub raw: serde_json::Value, } -impl DapCommand for RestartCommand { +impl LocalDapCommand for RestartCommand { type Response = ::Response; type DapRequest = dap::requests::Restart; - type ProtoRequest = proto::DapRestartRequest; fn is_supported(capabilities: &Capabilities) -> bool { capabilities.supports_restart_request.unwrap_or_default() } - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) - } - - fn from_proto(request: &Self::ProtoRequest) -> Self { - Self { - raw: serde_json::from_slice(&request.raw_args) - .log_err() - .unwrap_or(serde_json::Value::Null), - } - } - - fn to_proto( - &self, - debug_client_id: DebugAdapterClientId, - upstream_project_id: u64, - ) -> proto::DapRestartRequest { - let raw_args = serde_json::to_vec(&self.raw).log_err().unwrap_or_default(); - - proto::DapRestartRequest { - project_id: upstream_project_id, - client_id: debug_client_id.to_proto(), - raw_args, - } - } - - fn response_to_proto( - _debug_client_id: DebugAdapterClientId, - _message: Self::Response, - ) -> ::Response { - proto::Ack {} - } - fn to_dap(&self) -> ::Arguments { dap::RestartArguments { raw: self.raw.clone(), @@ -795,6 +780,43 @@ impl DapCommand for RestartCommand { ) -> Result { Ok(()) } +} + +impl DapCommand for RestartCommand { + type ProtoRequest = proto::DapRestartRequest; + + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) + } + + fn from_proto(request: &Self::ProtoRequest) -> Self { + Self { + raw: serde_json::from_slice(&request.raw_args) + .log_err() + .unwrap_or(serde_json::Value::Null), + } + } + + fn to_proto( + &self, + debug_client_id: SessionId, + upstream_project_id: u64, + ) -> proto::DapRestartRequest { + let raw_args = serde_json::to_vec(&self.raw).log_err().unwrap_or_default(); + + proto::DapRestartRequest { + project_id: upstream_project_id, + client_id: debug_client_id.to_proto(), + raw_args, + } + } + + fn response_to_proto( + _debug_client_id: SessionId, + _message: Self::Response, + ) -> ::Response { + proto::Ack {} + } fn response_from_proto( &self, @@ -809,21 +831,15 @@ pub struct VariablesCommand { pub stack_frame_id: u64, pub thread_id: u64, pub variables_reference: u64, - pub session_id: DebugSessionId, pub filter: Option, pub start: Option, pub count: Option, pub format: Option, } -impl DapCommand for VariablesCommand { +impl LocalDapCommand for VariablesCommand { type Response = Vec; type DapRequest = dap::requests::Variables; - type ProtoRequest = proto::VariablesRequest; - - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) - } fn to_dap(&self) -> ::Arguments { dap::VariablesArguments { @@ -841,17 +857,20 @@ impl DapCommand for VariablesCommand { ) -> Result { Ok(message.variables) } +} - fn to_proto( - &self, - debug_client_id: DebugAdapterClientId, - upstream_project_id: u64, - ) -> Self::ProtoRequest { +impl DapCommand for VariablesCommand { + type ProtoRequest = proto::VariablesRequest; + + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) + } + + fn to_proto(&self, debug_client_id: SessionId, upstream_project_id: u64) -> Self::ProtoRequest { proto::VariablesRequest { project_id: upstream_project_id, client_id: debug_client_id.to_proto(), thread_id: self.thread_id, - session_id: self.session_id.to_proto(), stack_frame_id: self.stack_frame_id, variables_reference: self.variables_reference, filter: None, @@ -864,7 +883,6 @@ impl DapCommand for VariablesCommand { fn from_proto(request: &Self::ProtoRequest) -> Self { Self { thread_id: request.thread_id, - session_id: DebugSessionId::from_proto(request.session_id), stack_frame_id: request.stack_frame_id, variables_reference: request.variables_reference, filter: None, @@ -875,7 +893,7 @@ impl DapCommand for VariablesCommand { } fn response_to_proto( - debug_client_id: DebugAdapterClientId, + debug_client_id: SessionId, message: Self::Response, ) -> ::Response { proto::DapVariables { @@ -898,20 +916,12 @@ pub(crate) struct SetVariableValueCommand { pub value: String, pub variables_reference: u64, } - -impl DapCommand for SetVariableValueCommand { +impl LocalDapCommand for SetVariableValueCommand { type Response = SetVariableResponse; type DapRequest = dap::requests::SetVariable; - type ProtoRequest = proto::DapSetVariableValueRequest; - fn is_supported(capabilities: &Capabilities) -> bool { capabilities.supports_set_variable.unwrap_or_default() } - - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) - } - fn to_dap(&self) -> ::Arguments { dap::SetVariableArguments { format: None, @@ -920,19 +930,22 @@ impl DapCommand for SetVariableValueCommand { variables_reference: self.variables_reference, } } - fn response_from_dap( &self, message: ::Response, ) -> Result { Ok(message) } +} - fn to_proto( - &self, - debug_client_id: DebugAdapterClientId, - upstream_project_id: u64, - ) -> Self::ProtoRequest { +impl DapCommand for SetVariableValueCommand { + type ProtoRequest = proto::DapSetVariableValueRequest; + + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) + } + + fn to_proto(&self, debug_client_id: SessionId, upstream_project_id: u64) -> Self::ProtoRequest { proto::DapSetVariableValueRequest { project_id: upstream_project_id, client_id: debug_client_id.to_proto(), @@ -951,7 +964,7 @@ impl DapCommand for SetVariableValueCommand { } fn response_to_proto( - debug_client_id: DebugAdapterClientId, + debug_client_id: SessionId, message: Self::Response, ) -> ::Response { proto::DapSetVariableValueResponse { @@ -985,44 +998,14 @@ pub(crate) struct RestartStackFrameCommand { pub stack_frame_id: u64, } -impl DapCommand for RestartStackFrameCommand { +impl LocalDapCommand for RestartStackFrameCommand { type Response = ::Response; type DapRequest = dap::requests::RestartFrame; - type ProtoRequest = proto::DapRestartStackFrameRequest; fn is_supported(capabilities: &Capabilities) -> bool { capabilities.supports_restart_frame.unwrap_or_default() } - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) - } - - fn from_proto(request: &Self::ProtoRequest) -> Self { - Self { - stack_frame_id: request.stack_frame_id, - } - } - - fn to_proto( - &self, - debug_client_id: DebugAdapterClientId, - upstream_project_id: u64, - ) -> proto::DapRestartStackFrameRequest { - proto::DapRestartStackFrameRequest { - project_id: upstream_project_id, - client_id: debug_client_id.to_proto(), - stack_frame_id: self.stack_frame_id, - } - } - - fn response_to_proto( - _debug_client_id: DebugAdapterClientId, - _message: Self::Response, - ) -> ::Response { - proto::Ack {} - } - fn to_dap(&self) -> ::Arguments { dap::RestartFrameArguments { frame_id: self.stack_frame_id, @@ -1035,6 +1018,39 @@ impl DapCommand for RestartStackFrameCommand { ) -> Result { Ok(()) } +} + +impl DapCommand for RestartStackFrameCommand { + type ProtoRequest = proto::DapRestartStackFrameRequest; + + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) + } + + fn from_proto(request: &Self::ProtoRequest) -> Self { + Self { + stack_frame_id: request.stack_frame_id, + } + } + + fn to_proto( + &self, + debug_client_id: SessionId, + upstream_project_id: u64, + ) -> proto::DapRestartStackFrameRequest { + proto::DapRestartStackFrameRequest { + project_id: upstream_project_id, + client_id: debug_client_id.to_proto(), + stack_frame_id: self.stack_frame_id, + } + } + + fn response_to_proto( + _debug_client_id: SessionId, + _message: Self::Response, + ) -> ::Response { + proto::Ack {} + } fn response_from_proto( &self, @@ -1047,47 +1063,14 @@ impl DapCommand for RestartStackFrameCommand { #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub(crate) struct ModulesCommand; -impl DapCommand for ModulesCommand { +impl LocalDapCommand for ModulesCommand { type Response = Vec; type DapRequest = dap::requests::Modules; - type ProtoRequest = proto::DapModulesRequest; fn is_supported(capabilities: &Capabilities) -> bool { capabilities.supports_modules_request.unwrap_or_default() } - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) - } - - fn from_proto(_request: &Self::ProtoRequest) -> Self { - Self {} - } - - fn to_proto( - &self, - debug_client_id: DebugAdapterClientId, - upstream_project_id: u64, - ) -> proto::DapModulesRequest { - proto::DapModulesRequest { - project_id: upstream_project_id, - client_id: debug_client_id.to_proto(), - } - } - - fn response_to_proto( - debug_client_id: DebugAdapterClientId, - message: Self::Response, - ) -> ::Response { - proto::DapModulesResponse { - modules: message - .into_iter() - .map(|module| module.to_proto()) - .collect(), - client_id: debug_client_id.to_proto(), - } - } - fn to_dap(&self) -> ::Arguments { dap::ModulesArguments { start_module: None, @@ -1101,6 +1084,42 @@ impl DapCommand for ModulesCommand { ) -> Result { Ok(message.modules) } +} + +impl DapCommand for ModulesCommand { + type ProtoRequest = proto::DapModulesRequest; + + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) + } + + fn from_proto(_request: &Self::ProtoRequest) -> Self { + Self {} + } + + fn to_proto( + &self, + debug_client_id: SessionId, + upstream_project_id: u64, + ) -> proto::DapModulesRequest { + proto::DapModulesRequest { + project_id: upstream_project_id, + client_id: debug_client_id.to_proto(), + } + } + + fn response_to_proto( + debug_client_id: SessionId, + message: Self::Response, + ) -> ::Response { + proto::DapModulesResponse { + modules: message + .into_iter() + .map(|module| module.to_proto()) + .collect(), + client_id: debug_client_id.to_proto(), + } + } fn response_from_proto( &self, @@ -1117,49 +1136,14 @@ impl DapCommand for ModulesCommand { #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub(crate) struct LoadedSourcesCommand; -impl DapCommand for LoadedSourcesCommand { +impl LocalDapCommand for LoadedSourcesCommand { type Response = Vec; type DapRequest = dap::requests::LoadedSources; - type ProtoRequest = proto::DapLoadedSourcesRequest; - fn is_supported(capabilities: &Capabilities) -> bool { capabilities .supports_loaded_sources_request .unwrap_or_default() } - - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) - } - - fn from_proto(_request: &Self::ProtoRequest) -> Self { - Self {} - } - - fn to_proto( - &self, - debug_client_id: DebugAdapterClientId, - upstream_project_id: u64, - ) -> proto::DapLoadedSourcesRequest { - proto::DapLoadedSourcesRequest { - project_id: upstream_project_id, - client_id: debug_client_id.to_proto(), - } - } - - fn response_to_proto( - debug_client_id: DebugAdapterClientId, - message: Self::Response, - ) -> ::Response { - proto::DapLoadedSourcesResponse { - sources: message - .into_iter() - .map(|source| source.to_proto()) - .collect(), - client_id: debug_client_id.to_proto(), - } - } - fn to_dap(&self) -> ::Arguments { dap::LoadedSourcesArguments {} } @@ -1170,6 +1154,42 @@ impl DapCommand for LoadedSourcesCommand { ) -> Result { Ok(message.sources) } +} + +impl DapCommand for LoadedSourcesCommand { + type ProtoRequest = proto::DapLoadedSourcesRequest; + + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) + } + + fn from_proto(_request: &Self::ProtoRequest) -> Self { + Self {} + } + + fn to_proto( + &self, + debug_client_id: SessionId, + upstream_project_id: u64, + ) -> proto::DapLoadedSourcesRequest { + proto::DapLoadedSourcesRequest { + project_id: upstream_project_id, + client_id: debug_client_id.to_proto(), + } + } + + fn response_to_proto( + debug_client_id: SessionId, + message: Self::Response, + ) -> ::Response { + proto::DapLoadedSourcesResponse { + sources: message + .into_iter() + .map(|source| source.to_proto()) + .collect(), + client_id: debug_client_id.to_proto(), + } + } fn response_from_proto( &self, @@ -1190,10 +1210,9 @@ pub(crate) struct StackTraceCommand { pub levels: Option, } -impl DapCommand for StackTraceCommand { +impl LocalDapCommand for StackTraceCommand { type Response = Vec; type DapRequest = dap::requests::StackTrace; - type ProtoRequest = proto::DapStackTraceRequest; fn to_dap(&self) -> ::Arguments { dap::StackTraceArguments { @@ -1210,12 +1229,12 @@ impl DapCommand for StackTraceCommand { ) -> Result { Ok(message.stack_frames) } +} - fn to_proto( - &self, - debug_client_id: DebugAdapterClientId, - upstream_project_id: u64, - ) -> Self::ProtoRequest { +impl DapCommand for StackTraceCommand { + type ProtoRequest = proto::DapStackTraceRequest; + + fn to_proto(&self, debug_client_id: SessionId, upstream_project_id: u64) -> Self::ProtoRequest { proto::DapStackTraceRequest { project_id: upstream_project_id, client_id: debug_client_id.to_proto(), @@ -1233,8 +1252,8 @@ impl DapCommand for StackTraceCommand { } } - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) } fn response_from_proto( @@ -1249,7 +1268,7 @@ impl DapCommand for StackTraceCommand { } fn response_to_proto( - _debug_client_id: DebugAdapterClientId, + _debug_client_id: SessionId, message: Self::Response, ) -> ::Response { proto::DapStackTraceResponse { @@ -1264,10 +1283,9 @@ pub(crate) struct ScopesCommand { pub stack_frame_id: u64, } -impl DapCommand for ScopesCommand { +impl LocalDapCommand for ScopesCommand { type Response = Vec; type DapRequest = dap::requests::Scopes; - type ProtoRequest = proto::DapScopesRequest; fn to_dap(&self) -> ::Arguments { dap::ScopesArguments { @@ -1281,12 +1299,12 @@ impl DapCommand for ScopesCommand { ) -> Result { Ok(message.scopes) } +} - fn to_proto( - &self, - debug_client_id: DebugAdapterClientId, - upstream_project_id: u64, - ) -> Self::ProtoRequest { +impl DapCommand for ScopesCommand { + type ProtoRequest = proto::DapScopesRequest; + + fn to_proto(&self, debug_client_id: SessionId, upstream_project_id: u64) -> Self::ProtoRequest { proto::DapScopesRequest { project_id: upstream_project_id, client_id: debug_client_id.to_proto(), @@ -1302,8 +1320,8 @@ impl DapCommand for ScopesCommand { } } - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) } fn response_from_proto( @@ -1314,7 +1332,7 @@ impl DapCommand for ScopesCommand { } fn response_to_proto( - _debug_client_id: DebugAdapterClientId, + _debug_client_id: SessionId, message: Self::Response, ) -> ::Response { proto::DapScopesResponse { @@ -1323,10 +1341,9 @@ impl DapCommand for ScopesCommand { } } -impl DapCommand for super::dap_session::CompletionsQuery { +impl LocalDapCommand for super::session::CompletionsQuery { type Response = dap::CompletionsResponse; type DapRequest = dap::requests::Completions; - type ProtoRequest = proto::DapCompletionRequest; fn to_dap(&self) -> ::Arguments { dap::CompletionsArguments { @@ -1349,12 +1366,11 @@ impl DapCommand for super::dap_session::CompletionsQuery { .supports_completions_request .unwrap_or_default() } +} +impl DapCommand for super::session::CompletionsQuery { + type ProtoRequest = proto::DapCompletionRequest; - fn to_proto( - &self, - debug_client_id: DebugAdapterClientId, - upstream_project_id: u64, - ) -> Self::ProtoRequest { + fn to_proto(&self, debug_client_id: SessionId, upstream_project_id: u64) -> Self::ProtoRequest { proto::DapCompletionRequest { client_id: debug_client_id.to_proto(), project_id: upstream_project_id, @@ -1365,8 +1381,8 @@ impl DapCommand for super::dap_session::CompletionsQuery { } } - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) } fn from_proto(request: &Self::ProtoRequest) -> Self { @@ -1388,7 +1404,7 @@ impl DapCommand for super::dap_session::CompletionsQuery { } fn response_to_proto( - _debug_client_id: DebugAdapterClientId, + _debug_client_id: SessionId, message: Self::Response, ) -> ::Response { proto::DapCompletionResponse { @@ -1406,11 +1422,9 @@ pub(crate) struct EvaluateCommand { pub source: Option, } -impl DapCommand for EvaluateCommand { +impl LocalDapCommand for EvaluateCommand { type Response = dap::EvaluateResponse; type DapRequest = dap::requests::Evaluate; - type ProtoRequest = proto::DapEvaluateRequest; - fn to_dap(&self) -> ::Arguments { dap::EvaluateArguments { expression: self.expression.clone(), @@ -1429,12 +1443,11 @@ impl DapCommand for EvaluateCommand { ) -> Result { Ok(message) } +} +impl DapCommand for EvaluateCommand { + type ProtoRequest = proto::DapEvaluateRequest; - fn to_proto( - &self, - debug_client_id: DebugAdapterClientId, - upstream_project_id: u64, - ) -> Self::ProtoRequest { + fn to_proto(&self, debug_client_id: SessionId, upstream_project_id: u64) -> Self::ProtoRequest { proto::DapEvaluateRequest { client_id: debug_client_id.to_proto(), project_id: upstream_project_id, @@ -1447,8 +1460,8 @@ impl DapCommand for EvaluateCommand { } } - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) } fn from_proto(request: &Self::ProtoRequest) -> Self { @@ -1476,7 +1489,7 @@ impl DapCommand for EvaluateCommand { } fn response_to_proto( - _debug_client_id: DebugAdapterClientId, + _debug_client_id: SessionId, message: Self::Response, ) -> ::Response { proto::DapEvaluateResponse { @@ -1493,10 +1506,9 @@ impl DapCommand for EvaluateCommand { #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub(crate) struct ThreadsCommand; -impl DapCommand for ThreadsCommand { +impl LocalDapCommand for ThreadsCommand { type Response = Vec; type DapRequest = dap::requests::Threads; - type ProtoRequest = proto::DapThreadsRequest; fn to_dap(&self) -> ::Arguments { () @@ -1508,12 +1520,12 @@ impl DapCommand for ThreadsCommand { ) -> Result { Ok(message.threads) } +} - fn to_proto( - &self, - debug_client_id: DebugAdapterClientId, - upstream_project_id: u64, - ) -> Self::ProtoRequest { +impl DapCommand for ThreadsCommand { + type ProtoRequest = proto::DapThreadsRequest; + + fn to_proto(&self, debug_client_id: SessionId, upstream_project_id: u64) -> Self::ProtoRequest { proto::DapThreadsRequest { project_id: upstream_project_id, client_id: debug_client_id.to_proto(), @@ -1524,8 +1536,8 @@ impl DapCommand for ThreadsCommand { Self {} } - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { - DebugAdapterClientId::from_proto(request.client_id) + fn client_id_from_proto(request: &Self::ProtoRequest) -> SessionId { + SessionId::from_proto(request.client_id) } fn response_from_proto( @@ -1536,7 +1548,7 @@ impl DapCommand for ThreadsCommand { } fn response_to_proto( - _debug_client_id: DebugAdapterClientId, + _debug_client_id: SessionId, message: Self::Response, ) -> ::Response { proto::DapThreadsResponse { @@ -1544,3 +1556,45 @@ impl DapCommand for ThreadsCommand { } } } + +#[derive(Clone, Debug, Hash, PartialEq)] +pub(super) struct Initialize { + pub(super) adapter_id: String, +} + +fn dap_client_capabilities(adapter_id: String) -> InitializeRequestArguments { + InitializeRequestArguments { + client_id: Some("zed".to_owned()), + client_name: Some("Zed".to_owned()), + adapter_id, + locale: Some("en-US".to_owned()), + path_format: Some(InitializeRequestArgumentsPathFormat::Path), + supports_variable_type: Some(true), + supports_variable_paging: Some(false), + supports_run_in_terminal_request: Some(true), + supports_memory_references: Some(true), + supports_progress_reporting: Some(false), + supports_invalidated_event: Some(false), + lines_start_at1: Some(true), + columns_start_at1: Some(true), + supports_memory_event: Some(false), + supports_args_can_be_interpreted_by_shell: Some(false), + supports_start_debugging_request: Some(true), + } +} + +impl LocalDapCommand for Initialize { + type Response = Capabilities; + type DapRequest = dap::requests::Initialize; + + fn to_dap(&self) -> ::Arguments { + dap_client_capabilities(self.adapter_id.clone()) + } + + fn response_from_dap( + &self, + message: ::Response, + ) -> Result { + Ok(message) + } +} diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index 18dd429d3e..24da8c6f38 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -7,15 +7,18 @@ use super::{ // TerminateCommand, TerminateThreadsCommand, VariablesCommand, // }, dap_command::DapCommand, - dap_session::{self, DebugSession, DebugSessionId}, + session::{self, Session}, +}; +use crate::{ + debugger, project_settings::ProjectSettings, DebugAdapterClientState, ProjectEnvironment, + ProjectPath, }; -use crate::{project_settings::ProjectSettings, ProjectEnvironment, ProjectPath}; use anyhow::{anyhow, bail, Context as _, Result}; use async_trait::async_trait; use collections::HashMap; use dap::{ adapters::{DapDelegate, DapStatus, DebugAdapter, DebugAdapterBinary, DebugAdapterName}, - client::{DebugAdapterClient, DebugAdapterClientId}, + client::{DebugAdapterClient, SessionId}, messages::{Message, Response}, requests::{ Attach, Completions, Evaluate, Initialize, Launch, Request as _, RunInTerminal, @@ -36,7 +39,7 @@ use language::{BinaryStatus, BufferSnapshot, LanguageRegistry, LanguageToolchain use lsp::LanguageServerName; use node_runtime::NodeRuntime; use rpc::{ - proto::{self, SetDebuggerPanelItem, UpdateDebugAdapter, UpdateThreadStatus}, + proto::{self, UpdateDebugAdapter, UpdateThreadStatus}, AnyProtoClient, TypedEnvelope, }; use serde_json::Value; @@ -44,14 +47,11 @@ use settings::{Settings as _, WorktreeId}; use smol::lock::Mutex; use std::{ borrow::Borrow, - collections::HashSet, + collections::{BTreeMap, HashSet}, ffi::OsStr, hash::Hash, path::{Path, PathBuf}, - sync::{ - atomic::{AtomicUsize, Ordering::SeqCst}, - Arc, - }, + sync::{atomic::Ordering::SeqCst, Arc}, }; use std::{collections::VecDeque, sync::atomic::AtomicU32}; use task::{AttachConfig, DebugAdapterConfig, DebugRequestType}; @@ -59,17 +59,15 @@ use util::{merge_json_value_into, ResultExt as _}; use worktree::Worktree; pub enum DapStoreEvent { - DebugClientStarted((DebugSessionId, DebugAdapterClientId)), - DebugClientShutdown(DebugAdapterClientId), + DebugClientStarted(SessionId), + DebugClientShutdown(SessionId), DebugClientEvent { - session_id: DebugSessionId, - client_id: DebugAdapterClientId, + session_id: SessionId, message: Message, }, Notification(String), ActiveDebugLineChanged, RemoteHasInitialized, - SetDebugPanelItem(SetDebuggerPanelItem), UpdateDebugAdapter(UpdateDebugAdapter), UpdateThreadStatus(UpdateThreadStatus), } @@ -83,8 +81,7 @@ pub enum DapStoreMode { pub struct LocalDapStore { fs: Arc, node_runtime: NodeRuntime, - next_client_id: AtomicU32, - next_session_id: AtomicUsize, + next_session_id: AtomicU32, http_client: Arc, environment: Entity, language_registry: Arc, @@ -92,17 +89,116 @@ pub struct LocalDapStore { } impl LocalDapStore { - fn next_client_id(&self) -> DebugAdapterClientId { - DebugAdapterClientId(self.next_client_id.fetch_add(1, SeqCst)) + fn next_session_id(&self) -> SessionId { + SessionId(self.next_session_id.fetch_add(1, SeqCst)) } + pub fn respond_to_start_debugging( + &mut self, + session: &Entity, + seq: u64, + args: Option, + cx: &mut Context, + ) -> Task> { + let config = session.read(cx).configuration(); - fn next_session_id(&self) -> DebugSessionId { - DebugSessionId(self.next_session_id.fetch_add(1, SeqCst)) + let request_args = args.unwrap_or_else(|| StartDebuggingRequestArguments { + configuration: config.initialize_args.clone().unwrap_or_default(), + request: match config.request { + DebugRequestType::Launch => StartDebuggingRequestArgumentsRequest::Launch, + DebugRequestType::Attach(_) => StartDebuggingRequestArgumentsRequest::Attach, + }, + }); + + // Merge the new configuration over the existing configuration + let mut initialize_args = config.initialize_args.clone().unwrap_or_default(); + merge_json_value_into(request_args.configuration, &mut initialize_args); + + let new_config = DebugAdapterConfig { + label: config.label.clone(), + kind: config.kind.clone(), + request: match &request_args.request { + StartDebuggingRequestArgumentsRequest::Launch => DebugRequestType::Launch, + StartDebuggingRequestArgumentsRequest::Attach => DebugRequestType::Attach( + if let DebugRequestType::Attach(attach_config) = &config.request { + attach_config.clone() + } else { + AttachConfig::default() + }, + ), + }, + program: config.program.clone(), + cwd: config.cwd.clone(), + initialize_args: Some(initialize_args), + supports_attach: true, + }; + + cx.spawn(|this, mut cx| async move { + let (success, body) = { + let reconnect_task = this.update(&mut cx, |store, cx| { + if !unimplemented!("client.adapter().supports_attach()") + && matches!(new_config.request, DebugRequestType::Attach(_)) + { + Task::>::ready(Err(anyhow!( + "Debug adapter does not support `attach` request" + ))) + } else { + unimplemented!( + "store.reconnect_client(client.binary().clone(), new_config, cx)" + ); + } + }); + + match reconnect_task { + Ok(task) => match task.await { + Ok(_) => (true, None), + Err(error) => ( + false, + Some(serde_json::to_value(ErrorResponse { + error: Some(dap::Message { + id: seq, + format: error.to_string(), + variables: None, + send_telemetry: None, + show_user: None, + url: None, + url_label: None, + }), + })?), + ), + }, + Err(error) => ( + false, + Some(serde_json::to_value(ErrorResponse { + error: Some(dap::Message { + id: seq, + format: error.to_string(), + variables: None, + send_telemetry: None, + show_user: None, + url: None, + url_label: None, + }), + })?), + ), + } + }; + unimplemented!(); + Ok(()) + /*client + .send_message(Message::Response(Response { + seq, + body, + success, + request_seq: seq, + command: StartDebugging::COMMAND.to_string(), + })) + .await*/ + }) } } pub struct RemoteDapStore { - upstream_client: Option, + upstream_client: AnyProtoClient, upstream_project_id: u64, event_queue: Option>, } @@ -111,26 +207,21 @@ pub struct DapStore { mode: DapStoreMode, downstream_client: Option<(AnyProtoClient, u64)>, breakpoint_store: Entity, - active_debug_line: Option<(DebugAdapterClientId, ProjectPath, u32)>, - sessions: HashMap>, - client_by_session: HashMap, + active_debug_line: Option<(SessionId, ProjectPath, u32)>, + sessions: BTreeMap>, } impl EventEmitter for DapStore {} impl DapStore { - const INDEX_STARTS_AT_ONE: bool = true; - pub fn init(client: &AnyProtoClient) { client.add_entity_message_handler(Self::handle_remove_active_debug_line); client.add_entity_message_handler(Self::handle_shutdown_debug_client); client.add_entity_message_handler(Self::handle_set_active_debug_line); client.add_entity_message_handler(Self::handle_set_debug_client_capabilities); - client.add_entity_message_handler(Self::handle_set_debug_panel_item); client.add_entity_message_handler(Self::handle_update_debug_adapter); client.add_entity_message_handler(Self::handle_update_thread_status); client.add_entity_message_handler(Self::handle_ignore_breakpoint_state); - client.add_entity_message_handler(Self::handle_session_has_shutdown); // todo(debugger): Reenable these after we finish handle_dap_command refactor // client.add_entity_request_handler(Self::handle_dap_command::); @@ -145,7 +236,6 @@ impl DapStore { // client.add_entity_request_handler(Self::handle_dap_command::); // client.add_entity_request_handler(Self::handle_dap_command::); // client.add_entity_request_handler(Self::handle_dap_command::); - client.add_entity_request_handler(Self::handle_shutdown_session_request); } pub fn new_local( @@ -168,14 +258,12 @@ impl DapStore { node_runtime, toolchain_store, language_registry, - next_client_id: Default::default(), next_session_id: Default::default(), }), downstream_client: None, active_debug_line: None, breakpoint_store, sessions: Default::default(), - client_by_session: Default::default(), } } @@ -186,7 +274,7 @@ impl DapStore { ) -> Self { Self { mode: DapStoreMode::Remote(RemoteDapStore { - upstream_client: Some(upstream_client), + upstream_client, upstream_project_id: project_id, event_queue: Some(VecDeque::default()), }), @@ -194,7 +282,6 @@ impl DapStore { active_debug_line: None, breakpoint_store, sessions: Default::default(), - client_by_session: Default::default(), } } @@ -230,15 +317,11 @@ impl DapStore { pub fn upstream_client(&self) -> Option<(AnyProtoClient, u64)> { match &self.mode { DapStoreMode::Remote(RemoteDapStore { - upstream_client: Some(upstream_client), + upstream_client, upstream_project_id, .. }) => Some((upstream_client.clone(), *upstream_project_id)), - DapStoreMode::Remote(RemoteDapStore { - upstream_client: None, - .. - }) => None, DapStoreMode::Local(_) => None, } } @@ -247,96 +330,62 @@ impl DapStore { self.downstream_client.as_ref() } - pub fn add_remote_session( + pub fn add_remote_client( &mut self, - session_id: DebugSessionId, + session_id: SessionId, ignore: Option, cx: &mut Context, ) { - self.sessions.entry(session_id).or_insert(cx.new(|_| { - DebugSession::new_remote( + if let DapStoreMode::Remote(remote) = &self.mode { + self.sessions.insert( session_id, - "Remote-Debug".to_owned(), - ignore.unwrap_or(false), - ) - })); - debug_assert!(matches!(self.mode, DapStoreMode::Remote(_))); + cx.new(|_| { + debugger::session::Session::remote( + session_id, + remote.upstream_client.clone(), + remote.upstream_project_id, + ignore.unwrap_or(false), + ) + }), + ); + } else { + debug_assert!(false); + } } - pub fn add_client_to_session( - &mut self, - session_id: DebugSessionId, - client_id: DebugAdapterClientId, - ) { - self.sessions.entry(session_id).and_modify(|_| { - let existing_value = self.client_by_session.insert(client_id, session_id); - debug_assert!(existing_value.map_or(true, |old| old == session_id)); - }); - } - - pub fn remove_session(&mut self, session_id: DebugSessionId) { - self.sessions.remove(&session_id); - self.client_by_session.retain(|_, id| *id != session_id); - } - - pub fn sessions(&self) -> impl Iterator> + '_ { - self.sessions.values().cloned() - } - - pub fn session_by_id(&self, session_id: &DebugSessionId) -> Option> { - self.sessions.get(session_id).cloned() - } - - pub fn session_by_client_id( + pub fn session_by_id( &self, - client_id: impl Borrow, - ) -> Option> { - self.sessions - .get(self.client_by_session.get(client_id.borrow())?) - .cloned() + session_id: impl Borrow, + ) -> Option> { + let session_id = session_id.borrow(); + let client = self.sessions.get(session_id).cloned(); + + client } - - pub fn client_by_id( - &self, - client_id: impl Borrow, - cx: &App, - ) -> Option<(Entity, Entity)> { - let client_id = client_id.borrow(); - let session = self.session_by_client_id(client_id)?; - - let client = session.read(cx).client_by_id(*client_id)?; - - Some((session, client)) + pub fn sessions(&self) -> impl Iterator> { + self.sessions.values() } pub fn capabilities_by_id( &self, - client_id: impl Borrow, + session_id: impl Borrow, cx: &App, ) -> Option { - let client_id = client_id.borrow(); - self.session_by_client_id(client_id).and_then(|session| { - session - .read(cx) - .client_state(*client_id) - .map(|state| state.read(cx).capabilities.clone()) - }) + let session_id = session_id.borrow(); + self.sessions + .get(session_id) + .map(|client| client.read(cx).capabilities.clone()) } pub fn update_capabilities_for_client( &mut self, - session_id: &DebugSessionId, - client_id: DebugAdapterClientId, + session_id: SessionId, capabilities: &Capabilities, cx: &mut Context, ) { - if let Some((client, _)) = self.client_by_id(client_id, cx) { + if let Some(client) = self.session_by_id(session_id) { client.update(cx, |this, cx| { - if let Some(state) = this.client_state(client_id) { - state.update(cx, |this, _| { - this.capabilities = this.capabilities.merge(capabilities.clone()); - }); - } + this.capabilities = this.capabilities.merge(capabilities.clone()); }); } @@ -348,42 +397,41 @@ impl DapStore { &capabilities, *project_id, session_id.to_proto(), - client_id.to_proto(), )) .log_err(); } } - pub fn active_debug_line(&self) -> Option<(DebugAdapterClientId, ProjectPath, u32)> { + pub fn active_debug_line(&self) -> Option<(SessionId, ProjectPath, u32)> { self.active_debug_line.clone() } pub fn set_active_debug_line( &mut self, - client_id: &DebugAdapterClientId, + session_id: &SessionId, project_path: &ProjectPath, row: u32, cx: &mut Context, ) { - self.active_debug_line = Some((*client_id, project_path.clone(), row)); + self.active_debug_line = Some((*session_id, project_path.clone(), row)); cx.emit(DapStoreEvent::ActiveDebugLineChanged); cx.notify(); } pub fn remove_active_debug_line_for_client( &mut self, - client_id: &DebugAdapterClientId, + session_id: &SessionId, cx: &mut Context, ) { if let Some(active_line) = &self.active_debug_line { - if active_line.0 == *client_id { + if active_line.0 == *session_id { self.active_debug_line.take(); cx.emit(DapStoreEvent::ActiveDebugLineChanged); cx.notify(); if let Some((client, project_id)) = self.downstream_client.clone() { client - .send(client::proto::RemoveActiveDebugLine { project_id }) + .send(::client::proto::RemoveActiveDebugLine { project_id }) .log_err(); } } @@ -394,29 +442,17 @@ impl DapStore { &self.breakpoint_store } - async fn handle_session_has_shutdown( - this: Entity, - envelope: TypedEnvelope, - mut cx: AsyncApp, - ) -> Result<()> { - this.update(&mut cx, |this, _| { - this.remove_session(DebugSessionId::from_proto(envelope.payload.session_id)); - })?; - - Ok(()) - } - async fn handle_ignore_breakpoint_state( this: Entity, envelope: TypedEnvelope, mut cx: AsyncApp, ) -> Result<()> { - let session_id = DebugSessionId::from_proto(envelope.payload.session_id); + let session_id = SessionId::from_proto(envelope.payload.session_id); this.update(&mut cx, |this, cx| { - if let Some(session) = this.session_by_id(&session_id) { - session.update(cx, |session, cx| { - session.set_ignore_breakpoints(envelope.payload.ignore, cx) + if let Some(client) = this.session_by_id(&session_id) { + client.update(cx, |client, cx| { + client.set_ignore_breakpoints(envelope.payload.ignore) }); } })?; @@ -426,108 +462,86 @@ impl DapStore { pub fn set_ignore_breakpoints( &mut self, - session_id: &DebugSessionId, + session_id: &SessionId, ignore: bool, cx: &mut Context, ) { if let Some(session) = self.session_by_id(session_id) { - session.update(cx, |session, cx| { - session.set_ignore_breakpoints(ignore, cx); + session.update(cx, |session, _| { + session.set_ignore_breakpoints(ignore); }); } } - pub fn ignore_breakpoints(&self, session_id: &DebugSessionId, cx: &App) -> bool { + pub fn ignore_breakpoints(&self, session_id: &SessionId, cx: &App) -> bool { self.session_by_id(session_id) - .map(|session| session.read(cx).ignore_breakpoints()) + .map(|client| client.read(cx).breakpoints_enabled()) .unwrap_or_default() } - pub fn toggle_ignore_breakpoints( - &mut self, - session_id: &DebugSessionId, - cx: &mut Context, - ) { - if let Some(session) = self.session_by_id(session_id) { - session.update(cx, |session, cx| { - session.set_ignore_breakpoints(!session.ignore_breakpoints(), cx); + pub fn toggle_ignore_breakpoints(&mut self, session_id: &SessionId, cx: &mut Context) { + if let Some(client) = self.session_by_id(session_id) { + client.update(cx, |client, _| { + client.set_ignore_breakpoints(!client.breakpoints_enabled()); }); } } - fn reconnect_client( - &mut self, - session_id: &DebugSessionId, - adapter: Arc, - binary: DebugAdapterBinary, - config: DebugAdapterConfig, - cx: &mut Context, - ) -> Task> { - if !adapter.supports_attach() && matches!(config.request, DebugRequestType::Attach(_)) { - return Task::ready(Err(anyhow!( - "Debug adapter does not support `attach` request" - ))); - } + // fn reconnect_client( + // &mut self, + // adapter: Arc, + // binary: DebugAdapterBinary, + // config: DebugAdapterConfig, + // cx: &mut Context, + // ) -> Task> { + // if !config.supports_attach && matches!(config.request, DebugRequestType::Attach(_)) { + // return Task::ready(Err(anyhow!( + // "Debug adapter does not support `attach` request" + // ))); + // } - let session_id = *session_id; - let client_id = self.as_local().unwrap().next_client_id(); + // let session_id = self.as_local().unwrap().next_session_id(); - cx.spawn(|dap_store, mut cx| async move { - let mut client = DebugAdapterClient::new(client_id, adapter, binary, &cx); + // cx.spawn(|dap_store, mut cx| async move { + // let mut client = DebugAdapterClient::new(session_id, adapter, binary, &cx); - client - .reconnect( - { - let dap_store = dap_store.clone(); - move |message, cx| { - dap_store - .update(cx, |_, cx| { - cx.emit(DapStoreEvent::DebugClientEvent { - session_id, - client_id, - message, - }) - }) - .log_err(); - } - }, - &mut cx, - ) - .await?; + // client + // .reconnect( + // { + // let dap_store = dap_store.clone(); + // move |message, cx| { + // dap_store + // .update(cx, |_, cx| { + // cx.emit(DapStoreEvent::DebugClientEvent { session_id, message }) + // }) + // .log_err(); + // } + // }, + // &mut cx, + // ) + // .await?; - dap_store.update(&mut cx, |store, cx| { - store.client_by_session.insert(client_id, session_id); + // dap_store.update(&mut cx, |store, cx| { + // cx.new(|cx| { + // let client_state = + // debugger::client::Client::local(Arc::new(client), capabilities); + // }); - let session = store.session_by_id(&session_id).unwrap(); + // store.clients.insert(Arc::new(client), session_id); - session.update(cx, |session, cx| { - session.add_client(Arc::new(client), client_id, cx); - let local_session = session - .as_local_mut() - .expect("Only local sessions should attempt to reconnect"); + // // don't emit this event ourself in tests, so we can add request, + // // response and event handlers for this client + // if !cfg!(any(test, feature = "test-support")) { + // cx.emit(DapStoreEvent::DebugClientStarted(session_id)); + // } - local_session.update_configuration( - |old_config| { - *old_config = config.clone(); - }, - cx, - ); - }); - - // don't emit this event ourself in tests, so we can add request, - // response and event handlers for this client - if !cfg!(any(test, feature = "test-support")) { - cx.emit(DapStoreEvent::DebugClientStarted((session_id, client_id))); - } - - cx.notify(); - }) - }) - } + // cx.notify(); + // }) + // }) + // } fn start_client_internal( &mut self, - session_id: DebugSessionId, delegate: DapAdapterDelegate, config: DebugAdapterConfig, cx: &mut Context, @@ -536,12 +550,14 @@ impl DapStore { return Task::ready(Err(anyhow!("cannot start client on remote side"))); }; - let client_id = local_store.next_client_id(); + let session_id = local_store.next_session_id(); cx.spawn(|this, mut cx| async move { let adapter = build_adapter(&config.kind).await?; - if !adapter.supports_attach() && matches!(config.request, DebugRequestType::Attach(_)) { + if !unimplemented!("adapter.supports_attach()") + && matches!(config.request, DebugRequestType::Attach(_)) + { bail!("Debug adapter does not support `attach` request"); } @@ -580,27 +596,7 @@ impl DapStore { } }; - let mut client = DebugAdapterClient::new(client_id, adapter, binary, &cx); - - client - .start( - { - let dap_store = this.clone(); - move |message, cx| { - dap_store - .update(cx, |_, cx| { - cx.emit(DapStoreEvent::DebugClientEvent { - session_id, - client_id, - message, - }) - }) - .log_err(); - } - }, - &mut cx, - ) - .await?; + let mut client = DebugAdapterClient::start(session_id, binary, |_, _| {}, cx).await?; Ok(Arc::new(client)) }) @@ -611,7 +607,7 @@ impl DapStore { config: DebugAdapterConfig, worktree: &Entity, cx: &mut Context, - ) -> Task, Arc)>> { + ) -> Task>> { let Some(local_store) = self.as_local() else { return Task::ready(Err(anyhow!("cannot start session on remote side"))); }; @@ -629,13 +625,9 @@ impl DapStore { }), ); - let session_id = local_store.next_session_id(); - let start_client_task = - self.start_client_internal(session_id, delegate, config.clone(), cx); + let start_client_task = self.start_client_internal(delegate, config.clone(), cx); cx.spawn(|this, mut cx| async move { - let session = cx.new(|_| DebugSession::new_local(session_id, config))?; - let client = match start_client_task.await { Ok(client) => client, Err(error) => { @@ -649,84 +641,32 @@ impl DapStore { }; this.update(&mut cx, |store, cx| { - session.update(cx, |session, cx| { - session.add_client(client.clone(), client.id(), cx); - }); + let session_id = client.id(); - let client_id = client.id(); + unimplemented!("store.clients.insert(session_id, client);"); - store.client_by_session.insert(client_id, session_id); - store.sessions.insert(session_id, session.clone()); - - cx.emit(DapStoreEvent::DebugClientStarted((session_id, client_id))); + cx.emit(DapStoreEvent::DebugClientStarted(session_id)); cx.notify(); - (session, client) - }) - }) - } - - pub fn initialize( - &mut self, - session_id: &DebugSessionId, - client_id: DebugAdapterClientId, - cx: &mut Context, - ) -> Task> { - let Some(client) = self - .client_by_id(client_id, cx) - .and_then(|(_, client)| client.read(cx).adapter_client()) - else { - return Task::ready(Err(anyhow!( - "Could not find debug client: {:?} for session {:?}", - client_id, - session_id - ))); - }; - - let session_id = *session_id; - - cx.spawn(|this, mut cx| async move { - let capabilities = client - .request::(InitializeRequestArguments { - client_id: Some("zed".to_owned()), - client_name: Some("Zed".to_owned()), - adapter_id: client.adapter_id(), - locale: Some("en-US".to_owned()), - path_format: Some(InitializeRequestArgumentsPathFormat::Path), - supports_variable_type: Some(true), - supports_variable_paging: Some(false), - supports_run_in_terminal_request: Some(true), - supports_memory_references: Some(true), - supports_progress_reporting: Some(false), - supports_invalidated_event: Some(false), - lines_start_at1: Some(Self::INDEX_STARTS_AT_ONE), - columns_start_at1: Some(Self::INDEX_STARTS_AT_ONE), - supports_memory_event: Some(false), - supports_args_can_be_interpreted_by_shell: Some(false), - supports_start_debugging_request: Some(true), - }) - .await?; - - this.update(&mut cx, |store, cx| { - store.update_capabilities_for_client(&session_id, client_id, &capabilities, cx); + client }) }) } pub fn configuration_done( &self, - client_id: DebugAdapterClientId, + session_id: SessionId, cx: &mut Context, ) -> Task> { let Some(client) = self - .client_by_id(client_id, cx) - .and_then(|(_, client)| client.read(cx).adapter_client()) + .session_by_id(session_id) + .and_then(|client| client.read(cx).adapter_client()) else { - return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); + return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id))); }; if self - .capabilities_by_id(client_id, cx) + .capabilities_by_id(session_id, cx) .map(|caps| caps.supports_configuration_done_request) .flatten() .unwrap_or_default() @@ -741,246 +681,98 @@ impl DapStore { } } - pub fn launch( + pub fn new_session( &mut self, - session_id: &DebugSessionId, - client_id: DebugAdapterClientId, + config: DebugAdapterConfig, cx: &mut Context, ) -> Task> { - let Some((session, client)) = self - .client_by_id(client_id, cx) - .and_then(|(session, client)| Some((session, client.read(cx).adapter_client()?))) - else { - return Task::ready(Err(anyhow!( - "Could not find debug client: {:?} for session {:?}", - client_id, - session_id - ))); - }; + // let config = session.read(cx).as_local().unwrap().configuration(); + // let mut adapter_args = client.adapter().request_args(&config); + // if let Some(args) = config.initialize_args.clone() { + // merge_json_value_into(args, &mut adapter_args); + // } - let config = session.read(cx).as_local().unwrap().configuration(); - let mut adapter_args = client.adapter().request_args(&config); - if let Some(args) = config.initialize_args.clone() { - merge_json_value_into(args, &mut adapter_args); - } + // // TODO(debugger): GDB starts the debuggee program on launch instead of configurationDone + // // causing our sent breakpoints to not be valid. This delay should eventually be taken out + // let delay = if &client.adapter_id() == "gdb" { + // Some( + // cx.background_executor() + // .timer(std::time::Duration::from_millis(20u64)), + // ) + // } else { + // None + // }; - // TODO(debugger): GDB starts the debuggee program on launch instead of configurationDone - // causing our sent breakpoints to not be valid. This delay should eventually be taken out - let delay = if &client.adapter_id() == "gdb" { - Some( - cx.background_executor() - .timer(std::time::Duration::from_millis(20u64)), - ) - } else { - None - }; + // cx.background_executor().spawn(async move { + // if let Some(delay) = delay { + // delay.await; + // } - cx.background_executor().spawn(async move { - if let Some(delay) = delay { - delay.await; - } - - client - .request::(LaunchRequestArguments { raw: adapter_args }) - .await - }) + // client + // .request::(LaunchRequestArguments { raw: adapter_args }) + // .await + // }) + Task::ready(Ok(())) } pub fn attach( &mut self, - session_id: &DebugSessionId, - client_id: DebugAdapterClientId, + session_id: SessionId, process_id: u32, cx: &mut Context, ) -> Task> { - let Some((session, client)) = self - .client_by_id(client_id, cx) - .and_then(|(session, client)| Some((session, client.read(cx).adapter_client()?))) - else { - return Task::ready(Err(anyhow!( - "Could not find debug client: {:?} for session {:?}", - client_id, - session_id - ))); - }; + unimplemented!(); + // let Some(client) = self + // .client_by_id(session_id) + // .and_then(|client| Some(client.read(cx).adapter_client()?)) + // else { + // return Task::ready(Err( + // anyhow!("Could not find debug client: {:?}", session_id,), + // )); + // }; - // update the process id on the config, so when the `startDebugging` reverse request - // comes in we send another `attach` request with the already selected PID - // If we don't do this the user has to select the process twice if the adapter sends a `startDebugging` request - session.update(cx, |session, cx| { - session.as_local_mut().unwrap().update_configuration( - |config| { - config.request = DebugRequestType::Attach(task::AttachConfig { - process_id: Some(process_id), - }); - }, - cx, - ); - }); + // // update the process id on the config, so when the `startDebugging` reverse request + // // comes in we send another `attach` request with the already selected PID + // // If we don't do this the user has to select the process twice if the adapter sends a `startDebugging` request + // session.update(cx, |session, cx| { + // session.as_local_mut().unwrap().update_configuration( + // |config| { + // config.request = DebugRequestType::Attach(task::AttachConfig { + // process_id: Some(process_id), + // }); + // }, + // cx, + // ); + // }); - let config = session.read(cx).as_local().unwrap().configuration(); - let mut adapter_args = client.adapter().request_args(&config); + // let config = session.read(cx).as_local().unwrap().configuration(); + // let mut adapter_args = client.adapter().request_args(&config); - if let Some(args) = config.initialize_args.clone() { - merge_json_value_into(args, &mut adapter_args); - } + // if let Some(args) = config.initialize_args.clone() { + // merge_json_value_into(args, &mut adapter_args); + // } - cx.background_executor().spawn(async move { - client - .request::(AttachRequestArguments { raw: adapter_args }) - .await - }) - } - - pub fn respond_to_start_debugging( - &mut self, - session_id: &DebugSessionId, - client_id: DebugAdapterClientId, - seq: u64, - args: Option, - cx: &mut Context, - ) -> Task> { - let Some((session, client)) = self - .client_by_id(client_id, cx) - .and_then(|(session, client)| Some((session, client.read(cx).adapter_client()?))) - else { - return Task::ready(Err(anyhow!( - "Could not find debug client: {:?} for session {:?}", - client_id, - session_id - ))); - }; - - let Some(config) = session - .read(cx) - .as_local() - .map(|session| session.configuration()) - else { - return Task::ready(Err(anyhow!("Cannot find debug session: {:?}", session_id))); - }; - - let session_id = *session_id; - - let request_args = args.unwrap_or_else(|| StartDebuggingRequestArguments { - configuration: config.initialize_args.clone().unwrap_or_default(), - request: match config.request { - DebugRequestType::Launch => StartDebuggingRequestArgumentsRequest::Launch, - DebugRequestType::Attach(_) => StartDebuggingRequestArgumentsRequest::Attach, - }, - }); - - // Merge the new configuration over the existing configuration - let mut initialize_args = config.initialize_args.clone().unwrap_or_default(); - merge_json_value_into(request_args.configuration, &mut initialize_args); - - let new_config = DebugAdapterConfig { - label: config.label.clone(), - kind: config.kind.clone(), - request: match &request_args.request { - StartDebuggingRequestArgumentsRequest::Launch => DebugRequestType::Launch, - StartDebuggingRequestArgumentsRequest::Attach => DebugRequestType::Attach( - if let DebugRequestType::Attach(attach_config) = &config.request { - attach_config.clone() - } else { - AttachConfig::default() - }, - ), - }, - program: config.program.clone(), - cwd: config.cwd.clone(), - initialize_args: Some(initialize_args), - }; - - cx.spawn(|this, mut cx| async move { - let (success, body) = { - let reconnect_task = this.update(&mut cx, |store, cx| { - if !client.adapter().supports_attach() - && matches!(new_config.request, DebugRequestType::Attach(_)) - { - Task::ready(Err(anyhow!( - "Debug adapter does not support `attach` request" - ))) - } else { - store.reconnect_client( - &session_id, - client.adapter().clone(), - client.binary().clone(), - new_config, - cx, - ) - } - }); - - match reconnect_task { - Ok(task) => match task.await { - Ok(_) => (true, None), - Err(error) => { - this.update(&mut cx, |_, cx| { - cx.emit(DapStoreEvent::Notification(error.to_string())); - }) - .log_err(); - - ( - false, - Some(serde_json::to_value(ErrorResponse { - error: Some(dap::Message { - id: seq, - format: error.to_string(), - variables: None, - send_telemetry: None, - show_user: None, - url: None, - url_label: None, - }), - })?), - ) - } - }, - Err(error) => ( - false, - Some(serde_json::to_value(ErrorResponse { - error: Some(dap::Message { - id: seq, - format: error.to_string(), - variables: None, - send_telemetry: None, - show_user: None, - url: None, - url_label: None, - }), - })?), - ), - } - }; - - client - .send_message(Message::Response(Response { - seq, - body, - success, - request_seq: seq, - command: StartDebugging::COMMAND.to_string(), - })) - .await - }) + // cx.background_executor().spawn(async move { + // client + // .request::(AttachRequestArguments { raw: adapter_args }) + // .await + // }) } pub fn respond_to_run_in_terminal( &self, - session_id: &DebugSessionId, - client_id: DebugAdapterClientId, + session_id: SessionId, success: bool, seq: u64, body: Option, cx: &mut Context, ) -> Task> { let Some(client) = self - .client_by_id(client_id, cx) - .and_then(|(_, client)| client.read(cx).adapter_client()) + .session_by_id(session_id) + .and_then(|client| client.read(cx).adapter_client()) else { return Task::ready(Err(anyhow!( - "Could not find debug client: {:?} for session {:?}", - client_id, + "Could not find debug client: {:?}", session_id ))); }; @@ -1000,7 +792,7 @@ impl DapStore { pub fn evaluate( &self, - client_id: &DebugAdapterClientId, + session_id: &SessionId, stack_frame_id: u64, expression: String, context: EvaluateArgumentsContext, @@ -1008,10 +800,10 @@ impl DapStore { cx: &mut Context, ) -> Task> { let Some(client) = self - .client_by_id(client_id, cx) - .and_then(|(_, client)| client.read(cx).adapter_client()) + .session_by_id(session_id) + .and_then(|client| client.read(cx).adapter_client()) else { - return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); + return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id))); }; cx.background_executor().spawn(async move { @@ -1031,17 +823,17 @@ impl DapStore { pub fn completions( &self, - client_id: &DebugAdapterClientId, + session_id: &SessionId, stack_frame_id: u64, text: String, completion_column: u64, cx: &mut Context, ) -> Task>> { let Some(client) = self - .client_by_id(client_id, cx) - .and_then(|(_, client)| client.read(cx).adapter_client()) + .session_by_id(session_id) + .and_then(|client| client.read(cx).adapter_client()) else { - return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); + return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id))); }; cx.background_executor().spawn(async move { @@ -1060,7 +852,7 @@ impl DapStore { #[allow(clippy::too_many_arguments)] pub fn set_variable_value( &self, - client_id: &DebugAdapterClientId, + session_id: &SessionId, stack_frame_id: u64, variables_reference: u64, name: String, @@ -1069,14 +861,14 @@ impl DapStore { cx: &mut Context, ) -> Task> { let Some(client) = self - .client_by_id(client_id, cx) - .and_then(|(_, client)| client.read(cx).adapter_client()) + .session_by_id(session_id) + .and_then(|client| client.read(cx).adapter_client()) else { - return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); + return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id))); }; let supports_set_expression = self - .capabilities_by_id(client_id, cx) + .capabilities_by_id(session_id, cx) .map(|caps| caps.supports_set_expression) .flatten() .unwrap_or_default(); @@ -1112,25 +904,7 @@ impl DapStore { // let returned_value = client.modules(); // this is a cheap getter. pub fn shutdown_sessions(&mut self, cx: &mut Context) -> Task<()> { - let Some(_) = self.as_local() else { - if let Some((upstream_client, project_id)) = self.upstream_client() { - return cx.background_executor().spawn(async move { - upstream_client - .request(proto::DapShutdownSession { - project_id, - session_id: None, - }) - .await - .log_err(); - - () - }); - } - return Task::ready(()); - }; - - let mut tasks = Vec::new(); - + let mut tasks = vec![]; for session_id in self.sessions.keys().cloned().collect::>() { tasks.push(self.shutdown_session(&session_id, cx)); } @@ -1142,14 +916,14 @@ impl DapStore { pub fn shutdown_session( &mut self, - session_id: &DebugSessionId, + session_id: &SessionId, cx: &mut Context, ) -> Task> { let Some(_) = self.as_local_mut() else { if let Some((upstream_client, project_id)) = self.upstream_client() { - let future = upstream_client.request(proto::DapShutdownSession { + let future = upstream_client.request(proto::ShutdownDebugClient { project_id, - session_id: Some(session_id.to_proto()), + session_id: session_id.to_proto(), }); return cx @@ -1159,119 +933,43 @@ impl DapStore { return Task::ready(Err(anyhow!("Cannot shutdown session on remote side"))); }; - - let Some(session) = self.sessions.remove(session_id) else { + let Some(client) = self.sessions.remove(session_id) else { return Task::ready(Err(anyhow!("Could not find session: {:?}", session_id))); }; - for client_id in session.read(cx).client_ids().collect::>() { - session.update(cx, |this, cx| { - this.shutdown_client(client_id, cx); - }); - } + client.update(cx, |this, cx| { + this.shutdown(cx); + }); + Task::ready(Ok(())) } - pub fn request_active_debug_sessions(&mut self, cx: &mut Context) { - if let Some((client, project_id)) = self.upstream_client() { - cx.spawn(|this, mut cx| async move { - let response = dbg!( - client - .request(proto::ActiveDebugSessionsRequest { project_id }) - .await - ) - .log_err(); + // async fn _handle_dap_command_2( + // this: Entity, + // envelope: TypedEnvelope, + // mut cx: AsyncApp, + // ) -> Result<::Response> + // where + // ::Arguments: Send, + // ::Response: Send, + // { + // let request = T::from_proto(&envelope.payload); + // let session_id = T::session_id_from_proto(&envelope.payload); - if let Some(response) = response { - this.update(&mut cx, |dap_store, cx| { - dap_store.set_debug_sessions_from_proto(response.sessions, cx) - }) - .log_err(); - } - }) - .detach(); - } - } + // let _state = this + // .update(&mut cx, |this, cx| { + // this.client_by_id(session_id)? + // .read(cx) + // ._wait_for_request(request) + // }) + // .ok() + // .flatten(); + // if let Some(_state) = _state { + // let _ = _state.await; + // } - pub fn set_debug_sessions_from_proto( - &mut self, - debug_sessions: Vec, - cx: &mut Context, - ) { - for session in debug_sessions.into_iter() { - let session_id = DebugSessionId::from_proto(session.session_id); - let ignore_breakpoints = Some(session.ignore_breakpoints); - - self.add_remote_session(session_id, ignore_breakpoints, cx); - - for debug_client in session.clients { - if let DapStoreMode::Remote(remote) = &mut self.mode { - if let Some(queue) = &mut remote.event_queue { - debug_client.debug_panel_items.into_iter().for_each(|item| { - queue.push_back(DapStoreEvent::SetDebugPanelItem(item)); - }); - } - cx.emit(DapStoreEvent::RemoteHasInitialized); - } - - let client = DebugAdapterClientId::from_proto(debug_client.client_id); - - self.add_client_to_session(session_id, client); - - self.update_capabilities_for_client( - &session_id, - client, - &dap::proto_conversions::capabilities_from_proto( - &debug_client.capabilities.unwrap_or_default(), - ), - cx, - ); - } - } - - cx.notify(); - } - - async fn handle_shutdown_session_request( - this: Entity, - envelope: TypedEnvelope, - mut cx: AsyncApp, - ) -> Result { - if let Some(session_id) = envelope.payload.session_id { - this.update(&mut cx, |dap_store, cx| { - dap_store.shutdown_session(&DebugSessionId::from_proto(session_id), cx) - })? - .await?; - } else { - this.update(&mut cx, |dap_store, cx| dap_store.shutdown_sessions(cx))? - .await; - } - - Ok(proto::Ack {}) - } - - async fn _handle_dap_command_2( - this: Entity, - envelope: TypedEnvelope, - mut cx: AsyncApp, - ) -> Result<::Response> - where - ::Arguments: Send, - ::Response: Send, - { - let request = T::from_proto(&envelope.payload); - let client_id = T::client_id_from_proto(&envelope.payload); - - let _state = this.update(&mut cx, |this, cx| { - this.session_by_client_id(client_id)? - .read(cx) - .client_state(client_id)? - .read(cx) - ._wait_for_request(request) - }); - - todo!() - } + // todo!() + // } // async fn handle_dap_command( // this: Entity, @@ -1283,28 +981,18 @@ impl DapStore { // ::Response: Send, // { // let _sender_id = envelope.original_sender_id().unwrap_or_default(); - // let client_id = T::client_id_from_proto(&envelope.payload); + // let session_id = T::session_id_from_proto(&envelope.payload); // let request = T::from_proto(&envelope.payload); // let response = this // .update(&mut cx, |this, cx| { - // this.request_dap::(&client_id, request, cx) + // this.request_dap::(&session_id, request, cx) // })? // .await?; - // Ok(T::response_to_proto(&client_id, response)) + // Ok(T::response_to_proto(&session_id, response)) // } - async fn handle_set_debug_panel_item( - this: Entity, - envelope: TypedEnvelope, - mut cx: AsyncApp, - ) -> Result<()> { - this.update(&mut cx, |_, cx| { - cx.emit(DapStoreEvent::SetDebugPanelItem(envelope.payload)); - }) - } - async fn handle_update_debug_adapter( this: Entity, envelope: TypedEnvelope, @@ -1332,8 +1020,7 @@ impl DapStore { ) -> Result<()> { this.update(&mut cx, |dap_store, cx| { dap_store.update_capabilities_for_client( - &DebugSessionId::from_proto(envelope.payload.session_id), - DebugAdapterClientId::from_proto(envelope.payload.client_id), + SessionId::from_proto(envelope.payload.session_id), &dap::proto_conversions::capabilities_from_proto(&envelope.payload), cx, ); @@ -1346,15 +1033,15 @@ impl DapStore { mut cx: AsyncApp, ) -> Result<()> { this.update(&mut cx, |dap_store, cx| { - let client_id = DebugAdapterClientId::from_proto(envelope.payload.client_id); + let session_id = SessionId::from_proto(envelope.payload.session_id); - dap_store.session_by_client_id(client_id).map(|state| { - state.update(cx, |this, _| { - this.states.remove(&client_id); + dap_store.session_by_id(session_id).map(|state| { + state.update(cx, |state, cx| { + state.shutdown(cx); }) }); - cx.emit(DapStoreEvent::DebugClientShutdown(client_id)); + cx.emit(DapStoreEvent::DebugClientShutdown(session_id)); cx.notify(); }) } @@ -1373,7 +1060,7 @@ impl DapStore { this.update(&mut cx, |store, cx| { store.active_debug_line = Some(( - DebugAdapterClientId::from_proto(envelope.payload.client_id), + SessionId::from_proto(envelope.payload.session_id), project_path, envelope.payload.row, )); @@ -1398,7 +1085,7 @@ impl DapStore { pub fn send_breakpoints( &self, - client_id: DebugAdapterClientId, + session_id: SessionId, absolute_file_path: Arc, mut breakpoints: Vec, ignore: bool, @@ -1406,15 +1093,14 @@ impl DapStore { cx: &App, ) -> Task> { let Some(client) = self - .client_by_id(client_id, cx) - .and_then(|(_, client)| client.read(cx).adapter_client()) + .session_by_id(session_id) + .and_then(|client| client.read(cx).adapter_client()) else { - return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); + return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id))); }; - if Self::INDEX_STARTS_AT_ONE { - breakpoints.iter_mut().for_each(|bp| bp.line += 1u64) - } + // Adjust breakpoints as our client declares that indices start at one. + breakpoints.iter_mut().for_each(|bp| bp.line += 1u64); cx.background_executor().spawn(async move { client @@ -1461,23 +1147,22 @@ impl DapStore { .collect::>(); let mut tasks = Vec::new(); - for session in self + for (session_id, client) in self .sessions - .values() - .filter(|session| session.read(cx).as_local().is_some()) + .iter() + .filter(|(_, client)| client.read(cx).adapter_client().is_some()) { - let session = session.read(cx); - let ignore_breakpoints = session.ignore_breakpoints(); - for client_id in session.client_ids().collect::>() { - tasks.push(self.send_breakpoints( - client_id, - Arc::from(absolute_path.clone()), - source_breakpoints.clone(), - ignore_breakpoints, - source_changed, - cx, - )); - } + let client = client.read(cx); + let ignore_breakpoints = !client.breakpoints_enabled(); + + tasks.push(self.send_breakpoints( + *session_id, + Arc::from(absolute_path.clone()), + source_breakpoints.clone(), + ignore_breakpoints, + source_changed, + cx, + )); } if tasks.is_empty() { diff --git a/crates/project/src/debugger/dap_session.rs b/crates/project/src/debugger/session.rs similarity index 80% rename from crates/project/src/debugger/dap_session.rs rename to crates/project/src/debugger/session.rs index ccbd7645a4..b680e20671 100644 --- a/crates/project/src/debugger/dap_session.rs +++ b/crates/project/src/debugger/session.rs @@ -1,20 +1,28 @@ +use crate::project_settings::ProjectSettings; + +use super::breakpoint_store::BreakpointStore; use super::dap_command::{ - self, ContinueCommand, DapCommand, DisconnectCommand, EvaluateCommand, NextCommand, - PauseCommand, RestartCommand, RestartStackFrameCommand, ScopesCommand, SetVariableValueCommand, - StepBackCommand, StepCommand, StepInCommand, StepOutCommand, TerminateCommand, - TerminateThreadsCommand, VariablesCommand, + self, ContinueCommand, DapCommand, DisconnectCommand, EvaluateCommand, Initialize, + LocalDapCommand, NextCommand, PauseCommand, RestartCommand, RestartStackFrameCommand, + ScopesCommand, SetVariableValueCommand, StepBackCommand, StepCommand, StepInCommand, + StepOutCommand, TerminateCommand, TerminateThreadsCommand, VariablesCommand, }; +use super::dap_store::DapAdapterDelegate; use anyhow::{anyhow, Result}; -use collections::{BTreeMap, HashMap, IndexMap}; -use dap::client::{DebugAdapterClient, DebugAdapterClientId}; +use collections::{HashMap, IndexMap}; +use dap::adapters::{DapDelegate, DapStatus, DebugAdapterName}; +use dap::client::{DebugAdapterClient, SessionId}; use dap::{ - Capabilities, ContinueArguments, EvaluateArgumentsContext, Module, Source, SteppingGranularity, + messages::Message, Capabilities, ContinueArguments, EvaluateArgumentsContext, Module, Source, + SteppingGranularity, }; +use dap_adapters::build_adapter; use futures::{future::Shared, FutureExt}; -use gpui::{App, AppContext, Context, Entity, Task}; +use gpui::{App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, Task}; use rpc::AnyProtoClient; use serde_json::Value; -use std::borrow::Borrow; +use settings::Settings; +use std::path::PathBuf; use std::u64; use std::{ any::Any, @@ -26,20 +34,6 @@ use task::DebugAdapterConfig; use text::{PointUtf16, ToPointUtf16}; use util::ResultExt; -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct DebugSessionId(pub usize); - -impl DebugSessionId { - pub fn from_proto(session_id: u64) -> Self { - Self(session_id as usize) - } - - pub fn to_proto(&self) -> u64 { - self.0 as u64 - } -} - #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Ord, Eq)] #[repr(transparent)] pub struct ThreadId(pub u64); @@ -123,7 +117,7 @@ impl From for Thread { type UpstreamProjectId = u64; -pub struct RemoteConnection { +struct RemoteConnection { client: AnyProtoClient, upstream_project_id: UpstreamProjectId, } @@ -132,52 +126,109 @@ impl RemoteConnection { fn send_proto_client_request( &self, request: R, - client_id: DebugAdapterClientId, + session_id: SessionId, cx: &mut App, ) -> Task> { - let message = request.to_proto(client_id, self.upstream_project_id); + let message = request.to_proto(session_id, self.upstream_project_id); let upstream_client = self.client.clone(); cx.background_executor().spawn(async move { let response = upstream_client.request(message).await?; request.response_from_proto(response) }) } - fn request_remote( + fn request( &self, request: R, - client_id: DebugAdapterClientId, + session_id: SessionId, cx: &mut App, ) -> Task> where ::Response: 'static, ::Arguments: 'static + Send, { - return self.send_proto_client_request::(request, client_id, cx); + return self.send_proto_client_request::(request, session_id, cx); } } -pub enum Mode { - Local(Arc), +enum Mode { + Local(LocalMode), Remote(RemoteConnection), } -impl From for Mode { - fn from(value: RemoteConnection) -> Self { - Self::Remote(value) - } +struct LocalMode { + client: Arc, } -impl From> for Mode { - fn from(client: Arc) -> Self { - Mode::Local(client) - } -} +impl LocalMode { + fn new( + session_id: SessionId, + breakpoint_store: Entity, + disposition: DebugAdapterConfig, + delegate: DapAdapterDelegate, + message_handler: F, + cx: AsyncApp, + ) -> Task> + where + F: FnMut(Message, &mut App) + 'static + Send + Sync + Clone, + { + cx.spawn(move |mut cx| async move { + let adapter = build_adapter(&disposition.kind).await?; -impl Mode { - fn request_local( - connection: &Arc, + let binary = cx.update(|cx| { + let name = DebugAdapterName::from(adapter.name().as_ref()); + + ProjectSettings::get_global(cx) + .dap + .get(&name) + .and_then(|s| s.binary.as_ref().map(PathBuf::from)) + })?; + + let binary = match adapter + .get_binary(&delegate, &disposition, binary, &mut 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 + } + }; + + let client = Arc::new( + DebugAdapterClient::start(session_id, binary, message_handler, cx.clone()).await?, + ); + let this = Self { client }; + let capabilities = this + .request( + Initialize { + adapter_id: "zed-dap-this-value-needs-changing".to_owned(), + }, + cx.background_executor().clone(), + ) + .await?; + Ok((this, capabilities)) + }) + } + + fn request( + &self, request: R, - cx: &mut Context, + executor: BackgroundExecutor, ) -> Task> where ::Response: 'static, @@ -186,23 +237,30 @@ impl Mode { let request = Arc::new(request); let request_clone = request.clone(); - let connection = connection.clone(); - let request_task = cx.background_executor().spawn(async move { + let connection = self.client.clone(); + let request_task = executor.spawn(async move { let args = request_clone.to_dap(); connection.request::(args).await }); - cx.background_executor().spawn(async move { + executor.spawn(async move { let response = request.response_from_dap(request_task.await?); response }) } +} +impl From for Mode { + fn from(value: RemoteConnection) -> Self { + Self::Remote(value) + } +} +impl Mode { fn request_dap( &self, - client_id: DebugAdapterClientId, + session_id: SessionId, request: R, - cx: &mut Context, + cx: &mut Context, ) -> Task> where ::Response: 'static, @@ -210,21 +268,20 @@ impl Mode { { match self { Mode::Local(debug_adapter_client) => { - Self::request_local(&debug_adapter_client, request, cx) - } - Mode::Remote(remote_connection) => { - remote_connection.request_remote(request, client_id, cx) + debug_adapter_client.request(request, cx.background_executor().clone()) } + Mode::Remote(remote_connection) => remote_connection.request(request, session_id, cx), } } } /// Represents a current state of a single debug adapter and provides ways to mutate it. -pub struct Client { +pub struct Session { mode: Mode, - + config: DebugAdapterConfig, pub(super) capabilities: Capabilities, - client_id: DebugAdapterClientId, + id: SessionId, + ignore_breakpoints: bool, modules: Vec, loaded_sources: Vec, threads: IndexMap, @@ -306,10 +363,66 @@ impl CompletionsQuery { } } -impl Client { +impl Session { + pub(crate) fn local( + breakpoints: Entity, + session_id: SessionId, + delegate: DapAdapterDelegate, + config: DebugAdapterConfig, + cx: &mut App, + ) -> Task>> { + cx.spawn(move |mut cx| async move { + let (mode, capabilities) = LocalMode::new( + session_id, + breakpoints, + config.clone(), + delegate, + |_, _| {}, + cx.clone(), + ) + .await?; + cx.new(|_| Self { + mode: Mode::Local(mode), + id: session_id, + config, + capabilities, + ignore_breakpoints: false, + requests: HashMap::default(), + modules: Vec::default(), + loaded_sources: Vec::default(), + threads: IndexMap::default(), + }) + }) + } + + pub(crate) fn remote( + session_id: SessionId, + client: AnyProtoClient, + upstream_project_id: u64, + ignore_breakpoints: bool, + ) -> Self { + Self { + mode: Mode::Remote(RemoteConnection { + client, + upstream_project_id, + }), + id: session_id, + capabilities: Capabilities::default(), + ignore_breakpoints, + requests: HashMap::default(), + modules: Vec::default(), + loaded_sources: Vec::default(), + threads: IndexMap::default(), + config: todo!(), + } + } + pub fn capabilities(&self) -> &Capabilities { &self.capabilities } + pub fn configuration(&self) -> DebugAdapterConfig { + self.config.clone() + } pub(crate) fn _wait_for_request( &self, @@ -331,7 +444,7 @@ impl Client { let task = Self::request_inner::>( &self.capabilities, - self.client_id, + self.id, &self.mode, command, process_result, @@ -351,7 +464,7 @@ impl Client { fn request_inner( capabilities: &Capabilities, - client_id: DebugAdapterClientId, + session_id: SessionId, mode: &Mode, request: T, process_result: impl FnOnce(&mut Self, &T::Response, &mut Context) + 'static, @@ -360,7 +473,7 @@ impl Client { if !T::is_supported(&capabilities) { return Task::ready(None); } - let request = mode.request_dap(client_id, request, cx); + let request = mode.request_dap(session_id, request, cx); cx.spawn(|this, mut cx| async move { let result = request.await.log_err()?; this.update(&mut cx, |this, cx| { @@ -379,7 +492,7 @@ impl Client { ) -> Task> { Self::request_inner( &self.capabilities, - self.client_id, + self.id, &self.mode, request, process_result, @@ -425,6 +538,12 @@ impl Client { &self.modules } + pub fn set_ignore_breakpoints(&mut self, ignore: bool) { + self.ignore_breakpoints = ignore; + } + pub fn breakpoints_enabled(&self) -> bool { + self.ignore_breakpoints + } pub fn handle_module_event(&mut self, event: &dap::ModuleEvent, cx: &mut Context) { match event.reason { dap::ModuleEventReason::New => self.modules.push(event.module.clone()), @@ -496,7 +615,7 @@ impl Client { } } - fn shutdown(&mut self, cx: &mut Context) { + pub(super) fn shutdown(&mut self, cx: &mut Context) { if self .capabilities .supports_terminate_request @@ -556,7 +675,7 @@ impl Client { pub fn adapter_client(&self) -> Option> { match self.mode { - Mode::Local(ref adapter_client) => Some(adapter_client.clone()), + Mode::Local(ref adapter_client) => Some(adapter_client.client.clone()), Mode::Remote(_) => None, } } @@ -776,13 +895,11 @@ impl Client { &mut self, thread_id: ThreadId, stack_frame_id: u64, - session_id: DebugSessionId, variables_reference: u64, cx: &mut Context, ) -> Vec { let command = VariablesCommand { stack_frame_id, - session_id, thread_id: thread_id.0, variables_reference, filter: None, @@ -884,153 +1001,3 @@ impl Client { } } } - -pub struct DebugSession { - id: DebugSessionId, - mode: DebugSessionMode, - pub(super) states: BTreeMap>, - ignore_breakpoints: bool, -} - -pub enum DebugSessionMode { - Local(LocalDebugSession), - Remote(RemoteDebugSession), -} - -pub struct LocalDebugSession { - configuration: DebugAdapterConfig, -} - -impl LocalDebugSession { - pub fn configuration(&self) -> &DebugAdapterConfig { - &self.configuration - } - - pub fn update_configuration( - &mut self, - f: impl FnOnce(&mut DebugAdapterConfig), - cx: &mut Context, - ) { - f(&mut self.configuration); - cx.notify(); - } -} - -pub struct RemoteDebugSession { - label: String, -} - -impl DebugSession { - pub fn new_local(id: DebugSessionId, configuration: DebugAdapterConfig) -> Self { - Self { - id, - ignore_breakpoints: false, - states: BTreeMap::default(), - mode: DebugSessionMode::Local(LocalDebugSession { configuration }), - } - } - - pub fn as_local(&self) -> Option<&LocalDebugSession> { - match &self.mode { - DebugSessionMode::Local(local) => Some(local), - _ => None, - } - } - - pub fn as_local_mut(&mut self) -> Option<&mut LocalDebugSession> { - match &mut self.mode { - DebugSessionMode::Local(local) => Some(local), - _ => None, - } - } - - pub fn new_remote(id: DebugSessionId, label: String, ignore_breakpoints: bool) -> Self { - Self { - id, - ignore_breakpoints, - states: BTreeMap::default(), - mode: DebugSessionMode::Remote(RemoteDebugSession { label }), - } - } - - pub fn id(&self) -> DebugSessionId { - self.id - } - - pub fn name(&self) -> String { - match &self.mode { - DebugSessionMode::Local(local) => local.configuration.label.clone(), - DebugSessionMode::Remote(remote) => remote.label.clone(), - } - } - - pub fn ignore_breakpoints(&self) -> bool { - self.ignore_breakpoints - } - - pub fn set_ignore_breakpoints(&mut self, ignore: bool, cx: &mut Context) { - self.ignore_breakpoints = ignore; - cx.notify(); - } - - pub fn client_state(&self, client_id: DebugAdapterClientId) -> Option> { - self.states.get(&client_id).cloned() - } - - pub(super) fn client_ids(&self) -> impl Iterator + '_ { - self.states.keys().copied() - } - - pub fn clients(&self, cx: &App) -> Vec> { - self.states - .values() - .filter_map(|state| state.read(cx).adapter_client()) - .collect() - } - - pub fn add_client( - &mut self, - client: impl Into, - client_id: DebugAdapterClientId, - cx: &mut Context, - ) { - if !self.states.contains_key(&client_id) { - let mode = client.into(); - let state = cx.new(|_cx| Client { - client_id, - modules: Vec::default(), - loaded_sources: Vec::default(), - threads: IndexMap::default(), - requests: HashMap::default(), - capabilities: Default::default(), - mode, - }); - - self.states.insert(client_id, state); - } - } - - pub(crate) fn client_by_id( - &self, - client_id: impl Borrow, - ) -> Option> { - self.states.get(client_id.borrow()).cloned() - } - - pub(crate) fn shutdown_client( - &mut self, - client_id: DebugAdapterClientId, - cx: &mut Context, - ) { - if let Some(client) = self.states.remove(&client_id) { - client.update(cx, |this, cx| { - this.shutdown(cx); - }) - } - } - - #[cfg(any(test, feature = "test-support"))] - pub fn clients_len(&self) -> usize { - self.states.len() - } -} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 82fc4d9e89..7211e83d2a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -29,10 +29,7 @@ use git::Repository; pub mod search_history; mod yarn; -use crate::{ - debugger::dap_session::{DebugSession, DebugSessionId}, - git::GitStore, -}; +use crate::git::GitStore; use anyhow::{anyhow, Context as _, Result}; use buffer_store::{BufferStore, BufferStoreEvent}; @@ -42,7 +39,7 @@ use client::{ use clock::ReplicaId; use dap::{ - client::{DebugAdapterClient, DebugAdapterClientId}, + client::{DebugAdapterClient, SessionId}, debugger_settings::DebuggerSettings, messages::Message, DebugAdapterConfig, @@ -90,9 +87,7 @@ pub use prettier_store::PrettierStore; use project_settings::{ProjectSettings, SettingsObserver, SettingsObserverEvent}; use remote::{SshConnectionOptions, SshRemoteClient}; use rpc::{ - proto::{ - FromProto, LanguageServerPromptResponse, SetDebuggerPanelItem, ToProto, SSH_PROJECT_ID, - }, + proto::{FromProto, LanguageServerPromptResponse, ToProto, SSH_PROJECT_ID}, AnyProtoClient, ErrorCode, }; use search::{SearchInputKind, SearchQuery, SearchResult}; @@ -275,16 +270,14 @@ pub enum Event { notification_id: SharedString, }, LanguageServerPrompt(LanguageServerPromptRequest), - DebugClientStarted((DebugSessionId, DebugAdapterClientId)), - DebugClientShutdown(DebugAdapterClientId), - SetDebugClient(SetDebuggerPanelItem), + DebugClientStarted(SessionId), + DebugClientShutdown(SessionId), ActiveDebugLineChanged, DebugClientEvent { - session_id: DebugSessionId, - client_id: DebugAdapterClientId, + session_id: SessionId, message: Message, }, - DebugClientLog(DebugAdapterClientId, String), + DebugClientLog(SessionId, String), LanguageNotFound(Entity), ActiveEntryChanged(Option), ActivateProjectPanel, @@ -1090,7 +1083,7 @@ impl Project { let breakpoint_store = cx.new(|cx| { let mut bp_store = { BreakpointStore::remote( - SSH_PROJECT_ID, + remote_id, client.clone().into(), buffer_store.clone(), worktree_store.clone(), @@ -1106,7 +1099,7 @@ impl Project { let mut dap_store = DapStore::new_remote(remote_id, client.clone().into(), breakpoint_store.clone()); - dap_store.request_active_debug_sessions(cx); + unimplemented!("dap_store.request_active_debug_sessions(cx)"); dap_store })?; @@ -1313,8 +1306,7 @@ impl Project { pub fn initial_send_breakpoints( &self, - session_id: &DebugSessionId, - client_id: DebugAdapterClientId, + session_id: SessionId, cx: &mut Context, ) -> Task<()> { let mut tasks = Vec::new(); @@ -1332,10 +1324,10 @@ impl Project { tasks.push(self.dap_store.update(cx, |store, cx| { store.send_breakpoints( - client_id, + session_id, abs_path, source_breakpoints, - store.ignore_breakpoints(session_id, cx), + store.ignore_breakpoints(&session_id, cx), false, cx, ) @@ -1351,7 +1343,7 @@ impl Project { &mut self, config: DebugAdapterConfig, cx: &mut Context, - ) -> Task, Arc)>> { + ) -> Task>> { let worktree = maybe!({ if let Some(cwd) = &config.cwd { Some(self.find_worktree(cwd.as_path(), cx)?.0) @@ -1387,8 +1379,7 @@ impl Project { if let Some((_, _)) = project.dap_store.read(cx).downstream_client() { project .toggle_ignore_breakpoints( - &DebugSessionId::from_proto(envelope.payload.session_id), - DebugAdapterClientId::from_proto(envelope.payload.client_id), + SessionId::from_proto(envelope.payload.client_id), cx, ) .detach_and_log_err(cx); @@ -1398,16 +1389,14 @@ impl Project { pub fn toggle_ignore_breakpoints( &self, - session_id: &DebugSessionId, - client_id: DebugAdapterClientId, + session_id: SessionId, cx: &mut Context, ) -> Task> { let tasks = self.dap_store.update(cx, |store, cx| { if let Some((upstream_client, project_id)) = store.upstream_client() { upstream_client .send(proto::ToggleIgnoreBreakpoints { - session_id: session_id.to_proto(), - client_id: client_id.to_proto(), + client_id: session_id.to_proto(), project_id, }) .log_err(); @@ -1415,14 +1404,14 @@ impl Project { return Vec::new(); } - store.toggle_ignore_breakpoints(session_id, cx); + store.toggle_ignore_breakpoints(&session_id, cx); if let Some((downstream_client, project_id)) = store.downstream_client() { downstream_client .send(proto::IgnoreBreakpointState { session_id: session_id.to_proto(), project_id: *project_id, - ignore: store.ignore_breakpoints(session_id, cx), + ignore: store.ignore_breakpoints(&session_id, cx), }) .log_err(); } @@ -1448,13 +1437,13 @@ impl Project { tasks.push( store.send_breakpoints( - client_id, + session_id, Arc::from(buffer_path), breakpoints .into_iter() .map(|breakpoint| breakpoint.to_source_breakpoint(buffer)) .collect::>(), - store.ignore_breakpoints(session_id, cx), + store.ignore_breakpoints(&session_id, cx), false, cx, ), @@ -2666,20 +2655,18 @@ impl Project { cx: &mut Context, ) { match event { - DapStoreEvent::DebugClientStarted(client_id) => { - cx.emit(Event::DebugClientStarted(*client_id)); + DapStoreEvent::DebugClientStarted(session_id) => { + cx.emit(Event::DebugClientStarted(*session_id)); } - DapStoreEvent::DebugClientShutdown(client_id) => { - cx.emit(Event::DebugClientShutdown(*client_id)); + DapStoreEvent::DebugClientShutdown(session_id) => { + cx.emit(Event::DebugClientShutdown(*session_id)); } DapStoreEvent::DebugClientEvent { session_id, - client_id, message, } => { cx.emit(Event::DebugClientEvent { session_id: *session_id, - client_id: *client_id, message: message.clone(), }); } diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index d7899d297b..017b79bb33 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -327,48 +327,42 @@ message Envelope { SynchronizeBreakpoints synchronize_breakpoints = 304; SetActiveDebugLine set_active_debug_line = 305; RemoveActiveDebugLine remove_active_debug_line = 306; - SetDebuggerPanelItem set_debugger_panel_item = 307; - UpdateDebugAdapter update_debug_adapter = 308; - ShutdownDebugClient shutdown_debug_client = 309; - SetDebugClientCapabilities set_debug_client_capabilities = 310; - - DapNextRequest dap_next_request = 311; - DapStepInRequest dap_step_in_request = 312; - DapStepOutRequest dap_step_out_request = 313; - DapStepBackRequest dap_step_back_request = 314; - DapContinueRequest dap_continue_request = 315; - DapContinueResponse dap_continue_response = 316; - DapPauseRequest dap_pause_request = 317; - DapDisconnectRequest dap_disconnect_request = 318; - DapTerminateThreadsRequest dap_terminate_threads_request = 319; - DapTerminateRequest dap_terminate_request = 320; - DapRestartRequest dap_restart_request = 321; - DapShutdownSession dap_shutdown_session = 322; - UpdateThreadStatus update_thread_status = 323; - VariablesRequest variables_request = 324; - DapVariables dap_variables = 325; - DapRestartStackFrameRequest dap_restart_stack_frame_request = 326; - IgnoreBreakpointState ignore_breakpoint_state = 327; - ToggleIgnoreBreakpoints toggle_ignore_breakpoints = 328; - DebuggerSessionEnded debugger_session_ended = 329; - DapModulesRequest dap_modules_request = 330; - DapModulesResponse dap_modules_response = 331; - DapLoadedSourcesRequest dap_loaded_sources_request = 332; - DapLoadedSourcesResponse dap_loaded_sources_response = 333; - ActiveDebugSessionsRequest active_debug_sessions_request = 334; - ActiveDebugSessionsResponse active_debug_sessions_response = 335; - DapStackTraceRequest dap_stack_trace_request = 336; - DapStackTraceResponse dap_stack_trace_response = 337; - DapScopesRequest dap_scopes_request = 338; - DapScopesResponse dap_scopes_response = 339; - DapSetVariableValueRequest dap_set_variable_value_request = 340; - DapSetVariableValueResponse dap_set_variable_value_response = 341; - DapEvaluateRequest dap_evaluate_request = 342; - DapEvaluateResponse dap_evaluate_response = 343; - DapCompletionRequest dap_completion_request = 344; - DapCompletionResponse dap_completion_response = 345; - DapThreadsRequest dap_threads_request = 346; - DapThreadsResponse dap_threads_response = 347; // current max + UpdateDebugAdapter update_debug_adapter = 307; + ShutdownDebugClient shutdown_debug_client = 308; + SetDebugClientCapabilities set_debug_client_capabilities = 309; + DapNextRequest dap_next_request = 310; + DapStepInRequest dap_step_in_request = 311; + DapStepOutRequest dap_step_out_request = 312; + DapStepBackRequest dap_step_back_request = 313; + DapContinueRequest dap_continue_request = 314; + DapContinueResponse dap_continue_response = 315; + DapPauseRequest dap_pause_request = 316; + DapDisconnectRequest dap_disconnect_request = 317; + DapTerminateThreadsRequest dap_terminate_threads_request = 318; + DapTerminateRequest dap_terminate_request = 319; + DapRestartRequest dap_restart_request = 320; + UpdateThreadStatus update_thread_status = 321; + VariablesRequest variables_request = 322; + DapVariables dap_variables = 323; + DapRestartStackFrameRequest dap_restart_stack_frame_request = 324; + IgnoreBreakpointState ignore_breakpoint_state = 325; + ToggleIgnoreBreakpoints toggle_ignore_breakpoints = 326; + DapModulesRequest dap_modules_request = 327; + DapModulesResponse dap_modules_response = 328; + DapLoadedSourcesRequest dap_loaded_sources_request = 329; + DapLoadedSourcesResponse dap_loaded_sources_response = 330; + DapStackTraceRequest dap_stack_trace_request = 331; + DapStackTraceResponse dap_stack_trace_response = 332; + DapScopesRequest dap_scopes_request = 333; + DapScopesResponse dap_scopes_response = 334; + DapSetVariableValueRequest dap_set_variable_value_request = 335; + DapSetVariableValueResponse dap_set_variable_value_response = 336; + DapEvaluateRequest dap_evaluate_request = 337; + DapEvaluateResponse dap_evaluate_response = 338; + DapCompletionRequest dap_completion_request = 339; + DapCompletionResponse dap_completion_response = 340; + DapThreadsRequest dap_threads_request = 341; + DapThreadsResponse dap_threads_response = 342;// current max } reserved 87 to 88; @@ -2561,51 +2555,35 @@ enum BreakpointKind { Log = 1; } -message DebuggerSessionEnded { - uint64 project_id = 1; - uint64 session_id = 2; -} -message ActiveDebugSessionsRequest { - uint64 project_id = 1; -} - -message ActiveDebugSessionsResponse { - repeated DebuggerSession sessions = 1; -} - -message DebuggerSession { - uint64 session_id = 1; - bool ignore_breakpoints = 2; - repeated DebugClient clients = 3; +message ActiveDebugClientsResponse { + repeated DebugClient clients = 1; } message DebugClient { uint64 client_id = 1; SetDebugClientCapabilities capabilities = 2; - repeated SetDebuggerPanelItem debug_panel_items = 3; + bool ignore_breakpoints = 3; } message ShutdownDebugClient { - uint64 session_id = 1; - uint64 client_id = 2; - uint64 project_id = 3; + uint64 project_id = 1; + uint64 session_id = 2; } message SetDebugClientCapabilities { uint64 session_id = 1; - uint64 client_id = 2; - uint64 project_id = 3; - bool supports_loaded_sources_request = 4; - bool supports_modules_request = 5; - bool supports_restart_request = 6; - bool supports_set_expression = 7; - bool supports_single_thread_execution_requests = 8; - bool supports_step_back = 9; - bool supports_stepping_granularity = 10; - bool supports_terminate_threads_request = 11; - bool supports_restart_frame_request = 12; - bool supports_clipboard_context = 13; + uint64 project_id = 2; + bool supports_loaded_sources_request = 3; + bool supports_modules_request = 4; + bool supports_restart_request = 5; + bool supports_set_expression = 6; + bool supports_single_thread_execution_requests = 7; + bool supports_step_back = 8; + bool supports_stepping_granularity = 9; + bool supports_terminate_threads_request = 10; + bool supports_restart_frame_request = 11; + bool supports_clipboard_context = 12; } message Breakpoint { @@ -2624,7 +2602,7 @@ message SynchronizeBreakpoints { message SetActiveDebugLine { uint64 project_id = 1; ProjectPath project_path = 2; - uint64 client_id = 3; + uint64 session_id = 3; uint32 row = 4; } @@ -2737,13 +2715,12 @@ message VariablesRequest { uint64 project_id = 1; uint64 client_id = 2; uint64 thread_id = 3; - uint64 session_id = 4; - uint64 stack_frame_id = 5; - uint64 variables_reference = 6; - optional VariablesArgumentsFilter filter = 7; - optional uint64 start = 8; - optional uint64 count = 9; - optional ValueFormat format = 10; + uint64 stack_frame_id = 4; + uint64 variables_reference = 5; + optional VariablesArgumentsFilter filter = 6; + optional uint64 start = 7; + optional uint64 count = 8; + optional ValueFormat format = 9; } @@ -2911,15 +2888,9 @@ message DapRestartStackFrameRequest { uint64 stack_frame_id = 3; } -message DapShutdownSession { - uint64 project_id = 1; - optional uint64 session_id = 2; // Shutdown all sessions if this is None -} - message ToggleIgnoreBreakpoints { uint64 project_id = 1; uint64 client_id = 2; - uint64 session_id = 3; } message IgnoreBreakpointState { @@ -3033,26 +3004,10 @@ message DebuggerModuleList { uint64 client_id = 2; } -message SetDebuggerPanelItem { - uint64 project_id = 1; - uint64 session_id = 2; - uint64 client_id = 3; - uint64 thread_id = 4; - string session_name = 5; - DebuggerConsole console = 6; - optional DebuggerModuleList module_list = 7; // We only send this when it's supported and once per client - DebuggerThreadItem active_thread_item = 8; - DebuggerThreadState thread_state = 9; - DebuggerVariableList variable_list = 10; - DebuggerStackFrameList stack_frame_list = 11; - DebuggerLoadedSourceList loaded_source_list = 12; -} - message UpdateDebugAdapter { uint64 project_id = 1; uint64 client_id = 2; optional uint64 thread_id = 3; - uint64 session_id = 4; oneof variant { DebuggerThreadState thread_state = 5; DebuggerStackFrameList stack_frame_list = 6; @@ -3062,8 +3017,6 @@ message UpdateDebugAdapter { } } - - message DapVariables { uint64 client_id = 1; repeated DapVariable variables = 2; diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index 8e03515594..7dcc61a777 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -249,11 +249,9 @@ messages!( (DapPauseRequest, Background), (DapRestartRequest, Background), (DapRestartStackFrameRequest, Background), - (DapShutdownSession, Background), (DapStepBackRequest, Background), (DapStepInRequest, Background), (DapStepOutRequest, Background), - (DapTerminateRequest, Background), (DapTerminateThreadsRequest, Background), (DeclineCall, Foreground), (DeleteChannel, Foreground), @@ -416,7 +414,6 @@ messages!( (SetChannelMemberRole, Foreground), (SetChannelVisibility, Foreground), (SetDebugClientCapabilities, Background), - (SetDebuggerPanelItem, Background), (SetRoomParticipantRole, Foreground), (ShareProject, Foreground), (ShareProjectResponse, Foreground), @@ -474,9 +471,6 @@ messages!( (DapVariables, Background), (IgnoreBreakpointState, Background), (ToggleIgnoreBreakpoints, Background), - (DebuggerSessionEnded, Background), - (ActiveDebugSessionsRequest, Foreground), - (ActiveDebugSessionsResponse, Foreground), (DapStackTraceRequest, Background), (DapStackTraceResponse, Background), (DapScopesRequest, Background), @@ -489,6 +483,7 @@ messages!( (DapCompletionResponse, Background), (DapThreadsRequest, Background), (DapThreadsResponse, Background), + (DapTerminateRequest, Background) ); request_messages!( @@ -636,18 +631,17 @@ request_messages!( (DapPauseRequest, Ack), (DapDisconnectRequest, Ack), (DapTerminateThreadsRequest, Ack), - (DapTerminateRequest, Ack), (DapRestartRequest, Ack), (DapRestartStackFrameRequest, Ack), - (DapShutdownSession, Ack), (VariablesRequest, DapVariables), - (ActiveDebugSessionsRequest, ActiveDebugSessionsResponse), (DapStackTraceRequest, DapStackTraceResponse), (DapScopesRequest, DapScopesResponse), (DapSetVariableValueRequest, DapSetVariableValueResponse), (DapEvaluateRequest, DapEvaluateResponse), (DapCompletionRequest, DapCompletionResponse), (DapThreadsRequest, DapThreadsResponse), + (DapTerminateRequest, Ack), + (ShutdownDebugClient, Ack), ); entity_messages!( @@ -748,7 +742,6 @@ entity_messages!( SynchronizeBreakpoints, SetActiveDebugLine, RemoveActiveDebugLine, - SetDebuggerPanelItem, ShutdownDebugClient, SetDebugClientCapabilities, DapNextRequest, @@ -759,22 +752,19 @@ entity_messages!( DapPauseRequest, DapDisconnectRequest, DapTerminateThreadsRequest, - DapTerminateRequest, DapRestartRequest, DapRestartStackFrameRequest, - DapShutdownSession, UpdateThreadStatus, VariablesRequest, IgnoreBreakpointState, ToggleIgnoreBreakpoints, - DebuggerSessionEnded, - ActiveDebugSessionsRequest, DapStackTraceRequest, DapScopesRequest, DapSetVariableValueRequest, DapEvaluateRequest, DapCompletionRequest, DapThreadsRequest, + DapTerminateRequest ); entity_messages!( diff --git a/crates/task/src/debug_format.rs b/crates/task/src/debug_format.rs index 1cd8492c54..f89728aa5e 100644 --- a/crates/task/src/debug_format.rs +++ b/crates/task/src/debug_format.rs @@ -131,6 +131,8 @@ pub struct DebugAdapterConfig { pub cwd: Option, /// Additional initialization arguments to be sent on DAP initialization pub initialize_args: Option, + /// Whether the debug adapter supports attaching to a running process. + pub supports_attach: bool, } /// Represents the type of the debugger adapter connection @@ -176,6 +178,7 @@ impl DebugTaskDefinition { program: self.program, cwd: cwd.clone(), initialize_args: self.initialize_args, + supports_attach: true, }); let args: Vec = Vec::new();