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 .pending_requests
112 .lock()
113 .insert(sequence_id, callback_tx)?;
114
115 log::debug!(
116 "Client {} send `{}` request with sequence_id: {}",
117 self.id.0,
118 R::COMMAND,
119 sequence_id
120 );
121
122 self.send_message(Message::Request(request)).await?;
123
124 let command = R::COMMAND.to_string();
125
126 let response = callback_rx.await??;
127 log::debug!(
128 "Client {} received response for: `{}` sequence_id: {}",
129 self.id.0,
130 command,
131 sequence_id
132 );
133 match response.success {
134 true => {
135 if let Some(json) = response.body {
136 Ok(serde_json::from_value(json)?)
137 // Note: dap types configure themselves to return `None` when an empty object is received,
138 // which then fails here...
139 } else if let Ok(result) =
140 serde_json::from_value(serde_json::Value::Object(Default::default()))
141 {
142 Ok(result)
143 } else {
144 Ok(serde_json::from_value(Default::default())?)
145 }
146 }
147 false => anyhow::bail!("Request failed: {}", response.message.unwrap_or_default()),
148 }
149 }
150
151 pub async fn send_message(&self, message: Message) -> Result<()> {
152 self.transport_delegate.send_message(message).await
153 }
154
155 pub fn id(&self) -> SessionId {
156 self.id
157 }
158
159 pub fn binary(&self) -> &DebugAdapterBinary {
160 &self.binary
161 }
162
163 /// Get the next sequence id to be used in a request
164 pub fn next_sequence_id(&self) -> u64 {
165 self.sequence_count.fetch_add(1, Ordering::Relaxed)
166 }
167
168 pub fn kill(&self) {
169 log::debug!("Killing DAP process");
170 self.transport_delegate.transport.lock().kill();
171 self.transport_delegate.pending_requests.lock().shutdown();
172 }
173
174 pub fn has_adapter_logs(&self) -> bool {
175 self.transport_delegate.has_adapter_logs()
176 }
177
178 pub fn add_log_handler<F>(&self, f: F, kind: LogKind)
179 where
180 F: 'static + Send + FnMut(IoKind, Option<&str>, &str),
181 {
182 self.transport_delegate.add_log_handler(f, kind);
183 }
184
185 #[cfg(any(test, feature = "test-support"))]
186 pub fn on_request<R: dap_types::requests::Request, F>(&self, mut handler: F)
187 where
188 F: 'static
189 + Send
190 + FnMut(u64, R::Arguments) -> Result<R::Response, dap_types::ErrorResponse>,
191 {
192 use crate::transport::RequestHandling;
193
194 self.transport_delegate
195 .transport
196 .lock()
197 .as_fake()
198 .on_request::<R, _>(move |seq, request| {
199 RequestHandling::Respond(handler(seq, request))
200 });
201 }
202
203 #[cfg(any(test, feature = "test-support"))]
204 pub fn on_request_ext<R: dap_types::requests::Request, F>(&self, handler: F)
205 where
206 F: 'static
207 + Send
208 + FnMut(
209 u64,
210 R::Arguments,
211 ) -> crate::transport::RequestHandling<
212 Result<R::Response, dap_types::ErrorResponse>,
213 >,
214 {
215 self.transport_delegate
216 .transport
217 .lock()
218 .as_fake()
219 .on_request::<R, F>(handler);
220 }
221
222 #[cfg(any(test, feature = "test-support"))]
223 pub async fn fake_reverse_request<R: dap_types::requests::Request>(&self, args: R::Arguments) {
224 self.send_message(Message::Request(dap_types::messages::Request {
225 seq: self.sequence_count.load(Ordering::Relaxed),
226 command: R::COMMAND.into(),
227 arguments: serde_json::to_value(args).ok(),
228 }))
229 .await
230 .unwrap();
231 }
232
233 #[cfg(any(test, feature = "test-support"))]
234 pub async fn on_response<R: dap_types::requests::Request, F>(&self, handler: F)
235 where
236 F: 'static + Send + Fn(Response),
237 {
238 self.transport_delegate
239 .transport
240 .lock()
241 .as_fake()
242 .on_response::<R, F>(handler);
243 }
244
245 #[cfg(any(test, feature = "test-support"))]
246 pub async fn fake_event(&self, event: dap_types::messages::Events) {
247 self.send_message(Message::Event(Box::new(event)))
248 .await
249 .unwrap();
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256 use crate::{client::DebugAdapterClient, debugger_settings::DebuggerSettings};
257 use dap_types::{
258 Capabilities, InitializeRequestArguments, InitializeRequestArgumentsPathFormat,
259 RunInTerminalRequestArguments, StartDebuggingRequestArguments,
260 messages::Events,
261 requests::{Initialize, Request, RunInTerminal},
262 };
263 use gpui::TestAppContext;
264 use serde_json::json;
265 use settings::{Settings, SettingsStore};
266 use std::sync::{
267 Arc,
268 atomic::{AtomicBool, Ordering},
269 };
270
271 pub fn init_test(cx: &mut gpui::TestAppContext) {
272 zlog::init_test();
273
274 cx.update(|cx| {
275 let settings = SettingsStore::test(cx);
276 cx.set_global(settings);
277 DebuggerSettings::register(cx);
278 });
279 }
280
281 #[gpui::test]
282 pub async fn test_initialize_client(cx: &mut TestAppContext) {
283 init_test(cx);
284
285 let client = DebugAdapterClient::start(
286 crate::client::SessionId(1),
287 DebugAdapterBinary {
288 command: Some("command".into()),
289 arguments: Default::default(),
290 envs: Default::default(),
291 connection: None,
292 cwd: None,
293 request_args: StartDebuggingRequestArguments {
294 configuration: serde_json::Value::Null,
295 request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
296 },
297 },
298 Box::new(|_| {}),
299 &mut cx.to_async(),
300 )
301 .await
302 .unwrap();
303
304 client.on_request::<Initialize, _>(move |_, _| {
305 Ok(dap_types::Capabilities {
306 supports_configuration_done_request: Some(true),
307 ..Default::default()
308 })
309 });
310
311 cx.run_until_parked();
312
313 let response = client
314 .request::<Initialize>(InitializeRequestArguments {
315 client_id: Some("zed".to_owned()),
316 client_name: Some("Zed".to_owned()),
317 adapter_id: "fake-adapter".to_owned(),
318 locale: Some("en-US".to_owned()),
319 path_format: Some(InitializeRequestArgumentsPathFormat::Path),
320 supports_variable_type: Some(true),
321 supports_variable_paging: Some(false),
322 supports_run_in_terminal_request: Some(true),
323 supports_memory_references: Some(true),
324 supports_progress_reporting: Some(false),
325 supports_invalidated_event: Some(false),
326 lines_start_at1: Some(true),
327 columns_start_at1: Some(true),
328 supports_memory_event: Some(false),
329 supports_args_can_be_interpreted_by_shell: Some(false),
330 supports_start_debugging_request: Some(true),
331 supports_ansistyling: Some(false),
332 })
333 .await
334 .unwrap();
335
336 cx.run_until_parked();
337
338 assert_eq!(
339 dap_types::Capabilities {
340 supports_configuration_done_request: Some(true),
341 ..Default::default()
342 },
343 response
344 );
345 }
346
347 #[gpui::test]
348 pub async fn test_calls_event_handler(cx: &mut TestAppContext) {
349 init_test(cx);
350
351 let called_event_handler = Arc::new(AtomicBool::new(false));
352
353 let client = DebugAdapterClient::start(
354 crate::client::SessionId(1),
355 DebugAdapterBinary {
356 command: Some("command".into()),
357 arguments: Default::default(),
358 envs: Default::default(),
359 connection: None,
360 cwd: None,
361 request_args: StartDebuggingRequestArguments {
362 configuration: serde_json::Value::Null,
363 request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
364 },
365 },
366 Box::new({
367 let called_event_handler = called_event_handler.clone();
368 move |event| {
369 called_event_handler.store(true, Ordering::SeqCst);
370
371 assert_eq!(
372 Message::Event(Box::new(Events::Initialized(
373 Some(Capabilities::default())
374 ))),
375 event
376 );
377 }
378 }),
379 &mut cx.to_async(),
380 )
381 .await
382 .unwrap();
383
384 cx.run_until_parked();
385
386 client
387 .fake_event(Events::Initialized(Some(Capabilities::default())))
388 .await;
389
390 cx.run_until_parked();
391
392 assert!(
393 called_event_handler.load(std::sync::atomic::Ordering::SeqCst),
394 "Event handler was not called"
395 );
396 }
397
398 #[gpui::test]
399 pub async fn test_calls_event_handler_for_reverse_request(cx: &mut TestAppContext) {
400 init_test(cx);
401
402 let called_event_handler = Arc::new(AtomicBool::new(false));
403
404 let client = DebugAdapterClient::start(
405 crate::client::SessionId(1),
406 DebugAdapterBinary {
407 command: Some("command".into()),
408 arguments: Default::default(),
409 envs: Default::default(),
410 connection: None,
411 cwd: None,
412 request_args: dap_types::StartDebuggingRequestArguments {
413 configuration: serde_json::Value::Null,
414 request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
415 },
416 },
417 Box::new({
418 let called_event_handler = called_event_handler.clone();
419 move |event| {
420 called_event_handler.store(true, Ordering::SeqCst);
421
422 assert_eq!(
423 Message::Request(dap_types::messages::Request {
424 seq: 1,
425 command: RunInTerminal::COMMAND.into(),
426 arguments: Some(json!({
427 "cwd": "/project/path/src",
428 "args": ["node", "test.js"],
429 }))
430 }),
431 event
432 );
433 }
434 }),
435 &mut cx.to_async(),
436 )
437 .await
438 .unwrap();
439
440 cx.run_until_parked();
441
442 client
443 .fake_reverse_request::<RunInTerminal>(RunInTerminalRequestArguments {
444 kind: None,
445 title: None,
446 cwd: "/project/path/src".into(),
447 args: vec!["node".into(), "test.js".into()],
448 env: None,
449 args_can_be_interpreted_by_shell: None,
450 })
451 .await;
452
453 cx.run_until_parked();
454
455 assert!(
456 called_event_handler.load(std::sync::atomic::Ordering::SeqCst),
457 "Event handler was not called"
458 );
459 }
460}