1use crate::{
2 adapters::DebugAdapterBinary,
3 transport::{IoKind, LogKind, TransportDelegate},
4};
5use anyhow::Result;
6use dap_types::{
7 messages::{Message, Response},
8 requests::Request,
9};
10use futures::channel::oneshot;
11use gpui::AsyncApp;
12use std::{
13 hash::Hash,
14 sync::atomic::{AtomicU64, Ordering},
15};
16
17#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
18#[repr(transparent)]
19pub struct SessionId(pub u32);
20
21impl SessionId {
22 pub fn from_proto(client_id: u64) -> Self {
23 Self(client_id as u32)
24 }
25
26 pub fn to_proto(&self) -> u64 {
27 self.0 as u64
28 }
29}
30
31/// Represents a connection to the debug adapter process, either via stdout/stdin or a socket.
32pub struct DebugAdapterClient {
33 id: SessionId,
34 sequence_count: AtomicU64,
35 binary: DebugAdapterBinary,
36 transport_delegate: TransportDelegate,
37}
38
39pub type DapMessageHandler = Box<dyn FnMut(Message) + 'static + Send + Sync>;
40
41impl DebugAdapterClient {
42 pub async fn start(
43 id: SessionId,
44 binary: DebugAdapterBinary,
45 message_handler: DapMessageHandler,
46 cx: &mut AsyncApp,
47 ) -> Result<Self> {
48 let transport_delegate = TransportDelegate::start(&binary, cx).await?;
49 let this = Self {
50 id,
51 binary,
52 transport_delegate,
53 sequence_count: AtomicU64::new(1),
54 };
55 this.connect(message_handler, cx).await?;
56
57 Ok(this)
58 }
59
60 pub fn should_reconnect_for_ssh(&self) -> bool {
61 self.transport_delegate.tcp_arguments().is_some()
62 && self.binary.command.as_deref() == Some("ssh")
63 }
64
65 pub async fn connect(
66 &self,
67 message_handler: DapMessageHandler,
68 cx: &mut AsyncApp,
69 ) -> Result<()> {
70 self.transport_delegate.connect(message_handler, cx).await
71 }
72
73 pub async fn create_child_connection(
74 &self,
75 session_id: SessionId,
76 binary: DebugAdapterBinary,
77 message_handler: DapMessageHandler,
78 cx: &mut AsyncApp,
79 ) -> Result<Self> {
80 let binary = if let Some(connection) = self.transport_delegate.tcp_arguments() {
81 DebugAdapterBinary {
82 command: None,
83 arguments: Default::default(),
84 envs: Default::default(),
85 cwd: Default::default(),
86 connection: Some(connection),
87 request_args: binary.request_args,
88 }
89 } else {
90 self.binary.clone()
91 };
92
93 Self::start(session_id, binary, message_handler, cx).await
94 }
95
96 /// Send a request to an adapter and get a response back
97 /// Note: This function will block until a response is sent back from the adapter
98 pub async fn request<R: Request>(&self, arguments: R::Arguments) -> Result<R::Response> {
99 let serialized_arguments = serde_json::to_value(arguments)?;
100
101 let (callback_tx, callback_rx) = oneshot::channel::<Result<Response>>();
102
103 let sequence_id = self.next_sequence_id();
104
105 let request = crate::messages::Request {
106 seq: sequence_id,
107 command: R::COMMAND.to_string(),
108 arguments: Some(serialized_arguments),
109 };
110 self.transport_delegate
111 .add_pending_request(sequence_id, callback_tx);
112
113 log::debug!(
114 "Client {} send `{}` request with sequence_id: {}",
115 self.id.0,
116 R::COMMAND,
117 sequence_id
118 );
119
120 self.send_message(Message::Request(request)).await?;
121
122 let command = R::COMMAND.to_string();
123
124 let response = callback_rx.await??;
125 log::debug!(
126 "Client {} received response for: `{}` sequence_id: {}",
127 self.id.0,
128 command,
129 sequence_id
130 );
131 match response.success {
132 true => {
133 if let Some(json) = response.body {
134 Ok(serde_json::from_value(json)?)
135 // Note: dap types configure themselves to return `None` when an empty object is received,
136 // which then fails here...
137 } else if let Ok(result) =
138 serde_json::from_value(serde_json::Value::Object(Default::default()))
139 {
140 Ok(result)
141 } else {
142 Ok(serde_json::from_value(Default::default())?)
143 }
144 }
145 false => anyhow::bail!("Request failed: {}", response.message.unwrap_or_default()),
146 }
147 }
148
149 pub async fn send_message(&self, message: Message) -> Result<()> {
150 self.transport_delegate.send_message(message).await
151 }
152
153 pub fn id(&self) -> SessionId {
154 self.id
155 }
156
157 pub fn binary(&self) -> &DebugAdapterBinary {
158 &self.binary
159 }
160
161 /// Get the next sequence id to be used in a request
162 pub fn next_sequence_id(&self) -> u64 {
163 self.sequence_count.fetch_add(1, Ordering::Relaxed)
164 }
165
166 pub async fn shutdown(&self) -> Result<()> {
167 self.transport_delegate.shutdown().await
168 }
169
170 pub fn has_adapter_logs(&self) -> bool {
171 self.transport_delegate.has_adapter_logs()
172 }
173
174 pub fn add_log_handler<F>(&self, f: F, kind: LogKind)
175 where
176 F: 'static + Send + FnMut(IoKind, Option<&str>, &str),
177 {
178 self.transport_delegate.add_log_handler(f, kind);
179 }
180
181 #[cfg(any(test, feature = "test-support"))]
182 pub fn on_request<R: dap_types::requests::Request, F>(&self, handler: F)
183 where
184 F: 'static
185 + Send
186 + FnMut(u64, R::Arguments) -> Result<R::Response, dap_types::ErrorResponse>,
187 {
188 self.transport_delegate
189 .transport
190 .lock()
191 .as_fake()
192 .on_request::<R, F>(handler);
193 }
194
195 #[cfg(any(test, feature = "test-support"))]
196 pub async fn fake_reverse_request<R: dap_types::requests::Request>(&self, args: R::Arguments) {
197 self.send_message(Message::Request(dap_types::messages::Request {
198 seq: self.sequence_count.load(Ordering::Relaxed),
199 command: R::COMMAND.into(),
200 arguments: serde_json::to_value(args).ok(),
201 }))
202 .await
203 .unwrap();
204 }
205
206 #[cfg(any(test, feature = "test-support"))]
207 pub async fn on_response<R: dap_types::requests::Request, F>(&self, handler: F)
208 where
209 F: 'static + Send + Fn(Response),
210 {
211 self.transport_delegate
212 .transport
213 .lock()
214 .as_fake()
215 .on_response::<R, F>(handler);
216 }
217
218 #[cfg(any(test, feature = "test-support"))]
219 pub async fn fake_event(&self, event: dap_types::messages::Events) {
220 self.send_message(Message::Event(Box::new(event)))
221 .await
222 .unwrap();
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 use crate::{client::DebugAdapterClient, debugger_settings::DebuggerSettings};
230 use dap_types::{
231 Capabilities, InitializeRequestArguments, InitializeRequestArgumentsPathFormat,
232 RunInTerminalRequestArguments, StartDebuggingRequestArguments,
233 messages::Events,
234 requests::{Initialize, Request, RunInTerminal},
235 };
236 use gpui::TestAppContext;
237 use serde_json::json;
238 use settings::{Settings, SettingsStore};
239 use std::sync::{
240 Arc,
241 atomic::{AtomicBool, Ordering},
242 };
243
244 pub fn init_test(cx: &mut gpui::TestAppContext) {
245 zlog::init_test();
246
247 cx.update(|cx| {
248 let settings = SettingsStore::test(cx);
249 cx.set_global(settings);
250 DebuggerSettings::register(cx);
251 });
252 }
253
254 #[gpui::test]
255 pub async fn test_initialize_client(cx: &mut TestAppContext) {
256 init_test(cx);
257
258 let client = DebugAdapterClient::start(
259 crate::client::SessionId(1),
260 DebugAdapterBinary {
261 command: Some("command".into()),
262 arguments: Default::default(),
263 envs: Default::default(),
264 connection: None,
265 cwd: None,
266 request_args: StartDebuggingRequestArguments {
267 configuration: serde_json::Value::Null,
268 request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
269 },
270 },
271 Box::new(|_| panic!("Did not expect to hit this code path")),
272 &mut cx.to_async(),
273 )
274 .await
275 .unwrap();
276
277 client.on_request::<Initialize, _>(move |_, _| {
278 Ok(dap_types::Capabilities {
279 supports_configuration_done_request: Some(true),
280 ..Default::default()
281 })
282 });
283
284 cx.run_until_parked();
285
286 let response = client
287 .request::<Initialize>(InitializeRequestArguments {
288 client_id: Some("zed".to_owned()),
289 client_name: Some("Zed".to_owned()),
290 adapter_id: "fake-adapter".to_owned(),
291 locale: Some("en-US".to_owned()),
292 path_format: Some(InitializeRequestArgumentsPathFormat::Path),
293 supports_variable_type: Some(true),
294 supports_variable_paging: Some(false),
295 supports_run_in_terminal_request: Some(true),
296 supports_memory_references: Some(true),
297 supports_progress_reporting: Some(false),
298 supports_invalidated_event: Some(false),
299 lines_start_at1: Some(true),
300 columns_start_at1: Some(true),
301 supports_memory_event: Some(false),
302 supports_args_can_be_interpreted_by_shell: Some(false),
303 supports_start_debugging_request: Some(true),
304 supports_ansistyling: Some(false),
305 })
306 .await
307 .unwrap();
308
309 cx.run_until_parked();
310
311 assert_eq!(
312 dap_types::Capabilities {
313 supports_configuration_done_request: Some(true),
314 ..Default::default()
315 },
316 response
317 );
318
319 client.shutdown().await.unwrap();
320 }
321
322 #[gpui::test]
323 pub async fn test_calls_event_handler(cx: &mut TestAppContext) {
324 init_test(cx);
325
326 let called_event_handler = Arc::new(AtomicBool::new(false));
327
328 let client = DebugAdapterClient::start(
329 crate::client::SessionId(1),
330 DebugAdapterBinary {
331 command: Some("command".into()),
332 arguments: Default::default(),
333 envs: Default::default(),
334 connection: None,
335 cwd: None,
336 request_args: StartDebuggingRequestArguments {
337 configuration: serde_json::Value::Null,
338 request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
339 },
340 },
341 Box::new({
342 let called_event_handler = called_event_handler.clone();
343 move |event| {
344 called_event_handler.store(true, Ordering::SeqCst);
345
346 assert_eq!(
347 Message::Event(Box::new(Events::Initialized(
348 Some(Capabilities::default())
349 ))),
350 event
351 );
352 }
353 }),
354 &mut cx.to_async(),
355 )
356 .await
357 .unwrap();
358
359 cx.run_until_parked();
360
361 client
362 .fake_event(Events::Initialized(Some(Capabilities::default())))
363 .await;
364
365 cx.run_until_parked();
366
367 assert!(
368 called_event_handler.load(std::sync::atomic::Ordering::SeqCst),
369 "Event handler was not called"
370 );
371
372 client.shutdown().await.unwrap();
373 }
374
375 #[gpui::test]
376 pub async fn test_calls_event_handler_for_reverse_request(cx: &mut TestAppContext) {
377 init_test(cx);
378
379 let called_event_handler = Arc::new(AtomicBool::new(false));
380
381 let client = DebugAdapterClient::start(
382 crate::client::SessionId(1),
383 DebugAdapterBinary {
384 command: Some("command".into()),
385 arguments: Default::default(),
386 envs: Default::default(),
387 connection: None,
388 cwd: None,
389 request_args: dap_types::StartDebuggingRequestArguments {
390 configuration: serde_json::Value::Null,
391 request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
392 },
393 },
394 Box::new({
395 let called_event_handler = called_event_handler.clone();
396 move |event| {
397 called_event_handler.store(true, Ordering::SeqCst);
398
399 assert_eq!(
400 Message::Request(dap_types::messages::Request {
401 seq: 1,
402 command: RunInTerminal::COMMAND.into(),
403 arguments: Some(json!({
404 "cwd": "/project/path/src",
405 "args": ["node", "test.js"],
406 }))
407 }),
408 event
409 );
410 }
411 }),
412 &mut cx.to_async(),
413 )
414 .await
415 .unwrap();
416
417 cx.run_until_parked();
418
419 client
420 .fake_reverse_request::<RunInTerminal>(RunInTerminalRequestArguments {
421 kind: None,
422 title: None,
423 cwd: "/project/path/src".into(),
424 args: vec!["node".into(), "test.js".into()],
425 env: None,
426 args_can_be_interpreted_by_shell: None,
427 })
428 .await;
429
430 cx.run_until_parked();
431
432 assert!(
433 called_event_handler.load(std::sync::atomic::Ordering::SeqCst),
434 "Event handler was not called"
435 );
436
437 client.shutdown().await.unwrap();
438 }
439}