use crate::debugger::breakpoint_store::BreakpointSessionState;use crate::debugger::dap_store::ActiveSessionEvent; 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};use dap::adapters::{DebugAdapterBinary, DebugAdapterName};use dap::messages::Response;use dap::requests::{Request, RunInTerminal, StartDebugging};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,};use futures::SinkExt;use futures::channel::{mpsc, oneshot};use futures::{FutureExt, future::Shared};use gpui::{ App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, SharedString, Task, WeakEntity,}; use rpc::ErrorExt;use serde_json::Value;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 text::{PointUtf16, ToPointUtf16};use util::ResultExt;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, Hash, PartialEq, Eq)]pub struct SourceStackFrameItem { pub row: u32, pub is_active: bool, pub abs_path: Arc<Path>,} #[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_frames: Vec<StackFrame>, stack_frames_error: Option<anyhow::Error>, _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, } }} pub enum Mode { Building, 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,} 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 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: AsyncApp, ) -> Result<Self> { 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.clone(), message_handler, cx.clone()) .await? } else { DebugAdapterClient::start(session_id, binary.clone(), message_handler, cx.clone()) .await? }, ); Ok(Self { client, worktree, tmp_breakpoint: None, binary, executor: cx.background_executor().clone(), is_started: false, has_ever_stopped: false, }) } 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![], }) }) .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); 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| { let breakpoint_store = dap_store.read_with(cx, |dap_store, _| dap_store.breakpoint_store().clone())?; 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)); } })?; this.send_exception_breakpoints(exception_filters, supports_exception_filters) .await .ok(); let ret = if configuration_done_supported { this.request(ConfigurationDone {}) } else { Task::ready(Ok(())) } .await; ret } }); let task = cx.background_spawn(futures::future::try_join(launch, configuration_sequence)); cx.spawn(async move |this, cx| { let result = task.await; this.update(cx, |this, cx| { if let Some(this) = this.as_running_mut() { this.is_started = true; cx.notify(); } }) .ok(); result?; anyhow::Ok(()) }) } fn request<R: LocalDapCommand>(&self, request: R) -> 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(); self.executor.spawn(async move {Lines 1–500 of 2330. Load more
use crate::debugger::breakpoint_store::BreakpointSessionState; use crate::debugger::dap_store::ActiveSessionEvent; 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}; use dap::adapters::{DebugAdapterBinary, DebugAdapterName}; use dap::messages::Response; use dap::requests::{Request, RunInTerminal, StartDebugging}; 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, }; use futures::SinkExt; use futures::channel::{mpsc, oneshot}; use futures::{FutureExt, future::Shared}; use gpui::{ App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, SharedString, Task, WeakEntity, }; use rpc::ErrorExt; use serde_json::Value; 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 text::{PointUtf16, ToPointUtf16}; use util::ResultExt; 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, Hash, PartialEq, Eq)] pub struct SourceStackFrameItem { pub row: u32, pub is_active: bool, pub abs_path: Arc<Path>, } #[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_frames: Vec<StackFrame>, stack_frames_error: Option<anyhow::Error>, _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, } } } pub enum Mode { Building, 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, } 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 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: AsyncApp, ) -> Result<Self> { 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.clone(), message_handler, cx.clone()) .await? } else { DebugAdapterClient::start(session_id, binary.clone(), message_handler, cx.clone()) .await? }, ); Ok(Self { client, worktree, tmp_breakpoint: None, binary, executor: cx.background_executor().clone(), is_started: false, has_ever_stopped: false, }) } 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![], }) }) .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); 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| { let breakpoint_store = dap_store.read_with(cx, |dap_store, _| dap_store.breakpoint_store().clone())?; 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)); } })?; this.send_exception_breakpoints(exception_filters, supports_exception_filters) .await .ok(); let ret = if configuration_done_supported { this.request(ConfigurationDone {}) } else { Task::ready(Ok(())) } .await; ret } }); let task = cx.background_spawn(futures::future::try_join(launch, configuration_sequence)); cx.spawn(async move |this, cx| { let result = task.await; this.update(cx, |this, cx| { if let Some(this) = this.as_running_mut() { this.is_started = true; cx.notify(); } }) .ok(); result?; anyhow::Ok(()) }) } fn request<R: LocalDapCommand>(&self, request: R) -> 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(); self.executor.spawn(async move {