dap_store.rs

  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        cx.spawn(async move |_, cx| {
581            let mut inlay_hints = Vec::with_capacity(inline_value_locations.len());
582            for inline_value_location in inline_value_locations.iter() {
583                let point = snapshot.point_to_point_utf16(language::Point::new(
584                    inline_value_location.row as u32,
585                    inline_value_location.column as u32,
586                ));
587                let position = snapshot.anchor_after(point);
588
589                match inline_value_location.lookup {
590                    VariableLookupKind::Variable => {
591                        let Some(variable) = all_variables
592                            .iter()
593                            .find(|variable| variable.name == inline_value_location.variable_name)
594                        else {
595                            continue;
596                        };
597
598                        inlay_hints.push(InlayHint {
599                            position,
600                            label: InlayHintLabel::String(format!(": {}", variable.value)),
601                            kind: Some(InlayHintKind::Type),
602                            padding_left: false,
603                            padding_right: false,
604                            tooltip: None,
605                            resolve_state: ResolveState::Resolved,
606                        });
607                    }
608                    VariableLookupKind::Expression => {
609                        let Ok(eval_task) = session.update(cx, |session, _| {
610                            session.mode.request_dap(EvaluateCommand {
611                                expression: inline_value_location.variable_name.clone(),
612                                frame_id: Some(stack_frame_id),
613                                source: None,
614                                context: Some(EvaluateArgumentsContext::Variables),
615                            })
616                        }) else {
617                            continue;
618                        };
619
620                        if let Some(response) = eval_task.await.log_err() {
621                            inlay_hints.push(InlayHint {
622                                position,
623                                label: InlayHintLabel::String(format!(": {}", response.result)),
624                                kind: Some(InlayHintKind::Type),
625                                padding_left: false,
626                                padding_right: false,
627                                tooltip: None,
628                                resolve_state: ResolveState::Resolved,
629                            });
630                        };
631                    }
632                };
633            }
634
635            Ok(inlay_hints)
636        })
637    }
638
639    pub fn shutdown_sessions(&mut self, cx: &mut Context<Self>) -> Task<()> {
640        let mut tasks = vec![];
641        for session_id in self.sessions.keys().cloned().collect::<Vec<_>>() {
642            tasks.push(self.shutdown_session(session_id, cx));
643        }
644
645        cx.background_executor().spawn(async move {
646            futures::future::join_all(tasks).await;
647        })
648    }
649
650    pub fn shutdown_session(
651        &mut self,
652        session_id: SessionId,
653        cx: &mut Context<Self>,
654    ) -> Task<Result<()>> {
655        let Some(session) = self.sessions.remove(&session_id) else {
656            return Task::ready(Err(anyhow!("Could not find session: {:?}", session_id)));
657        };
658
659        let shutdown_children = session
660            .read(cx)
661            .child_session_ids()
662            .iter()
663            .map(|session_id| self.shutdown_session(*session_id, cx))
664            .collect::<Vec<_>>();
665
666        let shutdown_parent_task = if let Some(parent_session) = session
667            .read(cx)
668            .parent_id(cx)
669            .and_then(|session_id| self.session_by_id(session_id))
670        {
671            let shutdown_id = parent_session.update(cx, |parent_session, _| {
672                parent_session.remove_child_session_id(session_id);
673
674                if parent_session.child_session_ids().len() == 0 {
675                    Some(parent_session.session_id())
676                } else {
677                    None
678                }
679            });
680
681            shutdown_id.map(|session_id| self.shutdown_session(session_id, cx))
682        } else {
683            None
684        };
685
686        let shutdown_task = session.update(cx, |this, cx| this.shutdown(cx));
687
688        cx.background_spawn(async move {
689            if shutdown_children.len() > 0 {
690                let _ = join_all(shutdown_children).await;
691            }
692
693            shutdown_task.await;
694
695            if let Some(parent_task) = shutdown_parent_task {
696                parent_task.await?;
697            }
698
699            Ok(())
700        })
701    }
702
703    pub fn shared(
704        &mut self,
705        project_id: u64,
706        downstream_client: AnyProtoClient,
707        _: &mut Context<Self>,
708    ) {
709        self.downstream_client = Some((downstream_client.clone(), project_id));
710    }
711
712    pub fn unshared(&mut self, cx: &mut Context<Self>) {
713        self.downstream_client.take();
714
715        cx.notify();
716    }
717
718    async fn handle_run_debug_locator(
719        this: Entity<Self>,
720        envelope: TypedEnvelope<proto::RunDebugLocators>,
721        mut cx: AsyncApp,
722    ) -> Result<proto::DebugRequest> {
723        let task = envelope
724            .payload
725            .build_command
726            .ok_or_else(|| anyhow!("missing definition"))?;
727        let build_task = SpawnInTerminal::from_proto(task);
728        let locator = envelope.payload.locator;
729        let request = this
730            .update(&mut cx, |this, cx| {
731                this.run_debug_locator(&locator, build_task, cx)
732            })?
733            .await?;
734
735        Ok(request.to_proto())
736    }
737
738    async fn handle_get_debug_adapter_binary(
739        this: Entity<Self>,
740        envelope: TypedEnvelope<proto::GetDebugAdapterBinary>,
741        mut cx: AsyncApp,
742    ) -> Result<proto::DebugAdapterBinary> {
743        let definition = DebugTaskDefinition::from_proto(
744            envelope
745                .payload
746                .definition
747                .ok_or_else(|| anyhow!("missing definition"))?,
748        )?;
749        let (tx, mut rx) = mpsc::unbounded();
750        let session_id = envelope.payload.session_id;
751        cx.spawn({
752            let this = this.clone();
753            async move |cx| {
754                while let Some(message) = rx.next().await {
755                    this.update(cx, |this, _| {
756                        if let Some((downstream, project_id)) = this.downstream_client.clone() {
757                            downstream
758                                .send(proto::LogToDebugConsole {
759                                    project_id,
760                                    session_id,
761                                    message,
762                                })
763                                .ok();
764                        }
765                    })
766                    .ok();
767                }
768            }
769        })
770        .detach();
771
772        let binary = this
773            .update(&mut cx, |this, cx| {
774                this.get_debug_adapter_binary(definition, SessionId::from_proto(session_id), tx, cx)
775            })?
776            .await?;
777        Ok(binary.to_proto())
778    }
779
780    async fn handle_log_to_debug_console(
781        this: Entity<Self>,
782        envelope: TypedEnvelope<proto::LogToDebugConsole>,
783        mut cx: AsyncApp,
784    ) -> Result<()> {
785        let session_id = SessionId::from_proto(envelope.payload.session_id);
786        this.update(&mut cx, |this, cx| {
787            let Some(session) = this.sessions.get(&session_id) else {
788                return;
789            };
790            session.update(cx, |session, cx| {
791                session
792                    .console_output(cx)
793                    .unbounded_send(envelope.payload.message)
794                    .ok();
795            })
796        })
797    }
798}
799
800#[derive(Clone)]
801pub struct DapAdapterDelegate {
802    fs: Arc<dyn Fs>,
803    console: mpsc::UnboundedSender<String>,
804    worktree_id: WorktreeId,
805    node_runtime: NodeRuntime,
806    http_client: Arc<dyn HttpClient>,
807    toolchain_store: Arc<dyn LanguageToolchainStore>,
808    load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
809}
810
811impl DapAdapterDelegate {
812    pub fn new(
813        fs: Arc<dyn Fs>,
814        worktree_id: WorktreeId,
815        status: mpsc::UnboundedSender<String>,
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    ) -> Self {
821        Self {
822            fs,
823            console: status,
824            worktree_id,
825            http_client,
826            node_runtime,
827            toolchain_store,
828            load_shell_env_task,
829        }
830    }
831}
832
833#[async_trait(?Send)]
834impl dap::adapters::DapDelegate for DapAdapterDelegate {
835    fn worktree_id(&self) -> WorktreeId {
836        self.worktree_id
837    }
838
839    fn http_client(&self) -> Arc<dyn HttpClient> {
840        self.http_client.clone()
841    }
842
843    fn node_runtime(&self) -> NodeRuntime {
844        self.node_runtime.clone()
845    }
846
847    fn fs(&self) -> Arc<dyn Fs> {
848        self.fs.clone()
849    }
850
851    fn output_to_console(&self, msg: String) {
852        self.console.unbounded_send(msg).ok();
853    }
854
855    fn which(&self, command: &OsStr) -> Option<PathBuf> {
856        which::which(command).ok()
857    }
858
859    async fn shell_env(&self) -> HashMap<String, String> {
860        let task = self.load_shell_env_task.clone();
861        task.await.unwrap_or_default()
862    }
863
864    fn toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {
865        self.toolchain_store.clone()
866    }
867}