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. Load more
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| {