971 lines · 31.5 KB · Rust
RawDownload
use super::{    breakpoint_store::BreakpointStore,    locator_store::LocatorStore,    session::{self, Session, SessionStateEvent},};use crate::{ProjectEnvironment, debugger, worktree_store::WorktreeStore};use anyhow::{Result, anyhow};use async_trait::async_trait;use collections::HashMap;use dap::{    Capabilities, CompletionItem, CompletionsArguments, DapRegistry, ErrorResponse,    EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments,    Source, StartDebuggingRequestArguments,    adapters::{DapStatus, DebugAdapterName},    client::SessionId,    messages::Message,    requests::{Completions, Evaluate, Request as _, RunInTerminal, StartDebugging},};use fs::Fs;use futures::{    channel::{mpsc, oneshot},    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 rpc::{    AnyProtoClient, TypedEnvelope,    proto::{self},};use serde_json::Value;use settings::WorktreeId;use smol::{lock::Mutex, stream::StreamExt};use std::{    borrow::Borrow,    collections::{BTreeMap, HashSet},    ffi::OsStr,    path::PathBuf,    sync::{Arc, atomic::Ordering::SeqCst},};use std::{collections::VecDeque, sync::atomic::AtomicU32};use task::{DebugAdapterConfig, DebugRequestDisposition};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: PathBuf,        command: Option<String>,        args: Vec<String>,        envs: HashMap<String, String>,        sender: mpsc::Sender<Result<u32>>,    },    Notification(String),    RemoteHasInitialized,} #[allow(clippy::large_enum_variant)]pub enum DapStoreMode {    Local(LocalDapStore),   // ssh host and collab host    Remote(RemoteDapStore), // collab guest} pub struct LocalDapStore {    fs: Arc<dyn Fs>,    node_runtime: NodeRuntime,    next_session_id: AtomicU32,    http_client: Arc<dyn HttpClient>,    worktree_store: Entity<WorktreeStore>,    environment: Entity<ProjectEnvironment>,    language_registry: Arc<LanguageRegistry>,    debug_adapters: Arc<DapRegistry>,    toolchain_store: Arc<dyn LanguageToolchainStore>,    locator_store: Arc<LocatorStore>,    start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,    _start_debugging_task: Task<()>,} impl LocalDapStore {    fn next_session_id(&self) -> SessionId {        SessionId(self.next_session_id.fetch_add(1, SeqCst))    }} pub struct RemoteDapStore {    upstream_client: AnyProtoClient,    upstream_project_id: u64,    event_queue: Option<VecDeque<DapStoreEvent>>,} pub struct DapStore {    mode: DapStoreMode,    downstream_client: Option<(AnyProtoClient, u64)>,    breakpoint_store: Entity<BreakpointStore>,    sessions: BTreeMap<SessionId, Entity<Session>>,} impl EventEmitter<DapStoreEvent> for DapStore {} impl DapStore {    pub fn init(_client: &AnyProtoClient) {        // todo(debugger): Reenable these after we finish handle_dap_command refactor        // client.add_entity_request_handler(Self::handle_dap_command::<NextCommand>);        // client.add_entity_request_handler(Self::handle_dap_command::<StepInCommand>);        // client.add_entity_request_handler(Self::handle_dap_command::<StepOutCommand>);        // client.add_entity_request_handler(Self::handle_dap_command::<StepBackCommand>);        // client.add_entity_request_handler(Self::handle_dap_command::<ContinueCommand>);        // client.add_entity_request_handler(Self::handle_dap_command::<PauseCommand>);        // client.add_entity_request_handler(Self::handle_dap_command::<DisconnectCommand>);        // client.add_entity_request_handler(Self::handle_dap_command::<TerminateThreadsCommand>);        // client.add_entity_request_handler(Self::handle_dap_command::<TerminateCommand>);        // client.add_entity_request_handler(Self::handle_dap_command::<RestartCommand>);        // client.add_entity_request_handler(Self::handle_dap_command::<VariablesCommand>);        // client.add_entity_request_handler(Self::handle_dap_command::<RestartStackFrameCommand>);    }     #[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>,        debug_adapters: Arc<DapRegistry>,        environment: Entity<ProjectEnvironment>,        toolchain_store: Arc<dyn LanguageToolchainStore>,        breakpoint_store: Entity<BreakpointStore>,        worktree_store: Entity<WorktreeStore>,        cx: &mut Context<Self>,    ) -> Self {        cx.on_app_quit(Self::shutdown_sessions).detach();         let (start_debugging_tx, mut message_rx) =            futures::channel::mpsc::unbounded::<(SessionId, Message)>();         let _start_debugging_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: DapStoreMode::Local(LocalDapStore {                fs,                environment,                http_client,                node_runtime,                worktree_store,                toolchain_store,                language_registry,                debug_adapters,                start_debugging_tx,                _start_debugging_task,                locator_store: Arc::from(LocatorStore::new()),                next_session_id: Default::default(),            }),            downstream_client: None,            breakpoint_store,            sessions: Default::default(),        }    }     pub fn new_remote(        project_id: u64,        upstream_client: AnyProtoClient,        breakpoint_store: Entity<BreakpointStore>,    ) -> Self {        Self {            mode: DapStoreMode::Remote(RemoteDapStore {                upstream_client,                upstream_project_id: project_id,                event_queue: Some(VecDeque::default()),            }),            downstream_client: None,            breakpoint_store,            sessions: Default::default(),        }    }     pub fn as_remote(&self) -> Option<&RemoteDapStore> {        match &self.mode {            DapStoreMode::Remote(remote_dap_store) => Some(remote_dap_store),            _ => None,        }    }     pub fn remote_event_queue(&mut self) -> Option<VecDeque<DapStoreEvent>> {        if let DapStoreMode::Remote(remote) = &mut self.mode {            remote.event_queue.take()        } else {            None        }    }     pub fn as_local(&self) -> Option<&LocalDapStore> {        match &self.mode {            DapStoreMode::Local(local_dap_store) => Some(local_dap_store),            _ => None,        }    }     pub fn as_local_mut(&mut self) -> Option<&mut LocalDapStore> {        match &mut self.mode {            DapStoreMode::Local(local_dap_store) => Some(local_dap_store),            _ => None,        }    }     pub fn upstream_client(&self) -> Option<(AnyProtoClient, u64)> {        match &self.mode {            DapStoreMode::Remote(RemoteDapStore {                upstream_client,                upstream_project_id,                ..            }) => Some((upstream_client.clone(), *upstream_project_id)),             DapStoreMode::Local(_) => None,        }    }     pub fn downstream_client(&self) -> Option<&(AnyProtoClient, u64)> {        self.downstream_client.as_ref()    }     pub fn add_remote_client(        &mut self,        session_id: SessionId,        ignore: Option<bool>,        cx: &mut Context<Self>,    ) {        if let DapStoreMode::Remote(remote) = &self.mode {            self.sessions.insert(                session_id,                cx.new(|_| {                    debugger::session::Session::remote(                        session_id,                        remote.upstream_client.clone(),                        remote.upstream_project_id,                        ignore.unwrap_or(false),                    )                }),            );        } else {            debug_assert!(false);        }    }     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    }     #[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(())            }        })?        .await;         Ok(())    }     pub fn new_session(        &mut self,        mut config: DebugAdapterConfig,        worktree: &Entity<Worktree>,        parent_session: Option<Entity<Session>>,        cx: &mut Context<Self>,    ) -> (SessionId, Task<Result<Entity<Session>>>) {        let Some(local_store) = self.as_local() else {            unimplemented!("Starting session on remote side");        };         let delegate = DapAdapterDelegate::new(            local_store.fs.clone(),            worktree.read(cx).id(),            local_store.node_runtime.clone(),            local_store.http_client.clone(),            local_store.language_registry.clone(),            local_store.toolchain_store.clone(),            local_store.environment.update(cx, |env, cx| {                let worktree = worktree.read(cx);                env.get_environment(worktree.abs_path().into(), cx)            }),        );        let session_id = local_store.next_session_id();         if let Some(session) = &parent_session {            session.update(cx, |session, _| {                session.add_child_session_id(session_id);            });        }         let (initialized_tx, initialized_rx) = oneshot::channel();        let locator_store = local_store.locator_store.clone();        let debug_adapters = local_store.debug_adapters.clone();         let start_debugging_tx = local_store.start_debugging_tx.clone();         let task = cx.spawn(async move |this, cx| {            if config.locator.is_some() {                config = cx                    .background_spawn(async move {                        locator_store                            .resolve_debug_config(&mut config)                            .await                            .map(|_| config)                    })                    .await?;            }             let start_client_task = this.update(cx, |this, cx| {                Session::local(                    this.breakpoint_store.clone(),                    session_id,                    parent_session,                    delegate,                    config,                    start_debugging_tx.clone(),                    initialized_tx,                    debug_adapters,                    cx,                )            })?;             this.update(cx, |_, cx| {                create_new_session(session_id, initialized_rx, start_client_task, cx)            })?            .await        });         (session_id, task)    }     #[cfg(any(test, feature = "test-support"))]    pub fn new_fake_session(        &mut self,        config: DebugAdapterConfig,        worktree: &Entity<Worktree>,        parent_session: Option<Entity<Session>>,        caps: Capabilities,        fails: bool,        cx: &mut Context<Self>,    ) -> (SessionId, Task<Result<Entity<Session>>>) {        let Some(local_store) = self.as_local() else {            unimplemented!("Starting session on remote side");        };         let delegate = DapAdapterDelegate::new(            local_store.fs.clone(),            worktree.read(cx).id(),            local_store.node_runtime.clone(),            local_store.http_client.clone(),            local_store.language_registry.clone(),            local_store.toolchain_store.clone(),            local_store.environment.update(cx, |env, cx| {                let worktree = worktree.read(cx);                env.get_environment(Some(worktree.abs_path()), cx)            }),        );        let session_id = local_store.next_session_id();         if let Some(session) = &parent_session {            session.update(cx, |session, _| {                session.add_child_session_id(session_id);            });        }         let (initialized_tx, initialized_rx) = oneshot::channel();         let start_client_task = Session::fake(            self.breakpoint_store.clone(),            session_id,            parent_session,            delegate,            config,            local_store.start_debugging_tx.clone(),            initialized_tx,            caps,            fails,            cx,        );         let task = create_new_session(session_id, initialized_rx, start_client_task, cx);        (session_id, task)    }     fn handle_start_debugging_request(        &mut self,        session_id: SessionId,        request: dap::messages::Request,        cx: &mut Context<Self>,    ) -> Task<Result<()>> {        let Some(local_store) = self.as_local() else {            unreachable!("Cannot response for non-local session");        };         let Some(parent_session) = self.session_by_id(session_id) else {            return Task::ready(Err(anyhow!("Session not found")));        };         let args = serde_json::from_value::<StartDebuggingRequestArguments>(            request.arguments.unwrap_or_default(),        )        .expect("To parse StartDebuggingRequestArguments");        let worktree = local_store            .worktree_store            .update(cx, |this, _| this.worktrees().next())            .expect("worktree-less project");         let Some(config) = parent_session.read(cx).configuration() else {            unreachable!("there must be a config for local sessions");        };         let debug_config = DebugAdapterConfig {            label: config.label,            adapter: config.adapter,            request: DebugRequestDisposition::ReverseRequest(args),            initialize_args: config.initialize_args.clone(),            tcp_connection: config.tcp_connection.clone(),            locator: None,            stop_on_entry: config.stop_on_entry,        };         #[cfg(any(test, feature = "test-support"))]        let new_session_task = {            let caps = parent_session.read(cx).capabilities.clone();            self.new_fake_session(                debug_config,                &worktree,                Some(parent_session.clone()),                caps,                false,                cx,            )            .1        };        #[cfg(not(any(test, feature = "test-support")))]        let new_session_task = self            .new_session(debug_config, &worktree, Some(parent_session.clone()), cx)

Lines 1–500 of 971.