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