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 _, merge_json_value_into};
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 label: SharedString,
397 adapter: DebugAdapterName,
398 parent_session: Option<Entity<Session>>,
399 cx: &mut Context<Self>,
400 ) -> Entity<Session> {
401 let session_id = SessionId(util::post_inc(&mut self.next_session_id));
402
403 if let Some(session) = &parent_session {
404 session.update(cx, |session, _| {
405 session.add_child_session_id(session_id);
406 });
407 }
408
409 let session = Session::new(
410 self.breakpoint_store.clone(),
411 session_id,
412 parent_session,
413 label,
414 adapter,
415 cx,
416 );
417
418 self.sessions.insert(session_id, session.clone());
419 cx.notify();
420
421 cx.subscribe(&session, {
422 move |this: &mut DapStore, _, event: &SessionStateEvent, cx| match event {
423 SessionStateEvent::Shutdown => {
424 this.shutdown_session(session_id, cx).detach_and_log_err(cx);
425 }
426 SessionStateEvent::Restart | SessionStateEvent::SpawnChildSession { .. } => {}
427 SessionStateEvent::Running => {
428 cx.emit(DapStoreEvent::DebugClientStarted(session_id));
429 }
430 }
431 })
432 .detach();
433
434 session
435 }
436
437 pub fn boot_session(
438 &self,
439 session: Entity<Session>,
440 definition: DebugTaskDefinition,
441 cx: &mut Context<Self>,
442 ) -> Task<Result<()>> {
443 let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next() else {
444 return Task::ready(Err(anyhow!("Failed to find a worktree")));
445 };
446
447 let dap_store = cx.weak_entity();
448
449 cx.spawn({
450 let session = session.clone();
451 async move |this, cx| {
452 let mut binary = this
453 .update(cx, |this, cx| {
454 this.get_debug_adapter_binary(definition.clone(), cx)
455 })?
456 .await?;
457
458 if let Some(args) = definition.initialize_args {
459 merge_json_value_into(args, &mut binary.request_args.configuration);
460 }
461
462 session
463 .update(cx, |session, cx| {
464 session.boot(binary, worktree, dap_store, cx)
465 })?
466 .await
467 }
468 })
469 }
470
471 pub fn session_by_id(
472 &self,
473 session_id: impl Borrow<SessionId>,
474 ) -> Option<Entity<session::Session>> {
475 let session_id = session_id.borrow();
476 let client = self.sessions.get(session_id).cloned();
477
478 client
479 }
480 pub fn sessions(&self) -> impl Iterator<Item = &Entity<Session>> {
481 self.sessions.values()
482 }
483
484 pub fn capabilities_by_id(
485 &self,
486 session_id: impl Borrow<SessionId>,
487 cx: &App,
488 ) -> Option<Capabilities> {
489 let session_id = session_id.borrow();
490 self.sessions
491 .get(session_id)
492 .map(|client| client.read(cx).capabilities.clone())
493 }
494
495 pub fn breakpoint_store(&self) -> &Entity<BreakpointStore> {
496 &self.breakpoint_store
497 }
498
499 pub fn worktree_store(&self) -> &Entity<WorktreeStore> {
500 &self.worktree_store
501 }
502
503 #[allow(dead_code)]
504 async fn handle_ignore_breakpoint_state(
505 this: Entity<Self>,
506 envelope: TypedEnvelope<proto::IgnoreBreakpointState>,
507 mut cx: AsyncApp,
508 ) -> Result<()> {
509 let session_id = SessionId::from_proto(envelope.payload.session_id);
510
511 this.update(&mut cx, |this, cx| {
512 if let Some(session) = this.session_by_id(&session_id) {
513 session.update(cx, |session, cx| {
514 session.set_ignore_breakpoints(envelope.payload.ignore, cx)
515 })
516 } else {
517 Task::ready(HashMap::default())
518 }
519 })?
520 .await;
521
522 Ok(())
523 }
524
525 fn delegate(&self, worktree: &Entity<Worktree>, cx: &mut App) -> DapAdapterDelegate {
526 let Some(local_store) = self.as_local() else {
527 unimplemented!("Starting session on remote side");
528 };
529
530 DapAdapterDelegate::new(
531 local_store.fs.clone(),
532 worktree.read(cx).id(),
533 local_store.node_runtime.clone(),
534 local_store.http_client.clone(),
535 local_store.language_registry.clone(),
536 local_store.toolchain_store.clone(),
537 local_store.environment.update(cx, |env, cx| {
538 env.get_worktree_environment(worktree.clone(), cx)
539 }),
540 )
541 }
542
543 pub fn evaluate(
544 &self,
545 session_id: &SessionId,
546 stack_frame_id: u64,
547 expression: String,
548 context: EvaluateArgumentsContext,
549 source: Option<Source>,
550 cx: &mut Context<Self>,
551 ) -> Task<Result<EvaluateResponse>> {
552 let Some(client) = self
553 .session_by_id(session_id)
554 .and_then(|client| client.read(cx).adapter_client())
555 else {
556 return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id)));
557 };
558
559 cx.background_executor().spawn(async move {
560 client
561 .request::<Evaluate>(EvaluateArguments {
562 expression: expression.clone(),
563 frame_id: Some(stack_frame_id),
564 context: Some(context),
565 format: None,
566 line: None,
567 column: None,
568 source,
569 })
570 .await
571 })
572 }
573
574 pub fn completions(
575 &self,
576 session_id: &SessionId,
577 stack_frame_id: u64,
578 text: String,
579 completion_column: u64,
580 cx: &mut Context<Self>,
581 ) -> Task<Result<Vec<CompletionItem>>> {
582 let Some(client) = self
583 .session_by_id(session_id)
584 .and_then(|client| client.read(cx).adapter_client())
585 else {
586 return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id)));
587 };
588
589 cx.background_executor().spawn(async move {
590 Ok(client
591 .request::<Completions>(CompletionsArguments {
592 frame_id: Some(stack_frame_id),
593 line: None,
594 text,
595 column: completion_column,
596 })
597 .await?
598 .targets)
599 })
600 }
601
602 pub fn resolve_inline_values(
603 &self,
604 session: Entity<Session>,
605 stack_frame_id: StackFrameId,
606 buffer_handle: Entity<Buffer>,
607 inline_values: Vec<lsp::InlineValue>,
608 cx: &mut Context<Self>,
609 ) -> Task<Result<Vec<InlayHint>>> {
610 let snapshot = buffer_handle.read(cx).snapshot();
611 let all_variables = session.read(cx).variables_by_stack_frame_id(stack_frame_id);
612
613 cx.spawn(async move |_, cx| {
614 let mut inlay_hints = Vec::with_capacity(inline_values.len());
615 for inline_value in inline_values.iter() {
616 match inline_value {
617 lsp::InlineValue::Text(text) => {
618 inlay_hints.push(InlayHint {
619 position: snapshot.anchor_after(range_from_lsp(text.range).end),
620 label: InlayHintLabel::String(format!(": {}", text.text)),
621 kind: Some(InlayHintKind::Type),
622 padding_left: false,
623 padding_right: false,
624 tooltip: None,
625 resolve_state: ResolveState::Resolved,
626 });
627 }
628 lsp::InlineValue::VariableLookup(variable_lookup) => {
629 let range = range_from_lsp(variable_lookup.range);
630
631 let mut variable_name = variable_lookup
632 .variable_name
633 .clone()
634 .unwrap_or_else(|| snapshot.text_for_range(range.clone()).collect());
635
636 if !variable_lookup.case_sensitive_lookup {
637 variable_name = variable_name.to_ascii_lowercase();
638 }
639
640 let Some(variable) = all_variables.iter().find(|variable| {
641 if variable_lookup.case_sensitive_lookup {
642 variable.name == variable_name
643 } else {
644 variable.name.to_ascii_lowercase() == variable_name
645 }
646 }) else {
647 continue;
648 };
649
650 inlay_hints.push(InlayHint {
651 position: snapshot.anchor_after(range.end),
652 label: InlayHintLabel::String(format!(": {}", variable.value)),
653 kind: Some(InlayHintKind::Type),
654 padding_left: false,
655 padding_right: false,
656 tooltip: None,
657 resolve_state: ResolveState::Resolved,
658 });
659 }
660 lsp::InlineValue::EvaluatableExpression(expression) => {
661 let range = range_from_lsp(expression.range);
662
663 let expression = expression
664 .expression
665 .clone()
666 .unwrap_or_else(|| snapshot.text_for_range(range.clone()).collect());
667
668 let Ok(eval_task) = session.update(cx, |session, _| {
669 session.mode.request_dap(EvaluateCommand {
670 expression,
671 frame_id: Some(stack_frame_id),
672 source: None,
673 context: Some(EvaluateArgumentsContext::Variables),
674 })
675 }) else {
676 continue;
677 };
678
679 if let Some(response) = eval_task.await.log_err() {
680 inlay_hints.push(InlayHint {
681 position: snapshot.anchor_after(range.end),
682 label: InlayHintLabel::String(format!(": {}", response.result)),
683 kind: Some(InlayHintKind::Type),
684 padding_left: false,
685 padding_right: false,
686 tooltip: None,
687 resolve_state: ResolveState::Resolved,
688 });
689 };
690 }
691 };
692 }
693
694 Ok(inlay_hints)
695 })
696 }
697
698 pub fn shutdown_sessions(&mut self, cx: &mut Context<Self>) -> Task<()> {
699 let mut tasks = vec![];
700 for session_id in self.sessions.keys().cloned().collect::<Vec<_>>() {
701 tasks.push(self.shutdown_session(session_id, cx));
702 }
703
704 cx.background_executor().spawn(async move {
705 futures::future::join_all(tasks).await;
706 })
707 }
708
709 pub fn shutdown_session(
710 &mut self,
711 session_id: SessionId,
712 cx: &mut Context<Self>,
713 ) -> Task<Result<()>> {
714 let Some(session) = self.sessions.remove(&session_id) else {
715 return Task::ready(Err(anyhow!("Could not find session: {:?}", session_id)));
716 };
717
718 let shutdown_children = session
719 .read(cx)
720 .child_session_ids()
721 .iter()
722 .map(|session_id| self.shutdown_session(*session_id, cx))
723 .collect::<Vec<_>>();
724
725 let shutdown_parent_task = if let Some(parent_session) = session
726 .read(cx)
727 .parent_id(cx)
728 .and_then(|session_id| self.session_by_id(session_id))
729 {
730 let shutdown_id = parent_session.update(cx, |parent_session, _| {
731 parent_session.remove_child_session_id(session_id);
732
733 if parent_session.child_session_ids().len() == 0 {
734 Some(parent_session.session_id())
735 } else {
736 None
737 }
738 });
739
740 shutdown_id.map(|session_id| self.shutdown_session(session_id, cx))
741 } else {
742 None
743 };
744
745 let shutdown_task = session.update(cx, |this, cx| this.shutdown(cx));
746
747 cx.background_spawn(async move {
748 if shutdown_children.len() > 0 {
749 let _ = join_all(shutdown_children).await;
750 }
751
752 shutdown_task.await;
753
754 if let Some(parent_task) = shutdown_parent_task {
755 parent_task.await?;
756 }
757
758 Ok(())
759 })
760 }
761
762 pub fn shared(
763 &mut self,
764 project_id: u64,
765 downstream_client: AnyProtoClient,
766 _: &mut Context<Self>,
767 ) {
768 self.downstream_client = Some((downstream_client.clone(), project_id));
769 }
770
771 pub fn unshared(&mut self, cx: &mut Context<Self>) {
772 self.downstream_client.take();
773
774 cx.notify();
775 }
776
777 async fn handle_run_debug_locator(
778 this: Entity<Self>,
779 envelope: TypedEnvelope<proto::RunDebugLocators>,
780 mut cx: AsyncApp,
781 ) -> Result<proto::DebugRequest> {
782 let task = envelope
783 .payload
784 .build_command
785 .ok_or_else(|| anyhow!("missing definition"))?;
786 let build_task = SpawnInTerminal::from_proto(task);
787 let request = this
788 .update(&mut cx, |this, cx| this.run_debug_locator(build_task, cx))?
789 .await?;
790
791 Ok(request.to_proto())
792 }
793
794 async fn handle_get_debug_adapter_binary(
795 this: Entity<Self>,
796 envelope: TypedEnvelope<proto::GetDebugAdapterBinary>,
797 mut cx: AsyncApp,
798 ) -> Result<proto::DebugAdapterBinary> {
799 let definition = DebugTaskDefinition::from_proto(
800 envelope
801 .payload
802 .definition
803 .ok_or_else(|| anyhow!("missing definition"))?,
804 )?;
805 let binary = this
806 .update(&mut cx, |this, cx| {
807 this.get_debug_adapter_binary(definition, cx)
808 })?
809 .await?;
810 Ok(binary.to_proto())
811 }
812}
813
814#[derive(Clone)]
815pub struct DapAdapterDelegate {
816 fs: Arc<dyn Fs>,
817 worktree_id: WorktreeId,
818 node_runtime: NodeRuntime,
819 http_client: Arc<dyn HttpClient>,
820 language_registry: Arc<LanguageRegistry>,
821 toolchain_store: Arc<dyn LanguageToolchainStore>,
822 updated_adapters: Arc<Mutex<HashSet<DebugAdapterName>>>,
823 load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
824}
825
826impl DapAdapterDelegate {
827 pub fn new(
828 fs: Arc<dyn Fs>,
829 worktree_id: WorktreeId,
830 node_runtime: NodeRuntime,
831 http_client: Arc<dyn HttpClient>,
832 language_registry: Arc<LanguageRegistry>,
833 toolchain_store: Arc<dyn LanguageToolchainStore>,
834 load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
835 ) -> Self {
836 Self {
837 fs,
838 worktree_id,
839 http_client,
840 node_runtime,
841 toolchain_store,
842 language_registry,
843 load_shell_env_task,
844 updated_adapters: Default::default(),
845 }
846 }
847}
848
849#[async_trait(?Send)]
850impl dap::adapters::DapDelegate for DapAdapterDelegate {
851 fn worktree_id(&self) -> WorktreeId {
852 self.worktree_id
853 }
854
855 fn http_client(&self) -> Arc<dyn HttpClient> {
856 self.http_client.clone()
857 }
858
859 fn node_runtime(&self) -> NodeRuntime {
860 self.node_runtime.clone()
861 }
862
863 fn fs(&self) -> Arc<dyn Fs> {
864 self.fs.clone()
865 }
866
867 fn updated_adapters(&self) -> Arc<Mutex<HashSet<DebugAdapterName>>> {
868 self.updated_adapters.clone()
869 }
870
871 fn update_status(&self, dap_name: DebugAdapterName, status: dap::adapters::DapStatus) {
872 let name = SharedString::from(dap_name.to_string());
873 let status = match status {
874 DapStatus::None => BinaryStatus::None,
875 DapStatus::Downloading => BinaryStatus::Downloading,
876 DapStatus::Failed { error } => BinaryStatus::Failed { error },
877 DapStatus::CheckingForUpdate => BinaryStatus::CheckingForUpdate,
878 };
879
880 self.language_registry
881 .update_dap_status(LanguageServerName(name), status);
882 }
883
884 fn which(&self, command: &OsStr) -> Option<PathBuf> {
885 which::which(command).ok()
886 }
887
888 async fn shell_env(&self) -> HashMap<String, String> {
889 let task = self.load_shell_env_task.clone();
890 task.await.unwrap_or_default()
891 }
892
893 fn toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {
894 self.toolchain_store.clone()
895 }
896}