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