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