967 lines · 31.5 KB · Rust
RawDownload
use super::{    breakpoint_store::BreakpointStore,    locators::DapLocator,    session::{self, Session, SessionStateEvent},};use crate::{    ProjectEnvironment,    project_settings::ProjectSettings,    terminals::{SshCommand, wrap_for_ssh},    worktree_store::WorktreeStore,};use anyhow::{Result, anyhow};use async_trait::async_trait;use collections::HashMap;use dap::{    Capabilities, CompletionItem, CompletionsArguments, DapRegistry, EvaluateArguments,    EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, Source,    StartDebuggingRequestArguments,    adapters::{DapStatus, DebugAdapterBinary, DebugAdapterName, TcpArguments},    client::SessionId,    messages::Message,    requests::{Completions, Evaluate, Request as _, RunInTerminal, StartDebugging},};use fs::Fs;use futures::{    channel::mpsc,    future::{Shared, join_all},};use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task};use http_client::HttpClient;use language::{BinaryStatus, LanguageRegistry, LanguageToolchainStore};use lsp::LanguageServerName;use node_runtime::NodeRuntime; use remote::SshRemoteClient;use rpc::{    AnyProtoClient, TypedEnvelope,    proto::{self},};use serde_json::Value;use settings::{Settings, WorktreeId};use smol::{lock::Mutex, stream::StreamExt};use std::{    borrow::Borrow,    collections::{BTreeMap, HashSet},    ffi::OsStr,    net::Ipv4Addr,    path::{Path, PathBuf},    sync::Arc,};use task::{DebugTaskDefinition, DebugTaskTemplate};use util::ResultExt as _;use worktree::Worktree; pub enum DapStoreEvent {    DebugClientStarted(SessionId),    DebugSessionInitialized(SessionId),    DebugClientShutdown(SessionId),    DebugClientEvent {        session_id: SessionId,        message: Message,    },    RunInTerminal {        session_id: SessionId,        title: Option<String>,        cwd: Option<Arc<Path>>,        command: Option<String>,        args: Vec<String>,        envs: HashMap<String, String>,        sender: mpsc::Sender<Result<u32>>,    },    SpawnChildSession {        request: StartDebuggingRequestArguments,        parent_session: Entity<Session>,    },    Notification(String),    RemoteHasInitialized,} #[allow(clippy::large_enum_variant)]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>,    language_registry: Arc<LanguageRegistry>,    toolchain_store: Arc<dyn LanguageToolchainStore>,    locators: HashMap<String, Arc<dyn DapLocator>>,} 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,    start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,    _start_debugging_task: Task<()>,} impl EventEmitter<DapStoreEvent> for DapStore {} impl DapStore {    pub fn init(client: &AnyProtoClient) {        client.add_entity_request_handler(Self::handle_run_debug_locator);        client.add_entity_request_handler(Self::handle_get_debug_adapter_binary);    }     #[expect(clippy::too_many_arguments)]    pub fn new_local(        http_client: Arc<dyn HttpClient>,        node_runtime: NodeRuntime,        fs: Arc<dyn Fs>,        language_registry: Arc<LanguageRegistry>,        environment: Entity<ProjectEnvironment>,        toolchain_store: Arc<dyn LanguageToolchainStore>,        worktree_store: Entity<WorktreeStore>,        breakpoint_store: Entity<BreakpointStore>,        cx: &mut Context<Self>,    ) -> Self {        cx.on_app_quit(Self::shutdown_sessions).detach();         let locators = HashMap::from_iter([(            "cargo".to_string(),            Arc::new(super::locators::cargo::CargoLocator {}) as _,        )]);         let mode = DapStoreMode::Local(LocalDapStore {            fs,            environment,            http_client,            node_runtime,            toolchain_store,            language_registry,            locators,        });         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 {        let (start_debugging_tx, mut message_rx) =            futures::channel::mpsc::unbounded::<(SessionId, Message)>();        let task = cx.spawn(async move |this, cx| {            while let Some((session_id, message)) = message_rx.next().await {                match message {                    Message::Request(request) => {                        let _ = this                            .update(cx, |this, cx| {                                if request.command == StartDebugging::COMMAND {                                    this.handle_start_debugging_request(session_id, request, cx)                                        .detach_and_log_err(cx);                                } else if request.command == RunInTerminal::COMMAND {                                    this.handle_run_in_terminal_request(session_id, request, cx)                                        .detach_and_log_err(cx);                                }                            })                            .log_err();                    }                    _ => {}                }            }        });         Self {            mode,            _start_debugging_task: task,            start_debugging_tx,            next_session_id: 0,            downstream_client: None,            breakpoint_store,            worktree_store,            sessions: Default::default(),        }    }     pub fn get_debug_adapter_binary(        &mut self,        definition: DebugTaskDefinition,        cx: &mut Context<Self>,    ) -> Task<Result<DebugAdapterBinary>> {        match &self.mode {            DapStoreMode::Local(_) => {                let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next()                else {                    return Task::ready(Err(anyhow!("Failed to find a worktree")));                };                let Some(adapter) = DapRegistry::global(cx).adapter(&definition.adapter) else {                    return Task::ready(Err(anyhow!("Failed to find a debug adapter")));                };                 let user_installed_path = ProjectSettings::get_global(cx)                    .dap                    .get(&adapter.name())                    .and_then(|s| s.binary.as_ref().map(PathBuf::from));                 let delegate = self.delegate(&worktree, cx);                let cwd: Arc<Path> = definition                    .cwd()                    .unwrap_or(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, 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 {                    project_id: ssh.upstream_project_id,                    task: 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 = ssh_client.update(cx, |ssh, _| {                        anyhow::Ok(SshCommand {                            arguments: ssh                                .ssh_args()                                .ok_or_else(|| anyhow!("SSH arguments not found"))?,                        })                    })??;                     let mut connection = None;                    if let Some(c) = binary.connection {                        let local_bind_addr = Ipv4Addr::new(127, 0, 0, 1);                        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: c.port,                            host: local_bind_addr,                            timeout: c.timeout,                        })                    }                     let (program, args) = wrap_for_ssh(                        &ssh_command,                        Some((&binary.command, &binary.arguments)),                        binary.cwd.as_deref(),                        binary.envs,                        None,                    );                     Ok(DebugAdapterBinary {                        command: program,                        arguments: args,                        envs: HashMap::default(),                        cwd: None,                        connection,                        request_args: binary.request_args,                    })                })            }            DapStoreMode::Collab => {                Task::ready(Err(anyhow!("Debugging is not yet supported via collab")))            }        }    }     pub fn run_debug_locator(        &mut self,        template: DebugTaskTemplate,        cx: &mut Context<Self>,    ) -> Task<Result<DebugTaskDefinition>> {        let Some(locator_name) = template.locator else {            return Task::ready(Ok(template.definition));        };         match &self.mode {            DapStoreMode::Local(local) => {                if let Some(locator) = local.locators.get(&locator_name).cloned() {                    cx.background_spawn(                        async move { locator.run_locator(template.definition).await },                    )                } else {                    Task::ready(Err(anyhow!("Couldn't find locator {}", locator_name)))                }            }            DapStoreMode::Ssh(ssh) => {                let request = ssh.upstream_client.request(proto::RunDebugLocator {                    project_id: ssh.upstream_project_id,                    locator: locator_name,                    task: Some(template.definition.to_proto()),                });                cx.background_spawn(async move {                    let response = request.await?;                    DebugTaskDefinition::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,        template: DebugTaskDefinition,        parent_session: Option<Entity<Session>>,        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 start_debugging_tx = self.start_debugging_tx.clone();         let session = Session::new(            self.breakpoint_store.clone(),            session_id,            parent_session,            template.clone(),            start_debugging_tx,            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::Running => {                    cx.emit(DapStoreEvent::DebugClientStarted(session_id));                }            }        })        .detach();         session    }     pub fn boot_session(        &self,        session: Entity<Session>,        cx: &mut Context<Self>,    ) -> Task<Result<()>> {        let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next() else {            return Task::ready(Err(anyhow!("Failed to find a worktree")));        };         let dap_store = cx.weak_entity();        let breakpoint_store = self.breakpoint_store.clone();        let definition = session.read(cx).definition();         cx.spawn({            let session = session.clone();            async move |this, cx| {                let binary = this                    .update(cx, |this, cx| {                        this.get_debug_adapter_binary(definition.clone(), cx)                    })?                    .await?;                 session                    .update(cx, |session, cx| {                        session.boot(binary, worktree, breakpoint_store, 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)]    async fn handle_ignore_breakpoint_state(        this: Entity<Self>,        envelope: TypedEnvelope<proto::IgnoreBreakpointState>,        mut cx: AsyncApp,    ) -> Result<()> {        let session_id = SessionId::from_proto(envelope.payload.session_id);         this.update(&mut cx, |this, cx| {            if let Some(session) = this.session_by_id(&session_id) {                session.update(cx, |session, cx| {                    session.set_ignore_breakpoints(envelope.payload.ignore, cx)                })            } else {                Task::ready(HashMap::default())            }        })?        .await;         Ok(())    }     fn delegate(&self, worktree: &Entity<Worktree>, cx: &mut App) -> DapAdapterDelegate {        let Some(local_store) = self.as_local() else {

Lines 1–500 of 967.