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::{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(); } fn stop_thread(&mut self, thread_id: ThreadId) {Lines 1–500 of 1945. Load more
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::{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(); } fn stop_thread(&mut self, thread_id: ThreadId) {