3217 lines · 109.5 KB · Rust
RawDownload
use super::breakpoint_store::{    BreakpointStore, BreakpointStoreEvent, BreakpointUpdatedReason, SourceBreakpoint,};use super::dap_command::{    self, Attach, ConfigurationDone, ContinueCommand, DataBreakpointInfoCommand, DisconnectCommand,    EvaluateCommand, Initialize, Launch, LoadedSourcesCommand, LocalDapCommand, LocationsCommand,    ModulesCommand, NextCommand, PauseCommand, RestartCommand, RestartStackFrameCommand,    ScopesCommand, SetDataBreakpointsCommand, SetExceptionBreakpoints, SetVariableValueCommand,    StackTraceCommand, StepBackCommand, StepCommand, StepInCommand, StepOutCommand,    TerminateCommand, TerminateThreadsCommand, ThreadsCommand, VariablesCommand,};use super::dap_store::DapStore;use crate::debugger::breakpoint_store::BreakpointSessionState;use crate::debugger::dap_command::{DataBreakpointContext, ReadMemory};use crate::debugger::memory::{self, Memory, MemoryIterator, MemoryPageBuilder, PageAddress};use anyhow::{Context as _, Result, anyhow, bail};use base64::Engine;use collections::{HashMap, HashSet, IndexMap};use dap::adapters::{DebugAdapterBinary, DebugAdapterName};use dap::messages::Response;use dap::requests::{Request, RunInTerminal, StartDebugging};use dap::transport::TcpTransport;use dap::{    Capabilities, ContinueArguments, EvaluateArgumentsContext, Module, Source, StackFrameId,    SteppingGranularity, StoppedEvent, VariableReference,    client::{DebugAdapterClient, SessionId},    messages::{Events, Message},};use dap::{    ExceptionBreakpointsFilter, ExceptionFilterOptions, OutputEvent, OutputEventCategory,    RunInTerminalRequestArguments, StackFramePresentationHint, StartDebuggingRequestArguments,    StartDebuggingRequestArgumentsRequest, VariablePresentationHint, WriteMemoryArguments,};use futures::channel::mpsc::UnboundedSender;use futures::channel::{mpsc, oneshot};use futures::io::BufReader;use futures::{AsyncBufReadExt as _, SinkExt, StreamExt, TryStreamExt};use futures::{FutureExt, future::Shared};use gpui::{    App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, SharedString,    Task, WeakEntity,};use http_client::HttpClient;use node_runtime::NodeRuntime;use remote::RemoteClient;use serde::{Deserialize, Serialize};use serde_json::Value;use smol::net::{TcpListener, TcpStream};use std::any::TypeId;use std::collections::{BTreeMap, VecDeque};use std::net::Ipv4Addr;use std::ops::RangeInclusive;use std::path::PathBuf;use std::time::Duration;use std::u64;use std::{    any::Any,    collections::hash_map::Entry,    hash::{Hash, Hasher},    path::Path,    sync::Arc,};use task::SharedTaskContext;use text::{PointUtf16, ToPointUtf16};use url::Url;use util::command::Stdio;use util::command::new_command;use util::{ResultExt, debug_panic, maybe};use worktree::Worktree; const MAX_TRACKED_OUTPUT_EVENTS: usize = 5000;const DEBUG_HISTORY_LIMIT: usize = 10; #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Ord, Eq)]#[repr(transparent)]pub struct ThreadId(pub i64); impl From<i64> for ThreadId {    fn from(id: i64) -> 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, Clone)]pub struct Thread {    dap: dap::Thread,    stack_frames: Vec<StackFrame>,    stack_frames_error: Option<SharedString>,    _has_stopped: bool,} impl From<dap::Thread> for Thread {    fn from(dap: dap::Thread) -> Self {        Self {            dap,            stack_frames: Default::default(),            stack_frames_error: None,            _has_stopped: false,        }    }} #[derive(Debug, Clone, PartialEq)]pub struct Watcher {    pub expression: SharedString,    pub value: SharedString,    pub variables_reference: u64,    pub presentation_hint: Option<VariablePresentationHint>,} #[derive(Debug, Clone, PartialEq)]pub struct DataBreakpointState {    pub dap: dap::DataBreakpoint,    pub is_enabled: bool,    pub context: Arc<DataBreakpointContext>,} pub enum SessionState {    /// Represents a session that is building/initializing    /// even if a session doesn't have a pre build task this state    /// is used to run all the async tasks that are required to start the session    Booting(Option<Task<Result<()>>>),    Running(RunningMode),} #[derive(Clone)]pub struct RunningMode {    client: Arc<DebugAdapterClient>,    binary: DebugAdapterBinary,    tmp_breakpoint: Option<SourceBreakpoint>,    worktree: WeakEntity<Worktree>,    executor: BackgroundExecutor,    is_started: bool,    has_ever_stopped: bool,    messages_tx: UnboundedSender<Message>,} #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]pub struct SessionQuirks {    pub compact: bool,    pub prefer_thread_name: bool,} fn client_source(abs_path: &Path) -> dap::Source {    dap::Source {        name: abs_path            .file_name()            .map(|filename| filename.to_string_lossy().into_owned()),        path: Some(abs_path.to_string_lossy().into_owned()),        source_reference: None,        presentation_hint: None,        origin: None,        sources: None,        adapter_data: None,        checksums: None,    }} impl RunningMode {    async fn new(        session_id: SessionId,        parent_session: Option<Entity<Session>>,        worktree: WeakEntity<Worktree>,        binary: DebugAdapterBinary,        messages_tx: futures::channel::mpsc::UnboundedSender<Message>,        cx: &mut AsyncApp,    ) -> Result<Self> {        let message_handler = Box::new({            let messages_tx = messages_tx.clone();            move |message| {                messages_tx.unbounded_send(message).ok();            }        });         let client = if let Some(client) =            parent_session.and_then(|session| cx.update(|cx| session.read(cx).adapter_client()))        {            client                .create_child_connection(session_id, binary.clone(), message_handler, cx)                .await?        } else {            DebugAdapterClient::start(session_id, binary.clone(), message_handler, cx).await?        };         Ok(Self {            client: Arc::new(client),            worktree,            tmp_breakpoint: None,            binary,            executor: cx.background_executor().clone(),            is_started: false,            has_ever_stopped: false,            messages_tx,        })    }     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            .iter()            .map(|path| {                self.request(dap_command::SetBreakpoints {                    source: client_source(path),                    source_modified: None,                    breakpoints: vec![],                })            })            .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,        breakpoint_store: &Entity<BreakpointStore>,        cx: &mut App,    ) -> Task<()> {        let breakpoints =            breakpoint_store                .read(cx)                .source_breakpoints_from_path(&abs_path, cx)                .into_iter()                .filter(|bp| bp.state.is_enabled())                .chain(self.tmp_breakpoint.iter().filter_map(|breakpoint| {                    breakpoint.path.eq(&abs_path).then(|| breakpoint.clone())                }))                .map(Into::into)                .collect();         let raw_breakpoints = breakpoint_store            .read(cx)            .breakpoints_from_path(&abs_path)            .into_iter()            .filter(|bp| bp.bp.state.is_enabled())            .collect::<Vec<_>>();         let task = self.request(dap_command::SetBreakpoints {            source: client_source(&abs_path),            source_modified: Some(matches!(reason, BreakpointUpdatedReason::FileSaved)),            breakpoints,        });        let session_id = self.client.id();        let breakpoint_store = breakpoint_store.downgrade();        cx.spawn(async move |cx| match cx.background_spawn(task).await {            Ok(breakpoints) => {                let breakpoints =                    breakpoints                        .into_iter()                        .zip(raw_breakpoints)                        .filter_map(|(dap_bp, zed_bp)| {                            Some((                                zed_bp,                                BreakpointSessionState {                                    id: dap_bp.id?,                                    verified: dap_bp.verified,                                },                            ))                        });                breakpoint_store                    .update(cx, |this, _| {                        this.mark_breakpoints_verified(session_id, &abs_path, breakpoints);                    })                    .ok();            }            Err(err) => log::warn!("Set breakpoints request failed for path: {}", err),        })    }     fn send_exception_breakpoints(        &self,        filters: Vec<ExceptionBreakpointsFilter>,        supports_filter_options: bool,    ) -> 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)    }     fn send_source_breakpoints(        &self,        ignore_breakpoints: bool,        breakpoint_store: &Entity<BreakpointStore>,        cx: &App,    ) -> Task<HashMap<Arc<Path>, anyhow::Error>> {        let mut breakpoint_tasks = Vec::new();        let breakpoints = breakpoint_store.read(cx).all_source_breakpoints(cx);        let mut raw_breakpoints = breakpoint_store.read_with(cx, |this, _| this.all_breakpoints());        debug_assert_eq!(raw_breakpoints.len(), breakpoints.len());        let session_id = self.client.id();        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()            };             let raw_breakpoints = raw_breakpoints                .remove(&path)                .unwrap_or_default()                .into_iter()                .filter(|bp| bp.bp.state.is_enabled());            let error_path = path.clone();            let send_request = self                .request(dap_command::SetBreakpoints {                    source: client_source(&path),                    source_modified: Some(false),                    breakpoints,                })                .map(|result| result.map_err(move |e| (error_path, e)));             let task = cx.spawn({                let breakpoint_store = breakpoint_store.downgrade();                async move |cx| {                    let breakpoints = cx.background_spawn(send_request).await?;                     let breakpoints = breakpoints.into_iter().zip(raw_breakpoints).filter_map(                        |(dap_bp, zed_bp)| {                            Some((                                zed_bp,                                BreakpointSessionState {                                    id: dap_bp.id?,                                    verified: dap_bp.verified,                                },                            ))                        },                    );                    breakpoint_store                        .update(cx, |this, _| {                            this.mark_breakpoints_verified(session_id, &path, breakpoints);                        })                        .ok();                     Ok(())                }            });            breakpoint_tasks.push(task);        }         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,        initialized_rx: oneshot::Receiver<()>,        dap_store: WeakEntity<DapStore>,        cx: &mut Context<Session>,    ) -> Task<Result<()>> {        let raw = self.binary.request_args.clone();         // 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,            }),            dap::StartDebuggingRequestArgumentsRequest::Attach => self.request(Attach {                raw: raw.configuration,            }),        };         let configuration_done_supported = ConfigurationDone::is_supported(capabilities);        // From spec (on initialization sequence):        // client sends a setExceptionBreakpoints request if one or more exceptionBreakpointFilters have been defined (or if supportsConfigurationDoneRequest is not true)        //        // Thus we should send setExceptionBreakpoints even if `exceptionFilters` variable is empty (as long as there were some options in the first place).        let should_send_exception_breakpoints = capabilities            .exception_breakpoint_filters            .as_ref()            .is_some_and(|filters| !filters.is_empty())            || !configuration_done_supported;        let supports_exception_filters = capabilities            .supports_exception_filter_options            .unwrap_or_default();        let this = self.clone();        let worktree = self.worktree().clone();        let mut filters = capabilities            .exception_breakpoint_filters            .clone()            .unwrap_or_default();        let configuration_sequence = cx.spawn({            async move |session, cx| {                let adapter_name = session.read_with(cx, |this, _| this.adapter())?;                let (breakpoint_store, adapter_defaults) =                    dap_store.read_with(cx, |dap_store, _| {                        (                            dap_store.breakpoint_store().clone(),                            dap_store.adapter_options(&adapter_name),                        )                    })?;                initialized_rx.await?;                let errors_by_path = cx                    .update(|cx| this.send_source_breakpoints(false, &breakpoint_store, 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));                    }                })?;                 if should_send_exception_breakpoints {                    _ = session.update(cx, |this, _| {                        filters.retain(|filter| {

Lines 1–500 of 3217.