use crate::project_settings::ProjectSettings; use super::breakpoint_store::{BreakpointStore, BreakpointStoreEvent, BreakpointUpdatedReason};use super::dap_command::{ self, Attach, ConfigurationDone, ContinueCommand, DapCommand, DisconnectCommand, EvaluateCommand, Initialize, Launch, LoadedSourcesCommand, LocalDapCommand, LocationsCommand, ModulesCommand, NextCommand, PauseCommand, RestartCommand, RestartStackFrameCommand, ScopesCommand, SetVariableValueCommand, StackTraceCommand, StepBackCommand, StepCommand, StepInCommand, StepOutCommand, TerminateCommand, TerminateThreadsCommand, ThreadsCommand, VariablesCommand,};use super::dap_store::DapAdapterDelegate;use anyhow::{Result, anyhow};use collections::{HashMap, HashSet, IndexMap, IndexSet};use dap::adapters::{DebugAdapter, DebugAdapterBinary};use dap::messages::Response;use dap::{ Capabilities, ContinueArguments, EvaluateArgumentsContext, Module, Source, StackFrameId, SteppingGranularity, StoppedEvent, VariableReference, adapters::{DapDelegate, DapStatus}, client::{DebugAdapterClient, SessionId}, messages::{Events, Message},};use dap::{DapRegistry, DebugRequestType, OutputEventCategory};use futures::channel::oneshot;use futures::{FutureExt, future::Shared};use gpui::{ App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, Task, WeakEntity,};use rpc::AnyProtoClient;use serde_json::{Value, json};use settings::Settings;use smol::stream::StreamExt;use std::any::TypeId;use std::path::PathBuf;use std::u64;use std::{ any::Any, collections::hash_map::Entry, hash::{Hash, Hasher}, path::Path, sync::Arc,};use task::{DebugAdapterConfig, 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,} 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>, config: DebugAdapterConfig, adapter: Arc<dyn DebugAdapter>, breakpoint_store: Entity<BreakpointStore>,} 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( debug_adapters: Arc<DapRegistry>, session_id: SessionId, parent_session: Option<Entity<Session>>, breakpoint_store: Entity<BreakpointStore>, config: DebugAdapterConfig, delegate: DapAdapterDelegate, messages_tx: futures::channel::mpsc::UnboundedSender<Message>, cx: AsyncApp, ) -> Task<Result<(Self, Capabilities)>> { Self::new_inner( debug_adapters, session_id, parent_session, breakpoint_store, config, delegate, messages_tx, async |_, _| {}, cx, ) } #[cfg(any(test, feature = "test-support"))] fn new_fake( session_id: SessionId, parent_session: Option<Entity<Session>>, breakpoint_store: Entity<BreakpointStore>, config: DebugAdapterConfig, delegate: DapAdapterDelegate, messages_tx: futures::channel::mpsc::UnboundedSender<Message>, caps: Capabilities, fail: bool, cx: AsyncApp, ) -> Task<Result<(Self, Capabilities)>> { use task::DebugRequestDisposition; let request = match config.request.clone() { DebugRequestDisposition::UserConfigured(request) => request, DebugRequestDisposition::ReverseRequest(reverse_request_args) => { match reverse_request_args.request { dap::StartDebuggingRequestArgumentsRequest::Launch => { DebugRequestType::Launch(task::LaunchConfig { program: "".to_owned(), cwd: None, }) } dap::StartDebuggingRequestArgumentsRequest::Attach => { DebugRequestType::Attach(task::AttachConfig { process_id: Some(0), }) } } } }; let callback = async move |session: &mut LocalMode, cx: AsyncApp| { session .client .on_request::<dap::requests::Initialize, _>(move |_, _| Ok(caps.clone())) .await; let paths = cx .update(|cx| session.breakpoint_store.read(cx).breakpoint_paths()) .expect("Breakpoint store should exist in all tests that start debuggers"); session .client .on_request::<dap::requests::SetBreakpoints, _>(move |_, args| { let p = Arc::from(Path::new(&args.source.path.unwrap())); if !paths.contains(&p) { panic!("Sent breakpoints for path without any") } Ok(dap::SetBreakpointsResponse { breakpoints: Vec::default(), }) }) .await; match request { dap::DebugRequestType::Launch(_) => { if fail { session .client .on_request::<dap::requests::Launch, _>(move |_, _| { Err(dap::ErrorResponse { error: Some(dap::Message { id: 1, format: "error".into(), variables: None, send_telemetry: None, show_user: None, url: None, url_label: None, }), }) }) .await; } else { session .client .on_request::<dap::requests::Launch, _>(move |_, _| Ok(())) .await; } } dap::DebugRequestType::Attach(attach_config) => { if fail { session .client .on_request::<dap::requests::Attach, _>(move |_, _| { Err(dap::ErrorResponse { error: Some(dap::Message { id: 1, format: "error".into(), variables: None, send_telemetry: None, show_user: None, url: None, url_label: None, }), }) }) .await; } else { session .client .on_request::<dap::requests::Attach, _>(move |_, args| { assert_eq!( json!({"request": "attach", "process_id": attach_config.process_id.unwrap()}), args.raw ); Ok(()) }) .await; } } } session .client .on_request::<dap::requests::Disconnect, _>(move |_, _| Ok(())) .await; session.client.fake_event(Events::Initialized(None)).await; }; Self::new_inner( DapRegistry::fake().into(), session_id, parent_session, breakpoint_store, config, delegate, messages_tx, callback, cx, ) } fn new_inner( registry: Arc<DapRegistry>, session_id: SessionId, parent_session: Option<Entity<Session>>, breakpoint_store: Entity<BreakpointStore>, config: DebugAdapterConfig, delegate: DapAdapterDelegate, messages_tx: futures::channel::mpsc::UnboundedSender<Message>, on_initialized: impl AsyncFnOnce(&mut LocalMode, AsyncApp) + 'static, cx: AsyncApp, ) -> Task<Result<(Self, Capabilities)>> { cx.spawn(async move |cx| { let (adapter, binary) = Self::get_adapter_binary(®istry, &config, &delegate, cx).await?; 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, message_handler, cx.clone()) .await? } else { DebugAdapterClient::start( session_id, adapter.name(), binary, message_handler, cx.clone(), ) .await? }, ); let adapter_id = adapter.name().to_string().to_owned(); let mut session = Self { client, adapter, breakpoint_store, config: config.clone(), }; on_initialized(&mut session, cx.clone()).await; let capabilities = session .request(Initialize { adapter_id }, cx.background_executor().clone()) .await?; Ok((session, capabilities)) }) } 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()) .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_all_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); } }); }) } async fn get_adapter_binary( registry: &Arc<DapRegistry>, config: &DebugAdapterConfig, delegate: &DapAdapterDelegate, cx: &mut AsyncApp,Lines 1–500 of 2015. Load more
use crate::project_settings::ProjectSettings; use super::breakpoint_store::{BreakpointStore, BreakpointStoreEvent, BreakpointUpdatedReason}; use super::dap_command::{ self, Attach, ConfigurationDone, ContinueCommand, DapCommand, DisconnectCommand, EvaluateCommand, Initialize, Launch, LoadedSourcesCommand, LocalDapCommand, LocationsCommand, ModulesCommand, NextCommand, PauseCommand, RestartCommand, RestartStackFrameCommand, ScopesCommand, SetVariableValueCommand, StackTraceCommand, StepBackCommand, StepCommand, StepInCommand, StepOutCommand, TerminateCommand, TerminateThreadsCommand, ThreadsCommand, VariablesCommand, }; use super::dap_store::DapAdapterDelegate; use anyhow::{Result, anyhow}; use collections::{HashMap, HashSet, IndexMap, IndexSet}; use dap::adapters::{DebugAdapter, DebugAdapterBinary}; use dap::messages::Response; use dap::{ Capabilities, ContinueArguments, EvaluateArgumentsContext, Module, Source, StackFrameId, SteppingGranularity, StoppedEvent, VariableReference, adapters::{DapDelegate, DapStatus}, client::{DebugAdapterClient, SessionId}, messages::{Events, Message}, }; use dap::{DapRegistry, DebugRequestType, OutputEventCategory}; use futures::channel::oneshot; use futures::{FutureExt, future::Shared}; use gpui::{ App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, Task, WeakEntity, }; use rpc::AnyProtoClient; use serde_json::{Value, json}; use settings::Settings; use smol::stream::StreamExt; use std::any::TypeId; use std::path::PathBuf; use std::u64; use std::{ any::Any, collections::hash_map::Entry, hash::{Hash, Hasher}, path::Path, sync::Arc, }; use task::{DebugAdapterConfig, 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, } 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>, config: DebugAdapterConfig, adapter: Arc<dyn DebugAdapter>, breakpoint_store: Entity<BreakpointStore>, } 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( debug_adapters: Arc<DapRegistry>, session_id: SessionId, parent_session: Option<Entity<Session>>, breakpoint_store: Entity<BreakpointStore>, config: DebugAdapterConfig, delegate: DapAdapterDelegate, messages_tx: futures::channel::mpsc::UnboundedSender<Message>, cx: AsyncApp, ) -> Task<Result<(Self, Capabilities)>> { Self::new_inner( debug_adapters, session_id, parent_session, breakpoint_store, config, delegate, messages_tx, async |_, _| {}, cx, ) } #[cfg(any(test, feature = "test-support"))] fn new_fake( session_id: SessionId, parent_session: Option<Entity<Session>>, breakpoint_store: Entity<BreakpointStore>, config: DebugAdapterConfig, delegate: DapAdapterDelegate, messages_tx: futures::channel::mpsc::UnboundedSender<Message>, caps: Capabilities, fail: bool, cx: AsyncApp, ) -> Task<Result<(Self, Capabilities)>> { use task::DebugRequestDisposition; let request = match config.request.clone() { DebugRequestDisposition::UserConfigured(request) => request, DebugRequestDisposition::ReverseRequest(reverse_request_args) => { match reverse_request_args.request { dap::StartDebuggingRequestArgumentsRequest::Launch => { DebugRequestType::Launch(task::LaunchConfig { program: "".to_owned(), cwd: None, }) } dap::StartDebuggingRequestArgumentsRequest::Attach => { DebugRequestType::Attach(task::AttachConfig { process_id: Some(0), }) } } } }; let callback = async move |session: &mut LocalMode, cx: AsyncApp| { session .client .on_request::<dap::requests::Initialize, _>(move |_, _| Ok(caps.clone())) .await; let paths = cx .update(|cx| session.breakpoint_store.read(cx).breakpoint_paths()) .expect("Breakpoint store should exist in all tests that start debuggers"); session .client .on_request::<dap::requests::SetBreakpoints, _>(move |_, args| { let p = Arc::from(Path::new(&args.source.path.unwrap())); if !paths.contains(&p) { panic!("Sent breakpoints for path without any") } Ok(dap::SetBreakpointsResponse { breakpoints: Vec::default(), }) }) .await; match request { dap::DebugRequestType::Launch(_) => { if fail { session .client .on_request::<dap::requests::Launch, _>(move |_, _| { Err(dap::ErrorResponse { error: Some(dap::Message { id: 1, format: "error".into(), variables: None, send_telemetry: None, show_user: None, url: None, url_label: None, }), }) }) .await; } else { session .client .on_request::<dap::requests::Launch, _>(move |_, _| Ok(())) .await; } } dap::DebugRequestType::Attach(attach_config) => { if fail { session .client .on_request::<dap::requests::Attach, _>(move |_, _| { Err(dap::ErrorResponse { error: Some(dap::Message { id: 1, format: "error".into(), variables: None, send_telemetry: None, show_user: None, url: None, url_label: None, }), }) }) .await; } else { session .client .on_request::<dap::requests::Attach, _>(move |_, args| { assert_eq!( json!({"request": "attach", "process_id": attach_config.process_id.unwrap()}), args.raw ); Ok(()) }) .await; } } } session .client .on_request::<dap::requests::Disconnect, _>(move |_, _| Ok(())) .await; session.client.fake_event(Events::Initialized(None)).await; }; Self::new_inner( DapRegistry::fake().into(), session_id, parent_session, breakpoint_store, config, delegate, messages_tx, callback, cx, ) } fn new_inner( registry: Arc<DapRegistry>, session_id: SessionId, parent_session: Option<Entity<Session>>, breakpoint_store: Entity<BreakpointStore>, config: DebugAdapterConfig, delegate: DapAdapterDelegate, messages_tx: futures::channel::mpsc::UnboundedSender<Message>, on_initialized: impl AsyncFnOnce(&mut LocalMode, AsyncApp) + 'static, cx: AsyncApp, ) -> Task<Result<(Self, Capabilities)>> { cx.spawn(async move |cx| { let (adapter, binary) = Self::get_adapter_binary(®istry, &config, &delegate, cx).await?; 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, message_handler, cx.clone()) .await? } else { DebugAdapterClient::start( session_id, adapter.name(), binary, message_handler, cx.clone(), ) .await? }, ); let adapter_id = adapter.name().to_string().to_owned(); let mut session = Self { client, adapter, breakpoint_store, config: config.clone(), }; on_initialized(&mut session, cx.clone()).await; let capabilities = session .request(Initialize { adapter_id }, cx.background_executor().clone()) .await?; Ok((session, capabilities)) }) } 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()) .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_all_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); } }); }) } async fn get_adapter_binary( registry: &Arc<DapRegistry>, config: &DebugAdapterConfig, delegate: &DapAdapterDelegate, cx: &mut AsyncApp,