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 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 rpc::AnyProtoClient;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}; #[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, } }} type UpstreamProjectId = u64; struct RemoteConnection { _client: AnyProtoClient, _upstream_project_id: UpstreamProjectId, _adapter_name: SharedString,} impl RemoteConnection { fn send_proto_client_request<R: DapCommand>( &self, _request: R, _session_id: SessionId, cx: &mut App, ) -> Task<Result<R::Response>> { // let message = request.to_proto(session_id, self.upstream_project_id); // let upstream_client = self.client.clone(); cx.background_executor().spawn(async move { // debugger(todo): Properly send messages when we wrap dap_commands in envelopes again // let response = upstream_client.request(message).await?; // request.response_from_proto(response) Err(anyhow!("Sending dap commands over RPC isn't supported yet")) }) } fn request<R: DapCommand>( &self, request: R, session_id: SessionId, cx: &mut App, ) -> Task<Result<R::Response>> where <R::DapRequest as dap::requests::Request>::Response: 'static, <R::DapRequest as dap::requests::Request>::Arguments: 'static + Send, { return self.send_proto_client_request::<R>(request, session_id, cx); }} enum Mode { Local(LocalMode), Remote(RemoteConnection),} #[derive(Clone)]pub struct LocalMode { client: Arc<DebugAdapterClient>, definition: DebugTaskDefinition, binary: DebugAdapterBinary, pub(crate) breakpoint_store: Entity<BreakpointStore>, tmp_breakpoint: Option<SourceBreakpoint>,} 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 { fn new( session_id: SessionId, parent_session: Option<Entity<Session>>, breakpoint_store: Entity<BreakpointStore>, config: DebugTaskDefinition, binary: DebugAdapterBinary, messages_tx: futures::channel::mpsc::UnboundedSender<Message>, cx: AsyncApp, ) -> Task<Result<Self>> { Self::new_inner( session_id, parent_session, breakpoint_store, config, binary, messages_tx, async |_, _| {}, cx, ) } fn new_inner( session_id: SessionId, parent_session: Option<Entity<Session>>, breakpoint_store: Entity<BreakpointStore>, config: DebugTaskDefinition, binary: DebugAdapterBinary, messages_tx: futures::channel::mpsc::UnboundedSender<Message>, on_initialized: impl AsyncFnOnce(&mut LocalMode, AsyncApp) + 'static, cx: AsyncApp, ) -> Task<Result<Self>> { cx.spawn(async move |cx| { 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 .with_context(|| "Failed to start communication with debug adapter")? }, ); let mut session = Self { client, breakpoint_store, tmp_breakpoint: None, definition: config, binary, }; on_initialized(&mut session, cx.clone()).await; Ok(session) }) } 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<()> { 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(), )); } cx.background_spawn(async move { futures::future::join_all(breakpoint_tasks) .await .iter() .for_each(|res| match res { Ok(_) => {} Err(err) => { log::warn!("Set breakpoints request failed: {}", err); } }); }) } pub fn label(&self) -> String { self.definition.label.clone() } fn request_initialization(&self, cx: &App) -> Task<Result<Capabilities>> { let adapter_id = self.binary.adapter_name.to_string(); self.request(Initialize { adapter_id }, cx.background_executor().clone()) } fn initialize_sequence( &self, capabilities: &Capabilities, initialized_rx: oneshot::Receiver<()>, cx: &App, ) -> Task<Result<()>> { let mut raw = self.binary.request_args.clone(); merge_json_value_into( self.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 configuration_sequence = cx.spawn({ let this = self.clone(); async move |cx| { initialized_rx.await?; // todo(debugger) figure out if we want to handle a breakpoint response error // This will probably consist of letting a user know that breakpoints failed to be set cx.update(|cx| this.send_source_breakpoints(false, cx))? .await; 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 From<RemoteConnection> for Mode { fn from(value: RemoteConnection) -> Self { Self::Remote(value) }} impl Mode { fn request_dap<R: DapCommand>( &self, session_id: SessionId, request: R, cx: &mut Context<Session>, ) -> Task<Result<R::Response>>Lines 1–500 of 1962. 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 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 rpc::AnyProtoClient; 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}; #[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, } } } type UpstreamProjectId = u64; struct RemoteConnection { _client: AnyProtoClient, _upstream_project_id: UpstreamProjectId, _adapter_name: SharedString, } impl RemoteConnection { fn send_proto_client_request<R: DapCommand>( &self, _request: R, _session_id: SessionId, cx: &mut App, ) -> Task<Result<R::Response>> { // let message = request.to_proto(session_id, self.upstream_project_id); // let upstream_client = self.client.clone(); cx.background_executor().spawn(async move { // debugger(todo): Properly send messages when we wrap dap_commands in envelopes again // let response = upstream_client.request(message).await?; // request.response_from_proto(response) Err(anyhow!("Sending dap commands over RPC isn't supported yet")) }) } fn request<R: DapCommand>( &self, request: R, session_id: SessionId, cx: &mut App, ) -> Task<Result<R::Response>> where <R::DapRequest as dap::requests::Request>::Response: 'static, <R::DapRequest as dap::requests::Request>::Arguments: 'static + Send, { return self.send_proto_client_request::<R>(request, session_id, cx); } } enum Mode { Local(LocalMode), Remote(RemoteConnection), } #[derive(Clone)] pub struct LocalMode { client: Arc<DebugAdapterClient>, definition: DebugTaskDefinition, binary: DebugAdapterBinary, pub(crate) breakpoint_store: Entity<BreakpointStore>, tmp_breakpoint: Option<SourceBreakpoint>, } 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 { fn new( session_id: SessionId, parent_session: Option<Entity<Session>>, breakpoint_store: Entity<BreakpointStore>, config: DebugTaskDefinition, binary: DebugAdapterBinary, messages_tx: futures::channel::mpsc::UnboundedSender<Message>, cx: AsyncApp, ) -> Task<Result<Self>> { Self::new_inner( session_id, parent_session, breakpoint_store, config, binary, messages_tx, async |_, _| {}, cx, ) } fn new_inner( session_id: SessionId, parent_session: Option<Entity<Session>>, breakpoint_store: Entity<BreakpointStore>, config: DebugTaskDefinition, binary: DebugAdapterBinary, messages_tx: futures::channel::mpsc::UnboundedSender<Message>, on_initialized: impl AsyncFnOnce(&mut LocalMode, AsyncApp) + 'static, cx: AsyncApp, ) -> Task<Result<Self>> { cx.spawn(async move |cx| { 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 .with_context(|| "Failed to start communication with debug adapter")? }, ); let mut session = Self { client, breakpoint_store, tmp_breakpoint: None, definition: config, binary, }; on_initialized(&mut session, cx.clone()).await; Ok(session) }) } 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<()> { 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(), )); } cx.background_spawn(async move { futures::future::join_all(breakpoint_tasks) .await .iter() .for_each(|res| match res { Ok(_) => {} Err(err) => { log::warn!("Set breakpoints request failed: {}", err); } }); }) } pub fn label(&self) -> String { self.definition.label.clone() } fn request_initialization(&self, cx: &App) -> Task<Result<Capabilities>> { let adapter_id = self.binary.adapter_name.to_string(); self.request(Initialize { adapter_id }, cx.background_executor().clone()) } fn initialize_sequence( &self, capabilities: &Capabilities, initialized_rx: oneshot::Receiver<()>, cx: &App, ) -> Task<Result<()>> { let mut raw = self.binary.request_args.clone(); merge_json_value_into( self.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 configuration_sequence = cx.spawn({ let this = self.clone(); async move |cx| { initialized_rx.await?; // todo(debugger) figure out if we want to handle a breakpoint response error // This will probably consist of letting a user know that breakpoints failed to be set cx.update(|cx| this.send_source_breakpoints(false, cx))? .await; 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 From<RemoteConnection> for Mode { fn from(value: RemoteConnection) -> Self { Self::Remote(value) } } impl Mode { fn request_dap<R: DapCommand>( &self, session_id: SessionId, request: R, cx: &mut Context<Session>, ) -> Task<Result<R::Response>>