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 fn kill(&self) {
167 log::debug!("Killing DAP process");
168 self.transport_delegate.transport.lock().kill();
169 }
170
171 pub fn has_adapter_logs(&self) -> bool {
172 self.transport_delegate.has_adapter_logs()
173 }
174
175 pub fn add_log_handler<F>(&self, f: F, kind: LogKind)
176 where
177 F: 'static + Send + FnMut(IoKind, Option<&str>, &str),
178 {
179 self.transport_delegate.add_log_handler(f, kind);
180 }
181
182 #[cfg(any(test, feature = "test-support"))]
183 pub fn on_request<R: dap_types::requests::Request, F>(&self, handler: F)
184 where
185 F: 'static
186 + Send
187 + FnMut(u64, R::Arguments) -> Result<R::Response, dap_types::ErrorResponse>,
188 {
189 self.transport_delegate
190 .transport
191 .lock()
192 .as_fake()
193 .on_request::<R, F>(handler);
194 }
195
196 #[cfg(any(test, feature = "test-support"))]
197 pub async fn fake_reverse_request<R: dap_types::requests::Request>(&self, args: R::Arguments) {
198 self.send_message(Message::Request(dap_types::messages::Request {
199 seq: self.sequence_count.load(Ordering::Relaxed),
200 command: R::COMMAND.into(),
201 arguments: serde_json::to_value(args).ok(),
202 }))
203 .await
204 .unwrap();
205 }
206
207 #[cfg(any(test, feature = "test-support"))]
208 pub async fn on_response<R: dap_types::requests::Request, F>(&self, handler: F)
209 where
210 F: 'static + Send + Fn(Response),
211 {
212 self.transport_delegate
213 .transport
214 .lock()
215 .as_fake()
216 .on_response::<R, F>(handler);
217 }
218
219 #[cfg(any(test, feature = "test-support"))]
220 pub async fn fake_event(&self, event: dap_types::messages::Events) {
221 self.send_message(Message::Event(Box::new(event)))
222 .await
223 .unwrap();
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230 use crate::{client::DebugAdapterClient, debugger_settings::DebuggerSettings};
231 use dap_types::{
232 Capabilities, InitializeRequestArguments, InitializeRequestArgumentsPathFormat,
233 RunInTerminalRequestArguments, StartDebuggingRequestArguments,
234 messages::Events,
235 requests::{Initialize, Request, RunInTerminal},
236 };
237 use gpui::TestAppContext;
238 use serde_json::json;
239 use settings::{Settings, SettingsStore};
240 use std::sync::{
241 Arc,
242 atomic::{AtomicBool, Ordering},
243 };
244
245 pub fn init_test(cx: &mut gpui::TestAppContext) {
246 zlog::init_test();
247
248 cx.update(|cx| {
249 let settings = SettingsStore::test(cx);
250 cx.set_global(settings);
251 DebuggerSettings::register(cx);
252 });
253 }
254
255 #[gpui::test]
256 pub async fn test_initialize_client(cx: &mut TestAppContext) {
257 init_test(cx);
258
259 let client = DebugAdapterClient::start(
260 crate::client::SessionId(1),
261 DebugAdapterBinary {
262 command: Some("command".into()),
263 arguments: Default::default(),
264 envs: Default::default(),
265 connection: None,
266 cwd: None,
267 request_args: StartDebuggingRequestArguments {
268 configuration: serde_json::Value::Null,
269 request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
270 },
271 },
272 Box::new(|_| panic!("Did not expect to hit this code path")),
273 &mut cx.to_async(),
274 )
275 .await
276 .unwrap();
277
278 client.on_request::<Initialize, _>(move |_, _| {
279 Ok(dap_types::Capabilities {
280 supports_configuration_done_request: Some(true),
281 ..Default::default()
282 })
283 });
284
285 cx.run_until_parked();
286
287 let response = client
288 .request::<Initialize>(InitializeRequestArguments {
289 client_id: Some("zed".to_owned()),
290 client_name: Some("Zed".to_owned()),
291 adapter_id: "fake-adapter".to_owned(),
292 locale: Some("en-US".to_owned()),
293 path_format: Some(InitializeRequestArgumentsPathFormat::Path),
294 supports_variable_type: Some(true),
295 supports_variable_paging: Some(false),
296 supports_run_in_terminal_request: Some(true),
297 supports_memory_references: Some(true),
298 supports_progress_reporting: Some(false),
299 supports_invalidated_event: Some(false),
300 lines_start_at1: Some(true),
301 columns_start_at1: Some(true),
302 supports_memory_event: Some(false),
303 supports_args_can_be_interpreted_by_shell: Some(false),
304 supports_start_debugging_request: Some(true),
305 supports_ansistyling: Some(false),
306 })
307 .await
308 .unwrap();
309
310 cx.run_until_parked();
311
312 assert_eq!(
313 dap_types::Capabilities {
314 supports_configuration_done_request: Some(true),
315 ..Default::default()
316 },
317 response
318 );
319 }
320
321 #[gpui::test]
322 pub async fn test_calls_event_handler(cx: &mut TestAppContext) {
323 init_test(cx);
324
325 let called_event_handler = Arc::new(AtomicBool::new(false));
326
327 let client = DebugAdapterClient::start(
328 crate::client::SessionId(1),
329 DebugAdapterBinary {
330 command: Some("command".into()),
331 arguments: Default::default(),
332 envs: Default::default(),
333 connection: None,
334 cwd: None,
335 request_args: StartDebuggingRequestArguments {
336 configuration: serde_json::Value::Null,
337 request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
338 },
339 },
340 Box::new({
341 let called_event_handler = called_event_handler.clone();
342 move |event| {
343 called_event_handler.store(true, Ordering::SeqCst);
344
345 assert_eq!(
346 Message::Event(Box::new(Events::Initialized(
347 Some(Capabilities::default())
348 ))),
349 event
350 );
351 }
352 }),
353 &mut cx.to_async(),
354 )
355 .await
356 .unwrap();
357
358 cx.run_until_parked();
359
360 client
361 .fake_event(Events::Initialized(Some(Capabilities::default())))
362 .await;
363
364 cx.run_until_parked();
365
366 assert!(
367 called_event_handler.load(std::sync::atomic::Ordering::SeqCst),
368 "Event handler was not called"
369 );
370 }
371
372 #[gpui::test]
373 pub async fn test_calls_event_handler_for_reverse_request(cx: &mut TestAppContext) {
374 init_test(cx);
375
376 let called_event_handler = Arc::new(AtomicBool::new(false));
377
378 let client = DebugAdapterClient::start(
379 crate::client::SessionId(1),
380 DebugAdapterBinary {
381 command: Some("command".into()),
382 arguments: Default::default(),
383 envs: Default::default(),
384 connection: None,
385 cwd: None,
386 request_args: dap_types::StartDebuggingRequestArguments {
387 configuration: serde_json::Value::Null,
388 request: dap_types::StartDebuggingRequestArgumentsRequest::Launch,
389 },
390 },
391 Box::new({
392 let called_event_handler = called_event_handler.clone();
393 move |event| {
394 called_event_handler.store(true, Ordering::SeqCst);
395
396 assert_eq!(
397 Message::Request(dap_types::messages::Request {
398 seq: 1,
399 command: RunInTerminal::COMMAND.into(),
400 arguments: Some(json!({
401 "cwd": "/project/path/src",
402 "args": ["node", "test.js"],
403 }))
404 }),
405 event
406 );
407 }
408 }),
409 &mut cx.to_async(),
410 )
411 .await
412 .unwrap();
413
414 cx.run_until_parked();
415
416 client
417 .fake_reverse_request::<RunInTerminal>(RunInTerminalRequestArguments {
418 kind: None,
419 title: None,
420 cwd: "/project/path/src".into(),
421 args: vec!["node".into(), "test.js".into()],
422 env: None,
423 args_can_be_interpreted_by_shell: None,
424 })
425 .await;
426
427 cx.run_until_parked();
428
429 assert!(
430 called_event_handler.load(std::sync::atomic::Ordering::SeqCst),
431 "Event handler was not called"
432 );
433 }
434}