Tidy up DAP initialization (#28730)

To make DAP work over SSH we want to create the binary
at the project level (so we can wrap it in an `ssh` invocation
transparently).

This means not pushing the adapter down into the session, and resolving
more information ahead-of-time.

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Piotr <piotr@zed.dev>

Release Notes:

- N/A

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Piotr <piotr@zed.dev>
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
Conrad Irwin 2025-04-15 09:11:29 -06:00 committed by GitHub
parent 6f0951ff77
commit aef78dcffd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 1319 additions and 1738 deletions

View file

@ -3,13 +3,13 @@ use anyhow::{Context as _, Ok, Result, anyhow};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
use dap_types::StartDebuggingRequestArguments;
use futures::io::BufReader;
use gpui::{AsyncApp, SharedString};
pub use http_client::{HttpClient, github::latest_github_release};
use language::LanguageToolchainStore;
use node_runtime::NodeRuntime;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use settings::WorktreeId;
use smol::{self, fs::File, lock::Mutex};
use std::{
@ -22,7 +22,7 @@ use std::{
path::PathBuf,
sync::Arc,
};
use task::{DebugAdapterConfig, DebugTaskDefinition};
use task::DebugTaskDefinition;
use util::ResultExt;
#[derive(Clone, Debug, PartialEq, Eq)]
@ -93,13 +93,15 @@ pub struct TcpArguments {
pub port: u16,
pub timeout: Option<u64>,
}
#[derive(Default, Debug, Clone)]
#[derive(Debug, Clone)]
pub struct DebugAdapterBinary {
pub adapter_name: DebugAdapterName,
pub command: String,
pub arguments: Option<Vec<OsString>>,
pub envs: Option<HashMap<String, String>>,
pub cwd: Option<PathBuf>,
pub connection: Option<TcpArguments>,
pub request_args: StartDebuggingRequestArguments,
}
#[derive(Debug)]
@ -220,7 +222,7 @@ pub trait DebugAdapter: 'static + Send + Sync {
async fn get_binary(
&self,
delegate: &dyn DapDelegate,
config: &DebugAdapterConfig,
config: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
@ -284,13 +286,10 @@ pub trait DebugAdapter: 'static + Send + Sync {
async fn get_installed_binary(
&self,
delegate: &dyn DapDelegate,
config: &DebugAdapterConfig,
config: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary>;
/// Should return base configuration to make the debug adapter work
fn request_args(&self, config: &DebugTaskDefinition) -> Value;
}
#[cfg(any(test, feature = "test-support"))]
pub struct FakeAdapter {}
@ -302,6 +301,31 @@ impl FakeAdapter {
pub fn new() -> Self {
Self {}
}
fn request_args(&self, config: &DebugTaskDefinition) -> StartDebuggingRequestArguments {
use serde_json::json;
use task::DebugRequestType;
let value = json!({
"request": match config.request {
DebugRequestType::Launch(_) => "launch",
DebugRequestType::Attach(_) => "attach",
},
"process_id": if let DebugRequestType::Attach(attach_config) = &config.request {
attach_config.process_id
} else {
None
},
});
let request = match config.request {
DebugRequestType::Launch(_) => dap_types::StartDebuggingRequestArgumentsRequest::Launch,
DebugRequestType::Attach(_) => dap_types::StartDebuggingRequestArgumentsRequest::Attach,
};
StartDebuggingRequestArguments {
configuration: value,
request,
}
}
}
#[cfg(any(test, feature = "test-support"))]
@ -314,16 +338,18 @@ impl DebugAdapter for FakeAdapter {
async fn get_binary(
&self,
_: &dyn DapDelegate,
_: &DebugAdapterConfig,
config: &DebugTaskDefinition,
_: Option<PathBuf>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
Ok(DebugAdapterBinary {
adapter_name: Self::ADAPTER_NAME.into(),
command: "command".into(),
arguments: None,
connection: None,
envs: None,
cwd: None,
request_args: self.request_args(config),
})
}
@ -345,27 +371,10 @@ impl DebugAdapter for FakeAdapter {
async fn get_installed_binary(
&self,
_: &dyn DapDelegate,
_: &DebugAdapterConfig,
_: &DebugTaskDefinition,
_: Option<PathBuf>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
unimplemented!("get installed binary");
}
fn request_args(&self, config: &DebugTaskDefinition) -> Value {
use serde_json::json;
use task::DebugRequestType;
json!({
"request": match config.request {
DebugRequestType::Launch(_) => "launch",
DebugRequestType::Attach(_) => "attach",
},
"process_id": if let DebugRequestType::Attach(attach_config) = &config.request {
attach_config.process_id
} else {
None
},
})
}
}

View file

@ -39,7 +39,6 @@ impl SessionId {
/// Represents a connection to the debug adapter process, either via stdout/stdin or a socket.
pub struct DebugAdapterClient {
id: SessionId,
name: DebugAdapterName,
sequence_count: AtomicU64,
binary: DebugAdapterBinary,
executor: BackgroundExecutor,
@ -51,7 +50,6 @@ pub type DapMessageHandler = Box<dyn FnMut(Message) + 'static + Send + Sync>;
impl DebugAdapterClient {
pub async fn start(
id: SessionId,
name: DebugAdapterName,
binary: DebugAdapterBinary,
message_handler: DapMessageHandler,
cx: AsyncApp,
@ -60,7 +58,6 @@ impl DebugAdapterClient {
TransportDelegate::start(&binary, cx.clone()).await?;
let this = Self {
id,
name,
binary,
transport_delegate,
sequence_count: AtomicU64::new(1),
@ -91,6 +88,7 @@ impl DebugAdapterClient {
) -> Result<Self> {
let binary = match self.transport_delegate.transport() {
crate::transport::Transport::Tcp(tcp_transport) => DebugAdapterBinary {
adapter_name: binary.adapter_name,
command: binary.command,
arguments: binary.arguments,
envs: binary.envs,
@ -100,11 +98,12 @@ impl DebugAdapterClient {
port: tcp_transport.port,
timeout: Some(tcp_transport.timeout),
}),
request_args: binary.request_args,
},
_ => self.binary.clone(),
};
Self::start(session_id, self.name(), binary, message_handler, cx).await
Self::start(session_id, binary, message_handler, cx).await
}
async fn handle_receive_messages(
@ -189,7 +188,17 @@ impl DebugAdapterClient {
let response = response??;
match response.success {
true => Ok(serde_json::from_value(response.body.unwrap_or_default())?),
true => {
if let Some(json) = response.body {
Ok(serde_json::from_value(json)?)
// Note: dap types configure themselves to return `None` when an empty object is received,
// which then fails here...
} else if let Ok(result) = serde_json::from_value(serde_json::Value::Object(Default::default())) {
Ok(result)
} else {
Ok(serde_json::from_value(Default::default())?)
}
}
false => Err(anyhow!("Request failed: {}", response.message.unwrap_or_default())),
}
}
@ -211,7 +220,7 @@ impl DebugAdapterClient {
}
pub fn name(&self) -> DebugAdapterName {
self.name.clone()
self.binary.adapter_name.clone()
}
pub fn binary(&self) -> &DebugAdapterBinary {
&self.binary
@ -238,14 +247,14 @@ impl DebugAdapterClient {
}
#[cfg(any(test, feature = "test-support"))]
pub async fn on_request<R: dap_types::requests::Request, F>(&self, handler: F)
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).await;
transport.on_request::<R, F>(handler);
}
#[cfg(any(test, feature = "test-support"))]
@ -282,7 +291,7 @@ mod tests {
use crate::{client::DebugAdapterClient, debugger_settings::DebuggerSettings};
use dap_types::{
Capabilities, InitializeRequestArguments, InitializeRequestArgumentsPathFormat,
RunInTerminalRequestArguments,
RunInTerminalRequestArguments, StartDebuggingRequestArguments,
messages::Events,
requests::{Initialize, Request, RunInTerminal},
};
@ -312,13 +321,17 @@ mod tests {
let client = DebugAdapterClient::start(
crate::client::SessionId(1),
DebugAdapterName("adapter".into()),
DebugAdapterBinary {
adapter_name: "adapter".into(),
command: "command".into(),
arguments: Default::default(),
envs: Default::default(),
connection: None,
cwd: None,
request_args: StartDebuggingRequestArguments {
configuration: serde_json::Value::Null,
request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
},
},
Box::new(|_| panic!("Did not expect to hit this code path")),
cx.to_async(),
@ -326,14 +339,12 @@ mod tests {
.await
.unwrap();
client
.on_request::<Initialize, _>(move |_, _| {
Ok(dap_types::Capabilities {
supports_configuration_done_request: Some(true),
..Default::default()
})
client.on_request::<Initialize, _>(move |_, _| {
Ok(dap_types::Capabilities {
supports_configuration_done_request: Some(true),
..Default::default()
})
.await;
});
cx.run_until_parked();
@ -381,13 +392,17 @@ mod tests {
let client = DebugAdapterClient::start(
crate::client::SessionId(1),
DebugAdapterName("adapter".into()),
DebugAdapterBinary {
adapter_name: "adapter".into(),
command: "command".into(),
arguments: Default::default(),
envs: Default::default(),
connection: None,
cwd: None,
request_args: StartDebuggingRequestArguments {
configuration: serde_json::Value::Null,
request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
},
},
Box::new({
let called_event_handler = called_event_handler.clone();
@ -431,13 +446,17 @@ mod tests {
let client = DebugAdapterClient::start(
crate::client::SessionId(1),
DebugAdapterName("test-adapter".into()),
DebugAdapterBinary {
adapter_name: "test-adapter".into(),
command: "command".into(),
arguments: Default::default(),
envs: Default::default(),
connection: None,
cwd: None,
request_args: dap_types::StartDebuggingRequestArguments {
configuration: serde_json::Value::Null,
request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
},
},
Box::new({
let called_event_handler = called_event_handler.clone();

View file

@ -699,14 +699,8 @@ impl StdioTransport {
}
#[cfg(any(test, feature = "test-support"))]
type RequestHandler = Box<
dyn Send
+ FnMut(
u64,
serde_json::Value,
Arc<Mutex<async_pipe::PipeWriter>>,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>,
>;
type RequestHandler =
Box<dyn Send + FnMut(u64, serde_json::Value) -> dap_types::messages::Response>;
#[cfg(any(test, feature = "test-support"))]
type ResponseHandler = Box<dyn Send + Fn(Response)>;
@ -714,45 +708,41 @@ type ResponseHandler = Box<dyn Send + Fn(Response)>;
#[cfg(any(test, feature = "test-support"))]
pub struct FakeTransport {
// for sending fake response back from adapter side
request_handlers: Arc<Mutex<HashMap<&'static str, RequestHandler>>>,
request_handlers: Arc<parking_lot::Mutex<HashMap<&'static str, RequestHandler>>>,
// for reverse request responses
response_handlers: Arc<Mutex<HashMap<&'static str, ResponseHandler>>>,
response_handlers: Arc<parking_lot::Mutex<HashMap<&'static str, ResponseHandler>>>,
}
#[cfg(any(test, feature = "test-support"))]
impl FakeTransport {
pub async fn on_request<R: dap_types::requests::Request, F>(&self, mut handler: F)
pub fn on_request<R: dap_types::requests::Request, F>(&self, mut handler: F)
where
F: 'static + Send + FnMut(u64, R::Arguments) -> Result<R::Response, ErrorResponse>,
{
self.request_handlers.lock().await.insert(
self.request_handlers.lock().insert(
R::COMMAND,
Box::new(
move |seq, args, writer: Arc<Mutex<async_pipe::PipeWriter>>| {
let response = handler(seq, serde_json::from_value(args).unwrap());
let message = serde_json::to_string(&Message::Response(Response {
Box::new(move |seq, args| {
let result = handler(seq, serde_json::from_value(args).unwrap());
let response = match result {
Ok(response) => Response {
seq: seq + 1,
request_seq: seq,
success: response.as_ref().is_ok(),
success: true,
command: R::COMMAND.into(),
body: util::maybe!({ serde_json::to_value(response.ok()?).ok() }),
body: Some(serde_json::to_value(response).unwrap()),
message: None,
}))
.unwrap();
let writer = writer.clone();
Box::pin(async move {
let mut writer = writer.lock().await;
writer
.write_all(TransportDelegate::build_rpc_message(message).as_bytes())
.await
.unwrap();
writer.flush().await.unwrap();
})
},
),
},
Err(response) => Response {
seq: seq + 1,
request_seq: seq,
success: false,
command: R::COMMAND.into(),
body: Some(serde_json::to_value(response).unwrap()),
message: None,
},
};
response
}),
);
}
@ -762,14 +752,13 @@ impl FakeTransport {
{
self.response_handlers
.lock()
.await
.insert(R::COMMAND, Box::new(handler));
}
async fn start(cx: AsyncApp) -> Result<(TransportPipe, Self)> {
let this = Self {
request_handlers: Arc::new(Mutex::new(HashMap::default())),
response_handlers: Arc::new(Mutex::new(HashMap::default())),
request_handlers: Arc::new(parking_lot::Mutex::new(HashMap::default())),
response_handlers: Arc::new(parking_lot::Mutex::new(HashMap::default())),
};
use dap_types::requests::{Request, RunInTerminal, StartDebugging};
use serde_json::json;
@ -816,23 +805,31 @@ impl FakeTransport {
.unwrap();
writer.flush().await.unwrap();
} else {
if let Some(handle) = request_handlers
let response = if let Some(handle) = request_handlers
.lock()
.await
.get_mut(request.command.as_str())
{
handle(
request.seq,
request.arguments.unwrap_or(json!({})),
stdout_writer.clone(),
)
.await;
} else {
log::error!(
"No request handler for {}",
request.command
);
}
panic!("No request handler for {}", request.command);
};
let message =
serde_json::to_string(&Message::Response(response))
.unwrap();
let mut writer = stdout_writer.lock().await;
writer
.write_all(
TransportDelegate::build_rpc_message(message)
.as_bytes(),
)
.await
.unwrap();
writer.flush().await.unwrap();
}
}
Message::Event(event) => {
@ -850,10 +847,8 @@ impl FakeTransport {
writer.flush().await.unwrap();
}
Message::Response(response) => {
if let Some(handle) = response_handlers
.lock()
.await
.get(response.command.as_str())
if let Some(handle) =
response_handlers.lock().get(response.command.as_str())
{
handle(response);
} else {