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