1use super::{
2 breakpoint_store::BreakpointStore,
3 dap_command::EvaluateCommand,
4 locators,
5 session::{self, Session, SessionStateEvent},
6};
7use crate::{
8 InlayHint, InlayHintLabel, ProjectEnvironment, ResolveState,
9 project_settings::ProjectSettings,
10 terminals::{SshCommand, wrap_for_ssh},
11 worktree_store::WorktreeStore,
12};
13use anyhow::{Result, anyhow};
14use async_trait::async_trait;
15use collections::HashMap;
16use dap::{
17 Capabilities, CompletionItem, CompletionsArguments, DapRegistry, DebugRequest,
18 EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, Source, StackFrameId,
19 adapters::{
20 DapStatus, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition, TcpArguments,
21 },
22 client::SessionId,
23 messages::Message,
24 requests::{Completions, Evaluate},
25};
26use fs::Fs;
27use futures::future::{Shared, join_all};
28use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task};
29use http_client::HttpClient;
30use language::{
31 BinaryStatus, Buffer, LanguageRegistry, LanguageToolchainStore,
32 language_settings::InlayHintKind, range_from_lsp,
33};
34use lsp::LanguageServerName;
35use node_runtime::NodeRuntime;
36
37use remote::SshRemoteClient;
38use rpc::{
39 AnyProtoClient, TypedEnvelope,
40 proto::{self},
41};
42use settings::{Settings, WorktreeId};
43use smol::lock::Mutex;
44use std::{
45 borrow::Borrow,
46 collections::{BTreeMap, HashSet},
47 ffi::OsStr,
48 net::Ipv4Addr,
49 path::{Path, PathBuf},
50 sync::{Arc, Once},
51};
52use task::{DebugScenario, SpawnInTerminal};
53use util::ResultExt as _;
54use worktree::Worktree;
55
56#[derive(Debug)]
57pub enum DapStoreEvent {
58 DebugClientStarted(SessionId),
59 DebugSessionInitialized(SessionId),
60 DebugClientShutdown(SessionId),
61 DebugClientEvent {
62 session_id: SessionId,
63 message: Message,
64 },
65 Notification(String),
66 RemoteHasInitialized,
67}
68
69#[allow(clippy::large_enum_variant)]
70enum DapStoreMode {
71 Local(LocalDapStore),
72 Ssh(SshDapStore),
73 Collab,
74}
75
76pub struct LocalDapStore {
77 fs: Arc<dyn Fs>,
78 node_runtime: NodeRuntime,
79 http_client: Arc<dyn HttpClient>,
80 environment: Entity<ProjectEnvironment>,
81 language_registry: Arc<LanguageRegistry>,
82 toolchain_store: Arc<dyn LanguageToolchainStore>,
83}
84
85pub struct SshDapStore {
86 ssh_client: Entity<SshRemoteClient>,
87 upstream_client: AnyProtoClient,
88 upstream_project_id: u64,
89}
90
91pub struct DapStore {
92 mode: DapStoreMode,
93 downstream_client: Option<(AnyProtoClient, u64)>,
94 breakpoint_store: Entity<BreakpointStore>,
95 worktree_store: Entity<WorktreeStore>,
96 sessions: BTreeMap<SessionId, Entity<Session>>,
97 next_session_id: u32,
98}
99
100impl EventEmitter<DapStoreEvent> for DapStore {}
101
102impl DapStore {
103 pub fn init(client: &AnyProtoClient, cx: &mut App) {
104 static ADD_LOCATORS: Once = Once::new();
105 client.add_entity_request_handler(Self::handle_run_debug_locator);
106 client.add_entity_request_handler(Self::handle_get_debug_adapter_binary);
107 ADD_LOCATORS.call_once(|| {
108 DapRegistry::global(cx)
109 .add_locator("cargo".into(), Arc::new(locators::cargo::CargoLocator {}))
110 });
111 }
112
113 #[expect(clippy::too_many_arguments)]
114 pub fn new_local(
115 http_client: Arc<dyn HttpClient>,
116 node_runtime: NodeRuntime,
117 fs: Arc<dyn Fs>,
118 language_registry: Arc<LanguageRegistry>,
119 environment: Entity<ProjectEnvironment>,
120 toolchain_store: Arc<dyn LanguageToolchainStore>,
121 worktree_store: Entity<WorktreeStore>,
122 breakpoint_store: Entity<BreakpointStore>,
123 cx: &mut Context<Self>,
124 ) -> Self {
125 let mode = DapStoreMode::Local(LocalDapStore {
126 fs,
127 environment,
128 http_client,
129 node_runtime,
130 toolchain_store,
131 language_registry,
132 });
133
134 Self::new(mode, breakpoint_store, worktree_store, cx)
135 }
136
137 pub fn new_ssh(
138 project_id: u64,
139 ssh_client: Entity<SshRemoteClient>,
140 breakpoint_store: Entity<BreakpointStore>,
141 worktree_store: Entity<WorktreeStore>,
142 cx: &mut Context<Self>,
143 ) -> Self {
144 let mode = DapStoreMode::Ssh(SshDapStore {
145 upstream_client: ssh_client.read(cx).proto_client(),
146 ssh_client,
147 upstream_project_id: project_id,
148 });
149
150 Self::new(mode, breakpoint_store, worktree_store, cx)
151 }
152
153 pub fn new_collab(
154 _project_id: u64,
155 _upstream_client: AnyProtoClient,
156 breakpoint_store: Entity<BreakpointStore>,
157 worktree_store: Entity<WorktreeStore>,
158 cx: &mut Context<Self>,
159 ) -> Self {
160 Self::new(DapStoreMode::Collab, breakpoint_store, worktree_store, cx)
161 }
162
163 fn new(
164 mode: DapStoreMode,
165 breakpoint_store: Entity<BreakpointStore>,
166 worktree_store: Entity<WorktreeStore>,
167 _cx: &mut Context<Self>,
168 ) -> Self {
169 Self {
170 mode,
171 next_session_id: 0,
172 downstream_client: None,
173 breakpoint_store,
174 worktree_store,
175 sessions: Default::default(),
176 }
177 }
178
179 pub fn get_debug_adapter_binary(
180 &mut self,
181 definition: DebugTaskDefinition,
182 cx: &mut Context<Self>,
183 ) -> Task<Result<DebugAdapterBinary>> {
184 match &self.mode {
185 DapStoreMode::Local(_) => {
186 let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next()
187 else {
188 return Task::ready(Err(anyhow!("Failed to find a worktree")));
189 };
190 let Some(adapter) = DapRegistry::global(cx).adapter(&definition.adapter) else {
191 return Task::ready(Err(anyhow!("Failed to find a debug adapter")));
192 };
193
194 let user_installed_path = ProjectSettings::get_global(cx)
195 .dap
196 .get(&adapter.name())
197 .and_then(|s| s.binary.as_ref().map(PathBuf::from));
198
199 let delegate = self.delegate(&worktree, cx);
200 let cwd: Arc<Path> = definition
201 .cwd()
202 .unwrap_or(worktree.read(cx).abs_path().as_ref())
203 .into();
204
205 cx.spawn(async move |this, cx| {
206 let mut binary = adapter
207 .get_binary(&delegate, &definition, user_installed_path, cx)
208 .await?;
209
210 let env = this
211 .update(cx, |this, cx| {
212 this.as_local()
213 .unwrap()
214 .environment
215 .update(cx, |environment, cx| {
216 environment.get_directory_environment(cwd, cx)
217 })
218 })?
219 .await;
220
221 if let Some(mut env) = env {
222 env.extend(std::mem::take(&mut binary.envs));
223 binary.envs = env;
224 }
225
226 Ok(binary)
227 })
228 }
229 DapStoreMode::Ssh(ssh) => {
230 let request = ssh.upstream_client.request(proto::GetDebugAdapterBinary {
231 project_id: ssh.upstream_project_id,
232 definition: Some(definition.to_proto()),
233 });
234 let ssh_client = ssh.ssh_client.clone();
235
236 cx.spawn(async move |_, cx| {
237 let response = request.await?;
238 let binary = DebugAdapterBinary::from_proto(response)?;
239 let mut ssh_command = ssh_client.update(cx, |ssh, _| {
240 anyhow::Ok(SshCommand {
241 arguments: ssh
242 .ssh_args()
243 .ok_or_else(|| anyhow!("SSH arguments not found"))?,
244 })
245 })??;
246
247 let mut connection = None;
248 if let Some(c) = binary.connection {
249 let local_bind_addr = Ipv4Addr::new(127, 0, 0, 1);
250 let port =
251 dap::transport::TcpTransport::unused_port(local_bind_addr).await?;
252
253 ssh_command.add_port_forwarding(port, c.host.to_string(), c.port);
254 connection = Some(TcpArguments {
255 port: c.port,
256 host: local_bind_addr,
257 timeout: c.timeout,
258 })
259 }
260
261 let (program, args) = wrap_for_ssh(
262 &ssh_command,
263 Some((&binary.command, &binary.arguments)),
264 binary.cwd.as_deref(),
265 binary.envs,
266 None,
267 );
268
269 Ok(DebugAdapterBinary {
270 command: program,
271 arguments: args,
272 envs: HashMap::default(),
273 cwd: None,
274 connection,
275 request_args: binary.request_args,
276 })
277 })
278 }
279 DapStoreMode::Collab => {
280 Task::ready(Err(anyhow!("Debugging is not yet supported via collab")))
281 }
282 }
283 }
284
285 pub fn debug_scenario_for_build_task(
286 &self,
287 mut build: SpawnInTerminal,
288 unresoved_label: SharedString,
289 adapter: SharedString,
290 cx: &mut App,
291 ) -> Option<DebugScenario> {
292 build.args = build
293 .args
294 .into_iter()
295 .map(|arg| {
296 if arg.starts_with("$") {
297 arg.strip_prefix("$")
298 .and_then(|arg| build.env.get(arg).map(ToOwned::to_owned))
299 .unwrap_or_else(|| arg)
300 } else {
301 arg
302 }
303 })
304 .collect();
305
306 DapRegistry::global(cx)
307 .locators()
308 .values()
309 .find(|locator| locator.accepts(&build))
310 .map(|_| DebugScenario {
311 adapter,
312 label: format!("Debug `{}`", build.label).into(),
313 build: Some(unresoved_label),
314 request: None,
315 initialize_args: None,
316 tcp_connection: None,
317 stop_on_entry: None,
318 })
319 }
320
321 pub fn run_debug_locator(
322 &mut self,
323 mut build_command: SpawnInTerminal,
324 cx: &mut Context<Self>,
325 ) -> Task<Result<DebugRequest>> {
326 match &self.mode {
327 DapStoreMode::Local(_) => {
328 // Pre-resolve args with existing environment.
329 build_command.args = build_command
330 .args
331 .into_iter()
332 .map(|arg| {
333 if arg.starts_with("$") {
334 arg.strip_prefix("$")
335 .and_then(|arg| build_command.env.get(arg).map(ToOwned::to_owned))
336 .unwrap_or_else(|| arg)
337 } else {
338 arg
339 }
340 })
341 .collect();
342 let locators = DapRegistry::global(cx)
343 .locators()
344 .values()
345 .filter(|locator| locator.accepts(&build_command))
346 .cloned()
347 .collect::<Vec<_>>();
348 if !locators.is_empty() {
349 cx.background_spawn(async move {
350 for locator in locators {
351 let result = locator
352 .run(build_command.clone())
353 .await
354 .log_with_level(log::Level::Error);
355 if let Some(result) = result {
356 return Ok(result);
357 }
358 }
359 Err(anyhow!(
360 "None of the locators for task `{}` completed successfully",
361 build_command.label
362 ))
363 })
364 } else {
365 Task::ready(Err(anyhow!(
366 "Couldn't find any locator for task `{}`. Specify the `attach` or `launch` arguments in your debug scenario definition",
367 build_command.label
368 )))
369 }
370 }
371 DapStoreMode::Ssh(ssh) => {
372 let request = ssh.upstream_client.request(proto::RunDebugLocators {
373 project_id: ssh.upstream_project_id,
374 build_command: Some(build_command.to_proto()),
375 });
376 cx.background_spawn(async move {
377 let response = request.await?;
378 DebugRequest::from_proto(response)
379 })
380 }
381 DapStoreMode::Collab => {
382 Task::ready(Err(anyhow!("Debugging is not yet supported via collab")))
383 }
384 }
385 }
386
387 fn as_local(&self) -> Option<&LocalDapStore> {
388 match &self.mode {
389 DapStoreMode::Local(local_dap_store) => Some(local_dap_store),
390 _ => None,
391 }
392 }
393
394 pub fn new_session(
395 &mut self,
396 template: DebugTaskDefinition,
397 parent_session: Option<Entity<Session>>,
398 cx: &mut Context<Self>,
399 ) -> Entity<Session> {
400 let session_id = SessionId(util::post_inc(&mut self.next_session_id));
401
402 if let Some(session) = &parent_session {
403 session.update(cx, |session, _| {
404 session.add_child_session_id(session_id);
405 });
406 }
407
408 let session = Session::new(
409 self.breakpoint_store.clone(),
410 session_id,
411 parent_session,
412 template.clone(),
413 cx,
414 );
415
416 self.sessions.insert(session_id, session.clone());
417 cx.notify();
418
419 cx.subscribe(&session, {
420 move |this: &mut DapStore, _, event: &SessionStateEvent, cx| match event {
421 SessionStateEvent::Shutdown => {
422 this.shutdown_session(session_id, cx).detach_and_log_err(cx);
423 }
424 SessionStateEvent::Restart | SessionStateEvent::SpawnChildSession { .. } => {}
425 SessionStateEvent::Running => {
426 cx.emit(DapStoreEvent::DebugClientStarted(session_id));
427 }
428 }
429 })
430 .detach();
431
432 session
433 }
434
435 pub fn boot_session(
436 &self,
437 session: Entity<Session>,
438 cx: &mut Context<Self>,
439 ) -> Task<Result<()>> {
440 let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next() else {
441 return Task::ready(Err(anyhow!("Failed to find a worktree")));
442 };
443
444 let dap_store = cx.weak_entity();
445 let breakpoint_store = self.breakpoint_store.clone();
446 let definition = session.read(cx).definition();
447
448 cx.spawn({
449 let session = session.clone();
450 async move |this, cx| {
451 let binary = this
452 .update(cx, |this, cx| {
453 this.get_debug_adapter_binary(definition.clone(), cx)
454 })?
455 .await?;
456
457 session
458 .update(cx, |session, cx| {
459 session.boot(binary, worktree, breakpoint_store, dap_store, cx)
460 })?
461 .await
462 }
463 })
464 }
465
466 pub fn session_by_id(
467 &self,
468 session_id: impl Borrow<SessionId>,
469 ) -> Option<Entity<session::Session>> {
470 let session_id = session_id.borrow();
471 let client = self.sessions.get(session_id).cloned();
472
473 client
474 }
475 pub fn sessions(&self) -> impl Iterator<Item = &Entity<Session>> {
476 self.sessions.values()
477 }
478
479 pub fn capabilities_by_id(
480 &self,
481 session_id: impl Borrow<SessionId>,
482 cx: &App,
483 ) -> Option<Capabilities> {
484 let session_id = session_id.borrow();
485 self.sessions
486 .get(session_id)
487 .map(|client| client.read(cx).capabilities.clone())
488 }
489
490 pub fn breakpoint_store(&self) -> &Entity<BreakpointStore> {
491 &self.breakpoint_store
492 }
493
494 pub fn worktree_store(&self) -> &Entity<WorktreeStore> {
495 &self.worktree_store
496 }
497
498 #[allow(dead_code)]
499 async fn handle_ignore_breakpoint_state(
500 this: Entity<Self>,
501 envelope: TypedEnvelope<proto::IgnoreBreakpointState>,
502 mut cx: AsyncApp,
503 ) -> Result<()> {
504 let session_id = SessionId::from_proto(envelope.payload.session_id);
505
506 this.update(&mut cx, |this, cx| {
507 if let Some(session) = this.session_by_id(&session_id) {
508 session.update(cx, |session, cx| {
509 session.set_ignore_breakpoints(envelope.payload.ignore, cx)
510 })
511 } else {
512 Task::ready(HashMap::default())
513 }
514 })?
515 .await;
516
517 Ok(())
518 }
519
520 fn delegate(&self, worktree: &Entity<Worktree>, cx: &mut App) -> DapAdapterDelegate {
521 let Some(local_store) = self.as_local() else {
522 unimplemented!("Starting session on remote side");
523 };
524
525 DapAdapterDelegate::new(
526 local_store.fs.clone(),
527 worktree.read(cx).id(),
528 local_store.node_runtime.clone(),
529 local_store.http_client.clone(),
530 local_store.language_registry.clone(),
531 local_store.toolchain_store.clone(),
532 local_store.environment.update(cx, |env, cx| {
533 env.get_worktree_environment(worktree.clone(), cx)
534 }),
535 )
536 }
537
538 pub fn evaluate(
539 &self,
540 session_id: &SessionId,
541 stack_frame_id: u64,
542 expression: String,
543 context: EvaluateArgumentsContext,
544 source: Option<Source>,
545 cx: &mut Context<Self>,
546 ) -> Task<Result<EvaluateResponse>> {
547 let Some(client) = self
548 .session_by_id(session_id)
549 .and_then(|client| client.read(cx).adapter_client())
550 else {
551 return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id)));
552 };
553
554 cx.background_executor().spawn(async move {
555 client
556 .request::<Evaluate>(EvaluateArguments {
557 expression: expression.clone(),
558 frame_id: Some(stack_frame_id),
559 context: Some(context),
560 format: None,
561 line: None,
562 column: None,
563 source,
564 })
565 .await
566 })
567 }
568
569 pub fn completions(
570 &self,
571 session_id: &SessionId,
572 stack_frame_id: u64,
573 text: String,
574 completion_column: u64,
575 cx: &mut Context<Self>,
576 ) -> Task<Result<Vec<CompletionItem>>> {
577 let Some(client) = self
578 .session_by_id(session_id)
579 .and_then(|client| client.read(cx).adapter_client())
580 else {
581 return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id)));
582 };
583
584 cx.background_executor().spawn(async move {
585 Ok(client
586 .request::<Completions>(CompletionsArguments {
587 frame_id: Some(stack_frame_id),
588 line: None,
589 text,
590 column: completion_column,
591 })
592 .await?
593 .targets)
594 })
595 }
596
597 pub fn resolve_inline_values(
598 &self,
599 session: Entity<Session>,
600 stack_frame_id: StackFrameId,
601 buffer_handle: Entity<Buffer>,
602 inline_values: Vec<lsp::InlineValue>,
603 cx: &mut Context<Self>,
604 ) -> Task<Result<Vec<InlayHint>>> {
605 let snapshot = buffer_handle.read(cx).snapshot();
606 let all_variables = session.read(cx).variables_by_stack_frame_id(stack_frame_id);
607
608 cx.spawn(async move |_, cx| {
609 let mut inlay_hints = Vec::with_capacity(inline_values.len());
610 for inline_value in inline_values.iter() {
611 match inline_value {
612 lsp::InlineValue::Text(text) => {
613 inlay_hints.push(InlayHint {
614 position: snapshot.anchor_after(range_from_lsp(text.range).end),
615 label: InlayHintLabel::String(format!(": {}", text.text)),
616 kind: Some(InlayHintKind::Type),
617 padding_left: false,
618 padding_right: false,
619 tooltip: None,
620 resolve_state: ResolveState::Resolved,
621 });
622 }
623 lsp::InlineValue::VariableLookup(variable_lookup) => {
624 let range = range_from_lsp(variable_lookup.range);
625
626 let mut variable_name = variable_lookup
627 .variable_name
628 .clone()
629 .unwrap_or_else(|| snapshot.text_for_range(range.clone()).collect());
630
631 if !variable_lookup.case_sensitive_lookup {
632 variable_name = variable_name.to_ascii_lowercase();
633 }
634
635 let Some(variable) = all_variables.iter().find(|variable| {
636 if variable_lookup.case_sensitive_lookup {
637 variable.name == variable_name
638 } else {
639 variable.name.to_ascii_lowercase() == variable_name
640 }
641 }) else {
642 continue;
643 };
644
645 inlay_hints.push(InlayHint {
646 position: snapshot.anchor_after(range.end),
647 label: InlayHintLabel::String(format!(": {}", variable.value)),
648 kind: Some(InlayHintKind::Type),
649 padding_left: false,
650 padding_right: false,
651 tooltip: None,
652 resolve_state: ResolveState::Resolved,
653 });
654 }
655 lsp::InlineValue::EvaluatableExpression(expression) => {
656 let range = range_from_lsp(expression.range);
657
658 let expression = expression
659 .expression
660 .clone()
661 .unwrap_or_else(|| snapshot.text_for_range(range.clone()).collect());
662
663 let Ok(eval_task) = session.update(cx, |session, _| {
664 session.mode.request_dap(EvaluateCommand {
665 expression,
666 frame_id: Some(stack_frame_id),
667 source: None,
668 context: Some(EvaluateArgumentsContext::Variables),
669 })
670 }) else {
671 continue;
672 };
673
674 if let Some(response) = eval_task.await.log_err() {
675 inlay_hints.push(InlayHint {
676 position: snapshot.anchor_after(range.end),
677 label: InlayHintLabel::String(format!(": {}", response.result)),
678 kind: Some(InlayHintKind::Type),
679 padding_left: false,
680 padding_right: false,
681 tooltip: None,
682 resolve_state: ResolveState::Resolved,
683 });
684 };
685 }
686 };
687 }
688
689 Ok(inlay_hints)
690 })
691 }
692
693 pub fn shutdown_sessions(&mut self, cx: &mut Context<Self>) -> Task<()> {
694 let mut tasks = vec![];
695 for session_id in self.sessions.keys().cloned().collect::<Vec<_>>() {
696 tasks.push(self.shutdown_session(session_id, cx));
697 }
698
699 cx.background_executor().spawn(async move {
700 futures::future::join_all(tasks).await;
701 })
702 }
703
704 pub fn shutdown_session(
705 &mut self,
706 session_id: SessionId,
707 cx: &mut Context<Self>,
708 ) -> Task<Result<()>> {
709 let Some(session) = self.sessions.remove(&session_id) else {
710 return Task::ready(Err(anyhow!("Could not find session: {:?}", session_id)));
711 };
712
713 let shutdown_children = session
714 .read(cx)
715 .child_session_ids()
716 .iter()
717 .map(|session_id| self.shutdown_session(*session_id, cx))
718 .collect::<Vec<_>>();
719
720 let shutdown_parent_task = if let Some(parent_session) = session
721 .read(cx)
722 .parent_id(cx)
723 .and_then(|session_id| self.session_by_id(session_id))
724 {
725 let shutdown_id = parent_session.update(cx, |parent_session, _| {
726 parent_session.remove_child_session_id(session_id);
727
728 if parent_session.child_session_ids().len() == 0 {
729 Some(parent_session.session_id())
730 } else {
731 None
732 }
733 });
734
735 shutdown_id.map(|session_id| self.shutdown_session(session_id, cx))
736 } else {
737 None
738 };
739
740 let shutdown_task = session.update(cx, |this, cx| this.shutdown(cx));
741
742 cx.background_spawn(async move {
743 if shutdown_children.len() > 0 {
744 let _ = join_all(shutdown_children).await;
745 }
746
747 shutdown_task.await;
748
749 if let Some(parent_task) = shutdown_parent_task {
750 parent_task.await?;
751 }
752
753 Ok(())
754 })
755 }
756
757 pub fn shared(
758 &mut self,
759 project_id: u64,
760 downstream_client: AnyProtoClient,
761 _: &mut Context<Self>,
762 ) {
763 self.downstream_client = Some((downstream_client.clone(), project_id));
764 }
765
766 pub fn unshared(&mut self, cx: &mut Context<Self>) {
767 self.downstream_client.take();
768
769 cx.notify();
770 }
771
772 async fn handle_run_debug_locator(
773 this: Entity<Self>,
774 envelope: TypedEnvelope<proto::RunDebugLocators>,
775 mut cx: AsyncApp,
776 ) -> Result<proto::DebugRequest> {
777 let task = envelope
778 .payload
779 .build_command
780 .ok_or_else(|| anyhow!("missing definition"))?;
781 let build_task = SpawnInTerminal::from_proto(task);
782 let request = this
783 .update(&mut cx, |this, cx| this.run_debug_locator(build_task, cx))?
784 .await?;
785
786 Ok(request.to_proto())
787 }
788
789 async fn handle_get_debug_adapter_binary(
790 this: Entity<Self>,
791 envelope: TypedEnvelope<proto::GetDebugAdapterBinary>,
792 mut cx: AsyncApp,
793 ) -> Result<proto::DebugAdapterBinary> {
794 let definition = DebugTaskDefinition::from_proto(
795 envelope
796 .payload
797 .definition
798 .ok_or_else(|| anyhow!("missing definition"))?,
799 )?;
800 let binary = this
801 .update(&mut cx, |this, cx| {
802 this.get_debug_adapter_binary(definition, cx)
803 })?
804 .await?;
805 Ok(binary.to_proto())
806 }
807}
808
809#[derive(Clone)]
810pub struct DapAdapterDelegate {
811 fs: Arc<dyn Fs>,
812 worktree_id: WorktreeId,
813 node_runtime: NodeRuntime,
814 http_client: Arc<dyn HttpClient>,
815 language_registry: Arc<LanguageRegistry>,
816 toolchain_store: Arc<dyn LanguageToolchainStore>,
817 updated_adapters: Arc<Mutex<HashSet<DebugAdapterName>>>,
818 load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
819}
820
821impl DapAdapterDelegate {
822 pub fn new(
823 fs: Arc<dyn Fs>,
824 worktree_id: WorktreeId,
825 node_runtime: NodeRuntime,
826 http_client: Arc<dyn HttpClient>,
827 language_registry: Arc<LanguageRegistry>,
828 toolchain_store: Arc<dyn LanguageToolchainStore>,
829 load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
830 ) -> Self {
831 Self {
832 fs,
833 worktree_id,
834 http_client,
835 node_runtime,
836 toolchain_store,
837 language_registry,
838 load_shell_env_task,
839 updated_adapters: Default::default(),
840 }
841 }
842}
843
844#[async_trait(?Send)]
845impl dap::adapters::DapDelegate for DapAdapterDelegate {
846 fn worktree_id(&self) -> WorktreeId {
847 self.worktree_id
848 }
849
850 fn http_client(&self) -> Arc<dyn HttpClient> {
851 self.http_client.clone()
852 }
853
854 fn node_runtime(&self) -> NodeRuntime {
855 self.node_runtime.clone()
856 }
857
858 fn fs(&self) -> Arc<dyn Fs> {
859 self.fs.clone()
860 }
861
862 fn updated_adapters(&self) -> Arc<Mutex<HashSet<DebugAdapterName>>> {
863 self.updated_adapters.clone()
864 }
865
866 fn update_status(&self, dap_name: DebugAdapterName, status: dap::adapters::DapStatus) {
867 let name = SharedString::from(dap_name.to_string());
868 let status = match status {
869 DapStatus::None => BinaryStatus::None,
870 DapStatus::Downloading => BinaryStatus::Downloading,
871 DapStatus::Failed { error } => BinaryStatus::Failed { error },
872 DapStatus::CheckingForUpdate => BinaryStatus::CheckingForUpdate,
873 };
874
875 self.language_registry
876 .update_dap_status(LanguageServerName(name), status);
877 }
878
879 fn which(&self, command: &OsStr) -> Option<PathBuf> {
880 which::which(command).ok()
881 }
882
883 async fn shell_env(&self) -> HashMap<String, String> {
884 let task = self.load_shell_env_task.clone();
885 task.await.unwrap_or_default()
886 }
887
888 fn toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {
889 self.toolchain_store.clone()
890 }
891}