use crate::{ adapters::DebugAdapterBinary, transport::{IoKind, LogKind, TransportDelegate},};use anyhow::Result;use dap_types::{ messages::{Message, Response}, requests::Request,};use futures::channel::oneshot;use gpui::{AppContext, AsyncApp};use smol::channel::{Receiver, Sender};use std::{ hash::Hash, sync::atomic::{AtomicU64, Ordering},}; #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]#[repr(transparent)]pub struct SessionId(pub u32); impl SessionId { pub fn from_proto(client_id: u64) -> Self { Self(client_id as u32) } pub fn to_proto(&self) -> u64 { self.0 as u64 }} /// Represents a connection to the debug adapter process, either via stdout/stdin or a socket.pub struct DebugAdapterClient { id: SessionId, sequence_count: AtomicU64, binary: DebugAdapterBinary, transport_delegate: TransportDelegate,} pub type DapMessageHandler = Box<dyn FnMut(Message) + 'static + Send + Sync>; impl DebugAdapterClient { pub async fn start( id: SessionId, binary: DebugAdapterBinary, message_handler: DapMessageHandler, cx: AsyncApp, ) -> Result<Self> { let ((server_rx, server_tx), transport_delegate) = TransportDelegate::start(&binary, cx.clone()).await?; let this = Self { id, binary, transport_delegate, sequence_count: AtomicU64::new(1), }; log::info!("Successfully connected to debug adapter"); let client_id = this.id; // start handling events/reverse requests cx.background_spawn(Self::handle_receive_messages( client_id, server_rx, server_tx.clone(), message_handler, )) .detach(); Ok(this) } pub async fn reconnect( &self, session_id: SessionId, binary: DebugAdapterBinary, message_handler: DapMessageHandler, cx: AsyncApp, ) -> Result<Self> { let binary = match self.transport_delegate.transport() { crate::transport::Transport::Tcp(tcp_transport) => DebugAdapterBinary { command: binary.command, arguments: binary.arguments, envs: binary.envs, cwd: binary.cwd, connection: Some(crate::adapters::TcpArguments { host: tcp_transport.host, port: tcp_transport.port, timeout: Some(tcp_transport.timeout), }), request_args: binary.request_args, }, _ => self.binary.clone(), }; Self::start(session_id, binary, message_handler, cx).await } async fn handle_receive_messages( client_id: SessionId, server_rx: Receiver<Message>, client_tx: Sender<Message>, mut message_handler: DapMessageHandler, ) -> Result<()> { let result = loop { let message = match server_rx.recv().await { Ok(message) => message, Err(e) => break Err(e.into()), }; match message { Message::Event(ev) => { log::debug!("Client {} received event `{}`", client_id.0, &ev); message_handler(Message::Event(ev)) } Message::Request(req) => { log::debug!( "Client {} received reverse request `{}`", client_id.0, &req.command ); message_handler(Message::Request(req)) } Message::Response(response) => { log::debug!("Received response after request timeout: {:#?}", response); } } smol::future::yield_now().await; }; drop(client_tx); log::debug!("Handle receive messages dropped"); result } /// Send a request to an adapter and get a response back /// Note: This function will block until a response is sent back from the adapter pub async fn request<R: Request>(&self, arguments: R::Arguments) -> Result<R::Response> { let serialized_arguments = serde_json::to_value(arguments)?; let (callback_tx, callback_rx) = oneshot::channel::<Result<Response>>(); let sequence_id = self.next_sequence_id(); let request = crate::messages::Request { seq: sequence_id, command: R::COMMAND.to_string(), arguments: Some(serialized_arguments), }; self.transport_delegate .add_pending_request(sequence_id, callback_tx) .await; log::debug!( "Client {} send `{}` request with sequence_id: {}", self.id.0, R::COMMAND, sequence_id ); self.send_message(Message::Request(request)).await?; let command = R::COMMAND.to_string(); let response = callback_rx.await??; log::debug!( "Client {} received response for: `{}` sequence_id: {}", self.id.0, command, sequence_id ); match response.success { true => { if let Some(json) = response.body { Ok(serde_json::from_value(json)?) // Note: dap types configure themselves to return `None` when an empty object is received, // which then fails here... } else if let Ok(result) = serde_json::from_value(serde_json::Value::Object(Default::default())) { Ok(result) } else { Ok(serde_json::from_value(Default::default())?) } } false => anyhow::bail!("Request failed: {}", response.message.unwrap_or_default()), } } pub async fn send_message(&self, message: Message) -> Result<()> { self.transport_delegate.send_message(message).await } pub fn id(&self) -> SessionId { self.id } pub fn binary(&self) -> &DebugAdapterBinary { &self.binary } /// 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) } pub async fn shutdown(&self) -> Result<()> { self.transport_delegate.shutdown().await } pub fn has_adapter_logs(&self) -> bool { self.transport_delegate.has_adapter_logs() } pub fn add_log_handler<F>(&self, f: F, kind: LogKind) where F: 'static + Send + FnMut(IoKind, &str), { self.transport_delegate.add_log_handler(f, kind); } #[cfg(any(test, feature = "test-support"))] pub fn on_request<R: dap_types::requests::Request, F>(&self, handler: F) where F: 'static + Send + FnMut(u64, R::Arguments) -> Result<R::Response, dap_types::ErrorResponse>, { let transport = self.transport_delegate.transport().as_fake(); transport.on_request::<R, F>(handler); } #[cfg(any(test, feature = "test-support"))] pub async fn fake_reverse_request<R: dap_types::requests::Request>(&self, args: R::Arguments) { self.send_message(Message::Request(dap_types::messages::Request { seq: self.sequence_count.load(Ordering::Relaxed), command: R::COMMAND.into(), arguments: serde_json::to_value(args).ok(), })) .await .unwrap(); } #[cfg(any(test, feature = "test-support"))] pub async fn on_response<R: dap_types::requests::Request, F>(&self, handler: F) where F: 'static + Send + Fn(Response), { let transport = self.transport_delegate.transport().as_fake(); transport.on_response::<R, F>(handler).await; } #[cfg(any(test, feature = "test-support"))] pub async fn fake_event(&self, event: dap_types::messages::Events) { self.send_message(Message::Event(Box::new(event))) .await .unwrap(); }} #[cfg(test)]mod tests { use super::*; use crate::{client::DebugAdapterClient, debugger_settings::DebuggerSettings}; use dap_types::{ Capabilities, InitializeRequestArguments, InitializeRequestArgumentsPathFormat, RunInTerminalRequestArguments, StartDebuggingRequestArguments, messages::Events, requests::{Initialize, Request, RunInTerminal}, }; use gpui::TestAppContext; use serde_json::json; use settings::{Settings, SettingsStore}; use std::sync::{ Arc, atomic::{AtomicBool, Ordering}, }; pub fn init_test(cx: &mut gpui::TestAppContext) { zlog::init_test(); cx.update(|cx| { let settings = SettingsStore::test(cx); cx.set_global(settings); DebuggerSettings::register(cx); }); } #[gpui::test] pub async fn test_initialize_client(cx: &mut TestAppContext) { init_test(cx); let client = DebugAdapterClient::start( crate::client::SessionId(1), DebugAdapterBinary { command: "command".into(), arguments: Default::default(), envs: Default::default(), connection: None, cwd: None, request_args: StartDebuggingRequestArguments { configuration: serde_json::Value::Null, request: dap_types::StartDebuggingRequestArgumentsRequest::Launch, }, }, Box::new(|_| panic!("Did not expect to hit this code path")), cx.to_async(), ) .await .unwrap(); client.on_request::<Initialize, _>(move |_, _| { Ok(dap_types::Capabilities { supports_configuration_done_request: Some(true), ..Default::default() }) }); cx.run_until_parked(); let response = client .request::<Initialize>(InitializeRequestArguments { client_id: Some("zed".to_owned()), client_name: Some("Zed".to_owned()), adapter_id: "fake-adapter".to_owned(), 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), supports_ansistyling: Some(false), }) .await .unwrap(); cx.run_until_parked(); assert_eq!( dap_types::Capabilities { supports_configuration_done_request: Some(true), ..Default::default() }, response ); client.shutdown().await.unwrap(); } #[gpui::test] pub async fn test_calls_event_handler(cx: &mut TestAppContext) { init_test(cx); let called_event_handler = Arc::new(AtomicBool::new(false)); let client = DebugAdapterClient::start( crate::client::SessionId(1), DebugAdapterBinary { command: "command".into(), arguments: Default::default(), envs: Default::default(), connection: None, cwd: None, request_args: StartDebuggingRequestArguments { configuration: serde_json::Value::Null, request: dap_types::StartDebuggingRequestArgumentsRequest::Launch, }, }, Box::new({ let called_event_handler = called_event_handler.clone(); move |event| { called_event_handler.store(true, Ordering::SeqCst); assert_eq!( Message::Event(Box::new(Events::Initialized( Some(Capabilities::default()) ))), event ); } }), cx.to_async(), ) .await .unwrap(); cx.run_until_parked(); client .fake_event(Events::Initialized(Some(Capabilities::default()))) .await; cx.run_until_parked(); assert!( called_event_handler.load(std::sync::atomic::Ordering::SeqCst), "Event handler was not called" ); client.shutdown().await.unwrap(); } #[gpui::test] pub async fn test_calls_event_handler_for_reverse_request(cx: &mut TestAppContext) { init_test(cx); let called_event_handler = Arc::new(AtomicBool::new(false)); let client = DebugAdapterClient::start( crate::client::SessionId(1), DebugAdapterBinary { command: "command".into(), arguments: Default::default(), envs: Default::default(), connection: None, cwd: None, request_args: dap_types::StartDebuggingRequestArguments { configuration: serde_json::Value::Null, request: dap_types::StartDebuggingRequestArgumentsRequest::Launch, }, }, Box::new({ let called_event_handler = called_event_handler.clone(); move |event| { called_event_handler.store(true, Ordering::SeqCst); assert_eq!( Message::Request(dap_types::messages::Request { seq: 1, command: RunInTerminal::COMMAND.into(), arguments: Some(json!({ "cwd": "/project/path/src", "args": ["node", "test.js"], })) }), event ); } }), cx.to_async(), ) .await .unwrap(); cx.run_until_parked(); client .fake_reverse_request::<RunInTerminal>(RunInTerminalRequestArguments { kind: None, title: None, cwd: "/project/path/src".into(), args: vec!["node".into(), "test.js".into()], env: None, args_can_be_interpreted_by_shell: None, }) .await; cx.run_until_parked(); assert!( called_event_handler.load(std::sync::atomic::Ordering::SeqCst), "Event handler was not called" ); client.shutdown().await.unwrap(); }}
use crate::{
adapters::DebugAdapterBinary,
transport::{IoKind, LogKind, TransportDelegate},
};
use anyhow::Result;
use dap_types::{
messages::{Message, Response},
requests::Request,
};
use futures::channel::oneshot;
use gpui::{AppContext, AsyncApp};
use smol::channel::{Receiver, Sender};
use std::{
hash::Hash,
sync::atomic::{AtomicU64, Ordering},
};
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct SessionId(pub u32);
impl SessionId {
pub fn from_proto(client_id: u64) -> Self {
Self(client_id as u32)
}
pub fn to_proto(&self) -> u64 {
self.0 as u64
}
}
/// Represents a connection to the debug adapter process, either via stdout/stdin or a socket.
pub struct DebugAdapterClient {
id: SessionId,
sequence_count: AtomicU64,
binary: DebugAdapterBinary,
transport_delegate: TransportDelegate,
}
pub type DapMessageHandler = Box<dyn FnMut(Message) + 'static + Send + Sync>;
impl DebugAdapterClient {
pub async fn start(
id: SessionId,
binary: DebugAdapterBinary,
message_handler: DapMessageHandler,
cx: AsyncApp,
) -> Result<Self> {
let ((server_rx, server_tx), transport_delegate) =
TransportDelegate::start(&binary, cx.clone()).await?;
let this = Self {
id,
binary,
transport_delegate,
sequence_count: AtomicU64::new(1),
};
log::info!("Successfully connected to debug adapter");
let client_id = this.id;
// start handling events/reverse requests
cx.background_spawn(Self::handle_receive_messages(
client_id,
server_rx,
server_tx.clone(),
message_handler,
))
.detach();
Ok(this)
}
pub async fn reconnect(
&self,
session_id: SessionId,
binary: DebugAdapterBinary,
message_handler: DapMessageHandler,
cx: AsyncApp,
) -> Result<Self> {
let binary = match self.transport_delegate.transport() {
crate::transport::Transport::Tcp(tcp_transport) => DebugAdapterBinary {
command: binary.command,
arguments: binary.arguments,
envs: binary.envs,
cwd: binary.cwd,
connection: Some(crate::adapters::TcpArguments {
host: tcp_transport.host,
port: tcp_transport.port,
timeout: Some(tcp_transport.timeout),
}),
request_args: binary.request_args,
},
_ => self.binary.clone(),
};
Self::start(session_id, binary, message_handler, cx).await
}
async fn handle_receive_messages(
client_id: SessionId,
server_rx: Receiver<Message>,
client_tx: Sender<Message>,
mut message_handler: DapMessageHandler,
) -> Result<()> {
let result = loop {
let message = match server_rx.recv().await {
Ok(message) => message,
Err(e) => break Err(e.into()),
};
match message {
Message::Event(ev) => {
log::debug!("Client {} received event `{}`", client_id.0, &ev);
message_handler(Message::Event(ev))
}
Message::Request(req) => {
log::debug!(
"Client {} received reverse request `{}`",
client_id.0,
&req.command
);
message_handler(Message::Request(req))
}
Message::Response(response) => {
log::debug!("Received response after request timeout: {:#?}", response);
}
}
smol::future::yield_now().await;
};
drop(client_tx);
log::debug!("Handle receive messages dropped");
result
}
/// Send a request to an adapter and get a response back
/// Note: This function will block until a response is sent back from the adapter
pub async fn request<R: Request>(&self, arguments: R::Arguments) -> Result<R::Response> {
let serialized_arguments = serde_json::to_value(arguments)?;
let (callback_tx, callback_rx) = oneshot::channel::<Result<Response>>();
let sequence_id = self.next_sequence_id();
let request = crate::messages::Request {
seq: sequence_id,
command: R::COMMAND.to_string(),
arguments: Some(serialized_arguments),
};
self.transport_delegate
.add_pending_request(sequence_id, callback_tx)
.await;
log::debug!(
"Client {} send `{}` request with sequence_id: {}",
self.id.0,
R::COMMAND,
sequence_id
);
self.send_message(Message::Request(request)).await?;
let command = R::COMMAND.to_string();
let response = callback_rx.await??;
log::debug!(
"Client {} received response for: `{}` sequence_id: {}",
self.id.0,
command,
sequence_id
);
match response.success {
true => {
if let Some(json) = response.body {
Ok(serde_json::from_value(json)?)
// Note: dap types configure themselves to return `None` when an empty object is received,
// which then fails here...
} else if let Ok(result) =
serde_json::from_value(serde_json::Value::Object(Default::default()))
{
Ok(result)
} else {
Ok(serde_json::from_value(Default::default())?)
}
}
false => anyhow::bail!("Request failed: {}", response.message.unwrap_or_default()),
}
}
pub async fn send_message(&self, message: Message) -> Result<()> {
self.transport_delegate.send_message(message).await
}
pub fn id(&self) -> SessionId {
self.id
}
pub fn binary(&self) -> &DebugAdapterBinary {
&self.binary
}
/// 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)
}
pub async fn shutdown(&self) -> Result<()> {
self.transport_delegate.shutdown().await
}
pub fn has_adapter_logs(&self) -> bool {
self.transport_delegate.has_adapter_logs()
}
pub fn add_log_handler<F>(&self, f: F, kind: LogKind)
where
F: 'static + Send + FnMut(IoKind, &str),
{
self.transport_delegate.add_log_handler(f, kind);
}
#[cfg(any(test, feature = "test-support"))]
pub fn on_request<R: dap_types::requests::Request, F>(&self, handler: F)
where
F: 'static
+ Send
+ FnMut(u64, R::Arguments) -> Result<R::Response, dap_types::ErrorResponse>,
{
let transport = self.transport_delegate.transport().as_fake();
transport.on_request::<R, F>(handler);
}
#[cfg(any(test, feature = "test-support"))]
pub async fn fake_reverse_request<R: dap_types::requests::Request>(&self, args: R::Arguments) {
self.send_message(Message::Request(dap_types::messages::Request {
seq: self.sequence_count.load(Ordering::Relaxed),
command: R::COMMAND.into(),
arguments: serde_json::to_value(args).ok(),
}))
.await
.unwrap();
}
#[cfg(any(test, feature = "test-support"))]
pub async fn on_response<R: dap_types::requests::Request, F>(&self, handler: F)
where
F: 'static + Send + Fn(Response),
{
let transport = self.transport_delegate.transport().as_fake();
transport.on_response::<R, F>(handler).await;
}
#[cfg(any(test, feature = "test-support"))]
pub async fn fake_event(&self, event: dap_types::messages::Events) {
self.send_message(Message::Event(Box::new(event)))
.await
.unwrap();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{client::DebugAdapterClient, debugger_settings::DebuggerSettings};
use dap_types::{
Capabilities, InitializeRequestArguments, InitializeRequestArgumentsPathFormat,
RunInTerminalRequestArguments, StartDebuggingRequestArguments,
messages::Events,
requests::{Initialize, Request, RunInTerminal},
};
use gpui::TestAppContext;
use serde_json::json;
use settings::{Settings, SettingsStore};
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
pub fn init_test(cx: &mut gpui::TestAppContext) {
zlog::init_test();
cx.update(|cx| {
let settings = SettingsStore::test(cx);
cx.set_global(settings);
DebuggerSettings::register(cx);
});
}
#[gpui::test]
pub async fn test_initialize_client(cx: &mut TestAppContext) {
init_test(cx);
let client = DebugAdapterClient::start(
crate::client::SessionId(1),
DebugAdapterBinary {
command: "command".into(),
arguments: Default::default(),
envs: Default::default(),
connection: None,
cwd: None,
request_args: StartDebuggingRequestArguments {
configuration: serde_json::Value::Null,
request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
},
},
Box::new(|_| panic!("Did not expect to hit this code path")),
cx.to_async(),
)
.await
.unwrap();
client.on_request::<Initialize, _>(move |_, _| {
Ok(dap_types::Capabilities {
supports_configuration_done_request: Some(true),
..Default::default()
})
});
cx.run_until_parked();
let response = client
.request::<Initialize>(InitializeRequestArguments {
client_id: Some("zed".to_owned()),
client_name: Some("Zed".to_owned()),
adapter_id: "fake-adapter".to_owned(),
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),
supports_ansistyling: Some(false),
})
.await
.unwrap();
cx.run_until_parked();
assert_eq!(
dap_types::Capabilities {
supports_configuration_done_request: Some(true),
..Default::default()
},
response
);
client.shutdown().await.unwrap();
}
#[gpui::test]
pub async fn test_calls_event_handler(cx: &mut TestAppContext) {
init_test(cx);
let called_event_handler = Arc::new(AtomicBool::new(false));
let client = DebugAdapterClient::start(
crate::client::SessionId(1),
DebugAdapterBinary {
command: "command".into(),
arguments: Default::default(),
envs: Default::default(),
connection: None,
cwd: None,
request_args: StartDebuggingRequestArguments {
configuration: serde_json::Value::Null,
request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
},
},
Box::new({
let called_event_handler = called_event_handler.clone();
move |event| {
called_event_handler.store(true, Ordering::SeqCst);
assert_eq!(
Message::Event(Box::new(Events::Initialized(
Some(Capabilities::default())
))),
event
);
}
}),
cx.to_async(),
)
.await
.unwrap();
cx.run_until_parked();
client
.fake_event(Events::Initialized(Some(Capabilities::default())))
.await;
cx.run_until_parked();
assert!(
called_event_handler.load(std::sync::atomic::Ordering::SeqCst),
"Event handler was not called"
);
client.shutdown().await.unwrap();
}
#[gpui::test]
pub async fn test_calls_event_handler_for_reverse_request(cx: &mut TestAppContext) {
init_test(cx);
let called_event_handler = Arc::new(AtomicBool::new(false));
let client = DebugAdapterClient::start(
crate::client::SessionId(1),
DebugAdapterBinary {
command: "command".into(),
arguments: Default::default(),
envs: Default::default(),
connection: None,
cwd: None,
request_args: dap_types::StartDebuggingRequestArguments {
configuration: serde_json::Value::Null,
request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
},
},
Box::new({
let called_event_handler = called_event_handler.clone();
move |event| {
called_event_handler.store(true, Ordering::SeqCst);
assert_eq!(
Message::Request(dap_types::messages::Request {
seq: 1,
command: RunInTerminal::COMMAND.into(),
arguments: Some(json!({
"cwd": "/project/path/src",
"args": ["node", "test.js"],
}))
}),
event
);
}
}),
cx.to_async(),
)
.await
.unwrap();
cx.run_until_parked();
client
.fake_reverse_request::<RunInTerminal>(RunInTerminalRequestArguments {
kind: None,
title: None,
cwd: "/project/path/src".into(),
args: vec!["node".into(), "test.js".into()],
env: None,
args_can_be_interpreted_by_shell: None,
})
.await;
cx.run_until_parked();
assert!(
called_event_handler.load(std::sync::atomic::Ordering::SeqCst),
"Event handler was not called"
);
client.shutdown().await.unwrap();
}
}