1840 lines · 58.9 KB · Rust
RawDownload
use crate::project_settings::ProjectSettings; use super::breakpoint_store::{BreakpointStore, BreakpointStoreEvent, BreakpointUpdatedReason};use super::dap_command::{    self, Attach, ConfigurationDone, ContinueCommand, DapCommand, DisconnectCommand,    EvaluateCommand, Initialize, Launch, LoadedSourcesCommand, LocalDapCommand, LocationsCommand,    ModulesCommand, NextCommand, PauseCommand, RestartCommand, RestartStackFrameCommand,    ScopesCommand, SetVariableValueCommand, StackTraceCommand, StepBackCommand, StepCommand,    StepInCommand, StepOutCommand, TerminateCommand, TerminateThreadsCommand, ThreadsCommand,    VariablesCommand,};use super::dap_store::DapAdapterDelegate;use anyhow::{anyhow, Result};use collections::{HashMap, HashSet, IndexMap, IndexSet};use dap::adapters::{DebugAdapter, DebugAdapterBinary};use dap::messages::Response;use dap::OutputEventCategory;use dap::{    adapters::{DapDelegate, DapStatus},    client::{DebugAdapterClient, SessionId},    messages::{Events, Message},    Capabilities, ContinueArguments, EvaluateArgumentsContext, Module, Source, StackFrameId,    SteppingGranularity, StoppedEvent, VariableReference,};use dap_adapters::build_adapter;use futures::channel::oneshot;use futures::{future::Shared, FutureExt};use gpui::{    App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, Task, WeakEntity,};use rpc::AnyProtoClient;use serde_json::{json, Value};use settings::Settings;use smol::stream::StreamExt;use std::any::TypeId;use std::path::PathBuf;use std::u64;use std::{    any::Any,    collections::hash_map::Entry,    hash::{Hash, Hasher},    path::Path,    sync::Arc,};use task::DebugAdapterConfig;use text::{PointUtf16, ToPointUtf16};use util::{merge_json_value_into, ResultExt}; #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Ord, Eq)]#[repr(transparent)]pub struct ThreadId(pub u64); impl ThreadId {    pub const MIN: ThreadId = ThreadId(u64::MIN);    pub const MAX: ThreadId = ThreadId(u64::MAX);} impl From<u64> for ThreadId {    fn from(id: u64) -> Self {        Self(id)    }} #[derive(Clone, Debug)]pub struct StackFrame {    pub dap: dap::StackFrame,    pub scopes: Vec<dap::Scope>,} impl From<dap::StackFrame> for StackFrame {    fn from(stack_frame: dap::StackFrame) -> Self {        Self {            scopes: vec![],            dap: stack_frame,        }    }} #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]pub enum ThreadStatus {    #[default]    Running,    Stopped,    Stepping,    Exited,    Ended,} impl ThreadStatus {    pub fn label(&self) -> &'static str {        match self {            ThreadStatus::Running => "Running",            ThreadStatus::Stopped => "Stopped",            ThreadStatus::Stepping => "Stepping",            ThreadStatus::Exited => "Exited",            ThreadStatus::Ended => "Ended",        }    }} #[derive(Debug)]pub struct Thread {    dap: dap::Thread,    stack_frame_ids: IndexSet<StackFrameId>,    _has_stopped: bool,} impl From<dap::Thread> for Thread {    fn from(dap: dap::Thread) -> Self {        Self {            dap,            stack_frame_ids: Default::default(),            _has_stopped: false,        }    }} type UpstreamProjectId = u64; struct RemoteConnection {    _client: AnyProtoClient,    _upstream_project_id: UpstreamProjectId,} impl RemoteConnection {    fn send_proto_client_request<R: DapCommand>(        &self,        _request: R,        _session_id: SessionId,        cx: &mut App,    ) -> Task<Result<R::Response>> {        // let message = request.to_proto(session_id, self.upstream_project_id);        // let upstream_client = self.client.clone();        cx.background_executor().spawn(async move {            // debugger(todo): Properly send messages when we wrap dap_commands in envelopes again            // let response = upstream_client.request(message).await?;            // request.response_from_proto(response)            Err(anyhow!("Sending dap commands over RPC isn't supported yet"))        })    }     fn request<R: DapCommand>(        &self,        request: R,        session_id: SessionId,        cx: &mut App,    ) -> Task<Result<R::Response>>    where        <R::DapRequest as dap::requests::Request>::Response: 'static,        <R::DapRequest as dap::requests::Request>::Arguments: 'static + Send,    {        return self.send_proto_client_request::<R>(request, session_id, cx);    }} enum Mode {    Local(LocalMode),    Remote(RemoteConnection),} #[derive(Clone)]pub struct LocalMode {    client: Arc<DebugAdapterClient>,    config: DebugAdapterConfig,    adapter: Arc<dyn DebugAdapter>,    breakpoint_store: Entity<BreakpointStore>,} fn client_source(abs_path: &Path) -> dap::Source {    dap::Source {        name: abs_path            .file_name()            .map(|filename| filename.to_string_lossy().to_string()),        path: Some(abs_path.to_string_lossy().to_string()),        source_reference: None,        presentation_hint: None,        origin: None,        sources: None,        adapter_data: None,        checksums: None,    }} impl LocalMode {    fn new(        session_id: SessionId,        parent_session: Option<Entity<Session>>,        breakpoint_store: Entity<BreakpointStore>,        config: DebugAdapterConfig,        delegate: DapAdapterDelegate,        messages_tx: futures::channel::mpsc::UnboundedSender<Message>,        cx: AsyncApp,    ) -> Task<Result<(Self, Capabilities)>> {        cx.spawn(async move |cx| {            let (adapter, binary) = Self::get_adapter_binary(&config, &delegate,  cx).await?;             let message_handler = Box::new(move |message| {                messages_tx.unbounded_send(message).ok();            });             let client = Arc::new(                if let Some(client) = parent_session                    .and_then(|session| cx.update(|cx| session.read(cx).adapter_client()).ok())                    .flatten()                {                    client                        .reconnect(session_id, binary, message_handler, cx.clone())                        .await?                } else {                    DebugAdapterClient::start(                        session_id,                        adapter.name(),                        binary,                        message_handler,                        cx.clone(),                    )                    .await?                },            );             let adapter_id = adapter.name().to_string().to_owned();            let session = Self {                client,                adapter,                breakpoint_store,                config: config.clone(),            };             #[cfg(any(test, feature = "test-support"))]            {                let dap::DebugAdapterKind::Fake((fail, caps)) = session.config.kind.clone() else {                    panic!("Only fake debug adapter configs should be used in tests");                };                 session                    .client                    .on_request::<dap::requests::Initialize, _>(move |_, _| Ok(caps.clone()))                    .await;                 let paths = cx.update(|cx| session.breakpoint_store.read(cx).breakpoint_paths()).expect("Breakpoint store should exist in all tests that start debuggers");                 session.client.on_request::<dap::requests::SetBreakpoints, _>(move |_, args| {                    let p = Arc::from(Path::new(&args.source.path.unwrap()));                    if !paths.contains(&p) {                        panic!("Sent breakpoints for path without any")                    }                     Ok(dap::SetBreakpointsResponse {                        breakpoints: Vec::default(),                    })                }).await;                 match config.request.clone() {                    dap::DebugRequestType::Launch if fail => {                        session                            .client                            .on_request::<dap::requests::Launch, _>(move |_, _| {                                Err(dap::ErrorResponse {                                    error: Some(dap::Message {                                        id: 1,                                        format: "error".into(),                                        variables: None,                                        send_telemetry: None,                                        show_user: None,                                        url: None,                                        url_label: None,                                    }),                                })                            })                            .await;                    }                    dap::DebugRequestType::Launch => {                        session                            .client                            .on_request::<dap::requests::Launch, _>(move |_, _| Ok(()))                            .await;                    }                    dap::DebugRequestType::Attach(_) if fail => {                        session                            .client                            .on_request::<dap::requests::Attach, _>(move |_, _| {                                Err(dap::ErrorResponse {                                    error: Some(dap::Message {                                        id: 1,                                        format: "error".into(),                                        variables: None,                                        send_telemetry: None,                                        show_user: None,                                        url: None,                                        url_label: None,                                    }),                                })                            })                            .await;                    }                    dap::DebugRequestType::Attach(attach_config) => {                        session                            .client                            .on_request::<dap::requests::Attach, _>(move |_, args| {                                assert_eq!(                                    json!({"request": "attach", "process_id": attach_config.process_id.unwrap()}),                                    args.raw                                );                                 Ok(())                            })                            .await;                    }                }                 session.client.on_request::<dap::requests::Disconnect, _>(move |_, _| Ok(())).await;                session.client.fake_event(Events::Initialized(None)).await;            }             let capabilities = session                .request(Initialize { adapter_id }, cx.background_executor().clone())                .await?;             Ok((session, capabilities))        })    }     fn unset_breakpoints_from_paths(&self, paths: &Vec<Arc<Path>>, cx: &mut App) -> Task<()> {        let tasks: Vec<_> = paths            .into_iter()            .map(|path| {                self.request(                    dap_command::SetBreakpoints {                        source: client_source(path),                        source_modified: None,                        breakpoints: vec![],                    },                    cx.background_executor().clone(),                )            })            .collect();         cx.background_spawn(async move {            futures::future::join_all(tasks)                .await                .iter()                .for_each(|res| match res {                    Ok(_) => {}                    Err(err) => {                        log::warn!("Set breakpoints request failed: {}", err);                    }                });        })    }     fn send_breakpoints_from_path(        &self,        abs_path: Arc<Path>,        reason: BreakpointUpdatedReason,        cx: &mut App,    ) -> Task<()> {        let breakpoints = self            .breakpoint_store            .read_with(cx, |store, cx| store.breakpoints_from_path(&abs_path, cx))            .into_iter()            .filter(|bp| bp.state.is_enabled())            .map(Into::into)            .collect();         let task = self.request(            dap_command::SetBreakpoints {                source: client_source(&abs_path),                source_modified: Some(matches!(reason, BreakpointUpdatedReason::FileSaved)),                breakpoints,            },            cx.background_executor().clone(),        );         cx.background_spawn(async move {            match task.await {                Ok(_) => {}                Err(err) => log::warn!("Set breakpoints request failed for path: {}", err),            }        })    }     fn send_all_breakpoints(&self, ignore_breakpoints: bool, cx: &App) -> Task<()> {        let mut breakpoint_tasks = Vec::new();        let breakpoints = self            .breakpoint_store            .read_with(cx, |store, cx| store.all_breakpoints(cx));         for (path, breakpoints) in breakpoints {            let breakpoints = if ignore_breakpoints {                vec![]            } else {                breakpoints                    .into_iter()                    .filter(|bp| bp.state.is_enabled())                    .map(Into::into)                    .collect()            };             breakpoint_tasks.push(self.request(                dap_command::SetBreakpoints {                    source: client_source(&path),                    source_modified: Some(false),                    breakpoints,                },                cx.background_executor().clone(),            ));        }         cx.background_spawn(async move {            futures::future::join_all(breakpoint_tasks)                .await                .iter()                .for_each(|res| match res {                    Ok(_) => {}                    Err(err) => {                        log::warn!("Set breakpoints request failed: {}", err);                    }                });        })    }     async fn get_adapter_binary(        config: &DebugAdapterConfig,        delegate: &DapAdapterDelegate,        cx: &mut AsyncApp,    ) -> Result<(Arc<dyn DebugAdapter>, DebugAdapterBinary)> {        let adapter = build_adapter(&config.kind).await?;         let binary = cx.update(|cx| {            ProjectSettings::get_global(cx)                .dap                .get(&adapter.name())                .and_then(|s| s.binary.as_ref().map(PathBuf::from))        })?;         let binary = match adapter.get_binary(delegate, &config, binary, cx).await {            Err(error) => {                delegate.update_status(                    adapter.name(),                    DapStatus::Failed {                        error: error.to_string(),                    },                );                 return Err(error);            }            Ok(mut binary) => {                delegate.update_status(adapter.name(), DapStatus::None);                 let shell_env = delegate.shell_env().await;                let mut envs = binary.envs.unwrap_or_default();                envs.extend(shell_env);                binary.envs = Some(envs);                 binary            }        };         Ok((adapter, binary))    }     pub fn initialize_sequence(        &self,        capabilities: &Capabilities,        initialized_rx: oneshot::Receiver<()>,        cx: &App,    ) -> Task<Result<()>> {        let mut raw = self.adapter.request_args(&self.config);        merge_json_value_into(            self.config.initialize_args.clone().unwrap_or(json!({})),            &mut raw,        );         // Of relevance: https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522        let launch = match &self.config.request {            dap::DebugRequestType::Launch => {                self.request(Launch { raw }, cx.background_executor().clone())            }            dap::DebugRequestType::Attach(_) => {                self.request(Attach { raw }, cx.background_executor().clone())            }        };         let configuration_done_supported = ConfigurationDone::is_supported(capabilities);         let configuration_sequence = cx.spawn({            let this = self.clone();            async move |cx| {                initialized_rx.await?;                // todo(debugger) figure out if we want to handle a breakpoint response error                // This will probably consist of letting a user know that breakpoints failed to be set                cx.update(|cx| this.send_all_breakpoints(false, cx))?.await;                 if configuration_done_supported {                    this.request(ConfigurationDone, cx.background_executor().clone())                } else {                    Task::ready(Ok(()))                }                .await            }

Lines 1–500 of 1840.