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