use super::{ breakpoint_store::BreakpointStore, locators::DapLocator, session::{self, Session, SessionStateEvent},};use crate::{ ProjectEnvironment, project_settings::ProjectSettings, terminals::{SshCommand, wrap_for_ssh}, worktree_store::WorktreeStore,};use anyhow::{Result, anyhow};use async_trait::async_trait;use collections::HashMap;use dap::{ Capabilities, CompletionItem, CompletionsArguments, DapRegistry, EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, Source, StartDebuggingRequestArguments, adapters::{DapStatus, DebugAdapterBinary, DebugAdapterName, TcpArguments}, client::SessionId, messages::Message, requests::{Completions, Evaluate, Request as _, RunInTerminal, StartDebugging},};use fs::Fs;use futures::{ channel::mpsc, future::{Shared, join_all},};use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task};use http_client::HttpClient;use language::{BinaryStatus, LanguageRegistry, LanguageToolchainStore};use lsp::LanguageServerName;use node_runtime::NodeRuntime; use remote::SshRemoteClient;use rpc::{ AnyProtoClient, TypedEnvelope, proto::{self},};use serde_json::Value;use settings::{Settings, WorktreeId};use smol::{lock::Mutex, stream::StreamExt};use std::{ borrow::Borrow, collections::{BTreeMap, HashSet}, ffi::OsStr, net::Ipv4Addr, path::{Path, PathBuf}, sync::Arc,};use task::{DebugTaskDefinition, DebugTaskTemplate};use util::ResultExt as _;use worktree::Worktree; pub enum DapStoreEvent { DebugClientStarted(SessionId), DebugSessionInitialized(SessionId), DebugClientShutdown(SessionId), DebugClientEvent { session_id: SessionId, message: Message, }, RunInTerminal { session_id: SessionId, title: Option<String>, cwd: Option<Arc<Path>>, command: Option<String>, args: Vec<String>, envs: HashMap<String, String>, sender: mpsc::Sender<Result<u32>>, }, SpawnChildSession { request: StartDebuggingRequestArguments, parent_session: Entity<Session>, }, Notification(String), RemoteHasInitialized,} #[allow(clippy::large_enum_variant)]enum DapStoreMode { Local(LocalDapStore), Ssh(SshDapStore), Collab,} pub struct LocalDapStore { fs: Arc<dyn Fs>, node_runtime: NodeRuntime, http_client: Arc<dyn HttpClient>, environment: Entity<ProjectEnvironment>, language_registry: Arc<LanguageRegistry>, toolchain_store: Arc<dyn LanguageToolchainStore>, locators: HashMap<String, Arc<dyn DapLocator>>,} pub struct SshDapStore { ssh_client: Entity<SshRemoteClient>, upstream_client: AnyProtoClient, upstream_project_id: u64,} pub struct DapStore { mode: DapStoreMode, downstream_client: Option<(AnyProtoClient, u64)>, breakpoint_store: Entity<BreakpointStore>, worktree_store: Entity<WorktreeStore>, sessions: BTreeMap<SessionId, Entity<Session>>, next_session_id: u32, start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>, _start_debugging_task: Task<()>,} impl EventEmitter<DapStoreEvent> for DapStore {} impl DapStore { pub fn init(client: &AnyProtoClient) { client.add_entity_request_handler(Self::handle_run_debug_locator); client.add_entity_request_handler(Self::handle_get_debug_adapter_binary); } #[expect(clippy::too_many_arguments)] pub fn new_local( http_client: Arc<dyn HttpClient>, node_runtime: NodeRuntime, fs: Arc<dyn Fs>, language_registry: Arc<LanguageRegistry>, environment: Entity<ProjectEnvironment>, toolchain_store: Arc<dyn LanguageToolchainStore>, worktree_store: Entity<WorktreeStore>, breakpoint_store: Entity<BreakpointStore>, cx: &mut Context<Self>, ) -> Self { cx.on_app_quit(Self::shutdown_sessions).detach(); let locators = HashMap::from_iter([( "cargo".to_string(), Arc::new(super::locators::cargo::CargoLocator {}) as _, )]); let mode = DapStoreMode::Local(LocalDapStore { fs, environment, http_client, node_runtime, toolchain_store, language_registry, locators, }); Self::new(mode, breakpoint_store, worktree_store, cx) } pub fn new_ssh( project_id: u64, ssh_client: Entity<SshRemoteClient>, breakpoint_store: Entity<BreakpointStore>, worktree_store: Entity<WorktreeStore>, cx: &mut Context<Self>, ) -> Self { let mode = DapStoreMode::Ssh(SshDapStore { upstream_client: ssh_client.read(cx).proto_client(), ssh_client, upstream_project_id: project_id, }); Self::new(mode, breakpoint_store, worktree_store, cx) } pub fn new_collab( _project_id: u64, _upstream_client: AnyProtoClient, breakpoint_store: Entity<BreakpointStore>, worktree_store: Entity<WorktreeStore>, cx: &mut Context<Self>, ) -> Self { Self::new(DapStoreMode::Collab, breakpoint_store, worktree_store, cx) } fn new( mode: DapStoreMode, breakpoint_store: Entity<BreakpointStore>, worktree_store: Entity<WorktreeStore>, cx: &mut Context<Self>, ) -> Self { let (start_debugging_tx, mut message_rx) = futures::channel::mpsc::unbounded::<(SessionId, Message)>(); let task = cx.spawn(async move |this, cx| { while let Some((session_id, message)) = message_rx.next().await { match message { Message::Request(request) => { let _ = this .update(cx, |this, cx| { if request.command == StartDebugging::COMMAND { this.handle_start_debugging_request(session_id, request, cx) .detach_and_log_err(cx); } else if request.command == RunInTerminal::COMMAND { this.handle_run_in_terminal_request(session_id, request, cx) .detach_and_log_err(cx); } }) .log_err(); } _ => {} } } }); Self { mode, _start_debugging_task: task, start_debugging_tx, next_session_id: 0, downstream_client: None, breakpoint_store, worktree_store, sessions: Default::default(), } } pub fn get_debug_adapter_binary( &mut self, definition: DebugTaskDefinition, cx: &mut Context<Self>, ) -> Task<Result<DebugAdapterBinary>> { match &self.mode { DapStoreMode::Local(_) => { let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next() else { return Task::ready(Err(anyhow!("Failed to find a worktree"))); }; let Some(adapter) = DapRegistry::global(cx).adapter(&definition.adapter) else { return Task::ready(Err(anyhow!("Failed to find a debug adapter"))); }; let user_installed_path = ProjectSettings::get_global(cx) .dap .get(&adapter.name()) .and_then(|s| s.binary.as_ref().map(PathBuf::from)); let delegate = self.delegate(&worktree, cx); let cwd: Arc<Path> = definition .cwd() .unwrap_or(worktree.read(cx).abs_path().as_ref()) .into(); cx.spawn(async move |this, cx| { let mut binary = adapter .get_binary(&delegate, &definition, user_installed_path, cx) .await?; let env = this .update(cx, |this, cx| { this.as_local() .unwrap() .environment .update(cx, |environment, cx| { environment.get_directory_environment(cwd, cx) }) })? .await; if let Some(mut env) = env { env.extend(std::mem::take(&mut binary.envs)); binary.envs = env; } Ok(binary) }) } DapStoreMode::Ssh(ssh) => { let request = ssh.upstream_client.request(proto::GetDebugAdapterBinary { project_id: ssh.upstream_project_id, task: Some(definition.to_proto()), }); let ssh_client = ssh.ssh_client.clone(); cx.spawn(async move |_, cx| { let response = request.await?; let binary = DebugAdapterBinary::from_proto(response)?; let mut ssh_command = ssh_client.update(cx, |ssh, _| { anyhow::Ok(SshCommand { arguments: ssh .ssh_args() .ok_or_else(|| anyhow!("SSH arguments not found"))?, }) })??; let mut connection = None; if let Some(c) = binary.connection { let local_bind_addr = Ipv4Addr::new(127, 0, 0, 1); let port = dap::transport::TcpTransport::unused_port(local_bind_addr).await?; ssh_command.add_port_forwarding(port, c.host.to_string(), c.port); connection = Some(TcpArguments { port: c.port, host: local_bind_addr, timeout: c.timeout, }) } let (program, args) = wrap_for_ssh( &ssh_command, Some((&binary.command, &binary.arguments)), binary.cwd.as_deref(), binary.envs, None, ); Ok(DebugAdapterBinary { command: program, arguments: args, envs: HashMap::default(), cwd: None, connection, request_args: binary.request_args, }) }) } DapStoreMode::Collab => { Task::ready(Err(anyhow!("Debugging is not yet supported via collab"))) } } } pub fn run_debug_locator( &mut self, template: DebugTaskTemplate, cx: &mut Context<Self>, ) -> Task<Result<DebugTaskDefinition>> { let Some(locator_name) = template.locator else { return Task::ready(Ok(template.definition)); }; match &self.mode { DapStoreMode::Local(local) => { if let Some(locator) = local.locators.get(&locator_name).cloned() { cx.background_spawn( async move { locator.run_locator(template.definition).await }, ) } else { Task::ready(Err(anyhow!("Couldn't find locator {}", locator_name))) } } DapStoreMode::Ssh(ssh) => { let request = ssh.upstream_client.request(proto::RunDebugLocator { project_id: ssh.upstream_project_id, locator: locator_name, task: Some(template.definition.to_proto()), }); cx.background_spawn(async move { let response = request.await?; DebugTaskDefinition::from_proto(response) }) } DapStoreMode::Collab => { Task::ready(Err(anyhow!("Debugging is not yet supported via collab"))) } } } fn as_local(&self) -> Option<&LocalDapStore> { match &self.mode { DapStoreMode::Local(local_dap_store) => Some(local_dap_store), _ => None, } } pub fn new_session( &mut self, template: DebugTaskDefinition, parent_session: Option<Entity<Session>>, cx: &mut Context<Self>, ) -> Entity<Session> { let session_id = SessionId(util::post_inc(&mut self.next_session_id)); if let Some(session) = &parent_session { session.update(cx, |session, _| { session.add_child_session_id(session_id); }); } let start_debugging_tx = self.start_debugging_tx.clone(); let session = Session::new( self.breakpoint_store.clone(), session_id, parent_session, template.clone(), start_debugging_tx, cx, ); self.sessions.insert(session_id, session.clone()); cx.notify(); cx.subscribe(&session, { move |this: &mut DapStore, _, event: &SessionStateEvent, cx| match event { SessionStateEvent::Shutdown => { this.shutdown_session(session_id, cx).detach_and_log_err(cx); } SessionStateEvent::Restart => {} SessionStateEvent::Running => { cx.emit(DapStoreEvent::DebugClientStarted(session_id)); } } }) .detach(); session } pub fn boot_session( &self, session: Entity<Session>, cx: &mut Context<Self>, ) -> Task<Result<()>> { let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next() else { return Task::ready(Err(anyhow!("Failed to find a worktree"))); }; let dap_store = cx.weak_entity(); let breakpoint_store = self.breakpoint_store.clone(); let definition = session.read(cx).definition(); cx.spawn({ let session = session.clone(); async move |this, cx| { let binary = this .update(cx, |this, cx| { this.get_debug_adapter_binary(definition.clone(), cx) })? .await?; session .update(cx, |session, cx| { session.boot(binary, worktree, breakpoint_store, dap_store, cx) })? .await } }) } pub fn session_by_id( &self, session_id: impl Borrow<SessionId>, ) -> Option<Entity<session::Session>> { let session_id = session_id.borrow(); let client = self.sessions.get(session_id).cloned(); client } pub fn sessions(&self) -> impl Iterator<Item = &Entity<Session>> { self.sessions.values() } pub fn capabilities_by_id( &self, session_id: impl Borrow<SessionId>, cx: &App, ) -> Option<Capabilities> { let session_id = session_id.borrow(); self.sessions .get(session_id) .map(|client| client.read(cx).capabilities.clone()) } pub fn breakpoint_store(&self) -> &Entity<BreakpointStore> { &self.breakpoint_store } pub fn worktree_store(&self) -> &Entity<WorktreeStore> { &self.worktree_store } #[allow(dead_code)] async fn handle_ignore_breakpoint_state( this: Entity<Self>, envelope: TypedEnvelope<proto::IgnoreBreakpointState>, mut cx: AsyncApp, ) -> Result<()> { let session_id = SessionId::from_proto(envelope.payload.session_id); this.update(&mut cx, |this, cx| { if let Some(session) = this.session_by_id(&session_id) { session.update(cx, |session, cx| { session.set_ignore_breakpoints(envelope.payload.ignore, cx) }) } else { Task::ready(HashMap::default()) } })? .await; Ok(()) } fn delegate(&self, worktree: &Entity<Worktree>, cx: &mut App) -> DapAdapterDelegate { let Some(local_store) = self.as_local() else {Lines 1–500 of 967. Load more
use super::{ breakpoint_store::BreakpointStore, locators::DapLocator, session::{self, Session, SessionStateEvent}, }; use crate::{ ProjectEnvironment, project_settings::ProjectSettings, terminals::{SshCommand, wrap_for_ssh}, worktree_store::WorktreeStore, }; use anyhow::{Result, anyhow}; use async_trait::async_trait; use collections::HashMap; use dap::{ Capabilities, CompletionItem, CompletionsArguments, DapRegistry, EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, Source, StartDebuggingRequestArguments, adapters::{DapStatus, DebugAdapterBinary, DebugAdapterName, TcpArguments}, client::SessionId, messages::Message, requests::{Completions, Evaluate, Request as _, RunInTerminal, StartDebugging}, }; use fs::Fs; use futures::{ channel::mpsc, future::{Shared, join_all}, }; use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task}; use http_client::HttpClient; use language::{BinaryStatus, LanguageRegistry, LanguageToolchainStore}; use lsp::LanguageServerName; use node_runtime::NodeRuntime; use remote::SshRemoteClient; use rpc::{ AnyProtoClient, TypedEnvelope, proto::{self}, }; use serde_json::Value; use settings::{Settings, WorktreeId}; use smol::{lock::Mutex, stream::StreamExt}; use std::{ borrow::Borrow, collections::{BTreeMap, HashSet}, ffi::OsStr, net::Ipv4Addr, path::{Path, PathBuf}, sync::Arc, }; use task::{DebugTaskDefinition, DebugTaskTemplate}; use util::ResultExt as _; use worktree::Worktree; pub enum DapStoreEvent { DebugClientStarted(SessionId), DebugSessionInitialized(SessionId), DebugClientShutdown(SessionId), DebugClientEvent { session_id: SessionId, message: Message, }, RunInTerminal { session_id: SessionId, title: Option<String>, cwd: Option<Arc<Path>>, command: Option<String>, args: Vec<String>, envs: HashMap<String, String>, sender: mpsc::Sender<Result<u32>>, }, SpawnChildSession { request: StartDebuggingRequestArguments, parent_session: Entity<Session>, }, Notification(String), RemoteHasInitialized, } #[allow(clippy::large_enum_variant)] enum DapStoreMode { Local(LocalDapStore), Ssh(SshDapStore), Collab, } pub struct LocalDapStore { fs: Arc<dyn Fs>, node_runtime: NodeRuntime, http_client: Arc<dyn HttpClient>, environment: Entity<ProjectEnvironment>, language_registry: Arc<LanguageRegistry>, toolchain_store: Arc<dyn LanguageToolchainStore>, locators: HashMap<String, Arc<dyn DapLocator>>, } pub struct SshDapStore { ssh_client: Entity<SshRemoteClient>, upstream_client: AnyProtoClient, upstream_project_id: u64, } pub struct DapStore { mode: DapStoreMode, downstream_client: Option<(AnyProtoClient, u64)>, breakpoint_store: Entity<BreakpointStore>, worktree_store: Entity<WorktreeStore>, sessions: BTreeMap<SessionId, Entity<Session>>, next_session_id: u32, start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>, _start_debugging_task: Task<()>, } impl EventEmitter<DapStoreEvent> for DapStore {} impl DapStore { pub fn init(client: &AnyProtoClient) { client.add_entity_request_handler(Self::handle_run_debug_locator); client.add_entity_request_handler(Self::handle_get_debug_adapter_binary); } #[expect(clippy::too_many_arguments)] pub fn new_local( http_client: Arc<dyn HttpClient>, node_runtime: NodeRuntime, fs: Arc<dyn Fs>, language_registry: Arc<LanguageRegistry>, environment: Entity<ProjectEnvironment>, toolchain_store: Arc<dyn LanguageToolchainStore>, worktree_store: Entity<WorktreeStore>, breakpoint_store: Entity<BreakpointStore>, cx: &mut Context<Self>, ) -> Self { cx.on_app_quit(Self::shutdown_sessions).detach(); let locators = HashMap::from_iter([( "cargo".to_string(), Arc::new(super::locators::cargo::CargoLocator {}) as _, )]); let mode = DapStoreMode::Local(LocalDapStore { fs, environment, http_client, node_runtime, toolchain_store, language_registry, locators, }); Self::new(mode, breakpoint_store, worktree_store, cx) } pub fn new_ssh( project_id: u64, ssh_client: Entity<SshRemoteClient>, breakpoint_store: Entity<BreakpointStore>, worktree_store: Entity<WorktreeStore>, cx: &mut Context<Self>, ) -> Self { let mode = DapStoreMode::Ssh(SshDapStore { upstream_client: ssh_client.read(cx).proto_client(), ssh_client, upstream_project_id: project_id, }); Self::new(mode, breakpoint_store, worktree_store, cx) } pub fn new_collab( _project_id: u64, _upstream_client: AnyProtoClient, breakpoint_store: Entity<BreakpointStore>, worktree_store: Entity<WorktreeStore>, cx: &mut Context<Self>, ) -> Self { Self::new(DapStoreMode::Collab, breakpoint_store, worktree_store, cx) } fn new( mode: DapStoreMode, breakpoint_store: Entity<BreakpointStore>, worktree_store: Entity<WorktreeStore>, cx: &mut Context<Self>, ) -> Self { let (start_debugging_tx, mut message_rx) = futures::channel::mpsc::unbounded::<(SessionId, Message)>(); let task = cx.spawn(async move |this, cx| { while let Some((session_id, message)) = message_rx.next().await { match message { Message::Request(request) => { let _ = this .update(cx, |this, cx| { if request.command == StartDebugging::COMMAND { this.handle_start_debugging_request(session_id, request, cx) .detach_and_log_err(cx); } else if request.command == RunInTerminal::COMMAND { this.handle_run_in_terminal_request(session_id, request, cx) .detach_and_log_err(cx); } }) .log_err(); } _ => {} } } }); Self { mode, _start_debugging_task: task, start_debugging_tx, next_session_id: 0, downstream_client: None, breakpoint_store, worktree_store, sessions: Default::default(), } } pub fn get_debug_adapter_binary( &mut self, definition: DebugTaskDefinition, cx: &mut Context<Self>, ) -> Task<Result<DebugAdapterBinary>> { match &self.mode { DapStoreMode::Local(_) => { let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next() else { return Task::ready(Err(anyhow!("Failed to find a worktree"))); }; let Some(adapter) = DapRegistry::global(cx).adapter(&definition.adapter) else { return Task::ready(Err(anyhow!("Failed to find a debug adapter"))); }; let user_installed_path = ProjectSettings::get_global(cx) .dap .get(&adapter.name()) .and_then(|s| s.binary.as_ref().map(PathBuf::from)); let delegate = self.delegate(&worktree, cx); let cwd: Arc<Path> = definition .cwd() .unwrap_or(worktree.read(cx).abs_path().as_ref()) .into(); cx.spawn(async move |this, cx| { let mut binary = adapter .get_binary(&delegate, &definition, user_installed_path, cx) .await?; let env = this .update(cx, |this, cx| { this.as_local() .unwrap() .environment .update(cx, |environment, cx| { environment.get_directory_environment(cwd, cx) }) })? .await; if let Some(mut env) = env { env.extend(std::mem::take(&mut binary.envs)); binary.envs = env; } Ok(binary) }) } DapStoreMode::Ssh(ssh) => { let request = ssh.upstream_client.request(proto::GetDebugAdapterBinary { project_id: ssh.upstream_project_id, task: Some(definition.to_proto()), }); let ssh_client = ssh.ssh_client.clone(); cx.spawn(async move |_, cx| { let response = request.await?; let binary = DebugAdapterBinary::from_proto(response)?; let mut ssh_command = ssh_client.update(cx, |ssh, _| { anyhow::Ok(SshCommand { arguments: ssh .ssh_args() .ok_or_else(|| anyhow!("SSH arguments not found"))?, }) })??; let mut connection = None; if let Some(c) = binary.connection { let local_bind_addr = Ipv4Addr::new(127, 0, 0, 1); let port = dap::transport::TcpTransport::unused_port(local_bind_addr).await?; ssh_command.add_port_forwarding(port, c.host.to_string(), c.port); connection = Some(TcpArguments { port: c.port, host: local_bind_addr, timeout: c.timeout, }) } let (program, args) = wrap_for_ssh( &ssh_command, Some((&binary.command, &binary.arguments)), binary.cwd.as_deref(), binary.envs, None, ); Ok(DebugAdapterBinary { command: program, arguments: args, envs: HashMap::default(), cwd: None, connection, request_args: binary.request_args, }) }) } DapStoreMode::Collab => { Task::ready(Err(anyhow!("Debugging is not yet supported via collab"))) } } } pub fn run_debug_locator( &mut self, template: DebugTaskTemplate, cx: &mut Context<Self>, ) -> Task<Result<DebugTaskDefinition>> { let Some(locator_name) = template.locator else { return Task::ready(Ok(template.definition)); }; match &self.mode { DapStoreMode::Local(local) => { if let Some(locator) = local.locators.get(&locator_name).cloned() { cx.background_spawn( async move { locator.run_locator(template.definition).await }, ) } else { Task::ready(Err(anyhow!("Couldn't find locator {}", locator_name))) } } DapStoreMode::Ssh(ssh) => { let request = ssh.upstream_client.request(proto::RunDebugLocator { project_id: ssh.upstream_project_id, locator: locator_name, task: Some(template.definition.to_proto()), }); cx.background_spawn(async move { let response = request.await?; DebugTaskDefinition::from_proto(response) }) } DapStoreMode::Collab => { Task::ready(Err(anyhow!("Debugging is not yet supported via collab"))) } } } fn as_local(&self) -> Option<&LocalDapStore> { match &self.mode { DapStoreMode::Local(local_dap_store) => Some(local_dap_store), _ => None, } } pub fn new_session( &mut self, template: DebugTaskDefinition, parent_session: Option<Entity<Session>>, cx: &mut Context<Self>, ) -> Entity<Session> { let session_id = SessionId(util::post_inc(&mut self.next_session_id)); if let Some(session) = &parent_session { session.update(cx, |session, _| { session.add_child_session_id(session_id); }); } let start_debugging_tx = self.start_debugging_tx.clone(); let session = Session::new( self.breakpoint_store.clone(), session_id, parent_session, template.clone(), start_debugging_tx, cx, ); self.sessions.insert(session_id, session.clone()); cx.notify(); cx.subscribe(&session, { move |this: &mut DapStore, _, event: &SessionStateEvent, cx| match event { SessionStateEvent::Shutdown => { this.shutdown_session(session_id, cx).detach_and_log_err(cx); } SessionStateEvent::Restart => {} SessionStateEvent::Running => { cx.emit(DapStoreEvent::DebugClientStarted(session_id)); } } }) .detach(); session } pub fn boot_session( &self, session: Entity<Session>, cx: &mut Context<Self>, ) -> Task<Result<()>> { let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next() else { return Task::ready(Err(anyhow!("Failed to find a worktree"))); }; let dap_store = cx.weak_entity(); let breakpoint_store = self.breakpoint_store.clone(); let definition = session.read(cx).definition(); cx.spawn({ let session = session.clone(); async move |this, cx| { let binary = this .update(cx, |this, cx| { this.get_debug_adapter_binary(definition.clone(), cx) })? .await?; session .update(cx, |session, cx| { session.boot(binary, worktree, breakpoint_store, dap_store, cx) })? .await } }) } pub fn session_by_id( &self, session_id: impl Borrow<SessionId>, ) -> Option<Entity<session::Session>> { let session_id = session_id.borrow(); let client = self.sessions.get(session_id).cloned(); client } pub fn sessions(&self) -> impl Iterator<Item = &Entity<Session>> { self.sessions.values() } pub fn capabilities_by_id( &self, session_id: impl Borrow<SessionId>, cx: &App, ) -> Option<Capabilities> { let session_id = session_id.borrow(); self.sessions .get(session_id) .map(|client| client.read(cx).capabilities.clone()) } pub fn breakpoint_store(&self) -> &Entity<BreakpointStore> { &self.breakpoint_store } pub fn worktree_store(&self) -> &Entity<WorktreeStore> { &self.worktree_store } #[allow(dead_code)] async fn handle_ignore_breakpoint_state( this: Entity<Self>, envelope: TypedEnvelope<proto::IgnoreBreakpointState>, mut cx: AsyncApp, ) -> Result<()> { let session_id = SessionId::from_proto(envelope.payload.session_id); this.update(&mut cx, |this, cx| { if let Some(session) = this.session_by_id(&session_id) { session.update(cx, |session, cx| { session.set_ignore_breakpoints(envelope.payload.ignore, cx) }) } else { Task::ready(HashMap::default()) } })? .await; Ok(()) } fn delegate(&self, worktree: &Entity<Worktree>, cx: &mut App) -> DapAdapterDelegate { let Some(local_store) = self.as_local() else {