1968 lines · 61.9 KB · Rust
RawDownload
use super::breakpoint_store::{    BreakpointStore, BreakpointStoreEvent, BreakpointUpdatedReason, SourceBreakpoint,};use super::dap_command::{    self, Attach, ConfigurationDone, ContinueCommand, DapCommand, DisconnectCommand,    EvaluateCommand, Initialize, Launch, LoadedSourcesCommand, LocalDapCommand, LocationsCommand,    ModulesCommand, NextCommand, PauseCommand, RestartCommand, RestartStackFrameCommand,    ScopesCommand, SetExceptionBreakpoints, SetVariableValueCommand, StackTraceCommand,    StepBackCommand, StepCommand, StepInCommand, StepOutCommand, TerminateCommand,    TerminateThreadsCommand, ThreadsCommand, VariablesCommand,};use super::dap_store::DapStore;use anyhow::{Context as _, Result, anyhow};use collections::{HashMap, HashSet, IndexMap, IndexSet};use dap::adapters::DebugAdapterBinary;use dap::messages::Response;use dap::{    Capabilities, ContinueArguments, EvaluateArgumentsContext, Module, Source, StackFrameId,    SteppingGranularity, StoppedEvent, VariableReference,    client::{DebugAdapterClient, SessionId},    messages::{Events, Message},};use dap::{    EvaluateResponse, ExceptionBreakpointsFilter, ExceptionFilterOptions, OutputEventCategory,};use futures::channel::oneshot;use futures::{FutureExt, future::Shared};use gpui::{    App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, SharedString,    Task, WeakEntity,}; use serde_json::{Value, json};use smol::stream::StreamExt;use std::any::TypeId;use std::collections::BTreeMap;use std::u64;use std::{    any::Any,    collections::hash_map::Entry,    hash::{Hash, Hasher},    path::Path,    sync::Arc,};use task::DebugTaskDefinition;use text::{PointUtf16, ToPointUtf16};use util::{ResultExt, merge_json_value_into};use worktree::Worktree; #[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,        }    }} enum Mode {    Building,    Running(LocalMode),} #[derive(Clone)]pub struct LocalMode {    client: Arc<DebugAdapterClient>,    binary: DebugAdapterBinary,    root_binary: Option<Arc<DebugAdapterBinary>>,    pub(crate) breakpoint_store: Entity<BreakpointStore>,    tmp_breakpoint: Option<SourceBreakpoint>,    worktree: WeakEntity<Worktree>,} 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 {    async fn new(        session_id: SessionId,        parent_session: Option<Entity<Session>>,        worktree: WeakEntity<Worktree>,        breakpoint_store: Entity<BreakpointStore>,        binary: DebugAdapterBinary,        messages_tx: futures::channel::mpsc::UnboundedSender<Message>,        cx: AsyncApp,    ) -> Result<Self> {        let message_handler = Box::new(move |message| {            messages_tx.unbounded_send(message).ok();        });         let root_binary = if let Some(parent_session) = parent_session.as_ref() {            Some(parent_session.read_with(&cx, |session, _| session.root_binary().clone())?)        } else {            None        };         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.clone(), message_handler, cx.clone())                    .await?            } else {                DebugAdapterClient::start(session_id, binary.clone(), message_handler, cx.clone())                    .await                    .with_context(|| "Failed to start communication with debug adapter")?            },        );         Ok(Self {            client,            breakpoint_store,            worktree,            tmp_breakpoint: None,            root_binary,            binary,        })    }     pub(crate) fn worktree(&self) -> &WeakEntity<Worktree> {        &self.worktree    }     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())            .chain(self.tmp_breakpoint.clone())            .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_exception_breakpoints(        &self,        filters: Vec<ExceptionBreakpointsFilter>,        supports_filter_options: bool,        cx: &App,    ) -> Task<Result<Vec<dap::Breakpoint>>> {        let arg = if supports_filter_options {            SetExceptionBreakpoints::WithOptions {                filters: filters                    .into_iter()                    .map(|filter| ExceptionFilterOptions {                        filter_id: filter.filter,                        condition: None,                        mode: None,                    })                    .collect(),            }        } else {            SetExceptionBreakpoints::Plain {                filters: filters.into_iter().map(|filter| filter.filter).collect(),            }        };        self.request(arg, cx.background_executor().clone())    }     fn send_source_breakpoints(        &self,        ignore_breakpoints: bool,        cx: &App,    ) -> Task<HashMap<Arc<Path>, anyhow::Error>> {        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(),                )                .map(|result| result.map_err(|e| (path, e))),            );        }         cx.background_spawn(async move {            futures::future::join_all(breakpoint_tasks)                .await                .into_iter()                .filter_map(Result::err)                .collect::<HashMap<_, _>>()        })    }     fn initialize_sequence(        &self,        capabilities: &Capabilities,        definition: &DebugTaskDefinition,        initialized_rx: oneshot::Receiver<()>,        dap_store: WeakEntity<DapStore>,        cx: &App,    ) -> Task<Result<()>> {        let mut raw = self.binary.request_args.clone();         merge_json_value_into(            definition.initialize_args.clone().unwrap_or(json!({})),            &mut raw.configuration,        );         // Of relevance: https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522        let launch = match raw.request {            dap::StartDebuggingRequestArgumentsRequest::Launch => self.request(                Launch {                    raw: raw.configuration,                },                cx.background_executor().clone(),            ),            dap::StartDebuggingRequestArgumentsRequest::Attach => self.request(                Attach {                    raw: raw.configuration,                },                cx.background_executor().clone(),            ),        };         let configuration_done_supported = ConfigurationDone::is_supported(capabilities);        let exception_filters = capabilities            .exception_breakpoint_filters            .as_ref()            .map(|exception_filters| {                exception_filters                    .iter()                    .filter(|filter| filter.default == Some(true))                    .cloned()                    .collect::<Vec<_>>()            })            .unwrap_or_default();        let supports_exception_filters = capabilities            .supports_exception_filter_options            .unwrap_or_default();        let this = self.clone();        let worktree = self.worktree().clone();        let configuration_sequence = cx.spawn({            async move |cx| {                initialized_rx.await?;                let errors_by_path = cx                    .update(|cx| this.send_source_breakpoints(false, cx))?                    .await;                 dap_store.update(cx, |_, cx| {                    let Some(worktree) = worktree.upgrade() else {                        return;                    };                     for (path, error) in &errors_by_path {                        log::error!("failed to set breakpoints for {path:?}: {error}");                    }                     if let Some(failed_path) = errors_by_path.keys().next() {                        let failed_path = failed_path                            .strip_prefix(worktree.read(cx).abs_path())                            .unwrap_or(failed_path)                            .display();                        let message = format!(                            "Failed to set breakpoints for {failed_path}{}",                            match errors_by_path.len() {                                0 => unreachable!(),                                1 => "".into(),                                2 => " and 1 other path".into(),                                n => format!(" and {} other paths", n - 1),                            }                        );                        cx.emit(super::dap_store::DapStoreEvent::Notification(message));                    }                })?;                 cx.update(|cx| {                    this.send_exception_breakpoints(                        exception_filters,                        supports_exception_filters,                        cx,                    )                })?                .await                .ok();                let ret = if configuration_done_supported {                    this.request(ConfigurationDone {}, cx.background_executor().clone())                } else {                    Task::ready(Ok(()))                }                .await;                ret            }        });         cx.background_spawn(async move {            futures::future::try_join(launch, configuration_sequence).await?;            Ok(())        })    }     fn request<R: LocalDapCommand>(        &self,        request: R,        executor: BackgroundExecutor,    ) -> Task<Result<R::Response>>    where        <R::DapRequest as dap::requests::Request>::Response: 'static,        <R::DapRequest as dap::requests::Request>::Arguments: 'static + Send,    {        let request = Arc::new(request);         let request_clone = request.clone();        let connection = self.client.clone();        let request_task = executor.spawn(async move {            let args = request_clone.to_dap();            connection.request::<R::DapRequest>(args).await        });         executor.spawn(async move {            let response = request.response_from_dap(request_task.await?);            response        })    }} impl Mode {    fn request_dap<R: DapCommand>(        &self,        request: R,        cx: &mut Context<Session>,    ) -> Task<Result<R::Response>>    where        <R::DapRequest as dap::requests::Request>::Response: 'static,        <R::DapRequest as dap::requests::Request>::Arguments: 'static + Send,    {        match self {            Mode::Running(debug_adapter_client) => {                debug_adapter_client.request(request, cx.background_executor().clone())            }            Mode::Building => Task::ready(Err(anyhow!(                "no adapter running to send request: {:?}",                request            ))),        }    }} #[derive(Default)]struct ThreadStates {    global_state: Option<ThreadStatus>,    known_thread_states: IndexMap<ThreadId, ThreadStatus>,} impl ThreadStates {    fn stop_all_threads(&mut self) {        self.global_state = Some(ThreadStatus::Stopped);        self.known_thread_states.clear();    }     fn exit_all_threads(&mut self) {        self.global_state = Some(ThreadStatus::Exited);        self.known_thread_states.clear();    }     fn continue_all_threads(&mut self) {        self.global_state = Some(ThreadStatus::Running);        self.known_thread_states.clear();    }

Lines 1–500 of 1968.