1037 lines · 34.8 KB · Rust
RawDownload
use super::{    breakpoint_store::BreakpointStore,    dap_command::EvaluateCommand,    locators,    session::{self, Session, SessionStateEvent},};use remote::Interactive; use crate::{    InlayHint, InlayHintLabel, ProjectEnvironment, ResolveState,    debugger::session::SessionQuirks,    project_settings::{DapBinary, ProjectSettings},    worktree_store::WorktreeStore,};use anyhow::{Context as _, Result, anyhow};use async_trait::async_trait;use collections::HashMap;use dap::{    Capabilities, DapRegistry, DebugRequest, EvaluateArgumentsContext, StackFrameId,    adapters::{        DapDelegate, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition, TcpArguments,    },    client::SessionId,    inline_value::VariableLookupKind,    messages::Message,};use fs::{Fs, RemoveOptions};use futures::{    StreamExt, TryStreamExt as _,    channel::mpsc::{self, UnboundedSender},    future::{Shared, join_all},};use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, TaskExt};use http_client::HttpClient;use language::{Buffer, LanguageToolchainStore};use node_runtime::NodeRuntime;use settings::InlayHintKind; use remote::RemoteClient;use rpc::{    AnyProtoClient, TypedEnvelope,    proto::{self},};use serde::{Deserialize, Serialize};use settings::{Settings, SettingsLocation, WorktreeId};use std::{    borrow::Borrow,    collections::BTreeMap,    ffi::OsStr,    net::{IpAddr, Ipv4Addr},    path::{Path, PathBuf},    sync::{Arc, Once},};use task::{DebugScenario, SharedTaskContext, SpawnInTerminal, TaskTemplate};use util::{ResultExt as _, rel_path::RelPath};use worktree::Worktree; #[derive(Debug)]pub enum DapStoreEvent {    DebugClientStarted(SessionId),    DebugSessionInitialized(SessionId),    DebugClientShutdown(SessionId),    DebugClientEvent {        session_id: SessionId,        message: Message,    },    Notification(String),    RemoteHasInitialized,} enum DapStoreMode {    Local(LocalDapStore),    Remote(RemoteDapStore),    Collab,} pub struct LocalDapStore {    fs: Arc<dyn Fs>,    node_runtime: NodeRuntime,    http_client: Arc<dyn HttpClient>,    environment: Entity<ProjectEnvironment>,    toolchain_store: Arc<dyn LanguageToolchainStore>,    is_headless: bool,} pub struct RemoteDapStore {    remote_client: Entity<RemoteClient>,    upstream_client: AnyProtoClient,    upstream_project_id: u64,    node_runtime: NodeRuntime,    http_client: Arc<dyn HttpClient>,} pub struct DapStore {    mode: DapStoreMode,    downstream_client: Option<(AnyProtoClient, u64)>,    breakpoint_store: Entity<BreakpointStore>,    worktree_store: Entity<WorktreeStore>,    sessions: BTreeMap<SessionId, Entity<Session>>,    next_session_id: u32,    adapter_options: BTreeMap<DebugAdapterName, Arc<PersistedAdapterOptions>>,} impl EventEmitter<DapStoreEvent> for DapStore {} #[derive(Clone, Serialize, Deserialize)]pub struct PersistedExceptionBreakpoint {    pub enabled: bool,} /// Represents best-effort serialization of adapter state during last session (e.g. watches)#[derive(Clone, Default, Serialize, Deserialize)]pub struct PersistedAdapterOptions {    /// Which exception breakpoints were enabled during the last session with this adapter?    pub exception_breakpoints: BTreeMap<String, PersistedExceptionBreakpoint>,} impl DapStore {    pub fn init(client: &AnyProtoClient, cx: &mut App) {        static ADD_LOCATORS: Once = Once::new();        ADD_LOCATORS.call_once(|| {            let registry = DapRegistry::global(cx);            registry.add_locator(Arc::new(locators::cargo::CargoLocator {}));            registry.add_locator(Arc::new(locators::go::GoLocator {}));            registry.add_locator(Arc::new(locators::node::NodeLocator));            registry.add_locator(Arc::new(locators::python::PythonLocator));        });        client.add_entity_request_handler(Self::handle_run_debug_locator);        client.add_entity_request_handler(Self::handle_get_debug_adapter_binary);        client.add_entity_message_handler(Self::handle_log_to_debug_console);    }     #[expect(clippy::too_many_arguments)]    pub fn new_local(        http_client: Arc<dyn HttpClient>,        node_runtime: NodeRuntime,        fs: Arc<dyn Fs>,        environment: Entity<ProjectEnvironment>,        toolchain_store: Arc<dyn LanguageToolchainStore>,        worktree_store: Entity<WorktreeStore>,        breakpoint_store: Entity<BreakpointStore>,        is_headless: bool,        cx: &mut Context<Self>,    ) -> Self {        let mode = DapStoreMode::Local(LocalDapStore {            fs: fs.clone(),            environment,            http_client,            node_runtime,            toolchain_store,            is_headless,        });         Self::new(mode, breakpoint_store, worktree_store, fs, cx)    }     pub fn new_remote(        project_id: u64,        remote_client: Entity<RemoteClient>,        breakpoint_store: Entity<BreakpointStore>,        worktree_store: Entity<WorktreeStore>,        node_runtime: NodeRuntime,        http_client: Arc<dyn HttpClient>,        fs: Arc<dyn Fs>,        cx: &mut Context<Self>,    ) -> Self {        let mode = DapStoreMode::Remote(RemoteDapStore {            upstream_client: remote_client.read(cx).proto_client(),            remote_client,            upstream_project_id: project_id,            node_runtime,            http_client,        });         Self::new(mode, breakpoint_store, worktree_store, fs, cx)    }     pub fn new_collab(        _project_id: u64,        _upstream_client: AnyProtoClient,        breakpoint_store: Entity<BreakpointStore>,        worktree_store: Entity<WorktreeStore>,        fs: Arc<dyn Fs>,        cx: &mut Context<Self>,    ) -> Self {        Self::new(            DapStoreMode::Collab,            breakpoint_store,            worktree_store,            fs,            cx,        )    }     fn new(        mode: DapStoreMode,        breakpoint_store: Entity<BreakpointStore>,        worktree_store: Entity<WorktreeStore>,        fs: Arc<dyn Fs>,        cx: &mut Context<Self>,    ) -> Self {        cx.background_spawn(async move {            let dir = paths::debug_adapters_dir().join("js-debug-companion");             let mut children = fs.read_dir(&dir).await?.try_collect::<Vec<_>>().await?;            children.sort_by_key(|child| semver::Version::parse(child.file_name()?.to_str()?).ok());             if let Some(child) = children.last()                && let Some(name) = child.file_name()                && let Some(name) = name.to_str()                && semver::Version::parse(name).is_ok()            {                children.pop();            }             for child in children {                fs.remove_dir(                    &child,                    RemoveOptions {                        recursive: true,                        ignore_if_not_exists: true,                    },                )                .await                .ok();            }             anyhow::Ok(())        })        .detach();         Self {            mode,            next_session_id: 0,            downstream_client: None,            breakpoint_store,            worktree_store,            sessions: Default::default(),            adapter_options: Default::default(),        }    }     pub fn get_debug_adapter_binary(        &mut self,        definition: DebugTaskDefinition,        session_id: SessionId,        worktree: &Entity<Worktree>,        console: UnboundedSender<String>,        cx: &mut Context<Self>,    ) -> Task<Result<DebugAdapterBinary>> {        match &self.mode {            DapStoreMode::Local(_) => {                let Some(adapter) = DapRegistry::global(cx).adapter(&definition.adapter) else {                    return Task::ready(Err(anyhow!("Failed to find a debug adapter")));                };                 let settings_location = SettingsLocation {                    worktree_id: worktree.read(cx).id(),                    path: RelPath::empty(),                };                let dap_settings = ProjectSettings::get(Some(settings_location), cx)                    .dap                    .get(&adapter.name());                let user_installed_path = dap_settings.and_then(|s| match &s.binary {                    DapBinary::Default => None,                    DapBinary::Custom(binary) => {                        let path = PathBuf::from(binary);                        Some(worktree.read(cx).resolve_relative_path(path))                    }                });                let user_args = dap_settings.and_then(|s| s.args.clone());                let user_env = dap_settings.and_then(|s| s.env.clone());                 let delegate = self.delegate(worktree, console, cx);                 let worktree = worktree.clone();                cx.spawn(async move |this, cx| {                    let mut binary = adapter                        .get_binary(                            &delegate,                            &definition,                            user_installed_path,                            user_args,                            user_env,                            cx,                        )                        .await?;                     let env = this                        .update(cx, |this, cx| {                            this.as_local()                                .unwrap()                                .environment                                .update(cx, |environment, cx| {                                    environment.worktree_environment(worktree, cx)                                })                        })?                        .await;                     if let Some(mut env) = env {                        env.extend(std::mem::take(&mut binary.envs));                        binary.envs = env;                    }                     Ok(binary)                })            }            DapStoreMode::Remote(remote) => {                let request = remote                    .upstream_client                    .request(proto::GetDebugAdapterBinary {                        session_id: session_id.to_proto(),                        project_id: remote.upstream_project_id,                        worktree_id: worktree.read(cx).id().to_proto(),                        definition: Some(definition.to_proto()),                    });                let remote = remote.remote_client.clone();                 cx.spawn(async move |_, cx| {                    let response = request.await?;                    let binary = DebugAdapterBinary::from_proto(response)?;                     let port_forwarding;                    let connection;                    if let Some(c) = binary.connection {                        let host = IpAddr::V4(Ipv4Addr::LOCALHOST);                        let port;                        if remote.read_with(cx, |remote, _cx| remote.shares_network_interface()) {                            port = c.port;                            port_forwarding = None;                        } else {                            port = dap::transport::TcpTransport::unused_port(host).await?;                            port_forwarding = Some((port, c.host.to_string(), c.port));                        }                        connection = Some(TcpArguments {                            port,                            host,                            timeout: c.timeout,                        })                    } else {                        port_forwarding = None;                        connection = None;                    }                     let command = remote.read_with(cx, |remote, _cx| {                        remote.build_command(                            binary.command,                            &binary.arguments,                            &binary.envs,                            binary.cwd.map(|path| path.display().to_string()),                            port_forwarding,                            Interactive::No,                        )                    })?;                     Ok(DebugAdapterBinary {                        command: Some(command.program),                        arguments: command.args,                        envs: command.env,                        cwd: None,                        connection,                        request_args: binary.request_args,                    })                })            }            DapStoreMode::Collab => {                Task::ready(Err(anyhow!("Debugging is not yet supported via collab")))            }        }    }     pub fn debug_scenario_for_build_task(        &self,        build: TaskTemplate,        adapter: DebugAdapterName,        label: SharedString,        cx: &mut App,    ) -> Task<Option<DebugScenario>> {        let locators = DapRegistry::global(cx).locators();         cx.background_spawn(async move {            for locator in locators.values() {                if let Some(scenario) = locator.create_scenario(&build, &label, &adapter).await {                    return Some(scenario);                }            }            None        })    }     pub fn run_debug_locator(        &mut self,        locator_name: &str,        build_command: SpawnInTerminal,        cx: &mut Context<Self>,    ) -> Task<Result<DebugRequest>> {        match &self.mode {            DapStoreMode::Local(_) => {                // Pre-resolve args with existing environment.                let locators = DapRegistry::global(cx).locators();                let locator = locators.get(locator_name);                let executor = cx.background_executor().clone();                 if let Some(locator) = locator.cloned() {                    cx.background_spawn(async move {                        let result = locator                            .run(build_command.clone(), executor)                            .await                            .log_with_level(log::Level::Error);                        if let Some(result) = result {                            return Ok(result);                        }                         anyhow::bail!(                            "None of the locators for task `{}` completed successfully",                            build_command.label                        )                    })                } else {                    Task::ready(Err(anyhow!(                        "Couldn't find any locator for task `{}`. Specify the `attach` or `launch` arguments in your debug scenario definition",                        build_command.label                    )))                }            }            DapStoreMode::Remote(remote) => {                let request = remote.upstream_client.request(proto::RunDebugLocators {                    project_id: remote.upstream_project_id,                    build_command: Some(build_command.to_proto()),                    locator: locator_name.to_owned(),                });                cx.background_spawn(async move {                    let response = request.await?;                    DebugRequest::from_proto(response)                })            }            DapStoreMode::Collab => {                Task::ready(Err(anyhow!("Debugging is not yet supported via collab")))            }        }    }     fn as_local(&self) -> Option<&LocalDapStore> {        match &self.mode {            DapStoreMode::Local(local_dap_store) => Some(local_dap_store),            _ => None,        }    }     pub fn new_session(        &mut self,        label: Option<SharedString>,        adapter: DebugAdapterName,        task_context: SharedTaskContext,        parent_session: Option<Entity<Session>>,        quirks: SessionQuirks,        cx: &mut Context<Self>,    ) -> Entity<Session> {        let session_id = SessionId(util::post_inc(&mut self.next_session_id));         if let Some(session) = &parent_session {            session.update(cx, |session, _| {                session.add_child_session_id(session_id);            });        }         let (remote_client, node_runtime, http_client) = match &self.mode {            DapStoreMode::Local(_) => (None, None, None),            DapStoreMode::Remote(remote_dap_store) => (                Some(remote_dap_store.remote_client.clone()),                Some(remote_dap_store.node_runtime.clone()),                Some(remote_dap_store.http_client.clone()),            ),            DapStoreMode::Collab => (None, None, None),        };        let session = Session::new(            self.breakpoint_store.clone(),            session_id,            parent_session,            label,            adapter,            task_context,            quirks,            remote_client,            node_runtime,            http_client,            cx,        );         self.sessions.insert(session_id, session.clone());        cx.notify();         cx.subscribe(&session, {            move |this: &mut DapStore, _, event: &SessionStateEvent, cx| match event {                SessionStateEvent::Shutdown => {                    this.shutdown_session(session_id, cx).detach_and_log_err(cx);                }                SessionStateEvent::Restart | SessionStateEvent::SpawnChildSession { .. } => {}                SessionStateEvent::Running => {                    cx.emit(DapStoreEvent::DebugClientStarted(session_id));

Lines 1–500 of 1037.