959 lines · 32.3 KB · Rust
RawDownload
use super::{    breakpoint_store::BreakpointStore,    dap_command::EvaluateCommand,    locators,    session::{self, Session, SessionStateEvent},};use crate::{    InlayHint, InlayHintLabel, ProjectEnvironment, ResolveState,    debugger::session::SessionQuirks,    project_settings::ProjectSettings,    terminals::{SshCommand, wrap_for_ssh},    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;use futures::{    StreamExt,    channel::mpsc::{self, UnboundedSender},    future::{Shared, join_all},};use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task};use http_client::HttpClient;use language::{Buffer, LanguageToolchainStore, language_settings::InlayHintKind};use node_runtime::NodeRuntime; use remote::{SshRemoteClient, ssh_session::SshArgs};use rpc::{    AnyProtoClient, TypedEnvelope,    proto::{self},};use serde::{Deserialize, Serialize};use settings::{Settings, SettingsLocation, WorktreeId};use std::{    borrow::Borrow,    collections::BTreeMap,    ffi::OsStr,    net::Ipv4Addr,    path::{Path, PathBuf},    sync::{Arc, Once},};use task::{DebugScenario, SpawnInTerminal, TaskContext, TaskTemplate};use util::ResultExt as _;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),    Ssh(SshDapStore),    Collab,} pub struct LocalDapStore {    fs: Arc<dyn Fs>,    node_runtime: NodeRuntime,    http_client: Arc<dyn HttpClient>,    environment: Entity<ProjectEnvironment>,    toolchain_store: Arc<dyn LanguageToolchainStore>,} pub struct SshDapStore {    ssh_client: Entity<SshRemoteClient>,    upstream_client: AnyProtoClient,    upstream_project_id: u64,} 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>,        cx: &mut Context<Self>,    ) -> Self {        let mode = DapStoreMode::Local(LocalDapStore {            fs,            environment,            http_client,            node_runtime,            toolchain_store,        });         Self::new(mode, breakpoint_store, worktree_store, cx)    }     pub fn new_ssh(        project_id: u64,        ssh_client: Entity<SshRemoteClient>,        breakpoint_store: Entity<BreakpointStore>,        worktree_store: Entity<WorktreeStore>,        cx: &mut Context<Self>,    ) -> Self {        let mode = DapStoreMode::Ssh(SshDapStore {            upstream_client: ssh_client.read(cx).proto_client(),            ssh_client,            upstream_project_id: project_id,        });         Self::new(mode, breakpoint_store, worktree_store, cx)    }     pub fn new_collab(        _project_id: u64,        _upstream_client: AnyProtoClient,        breakpoint_store: Entity<BreakpointStore>,        worktree_store: Entity<WorktreeStore>,        cx: &mut Context<Self>,    ) -> Self {        Self::new(DapStoreMode::Collab, breakpoint_store, worktree_store, cx)    }     fn new(        mode: DapStoreMode,        breakpoint_store: Entity<BreakpointStore>,        worktree_store: Entity<WorktreeStore>,        _cx: &mut Context<Self>,    ) -> Self {        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: Path::new(""),                };                let dap_settings = ProjectSettings::get(Some(settings_location), cx)                    .dap                    .get(&adapter.name());                let user_installed_path =                    dap_settings.and_then(|s| s.binary.as_ref().map(PathBuf::from));                let user_args = dap_settings.map(|s| s.args.clone());                 let delegate = self.delegate(&worktree, console, cx);                let cwd: Arc<Path> = worktree.read(cx).abs_path().as_ref().into();                 cx.spawn(async move |this, cx| {                    let mut binary = adapter                        .get_binary(&delegate, &definition, user_installed_path, user_args, cx)                        .await?;                     let env = this                        .update(cx, |this, cx| {                            this.as_local()                                .unwrap()                                .environment                                .update(cx, |environment, cx| {                                    environment.get_directory_environment(cwd, cx)                                })                        })?                        .await;                     if let Some(mut env) = env {                        env.extend(std::mem::take(&mut binary.envs));                        binary.envs = env;                    }                     Ok(binary)                })            }            DapStoreMode::Ssh(ssh) => {                let request = ssh.upstream_client.request(proto::GetDebugAdapterBinary {                    session_id: session_id.to_proto(),                    project_id: ssh.upstream_project_id,                    worktree_id: worktree.read(cx).id().to_proto(),                    definition: Some(definition.to_proto()),                });                let ssh_client = ssh.ssh_client.clone();                 cx.spawn(async move |_, cx| {                    let response = request.await?;                    let binary = DebugAdapterBinary::from_proto(response)?;                    let (mut ssh_command, envs, path_style) =                        ssh_client.read_with(cx, |ssh, _| {                            let (SshArgs { arguments, envs }, path_style) =                                ssh.ssh_info().context("SSH arguments not found")?;                            anyhow::Ok((                                SshCommand { arguments },                                envs.unwrap_or_default(),                                path_style,                            ))                        })??;                     let mut connection = None;                    if let Some(c) = binary.connection {                        let local_bind_addr = Ipv4Addr::LOCALHOST;                        let port =                            dap::transport::TcpTransport::unused_port(local_bind_addr).await?;                         ssh_command.add_port_forwarding(port, c.host.to_string(), c.port);                        connection = Some(TcpArguments {                            port,                            host: local_bind_addr,                            timeout: c.timeout,                        })                    }                     let (program, args) = wrap_for_ssh(                        &ssh_command,                        binary                            .command                            .as_ref()                            .map(|command| (command, &binary.arguments)),                        binary.cwd.as_deref(),                        binary.envs,                        None,                        path_style,                    );                     Ok(DebugAdapterBinary {                        command: Some(program),                        arguments: args,                        envs,                        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);                 if let Some(locator) = locator.cloned() {                    cx.background_spawn(async move {                        let result = locator                            .run(build_command.clone())                            .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::Ssh(ssh) => {                let request = ssh.upstream_client.request(proto::RunDebugLocators {                    project_id: ssh.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: TaskContext,        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 session = Session::new(            self.breakpoint_store.clone(),            session_id,            parent_session,            label,            adapter,            task_context,            quirks,            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));                }            }        })        .detach();         session    }     pub fn boot_session(        &self,        session: Entity<Session>,        definition: DebugTaskDefinition,        worktree: Entity<Worktree>,        cx: &mut Context<Self>,    ) -> Task<Result<()>> {        let dap_store = cx.weak_entity();        let console = session.update(cx, |session, cx| session.console_output(cx));        let session_id = session.read(cx).session_id();         cx.spawn({            let session = session.clone();            async move |this, cx| {                let binary = this                    .update(cx, |this, cx| {                        this.get_debug_adapter_binary(                            definition.clone(),                            session_id,                            &worktree,                            console,                            cx,                        )                    })?                    .await?;                session                    .update(cx, |session, cx| {                        session.boot(binary, worktree, dap_store, cx)                    })?                    .await            }        })    }     pub fn session_by_id(        &self,        session_id: impl Borrow<SessionId>,    ) -> Option<Entity<session::Session>> {        let session_id = session_id.borrow();        let client = self.sessions.get(session_id).cloned();         client    }    pub fn sessions(&self) -> impl Iterator<Item = &Entity<Session>> {        self.sessions.values()    }     pub fn capabilities_by_id(        &self,        session_id: impl Borrow<SessionId>,        cx: &App,    ) -> Option<Capabilities> {        let session_id = session_id.borrow();        self.sessions            .get(session_id)            .map(|client| client.read(cx).capabilities.clone())    }     pub fn breakpoint_store(&self) -> &Entity<BreakpointStore> {        &self.breakpoint_store    }     pub fn worktree_store(&self) -> &Entity<WorktreeStore> {        &self.worktree_store    }     #[allow(dead_code)]

Lines 1–500 of 959.