dap_store.rs

   1use super::{
   2    breakpoint_store::BreakpointStore,
   3    locators::DapLocator,
   4    session::{self, Session, SessionStateEvent},
   5};
   6use crate::{
   7    InlayHint, InlayHintLabel, ProjectEnvironment, ResolveState,
   8    project_settings::ProjectSettings,
   9    terminals::{SshCommand, wrap_for_ssh},
  10    worktree_store::WorktreeStore,
  11};
  12use anyhow::{Result, anyhow};
  13use async_trait::async_trait;
  14use collections::HashMap;
  15use dap::{
  16    Capabilities, CompletionItem, CompletionsArguments, DapRegistry, EvaluateArguments,
  17    EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, Source,
  18    StackFrameId, StartDebuggingRequestArguments,
  19    adapters::{DapStatus, DebugAdapterBinary, DebugAdapterName, TcpArguments},
  20    client::SessionId,
  21    messages::Message,
  22    requests::{Completions, Evaluate, Request as _, RunInTerminal, StartDebugging},
  23};
  24use fs::Fs;
  25use futures::{
  26    channel::mpsc,
  27    future::{Shared, join_all},
  28};
  29use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task};
  30use http_client::HttpClient;
  31use language::{
  32    BinaryStatus, Buffer, LanguageRegistry, LanguageToolchainStore,
  33    language_settings::InlayHintKind, range_from_lsp,
  34};
  35use lsp::LanguageServerName;
  36use node_runtime::NodeRuntime;
  37
  38use remote::SshRemoteClient;
  39use rpc::{
  40    AnyProtoClient, TypedEnvelope,
  41    proto::{self},
  42};
  43use serde_json::Value;
  44use settings::{Settings, WorktreeId};
  45use smol::{lock::Mutex, stream::StreamExt};
  46use std::{
  47    borrow::Borrow,
  48    collections::{BTreeMap, HashSet},
  49    ffi::OsStr,
  50    net::Ipv4Addr,
  51    path::{Path, PathBuf},
  52    sync::Arc,
  53};
  54use task::{DebugTaskDefinition, DebugTaskTemplate};
  55use util::ResultExt as _;
  56use worktree::Worktree;
  57
  58#[derive(Debug)]
  59pub enum DapStoreEvent {
  60    DebugClientStarted(SessionId),
  61    DebugSessionInitialized(SessionId),
  62    DebugClientShutdown(SessionId),
  63    DebugClientEvent {
  64        session_id: SessionId,
  65        message: Message,
  66    },
  67    RunInTerminal {
  68        session_id: SessionId,
  69        title: Option<String>,
  70        cwd: Option<Arc<Path>>,
  71        command: Option<String>,
  72        args: Vec<String>,
  73        envs: HashMap<String, String>,
  74        sender: mpsc::Sender<Result<u32>>,
  75    },
  76    SpawnChildSession {
  77        request: StartDebuggingRequestArguments,
  78        parent_session: Entity<Session>,
  79    },
  80    Notification(String),
  81    RemoteHasInitialized,
  82}
  83
  84#[allow(clippy::large_enum_variant)]
  85enum DapStoreMode {
  86    Local(LocalDapStore),
  87    Ssh(SshDapStore),
  88    Collab,
  89}
  90
  91pub struct LocalDapStore {
  92    fs: Arc<dyn Fs>,
  93    node_runtime: NodeRuntime,
  94    http_client: Arc<dyn HttpClient>,
  95    environment: Entity<ProjectEnvironment>,
  96    language_registry: Arc<LanguageRegistry>,
  97    toolchain_store: Arc<dyn LanguageToolchainStore>,
  98    locators: HashMap<String, Arc<dyn DapLocator>>,
  99}
 100
 101pub struct SshDapStore {
 102    ssh_client: Entity<SshRemoteClient>,
 103    upstream_client: AnyProtoClient,
 104    upstream_project_id: u64,
 105}
 106
 107pub struct DapStore {
 108    mode: DapStoreMode,
 109    downstream_client: Option<(AnyProtoClient, u64)>,
 110    breakpoint_store: Entity<BreakpointStore>,
 111    worktree_store: Entity<WorktreeStore>,
 112    sessions: BTreeMap<SessionId, Entity<Session>>,
 113    next_session_id: u32,
 114    start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
 115    _start_debugging_task: Task<()>,
 116}
 117
 118impl EventEmitter<DapStoreEvent> for DapStore {}
 119
 120impl DapStore {
 121    pub fn init(client: &AnyProtoClient) {
 122        client.add_entity_request_handler(Self::handle_run_debug_locator);
 123        client.add_entity_request_handler(Self::handle_get_debug_adapter_binary);
 124    }
 125
 126    #[expect(clippy::too_many_arguments)]
 127    pub fn new_local(
 128        http_client: Arc<dyn HttpClient>,
 129        node_runtime: NodeRuntime,
 130        fs: Arc<dyn Fs>,
 131        language_registry: Arc<LanguageRegistry>,
 132        environment: Entity<ProjectEnvironment>,
 133        toolchain_store: Arc<dyn LanguageToolchainStore>,
 134        worktree_store: Entity<WorktreeStore>,
 135        breakpoint_store: Entity<BreakpointStore>,
 136        cx: &mut Context<Self>,
 137    ) -> Self {
 138        let locators = HashMap::from_iter([(
 139            "cargo".to_string(),
 140            Arc::new(super::locators::cargo::CargoLocator {}) as _,
 141        )]);
 142
 143        let mode = DapStoreMode::Local(LocalDapStore {
 144            fs,
 145            environment,
 146            http_client,
 147            node_runtime,
 148            toolchain_store,
 149            language_registry,
 150            locators,
 151        });
 152
 153        Self::new(mode, breakpoint_store, worktree_store, cx)
 154    }
 155
 156    pub fn new_ssh(
 157        project_id: u64,
 158        ssh_client: Entity<SshRemoteClient>,
 159        breakpoint_store: Entity<BreakpointStore>,
 160        worktree_store: Entity<WorktreeStore>,
 161        cx: &mut Context<Self>,
 162    ) -> Self {
 163        let mode = DapStoreMode::Ssh(SshDapStore {
 164            upstream_client: ssh_client.read(cx).proto_client(),
 165            ssh_client,
 166            upstream_project_id: project_id,
 167        });
 168
 169        Self::new(mode, breakpoint_store, worktree_store, cx)
 170    }
 171
 172    pub fn new_collab(
 173        _project_id: u64,
 174        _upstream_client: AnyProtoClient,
 175        breakpoint_store: Entity<BreakpointStore>,
 176        worktree_store: Entity<WorktreeStore>,
 177        cx: &mut Context<Self>,
 178    ) -> Self {
 179        Self::new(DapStoreMode::Collab, breakpoint_store, worktree_store, cx)
 180    }
 181
 182    fn new(
 183        mode: DapStoreMode,
 184        breakpoint_store: Entity<BreakpointStore>,
 185        worktree_store: Entity<WorktreeStore>,
 186        cx: &mut Context<Self>,
 187    ) -> Self {
 188        let (start_debugging_tx, mut message_rx) =
 189            futures::channel::mpsc::unbounded::<(SessionId, Message)>();
 190        let task = cx.spawn(async move |this, cx| {
 191            while let Some((session_id, message)) = message_rx.next().await {
 192                match message {
 193                    Message::Request(request) => {
 194                        let _ = this
 195                            .update(cx, |this, cx| {
 196                                if request.command == StartDebugging::COMMAND {
 197                                    this.handle_start_debugging_request(session_id, request, cx)
 198                                        .detach_and_log_err(cx);
 199                                } else if request.command == RunInTerminal::COMMAND {
 200                                    this.handle_run_in_terminal_request(session_id, request, cx)
 201                                        .detach_and_log_err(cx);
 202                                }
 203                            })
 204                            .log_err();
 205                    }
 206                    _ => {}
 207                }
 208            }
 209        });
 210
 211        Self {
 212            mode,
 213            _start_debugging_task: task,
 214            start_debugging_tx,
 215            next_session_id: 0,
 216            downstream_client: None,
 217            breakpoint_store,
 218            worktree_store,
 219            sessions: Default::default(),
 220        }
 221    }
 222
 223    pub fn get_debug_adapter_binary(
 224        &mut self,
 225        definition: DebugTaskDefinition,
 226        cx: &mut Context<Self>,
 227    ) -> Task<Result<DebugAdapterBinary>> {
 228        match &self.mode {
 229            DapStoreMode::Local(_) => {
 230                let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next()
 231                else {
 232                    return Task::ready(Err(anyhow!("Failed to find a worktree")));
 233                };
 234                let Some(adapter) = DapRegistry::global(cx).adapter(&definition.adapter) else {
 235                    return Task::ready(Err(anyhow!("Failed to find a debug adapter")));
 236                };
 237
 238                let user_installed_path = ProjectSettings::get_global(cx)
 239                    .dap
 240                    .get(&adapter.name())
 241                    .and_then(|s| s.binary.as_ref().map(PathBuf::from));
 242
 243                let delegate = self.delegate(&worktree, cx);
 244                let cwd: Arc<Path> = definition
 245                    .cwd()
 246                    .unwrap_or(worktree.read(cx).abs_path().as_ref())
 247                    .into();
 248
 249                cx.spawn(async move |this, cx| {
 250                    let mut binary = adapter
 251                        .get_binary(&delegate, &definition, user_installed_path, cx)
 252                        .await?;
 253
 254                    let env = this
 255                        .update(cx, |this, cx| {
 256                            this.as_local()
 257                                .unwrap()
 258                                .environment
 259                                .update(cx, |environment, cx| {
 260                                    environment.get_directory_environment(cwd, cx)
 261                                })
 262                        })?
 263                        .await;
 264
 265                    if let Some(mut env) = env {
 266                        env.extend(std::mem::take(&mut binary.envs));
 267                        binary.envs = env;
 268                    }
 269
 270                    Ok(binary)
 271                })
 272            }
 273            DapStoreMode::Ssh(ssh) => {
 274                let request = ssh.upstream_client.request(proto::GetDebugAdapterBinary {
 275                    project_id: ssh.upstream_project_id,
 276                    task: Some(definition.to_proto()),
 277                });
 278                let ssh_client = ssh.ssh_client.clone();
 279
 280                cx.spawn(async move |_, cx| {
 281                    let response = request.await?;
 282                    let binary = DebugAdapterBinary::from_proto(response)?;
 283                    let mut ssh_command = ssh_client.update(cx, |ssh, _| {
 284                        anyhow::Ok(SshCommand {
 285                            arguments: ssh
 286                                .ssh_args()
 287                                .ok_or_else(|| anyhow!("SSH arguments not found"))?,
 288                        })
 289                    })??;
 290
 291                    let mut connection = None;
 292                    if let Some(c) = binary.connection {
 293                        let local_bind_addr = Ipv4Addr::new(127, 0, 0, 1);
 294                        let port =
 295                            dap::transport::TcpTransport::unused_port(local_bind_addr).await?;
 296
 297                        ssh_command.add_port_forwarding(port, c.host.to_string(), c.port);
 298                        connection = Some(TcpArguments {
 299                            port: c.port,
 300                            host: local_bind_addr,
 301                            timeout: c.timeout,
 302                        })
 303                    }
 304
 305                    let (program, args) = wrap_for_ssh(
 306                        &ssh_command,
 307                        Some((&binary.command, &binary.arguments)),
 308                        binary.cwd.as_deref(),
 309                        binary.envs,
 310                        None,
 311                    );
 312
 313                    Ok(DebugAdapterBinary {
 314                        command: program,
 315                        arguments: args,
 316                        envs: HashMap::default(),
 317                        cwd: None,
 318                        connection,
 319                        request_args: binary.request_args,
 320                    })
 321                })
 322            }
 323            DapStoreMode::Collab => {
 324                Task::ready(Err(anyhow!("Debugging is not yet supported via collab")))
 325            }
 326        }
 327    }
 328
 329    pub fn run_debug_locator(
 330        &mut self,
 331        template: DebugTaskTemplate,
 332        cx: &mut Context<Self>,
 333    ) -> Task<Result<DebugTaskDefinition>> {
 334        let Some(locator_name) = template.locator else {
 335            return Task::ready(Ok(template.definition));
 336        };
 337
 338        match &self.mode {
 339            DapStoreMode::Local(local) => {
 340                if let Some(locator) = local.locators.get(&locator_name).cloned() {
 341                    cx.background_spawn(
 342                        async move { locator.run_locator(template.definition).await },
 343                    )
 344                } else {
 345                    Task::ready(Err(anyhow!("Couldn't find locator {}", locator_name)))
 346                }
 347            }
 348            DapStoreMode::Ssh(ssh) => {
 349                let request = ssh.upstream_client.request(proto::RunDebugLocator {
 350                    project_id: ssh.upstream_project_id,
 351                    locator: locator_name,
 352                    task: Some(template.definition.to_proto()),
 353                });
 354                cx.background_spawn(async move {
 355                    let response = request.await?;
 356                    DebugTaskDefinition::from_proto(response)
 357                })
 358            }
 359            DapStoreMode::Collab => {
 360                Task::ready(Err(anyhow!("Debugging is not yet supported via collab")))
 361            }
 362        }
 363    }
 364
 365    fn as_local(&self) -> Option<&LocalDapStore> {
 366        match &self.mode {
 367            DapStoreMode::Local(local_dap_store) => Some(local_dap_store),
 368            _ => None,
 369        }
 370    }
 371
 372    pub fn new_session(
 373        &mut self,
 374        template: DebugTaskDefinition,
 375        parent_session: Option<Entity<Session>>,
 376        cx: &mut Context<Self>,
 377    ) -> Entity<Session> {
 378        let session_id = SessionId(util::post_inc(&mut self.next_session_id));
 379
 380        if let Some(session) = &parent_session {
 381            session.update(cx, |session, _| {
 382                session.add_child_session_id(session_id);
 383            });
 384        }
 385
 386        let start_debugging_tx = self.start_debugging_tx.clone();
 387
 388        let session = Session::new(
 389            self.breakpoint_store.clone(),
 390            session_id,
 391            parent_session,
 392            template.clone(),
 393            start_debugging_tx,
 394            cx,
 395        );
 396
 397        self.sessions.insert(session_id, session.clone());
 398        cx.notify();
 399
 400        cx.subscribe(&session, {
 401            move |this: &mut DapStore, _, event: &SessionStateEvent, cx| match event {
 402                SessionStateEvent::Shutdown => {
 403                    this.shutdown_session(session_id, cx).detach_and_log_err(cx);
 404                }
 405                SessionStateEvent::Restart => {}
 406                SessionStateEvent::Running => {
 407                    cx.emit(DapStoreEvent::DebugClientStarted(session_id));
 408                }
 409            }
 410        })
 411        .detach();
 412
 413        session
 414    }
 415
 416    pub fn boot_session(
 417        &self,
 418        session: Entity<Session>,
 419        cx: &mut Context<Self>,
 420    ) -> Task<Result<()>> {
 421        let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next() else {
 422            return Task::ready(Err(anyhow!("Failed to find a worktree")));
 423        };
 424
 425        let dap_store = cx.weak_entity();
 426        let breakpoint_store = self.breakpoint_store.clone();
 427        let definition = session.read(cx).definition();
 428
 429        cx.spawn({
 430            let session = session.clone();
 431            async move |this, cx| {
 432                let binary = this
 433                    .update(cx, |this, cx| {
 434                        this.get_debug_adapter_binary(definition.clone(), cx)
 435                    })?
 436                    .await?;
 437
 438                session
 439                    .update(cx, |session, cx| {
 440                        session.boot(binary, worktree, breakpoint_store, dap_store, cx)
 441                    })?
 442                    .await
 443            }
 444        })
 445    }
 446
 447    pub fn session_by_id(
 448        &self,
 449        session_id: impl Borrow<SessionId>,
 450    ) -> Option<Entity<session::Session>> {
 451        let session_id = session_id.borrow();
 452        let client = self.sessions.get(session_id).cloned();
 453
 454        client
 455    }
 456    pub fn sessions(&self) -> impl Iterator<Item = &Entity<Session>> {
 457        self.sessions.values()
 458    }
 459
 460    pub fn capabilities_by_id(
 461        &self,
 462        session_id: impl Borrow<SessionId>,
 463        cx: &App,
 464    ) -> Option<Capabilities> {
 465        let session_id = session_id.borrow();
 466        self.sessions
 467            .get(session_id)
 468            .map(|client| client.read(cx).capabilities.clone())
 469    }
 470
 471    pub fn breakpoint_store(&self) -> &Entity<BreakpointStore> {
 472        &self.breakpoint_store
 473    }
 474
 475    pub fn worktree_store(&self) -> &Entity<WorktreeStore> {
 476        &self.worktree_store
 477    }
 478
 479    #[allow(dead_code)]
 480    async fn handle_ignore_breakpoint_state(
 481        this: Entity<Self>,
 482        envelope: TypedEnvelope<proto::IgnoreBreakpointState>,
 483        mut cx: AsyncApp,
 484    ) -> Result<()> {
 485        let session_id = SessionId::from_proto(envelope.payload.session_id);
 486
 487        this.update(&mut cx, |this, cx| {
 488            if let Some(session) = this.session_by_id(&session_id) {
 489                session.update(cx, |session, cx| {
 490                    session.set_ignore_breakpoints(envelope.payload.ignore, cx)
 491                })
 492            } else {
 493                Task::ready(HashMap::default())
 494            }
 495        })?
 496        .await;
 497
 498        Ok(())
 499    }
 500
 501    fn delegate(&self, worktree: &Entity<Worktree>, cx: &mut App) -> DapAdapterDelegate {
 502        let Some(local_store) = self.as_local() else {
 503            unimplemented!("Starting session on remote side");
 504        };
 505
 506        DapAdapterDelegate::new(
 507            local_store.fs.clone(),
 508            worktree.read(cx).id(),
 509            local_store.node_runtime.clone(),
 510            local_store.http_client.clone(),
 511            local_store.language_registry.clone(),
 512            local_store.toolchain_store.clone(),
 513            local_store.environment.update(cx, |env, cx| {
 514                env.get_worktree_environment(worktree.clone(), cx)
 515            }),
 516        )
 517    }
 518
 519    fn handle_start_debugging_request(
 520        &mut self,
 521        session_id: SessionId,
 522        request: dap::messages::Request,
 523        cx: &mut Context<Self>,
 524    ) -> Task<Result<()>> {
 525        let Some(parent_session) = self.session_by_id(session_id) else {
 526            return Task::ready(Err(anyhow!("Session not found")));
 527        };
 528        let request_seq = request.seq;
 529
 530        let launch_request: Option<Result<StartDebuggingRequestArguments, _>> = request
 531            .arguments
 532            .as_ref()
 533            .map(|value| serde_json::from_value(value.clone()));
 534
 535        let mut success = true;
 536        if let Some(Ok(request)) = launch_request {
 537            cx.emit(DapStoreEvent::SpawnChildSession {
 538                request,
 539                parent_session: parent_session.clone(),
 540            });
 541        } else {
 542            log::error!(
 543                "Failed to parse launch request arguments: {:?}",
 544                request.arguments
 545            );
 546            success = false;
 547        }
 548
 549        cx.spawn(async move |_, cx| {
 550            parent_session
 551                .update(cx, |session, cx| {
 552                    session.respond_to_client(
 553                        request_seq,
 554                        success,
 555                        StartDebugging::COMMAND.to_string(),
 556                        None,
 557                        cx,
 558                    )
 559                })?
 560                .await
 561        })
 562    }
 563
 564    fn handle_run_in_terminal_request(
 565        &mut self,
 566        session_id: SessionId,
 567        request: dap::messages::Request,
 568        cx: &mut Context<Self>,
 569    ) -> Task<Result<()>> {
 570        let Some(session) = self.session_by_id(session_id) else {
 571            return Task::ready(Err(anyhow!("Session not found")));
 572        };
 573
 574        let request_args = serde_json::from_value::<RunInTerminalRequestArguments>(
 575            request.arguments.unwrap_or_default(),
 576        )
 577        .expect("To parse StartDebuggingRequestArguments");
 578
 579        let seq = request.seq;
 580
 581        let cwd = Path::new(&request_args.cwd);
 582
 583        match cwd.try_exists() {
 584            Ok(false) | Err(_) if !request_args.cwd.is_empty() => {
 585                return session.update(cx, |session, cx| {
 586                    session.respond_to_client(
 587                        seq,
 588                        false,
 589                        RunInTerminal::COMMAND.to_string(),
 590                        serde_json::to_value(dap::ErrorResponse {
 591                            error: Some(dap::Message {
 592                                id: seq,
 593                                format: format!("Received invalid/unknown cwd: {cwd:?}"),
 594                                variables: None,
 595                                send_telemetry: None,
 596                                show_user: None,
 597                                url: None,
 598                                url_label: None,
 599                            }),
 600                        })
 601                        .ok(),
 602                        cx,
 603                    )
 604                });
 605            }
 606            _ => (),
 607        }
 608        let mut args = request_args.args.clone();
 609
 610        // Handle special case for NodeJS debug adapter
 611        // If only the Node binary path is provided, we set the command to None
 612        // This prevents the NodeJS REPL from appearing, which is not the desired behavior
 613        // The expected usage is for users to provide their own Node command, e.g., `node test.js`
 614        // This allows the NodeJS debug client to attach correctly
 615        let command = if args.len() > 1 {
 616            Some(args.remove(0))
 617        } else {
 618            None
 619        };
 620
 621        let mut envs: HashMap<String, String> = Default::default();
 622        if let Some(Value::Object(env)) = request_args.env {
 623            for (key, value) in env {
 624                let value_str = match (key.as_str(), value) {
 625                    (_, Value::String(value)) => value,
 626                    _ => continue,
 627                };
 628
 629                envs.insert(key, value_str);
 630            }
 631        }
 632
 633        let (tx, mut rx) = mpsc::channel::<Result<u32>>(1);
 634        let cwd = Some(cwd)
 635            .filter(|cwd| cwd.as_os_str().len() > 0)
 636            .map(Arc::from)
 637            .or_else(|| {
 638                self.session_by_id(session_id)
 639                    .and_then(|session| session.read(cx).binary().cwd.as_deref().map(Arc::from))
 640            });
 641        cx.emit(DapStoreEvent::RunInTerminal {
 642            session_id,
 643            title: request_args.title,
 644            cwd,
 645            command,
 646            args,
 647            envs,
 648            sender: tx,
 649        });
 650        cx.notify();
 651
 652        let session = session.downgrade();
 653        cx.spawn(async move |_, cx| {
 654            let (success, body) = match rx.next().await {
 655                Some(Ok(pid)) => (
 656                    true,
 657                    serde_json::to_value(dap::RunInTerminalResponse {
 658                        process_id: None,
 659                        shell_process_id: Some(pid as u64),
 660                    })
 661                    .ok(),
 662                ),
 663                Some(Err(error)) => (
 664                    false,
 665                    serde_json::to_value(dap::ErrorResponse {
 666                        error: Some(dap::Message {
 667                            id: seq,
 668                            format: error.to_string(),
 669                            variables: None,
 670                            send_telemetry: None,
 671                            show_user: None,
 672                            url: None,
 673                            url_label: None,
 674                        }),
 675                    })
 676                    .ok(),
 677                ),
 678                None => (
 679                    false,
 680                    serde_json::to_value(dap::ErrorResponse {
 681                        error: Some(dap::Message {
 682                            id: seq,
 683                            format: "failed to receive response from spawn terminal".to_string(),
 684                            variables: None,
 685                            send_telemetry: None,
 686                            show_user: None,
 687                            url: None,
 688                            url_label: None,
 689                        }),
 690                    })
 691                    .ok(),
 692                ),
 693            };
 694
 695            session
 696                .update(cx, |session, cx| {
 697                    session.respond_to_client(
 698                        seq,
 699                        success,
 700                        RunInTerminal::COMMAND.to_string(),
 701                        body,
 702                        cx,
 703                    )
 704                })?
 705                .await
 706        })
 707    }
 708
 709    pub fn evaluate(
 710        &self,
 711        session_id: &SessionId,
 712        stack_frame_id: u64,
 713        expression: String,
 714        context: EvaluateArgumentsContext,
 715        source: Option<Source>,
 716        cx: &mut Context<Self>,
 717    ) -> Task<Result<EvaluateResponse>> {
 718        let Some(client) = self
 719            .session_by_id(session_id)
 720            .and_then(|client| client.read(cx).adapter_client())
 721        else {
 722            return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id)));
 723        };
 724
 725        cx.background_executor().spawn(async move {
 726            client
 727                .request::<Evaluate>(EvaluateArguments {
 728                    expression: expression.clone(),
 729                    frame_id: Some(stack_frame_id),
 730                    context: Some(context),
 731                    format: None,
 732                    line: None,
 733                    column: None,
 734                    source,
 735                })
 736                .await
 737        })
 738    }
 739
 740    pub fn completions(
 741        &self,
 742        session_id: &SessionId,
 743        stack_frame_id: u64,
 744        text: String,
 745        completion_column: u64,
 746        cx: &mut Context<Self>,
 747    ) -> Task<Result<Vec<CompletionItem>>> {
 748        let Some(client) = self
 749            .session_by_id(session_id)
 750            .and_then(|client| client.read(cx).adapter_client())
 751        else {
 752            return Task::ready(Err(anyhow!("Could not find client: {:?}", session_id)));
 753        };
 754
 755        cx.background_executor().spawn(async move {
 756            Ok(client
 757                .request::<Completions>(CompletionsArguments {
 758                    frame_id: Some(stack_frame_id),
 759                    line: None,
 760                    text,
 761                    column: completion_column,
 762                })
 763                .await?
 764                .targets)
 765        })
 766    }
 767
 768    pub fn resolve_inline_values(
 769        &self,
 770        session: Entity<Session>,
 771        stack_frame_id: StackFrameId,
 772        buffer_handle: Entity<Buffer>,
 773        inline_values: Vec<lsp::InlineValue>,
 774        cx: &mut Context<Self>,
 775    ) -> Task<Result<Vec<InlayHint>>> {
 776        let snapshot = buffer_handle.read(cx).snapshot();
 777        let all_variables = session.read(cx).variables_by_stack_frame_id(stack_frame_id);
 778
 779        cx.spawn(async move |_, cx| {
 780            let mut inlay_hints = Vec::with_capacity(inline_values.len());
 781            for inline_value in inline_values.iter() {
 782                match inline_value {
 783                    lsp::InlineValue::Text(text) => {
 784                        inlay_hints.push(InlayHint {
 785                            position: snapshot.anchor_after(range_from_lsp(text.range).end),
 786                            label: InlayHintLabel::String(format!(": {}", text.text)),
 787                            kind: Some(InlayHintKind::Type),
 788                            padding_left: false,
 789                            padding_right: false,
 790                            tooltip: None,
 791                            resolve_state: ResolveState::Resolved,
 792                        });
 793                    }
 794                    lsp::InlineValue::VariableLookup(variable_lookup) => {
 795                        let range = range_from_lsp(variable_lookup.range);
 796
 797                        let mut variable_name = variable_lookup
 798                            .variable_name
 799                            .clone()
 800                            .unwrap_or_else(|| snapshot.text_for_range(range.clone()).collect());
 801
 802                        if !variable_lookup.case_sensitive_lookup {
 803                            variable_name = variable_name.to_ascii_lowercase();
 804                        }
 805
 806                        let Some(variable) = all_variables.iter().find(|variable| {
 807                            if variable_lookup.case_sensitive_lookup {
 808                                variable.name == variable_name
 809                            } else {
 810                                variable.name.to_ascii_lowercase() == variable_name
 811                            }
 812                        }) else {
 813                            continue;
 814                        };
 815
 816                        inlay_hints.push(InlayHint {
 817                            position: snapshot.anchor_after(range.end),
 818                            label: InlayHintLabel::String(format!(": {}", variable.value)),
 819                            kind: Some(InlayHintKind::Type),
 820                            padding_left: false,
 821                            padding_right: false,
 822                            tooltip: None,
 823                            resolve_state: ResolveState::Resolved,
 824                        });
 825                    }
 826                    lsp::InlineValue::EvaluatableExpression(expression) => {
 827                        let range = range_from_lsp(expression.range);
 828
 829                        let expression = expression
 830                            .expression
 831                            .clone()
 832                            .unwrap_or_else(|| snapshot.text_for_range(range.clone()).collect());
 833
 834                        let Ok(eval_task) = session.update(cx, |session, cx| {
 835                            session.evaluate(
 836                                expression,
 837                                Some(EvaluateArgumentsContext::Variables),
 838                                Some(stack_frame_id),
 839                                None,
 840                                cx,
 841                            )
 842                        }) else {
 843                            continue;
 844                        };
 845
 846                        if let Some(response) = eval_task.await {
 847                            inlay_hints.push(InlayHint {
 848                                position: snapshot.anchor_after(range.end),
 849                                label: InlayHintLabel::String(format!(": {}", response.result)),
 850                                kind: Some(InlayHintKind::Type),
 851                                padding_left: false,
 852                                padding_right: false,
 853                                tooltip: None,
 854                                resolve_state: ResolveState::Resolved,
 855                            });
 856                        };
 857                    }
 858                };
 859            }
 860
 861            Ok(inlay_hints)
 862        })
 863    }
 864
 865    pub fn shutdown_sessions(&mut self, cx: &mut Context<Self>) -> Task<()> {
 866        let mut tasks = vec![];
 867        for session_id in self.sessions.keys().cloned().collect::<Vec<_>>() {
 868            tasks.push(self.shutdown_session(session_id, cx));
 869        }
 870
 871        cx.background_executor().spawn(async move {
 872            futures::future::join_all(tasks).await;
 873        })
 874    }
 875
 876    pub fn shutdown_session(
 877        &mut self,
 878        session_id: SessionId,
 879        cx: &mut Context<Self>,
 880    ) -> Task<Result<()>> {
 881        let Some(session) = self.sessions.remove(&session_id) else {
 882            return Task::ready(Err(anyhow!("Could not find session: {:?}", session_id)));
 883        };
 884
 885        let shutdown_children = session
 886            .read(cx)
 887            .child_session_ids()
 888            .iter()
 889            .map(|session_id| self.shutdown_session(*session_id, cx))
 890            .collect::<Vec<_>>();
 891
 892        let shutdown_parent_task = if let Some(parent_session) = session
 893            .read(cx)
 894            .parent_id(cx)
 895            .and_then(|session_id| self.session_by_id(session_id))
 896        {
 897            let shutdown_id = parent_session.update(cx, |parent_session, _| {
 898                parent_session.remove_child_session_id(session_id);
 899
 900                if parent_session.child_session_ids().len() == 0 {
 901                    Some(parent_session.session_id())
 902                } else {
 903                    None
 904                }
 905            });
 906
 907            shutdown_id.map(|session_id| self.shutdown_session(session_id, cx))
 908        } else {
 909            None
 910        };
 911
 912        let shutdown_task = session.update(cx, |this, cx| this.shutdown(cx));
 913
 914        cx.background_spawn(async move {
 915            if shutdown_children.len() > 0 {
 916                let _ = join_all(shutdown_children).await;
 917            }
 918
 919            shutdown_task.await;
 920
 921            if let Some(parent_task) = shutdown_parent_task {
 922                parent_task.await?;
 923            }
 924
 925            Ok(())
 926        })
 927    }
 928
 929    pub fn shared(
 930        &mut self,
 931        project_id: u64,
 932        downstream_client: AnyProtoClient,
 933        _: &mut Context<Self>,
 934    ) {
 935        self.downstream_client = Some((downstream_client.clone(), project_id));
 936    }
 937
 938    pub fn unshared(&mut self, cx: &mut Context<Self>) {
 939        self.downstream_client.take();
 940
 941        cx.notify();
 942    }
 943
 944    async fn handle_run_debug_locator(
 945        this: Entity<Self>,
 946        envelope: TypedEnvelope<proto::RunDebugLocator>,
 947        mut cx: AsyncApp,
 948    ) -> Result<proto::DebugTaskDefinition> {
 949        let template = DebugTaskTemplate {
 950            locator: Some(envelope.payload.locator),
 951            definition: DebugTaskDefinition::from_proto(
 952                envelope
 953                    .payload
 954                    .task
 955                    .ok_or_else(|| anyhow!("missing definition"))?,
 956            )?,
 957        };
 958        let definition = this
 959            .update(&mut cx, |this, cx| this.run_debug_locator(template, cx))?
 960            .await?;
 961        Ok(definition.to_proto())
 962    }
 963
 964    async fn handle_get_debug_adapter_binary(
 965        this: Entity<Self>,
 966        envelope: TypedEnvelope<proto::GetDebugAdapterBinary>,
 967        mut cx: AsyncApp,
 968    ) -> Result<proto::DebugAdapterBinary> {
 969        let definition = DebugTaskDefinition::from_proto(
 970            envelope
 971                .payload
 972                .task
 973                .ok_or_else(|| anyhow!("missing definition"))?,
 974        )?;
 975        let binary = this
 976            .update(&mut cx, |this, cx| {
 977                this.get_debug_adapter_binary(definition, cx)
 978            })?
 979            .await?;
 980        Ok(binary.to_proto())
 981    }
 982}
 983
 984#[derive(Clone)]
 985pub struct DapAdapterDelegate {
 986    fs: Arc<dyn Fs>,
 987    worktree_id: WorktreeId,
 988    node_runtime: NodeRuntime,
 989    http_client: Arc<dyn HttpClient>,
 990    language_registry: Arc<LanguageRegistry>,
 991    toolchain_store: Arc<dyn LanguageToolchainStore>,
 992    updated_adapters: Arc<Mutex<HashSet<DebugAdapterName>>>,
 993    load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
 994}
 995
 996impl DapAdapterDelegate {
 997    pub fn new(
 998        fs: Arc<dyn Fs>,
 999        worktree_id: WorktreeId,
1000        node_runtime: NodeRuntime,
1001        http_client: Arc<dyn HttpClient>,
1002        language_registry: Arc<LanguageRegistry>,
1003        toolchain_store: Arc<dyn LanguageToolchainStore>,
1004        load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
1005    ) -> Self {
1006        Self {
1007            fs,
1008            worktree_id,
1009            http_client,
1010            node_runtime,
1011            toolchain_store,
1012            language_registry,
1013            load_shell_env_task,
1014            updated_adapters: Default::default(),
1015        }
1016    }
1017}
1018
1019#[async_trait(?Send)]
1020impl dap::adapters::DapDelegate for DapAdapterDelegate {
1021    fn worktree_id(&self) -> WorktreeId {
1022        self.worktree_id
1023    }
1024
1025    fn http_client(&self) -> Arc<dyn HttpClient> {
1026        self.http_client.clone()
1027    }
1028
1029    fn node_runtime(&self) -> NodeRuntime {
1030        self.node_runtime.clone()
1031    }
1032
1033    fn fs(&self) -> Arc<dyn Fs> {
1034        self.fs.clone()
1035    }
1036
1037    fn updated_adapters(&self) -> Arc<Mutex<HashSet<DebugAdapterName>>> {
1038        self.updated_adapters.clone()
1039    }
1040
1041    fn update_status(&self, dap_name: DebugAdapterName, status: dap::adapters::DapStatus) {
1042        let name = SharedString::from(dap_name.to_string());
1043        let status = match status {
1044            DapStatus::None => BinaryStatus::None,
1045            DapStatus::Downloading => BinaryStatus::Downloading,
1046            DapStatus::Failed { error } => BinaryStatus::Failed { error },
1047            DapStatus::CheckingForUpdate => BinaryStatus::CheckingForUpdate,
1048        };
1049
1050        self.language_registry
1051            .update_dap_status(LanguageServerName(name), status);
1052    }
1053
1054    fn which(&self, command: &OsStr) -> Option<PathBuf> {
1055        which::which(command).ok()
1056    }
1057
1058    async fn shell_env(&self) -> HashMap<String, String> {
1059        let task = self.load_shell_env_task.clone();
1060        task.await.unwrap_or_default()
1061    }
1062
1063    fn toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {
1064        self.toolchain_store.clone()
1065    }
1066}