From 91e60d79bb5cfeff9e503bbdcaa6a9148502f60e Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Fri, 14 Feb 2025 07:55:03 -0500 Subject: [PATCH] Add `BreakpointStore` to debugger crate (#114) * Initial setup for breakpoint store * WIP Move more methods to breakpoint store * Move event handler to breakpoint store * Fix compiler errrors * Fix more compiler errors * Get Zed to compile --------- Co-authored-by: Remco Smits --- crates/editor/src/editor.rs | 27 +- crates/editor/src/element.rs | 2 +- crates/project/src/buffer_store.rs | 16 +- crates/project/src/debugger.rs | 1 + .../project/src/debugger/breakpoint_store.rs | 548 ++++++++++++++++++ crates/project/src/debugger/dap_session.rs | 2 +- crates/project/src/debugger/dap_store.rs | 478 +-------------- crates/project/src/lsp_store.rs | 8 +- crates/project/src/project.rs | 162 ++++-- crates/project_panel/src/project_panel.rs | 4 +- crates/remote_server/src/headless_project.rs | 7 +- crates/workspace/src/persistence.rs | 2 +- crates/workspace/src/persistence/model.rs | 2 +- crates/workspace/src/workspace.rs | 27 +- 14 files changed, 742 insertions(+), 544 deletions(-) create mode 100644 crates/project/src/debugger/breakpoint_store.rs diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3e4b764843..172e27ea44 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -108,7 +108,7 @@ use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange} use linked_editing_ranges::refresh_linked_ranges; use mouse_context_menu::MouseContextMenu; use project::{ - debugger::dap_store::{BreakpointEditAction, DapStoreEvent}, + debugger::breakpoint_store::{BreakpointEditAction, BreakpointStoreEvent}, ProjectPath, }; pub use proposed_changes_editor::{ @@ -136,7 +136,10 @@ use multi_buffer::{ }; use parking_lot::Mutex; use project::{ - debugger::dap_store::{Breakpoint, BreakpointKind, DapStore}, + debugger::{ + breakpoint_store::{Breakpoint, BreakpointKind}, + dap_store::DapStore, + }, lsp_store::{FormatTrigger, LspFormatTarget, OpenLspBufferHandle}, project_settings::{GitGutterSetting, ProjectSettings}, CodeAction, Completion, CompletionIntent, DocumentHighlight, InlayHint, Location, LocationLink, @@ -5546,7 +5549,7 @@ impl Editor { let snapshot = self.snapshot(window, cx); - let breakpoints = dap_store.read(cx).breakpoints(); + let breakpoints = &dap_store.read(cx).breakpoint_store().read(cx).breakpoints; if let Some(buffer) = self.buffer.read(cx).as_singleton() { let buffer = buffer.read(cx); @@ -7356,8 +7359,12 @@ impl Editor { .summary_for_anchor::(&breakpoint_position) .row; - let bp = self.dap_store.clone()?.read_with(cx, |store, _cx| { - store.breakpoint_at_row(row, &project_path, buffer_snapshot) + let bp = self.dap_store.clone()?.read_with(cx, |dap_store, cx| { + dap_store.breakpoint_store().read(cx).breakpoint_at_row( + row, + &project_path, + buffer_snapshot, + ) })?; Some((bp.active_position?, bp.kind)) @@ -14725,10 +14732,12 @@ impl Editor { if let Some(dap_store) = &self.dap_store { if let Some(project_path) = self.project_path(cx) { - dap_store.update(cx, |_, cx| { - cx.emit(DapStoreEvent::BreakpointsChanged { - project_path, - source_changed: true, + dap_store.update(cx, |dap_store, cx| { + dap_store.breakpoint_store().update(cx, |_, cx| { + cx.emit(BreakpointStoreEvent::BreakpointsChanged { + project_path, + source_changed: true, + }); }); }); } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7fc82faf65..90beb72e3e 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -55,7 +55,7 @@ use multi_buffer::{ RowInfo, ToOffset, }; use project::{ - debugger::dap_store::{Breakpoint, BreakpointKind}, + debugger::breakpoint_store::{Breakpoint, BreakpointKind}, project_settings::{GitGutterSetting, ProjectSettings}, }; use settings::{KeyBindingValidator, KeyBindingValidatorRegistration, Settings}; diff --git a/crates/project/src/buffer_store.rs b/crates/project/src/buffer_store.rs index eb701c9ce4..dbd66df87f 100644 --- a/crates/project/src/buffer_store.rs +++ b/crates/project/src/buffer_store.rs @@ -1,5 +1,5 @@ use crate::{ - debugger::dap_store::DapStore, + debugger::breakpoint_store::BreakpointStore, lsp_store::OpenLspBufferHandle, search::SearchQuery, worktree_store::{WorktreeStore, WorktreeStoreEvent}, @@ -317,7 +317,7 @@ struct RemoteBufferStore { struct LocalBufferStore { local_buffer_ids_by_path: HashMap, local_buffer_ids_by_entry_id: HashMap, - dap_store: Entity, + breakpoint_store: Entity, worktree_store: Entity, _subscription: Subscription, } @@ -1242,14 +1242,14 @@ impl BufferStore { /// Creates a buffer store, optionally retaining its buffers. pub fn local( worktree_store: Entity, - dap_store: Entity, + breakpoint_store: Entity, cx: &mut Context, ) -> Self { Self { state: BufferStoreState::Local(LocalBufferStore { local_buffer_ids_by_path: Default::default(), local_buffer_ids_by_entry_id: Default::default(), - dap_store, + breakpoint_store, worktree_store: worktree_store.clone(), _subscription: cx.subscribe(&worktree_store, |this, _, event, cx| { if let WorktreeStoreEvent::WorktreeAdded(worktree) = event { @@ -1291,14 +1291,14 @@ impl BufferStore { } } - pub fn dap_on_buffer_open( + pub fn breakpoint_store_on_buffer_open( &mut self, project_path: &ProjectPath, buffer: &Entity, cx: &mut Context, ) { if let Some(local_store) = self.as_local_mut() { - local_store.dap_store.update(cx, |store, cx| { + local_store.breakpoint_store.update(cx, |store, cx| { store.on_open_buffer(&project_path, buffer, cx); }); } @@ -1338,7 +1338,7 @@ impl BufferStore { cx: &mut Context, ) -> Task>> { if let Some(buffer) = self.get_by_path(&project_path, cx) { - self.dap_on_buffer_open(&project_path, &buffer, cx); + self.breakpoint_store_on_buffer_open(&project_path, &buffer, cx); return Task::ready(Ok(buffer)); } @@ -1368,7 +1368,7 @@ impl BufferStore { this.loading_buffers.remove(&project_path); let buffer = load_result.map_err(Arc::new)?; - this.dap_on_buffer_open(&project_path, &buffer, cx); + this.breakpoint_store_on_buffer_open(&project_path, &buffer, cx); Ok(buffer) })? }) diff --git a/crates/project/src/debugger.rs b/crates/project/src/debugger.rs index fc2a0107a2..1ca4f576c1 100644 --- a/crates/project/src/debugger.rs +++ b/crates/project/src/debugger.rs @@ -1,3 +1,4 @@ +pub mod breakpoint_store; pub mod dap_command; pub mod dap_session; pub mod dap_store; diff --git a/crates/project/src/debugger/breakpoint_store.rs b/crates/project/src/debugger/breakpoint_store.rs new file mode 100644 index 0000000000..c71d533bf6 --- /dev/null +++ b/crates/project/src/debugger/breakpoint_store.rs @@ -0,0 +1,548 @@ +use crate::{ProjectItem as _, ProjectPath}; +use anyhow::{Context as _, Result}; +use collections::{BTreeMap, HashSet}; +use dap::SourceBreakpoint; +use gpui::{AsyncApp, Context, Entity, EventEmitter}; +use language::{ + proto::{deserialize_anchor, serialize_anchor as serialize_text_anchor}, + Buffer, BufferSnapshot, +}; +use rpc::{proto, AnyProtoClient, TypedEnvelope}; +use settings::WorktreeId; +use std::{ + hash::{Hash, Hasher}, + path::Path, + sync::Arc, +}; +use text::Point; +use util::ResultExt as _; + +struct RemoteBreakpointStore { + upstream_client: Option, + upstream_project_id: u64, +} + +enum BreakpointMode { + Local, + Remote(RemoteBreakpointStore), +} + +pub struct BreakpointStore { + pub breakpoints: BTreeMap>, + downstream_client: Option<(AnyProtoClient, u64)>, + mode: BreakpointMode, +} + +pub enum BreakpointStoreEvent { + BreakpointsChanged { + project_path: ProjectPath, + source_changed: bool, + }, +} + +impl EventEmitter for BreakpointStore {} + +impl BreakpointStore { + pub fn init(client: &AnyProtoClient) { + client.add_entity_message_handler(Self::handle_synchronize_breakpoints); + } + + pub fn local() -> Self { + BreakpointStore { + breakpoints: BTreeMap::new(), + mode: BreakpointMode::Local, + downstream_client: None, + } + } + + pub(crate) fn remote(upstream_project_id: u64, upstream_client: AnyProtoClient) -> Self { + BreakpointStore { + breakpoints: BTreeMap::new(), + mode: BreakpointMode::Remote(RemoteBreakpointStore { + upstream_client: Some(upstream_client), + upstream_project_id, + }), + downstream_client: None, + } + } + + pub fn shared(&mut self, project_id: u64, downstream_client: AnyProtoClient) { + self.downstream_client = Some((downstream_client.clone(), project_id)); + + for (project_path, breakpoints) in self.breakpoints.iter() { + downstream_client + .send(proto::SynchronizeBreakpoints { + project_id, + project_path: Some(project_path.to_proto()), + breakpoints: breakpoints + .iter() + .filter_map(|breakpoint| breakpoint.to_proto()) + .collect(), + }) + .log_err(); + } + } + + pub fn unshared(&mut self, cx: &mut Context) { + self.downstream_client.take(); + + cx.notify(); + } + + pub fn upstream_client(&self) -> Option<(AnyProtoClient, u64)> { + match &self.mode { + BreakpointMode::Remote(RemoteBreakpointStore { + upstream_client: Some(upstream_client), + upstream_project_id, + .. + }) => Some((upstream_client.clone(), *upstream_project_id)), + + BreakpointMode::Remote(RemoteBreakpointStore { + upstream_client: None, + .. + }) => None, + BreakpointMode::Local => None, + } + } + + pub fn set_breakpoints_from_proto( + &mut self, + breakpoints: Vec, + cx: &mut Context, + ) { + let mut new_breakpoints = BTreeMap::new(); + for project_breakpoints in breakpoints { + let Some(project_path) = project_breakpoints.project_path else { + continue; + }; + + new_breakpoints.insert( + ProjectPath::from_proto(project_path), + project_breakpoints + .breakpoints + .into_iter() + .filter_map(Breakpoint::from_proto) + .collect::>(), + ); + } + + std::mem::swap(&mut self.breakpoints, &mut new_breakpoints); + cx.notify(); + } + + pub fn on_open_buffer( + &mut self, + project_path: &ProjectPath, + buffer: &Entity, + cx: &mut Context, + ) { + let entry = self.breakpoints.remove(project_path).unwrap_or_default(); + let mut set_bp: HashSet = HashSet::default(); + + let buffer = buffer.read(cx); + + for mut bp in entry.into_iter() { + bp.set_active_position(&buffer); + set_bp.insert(bp); + } + + self.breakpoints.insert(project_path.clone(), set_bp); + + cx.emit(BreakpointStoreEvent::BreakpointsChanged { + project_path: project_path.clone(), + source_changed: true, + }); + cx.notify(); + } + + pub fn on_file_rename( + &mut self, + old_project_path: ProjectPath, + new_project_path: ProjectPath, + cx: &mut Context, + ) { + if let Some(breakpoints) = self.breakpoints.remove(&old_project_path) { + self.breakpoints + .insert(new_project_path.clone(), breakpoints); + + cx.emit(BreakpointStoreEvent::BreakpointsChanged { + project_path: new_project_path, + source_changed: false, + }); + cx.notify(); + } + } + + pub fn sync_open_breakpoints_to_closed_breakpoints( + &mut self, + buffer: &Entity, + cx: &mut Context, + ) { + let Some(project_path) = buffer.read(cx).project_path(cx) else { + return; + }; + + if let Some(breakpoint_set) = self.breakpoints.remove(&project_path) { + let breakpoint_iter = breakpoint_set.into_iter().map(|mut breakpoint| { + breakpoint.cached_position = breakpoint.point_for_buffer(buffer.read(cx)).row; + breakpoint.active_position = None; + breakpoint + }); + + self.breakpoints.insert( + project_path.clone(), + breakpoint_iter.collect::>(), + ); + + cx.emit(BreakpointStoreEvent::BreakpointsChanged { + project_path, + source_changed: false, + }); + cx.notify(); + } + } + + pub fn breakpoint_at_row( + &self, + row: u32, + project_path: &ProjectPath, + buffer_snapshot: BufferSnapshot, + ) -> Option { + let breakpoint_set = self.breakpoints.get(project_path)?; + + breakpoint_set + .iter() + .find(|breakpoint| breakpoint.point_for_buffer_snapshot(&buffer_snapshot).row == row) + .cloned() + } + + pub fn toggle_breakpoint_for_buffer( + &mut self, + project_path: &ProjectPath, + mut breakpoint: Breakpoint, + edit_action: BreakpointEditAction, + cx: &mut Context, + ) { + let upstream_client = self.upstream_client(); + + let breakpoint_set = self.breakpoints.entry(project_path.clone()).or_default(); + + match edit_action { + BreakpointEditAction::Toggle => { + if !breakpoint_set.remove(&breakpoint) { + breakpoint_set.insert(breakpoint); + } + } + BreakpointEditAction::EditLogMessage(log_message) => { + if !log_message.is_empty() { + breakpoint.kind = BreakpointKind::Log(log_message.clone()); + breakpoint_set.remove(&breakpoint); + breakpoint_set.insert(breakpoint); + } else if matches!(&breakpoint.kind, BreakpointKind::Log(_)) { + breakpoint_set.remove(&breakpoint); + } + } + } + + if let Some((client, project_id)) = upstream_client.or(self.downstream_client.clone()) { + client + .send(client::proto::SynchronizeBreakpoints { + project_id, + project_path: Some(project_path.to_proto()), + breakpoints: breakpoint_set + .iter() + .filter_map(|breakpoint| breakpoint.to_proto()) + .collect(), + }) + .log_err(); + } + + if breakpoint_set.is_empty() { + self.breakpoints.remove(project_path); + } + + cx.emit(BreakpointStoreEvent::BreakpointsChanged { + project_path: project_path.clone(), + source_changed: false, + }); + cx.notify(); + } + + pub fn deserialize_breakpoints( + &mut self, + worktree_id: WorktreeId, + serialize_breakpoints: Vec, + ) { + for serialize_breakpoint in serialize_breakpoints { + self.breakpoints + .entry(ProjectPath { + worktree_id, + path: serialize_breakpoint.path.clone(), + }) + .or_default() + .insert(Breakpoint { + active_position: None, + cached_position: serialize_breakpoint.position, + kind: serialize_breakpoint.kind, + }); + } + } + + async fn handle_synchronize_breakpoints( + this: Entity, + envelope: TypedEnvelope, + mut cx: AsyncApp, + ) -> Result<()> { + let project_path = ProjectPath::from_proto( + envelope + .payload + .project_path + .context("Invalid Breakpoint call")?, + ); + + this.update(&mut cx, |store, cx| { + let breakpoints = envelope + .payload + .breakpoints + .into_iter() + .filter_map(Breakpoint::from_proto) + .collect::>(); + + if breakpoints.is_empty() { + store.breakpoints.remove(&project_path); + } else { + store.breakpoints.insert(project_path.clone(), breakpoints); + } + + cx.emit(BreakpointStoreEvent::BreakpointsChanged { + project_path, + source_changed: false, + }); + cx.notify(); + }) + } +} + +type LogMessage = Arc; + +#[derive(Clone, Debug)] +pub enum BreakpointEditAction { + Toggle, + EditLogMessage(LogMessage), +} + +#[derive(Clone, Debug)] +pub enum BreakpointKind { + Standard, + Log(LogMessage), +} + +impl BreakpointKind { + pub fn to_int(&self) -> i32 { + match self { + BreakpointKind::Standard => 0, + BreakpointKind::Log(_) => 1, + } + } + + pub fn log_message(&self) -> Option { + match self { + BreakpointKind::Standard => None, + BreakpointKind::Log(message) => Some(message.clone()), + } + } +} + +impl PartialEq for BreakpointKind { + fn eq(&self, other: &Self) -> bool { + std::mem::discriminant(self) == std::mem::discriminant(other) + } +} + +impl Eq for BreakpointKind {} + +impl Hash for BreakpointKind { + fn hash(&self, state: &mut H) { + std::mem::discriminant(self).hash(state); + } +} + +#[derive(Clone, Debug)] +pub struct Breakpoint { + pub active_position: Option, + pub cached_position: u32, + pub kind: BreakpointKind, +} + +// Custom implementation for PartialEq, Eq, and Hash is done +// to get toggle breakpoint to solely be based on a breakpoint's +// location. Otherwise, a user can get in situation's where there's +// overlapping breakpoint's with them being aware. +impl PartialEq for Breakpoint { + fn eq(&self, other: &Self) -> bool { + match (&self.active_position, &other.active_position) { + (None, None) => self.cached_position == other.cached_position, + (None, Some(_)) => false, + (Some(_), None) => false, + (Some(self_position), Some(other_position)) => self_position == other_position, + } + } +} + +impl Eq for Breakpoint {} + +impl Hash for Breakpoint { + fn hash(&self, state: &mut H) { + if self.active_position.is_some() { + self.active_position.hash(state); + } else { + self.cached_position.hash(state); + } + } +} + +impl Breakpoint { + pub fn to_source_breakpoint(&self, buffer: &Buffer) -> SourceBreakpoint { + let line = self + .active_position + .map(|position| buffer.summary_for_anchor::(&position).row) + .unwrap_or(self.cached_position) as u64; + + let log_message = match &self.kind { + BreakpointKind::Standard => None, + BreakpointKind::Log(message) => Some(message.clone().to_string()), + }; + + SourceBreakpoint { + line, + condition: None, + hit_condition: None, + log_message, + column: None, + mode: None, + } + } + + pub fn set_active_position(&mut self, buffer: &Buffer) { + if self.active_position.is_none() { + self.active_position = + Some(buffer.breakpoint_anchor(Point::new(self.cached_position, 0))); + } + } + + pub fn point_for_buffer(&self, buffer: &Buffer) -> Point { + self.active_position + .map(|position| buffer.summary_for_anchor::(&position)) + .unwrap_or(Point::new(self.cached_position, 0)) + } + + pub fn point_for_buffer_snapshot(&self, buffer_snapshot: &BufferSnapshot) -> Point { + self.active_position + .map(|position| buffer_snapshot.summary_for_anchor::(&position)) + .unwrap_or(Point::new(self.cached_position, 0)) + } + + pub fn source_for_snapshot(&self, snapshot: Option<&BufferSnapshot>) -> SourceBreakpoint { + let line = match snapshot { + Some(snapshot) => self + .active_position + .map(|position| snapshot.summary_for_anchor::(&position).row) + .unwrap_or(self.cached_position) as u64, + None => self.cached_position as u64, + }; + + let log_message = match &self.kind { + BreakpointKind::Standard => None, + BreakpointKind::Log(log_message) => Some(log_message.clone().to_string()), + }; + + SourceBreakpoint { + line, + condition: None, + hit_condition: None, + log_message, + column: None, + mode: None, + } + } + + pub fn to_serialized(&self, buffer: Option<&Buffer>, path: Arc) -> SerializedBreakpoint { + match buffer { + Some(buffer) => SerializedBreakpoint { + position: self + .active_position + .map(|position| buffer.summary_for_anchor::(&position).row) + .unwrap_or(self.cached_position), + path, + kind: self.kind.clone(), + }, + None => SerializedBreakpoint { + position: self.cached_position, + path, + kind: self.kind.clone(), + }, + } + } + + pub fn to_proto(&self) -> Option { + Some(client::proto::Breakpoint { + position: if let Some(position) = &self.active_position { + Some(serialize_text_anchor(position)) + } else { + None + }, + cached_position: self.cached_position, + kind: match self.kind { + BreakpointKind::Standard => proto::BreakpointKind::Standard.into(), + BreakpointKind::Log(_) => proto::BreakpointKind::Log.into(), + }, + message: if let BreakpointKind::Log(message) = &self.kind { + Some(message.to_string()) + } else { + None + }, + }) + } + + pub fn from_proto(breakpoint: client::proto::Breakpoint) -> Option { + Some(Self { + active_position: if let Some(position) = breakpoint.position.clone() { + deserialize_anchor(position) + } else { + None + }, + cached_position: breakpoint.cached_position, + kind: match proto::BreakpointKind::from_i32(breakpoint.kind) { + Some(proto::BreakpointKind::Log) => { + BreakpointKind::Log(breakpoint.message.clone().unwrap_or_default().into()) + } + None | Some(proto::BreakpointKind::Standard) => BreakpointKind::Standard, + }, + }) + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct SerializedBreakpoint { + pub position: u32, + pub path: Arc, + pub kind: BreakpointKind, +} + +impl SerializedBreakpoint { + pub fn to_source_breakpoint(&self) -> SourceBreakpoint { + let log_message = match &self.kind { + BreakpointKind::Standard => None, + BreakpointKind::Log(message) => Some(message.clone().to_string()), + }; + + SourceBreakpoint { + line: self.position as u64, + condition: None, + hit_condition: None, + log_message, + column: None, + mode: None, + } + } +} diff --git a/crates/project/src/debugger/dap_session.rs b/crates/project/src/debugger/dap_session.rs index ce9bb72ffd..ddfb67817b 100644 --- a/crates/project/src/debugger/dap_session.rs +++ b/crates/project/src/debugger/dap_session.rs @@ -16,7 +16,6 @@ use gpui::{App, AppContext, Context, Entity, Task}; use rpc::AnyProtoClient; use serde_json::Value; use std::borrow::Borrow; -use std::collections::btree_map::Entry as BTreeMapEntry; use std::u64; use std::{ any::Any, @@ -411,6 +410,7 @@ impl Client { .iter() .map(|thread| (ThreadId(thread.id), Thread::from(thread.clone()))), ); + cx.notify(); }, cx, ); diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index 017764355b..18dd429d3e 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -1,4 +1,5 @@ use super::{ + breakpoint_store::BreakpointStore, // Will need to uncomment this once we implement rpc message handler again // dap_command::{ // ContinueCommand, DapCommand, DisconnectCommand, NextCommand, PauseCommand, RestartCommand, @@ -8,7 +9,7 @@ use super::{ dap_command::DapCommand, dap_session::{self, DebugSession, DebugSessionId}, }; -use crate::{project_settings::ProjectSettings, ProjectEnvironment, ProjectItem as _, ProjectPath}; +use crate::{project_settings::ProjectSettings, ProjectEnvironment, ProjectPath}; use anyhow::{anyhow, bail, Context as _, Result}; use async_trait::async_trait; use collections::HashMap; @@ -31,10 +32,7 @@ use fs::Fs; use futures::future::Shared; use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task}; use http_client::HttpClient; -use language::{ - proto::{deserialize_anchor, serialize_anchor as serialize_text_anchor}, - BinaryStatus, Buffer, BufferSnapshot, LanguageRegistry, LanguageToolchainStore, -}; +use language::{BinaryStatus, BufferSnapshot, LanguageRegistry, LanguageToolchainStore}; use lsp::LanguageServerName; use node_runtime::NodeRuntime; use rpc::{ @@ -46,9 +44,9 @@ use settings::{Settings as _, WorktreeId}; use smol::lock::Mutex; use std::{ borrow::Borrow, - collections::{BTreeMap, HashSet}, + collections::HashSet, ffi::OsStr, - hash::{Hash, Hasher}, + hash::Hash, path::{Path, PathBuf}, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, @@ -57,7 +55,6 @@ use std::{ }; use std::{collections::VecDeque, sync::atomic::AtomicU32}; use task::{AttachConfig, DebugAdapterConfig, DebugRequestType}; -use text::Point; use util::{merge_json_value_into, ResultExt as _}; use worktree::Worktree; @@ -70,10 +67,6 @@ pub enum DapStoreEvent { message: Message, }, Notification(String), - BreakpointsChanged { - project_path: ProjectPath, - source_changed: bool, - }, ActiveDebugLineChanged, RemoteHasInitialized, SetDebugPanelItem(SetDebuggerPanelItem), @@ -117,7 +110,7 @@ pub struct RemoteDapStore { pub struct DapStore { mode: DapStoreMode, downstream_client: Option<(AnyProtoClient, u64)>, - breakpoints: BTreeMap>, + breakpoint_store: Entity, active_debug_line: Option<(DebugAdapterClientId, ProjectPath, u32)>, sessions: HashMap>, client_by_session: HashMap, @@ -134,7 +127,6 @@ impl DapStore { client.add_entity_message_handler(Self::handle_set_active_debug_line); client.add_entity_message_handler(Self::handle_set_debug_client_capabilities); client.add_entity_message_handler(Self::handle_set_debug_panel_item); - client.add_entity_message_handler(Self::handle_synchronize_breakpoints); client.add_entity_message_handler(Self::handle_update_debug_adapter); client.add_entity_message_handler(Self::handle_update_thread_status); client.add_entity_message_handler(Self::handle_ignore_breakpoint_state); @@ -163,6 +155,7 @@ impl DapStore { language_registry: Arc, environment: Entity, toolchain_store: Arc, + breakpoint_store: Entity, cx: &mut Context, ) -> Self { cx.on_app_quit(Self::shutdown_sessions).detach(); @@ -180,13 +173,17 @@ impl DapStore { }), downstream_client: None, active_debug_line: None, - breakpoints: Default::default(), + breakpoint_store, sessions: Default::default(), client_by_session: Default::default(), } } - pub fn new_remote(project_id: u64, upstream_client: AnyProtoClient) -> Self { + pub fn new_remote( + project_id: u64, + upstream_client: AnyProtoClient, + breakpoint_store: Entity, + ) -> Self { Self { mode: DapStoreMode::Remote(RemoteDapStore { upstream_client: Some(upstream_client), @@ -195,7 +192,7 @@ impl DapStore { }), downstream_client: None, active_debug_line: None, - breakpoints: Default::default(), + breakpoint_store, sessions: Default::default(), client_by_session: Default::default(), } @@ -302,7 +299,7 @@ impl DapStore { pub fn client_by_id( &self, client_id: impl Borrow, - cx: &Context, + cx: &App, ) -> Option<(Entity, Entity)> { let client_id = client_id.borrow(); let session = self.session_by_client_id(client_id)?; @@ -393,14 +390,8 @@ impl DapStore { } } - pub fn on_file_rename(&mut self, old_project_path: ProjectPath, new_project_path: ProjectPath) { - if let Some(breakpoints) = self.breakpoints.remove(&old_project_path) { - self.breakpoints.insert(new_project_path, breakpoints); - } - } - - pub fn breakpoints(&self) -> &BTreeMap> { - &self.breakpoints + pub fn breakpoint_store(&self) -> &Entity { + &self.breakpoint_store } async fn handle_session_has_shutdown( @@ -464,84 +455,6 @@ impl DapStore { } } - pub fn breakpoint_at_row( - &self, - row: u32, - project_path: &ProjectPath, - buffer_snapshot: BufferSnapshot, - ) -> Option { - let breakpoint_set = self.breakpoints.get(project_path)?; - - breakpoint_set - .iter() - .find(|bp| bp.point_for_buffer_snapshot(&buffer_snapshot).row == row) - .cloned() - } - - pub fn on_open_buffer( - &mut self, - project_path: &ProjectPath, - buffer: &Entity, - cx: &mut Context, - ) { - let entry = self.breakpoints.remove(project_path).unwrap_or_default(); - let mut set_bp: HashSet = HashSet::default(); - - let buffer = buffer.read(cx); - - for mut bp in entry.into_iter() { - bp.set_active_position(&buffer); - set_bp.insert(bp); - } - - self.breakpoints.insert(project_path.clone(), set_bp); - - cx.notify(); - } - - pub fn deserialize_breakpoints( - &mut self, - worktree_id: WorktreeId, - serialize_breakpoints: Vec, - ) { - for serialize_breakpoint in serialize_breakpoints { - self.breakpoints - .entry(ProjectPath { - worktree_id, - path: serialize_breakpoint.path.clone(), - }) - .or_default() - .insert(Breakpoint { - active_position: None, - cached_position: serialize_breakpoint.position, - kind: serialize_breakpoint.kind, - }); - } - } - - pub fn sync_open_breakpoints_to_closed_breakpoints( - &mut self, - buffer: &Entity, - cx: &mut Context, - ) { - let Some(project_path) = buffer.read(cx).project_path(cx) else { - return; - }; - - if let Some(breakpoint_set) = self.breakpoints.remove(&project_path) { - let breakpoint_iter = breakpoint_set.into_iter().map(|mut bp| { - bp.cached_position = bp.point_for_buffer(buffer.read(cx)).row; - bp.active_position = None; - bp - }); - - self.breakpoints - .insert(project_path, breakpoint_iter.collect::>()); - - cx.notify(); - } - } - fn reconnect_client( &mut self, session_id: &DebugSessionId, @@ -1319,31 +1232,6 @@ impl DapStore { cx.notify(); } - pub fn set_breakpoints_from_proto( - &mut self, - breakpoints: Vec, - cx: &mut Context, - ) { - let mut new_breakpoints = BTreeMap::new(); - for project_breakpoints in breakpoints { - let Some(project_path) = project_breakpoints.project_path else { - continue; - }; - - new_breakpoints.insert( - ProjectPath::from_proto(project_path), - project_breakpoints - .breakpoints - .into_iter() - .filter_map(Breakpoint::from_proto) - .collect::>(), - ); - } - - std::mem::swap(&mut self.breakpoints, &mut new_breakpoints); - cx.notify(); - } - async fn handle_shutdown_session_request( this: Entity, envelope: TypedEnvelope, @@ -1407,41 +1295,6 @@ impl DapStore { // Ok(T::response_to_proto(&client_id, response)) // } - async fn handle_synchronize_breakpoints( - this: Entity, - envelope: TypedEnvelope, - mut cx: AsyncApp, - ) -> Result<()> { - let project_path = ProjectPath::from_proto( - envelope - .payload - .project_path - .context("Invalid Breakpoint call")?, - ); - - this.update(&mut cx, |store, cx| { - let breakpoints = envelope - .payload - .breakpoints - .into_iter() - .filter_map(Breakpoint::from_proto) - .collect::>(); - - if breakpoints.is_empty() { - store.breakpoints.remove(&project_path); - } else { - store.breakpoints.insert(project_path.clone(), breakpoints); - } - - cx.emit(DapStoreEvent::BreakpointsChanged { - project_path, - source_changed: false, - }); - - cx.notify(); - }) - } - async fn handle_set_debug_panel_item( this: Entity, envelope: TypedEnvelope, @@ -1543,58 +1396,6 @@ impl DapStore { }) } - pub fn toggle_breakpoint_for_buffer( - &mut self, - project_path: &ProjectPath, - mut breakpoint: Breakpoint, - edit_action: BreakpointEditAction, - cx: &mut Context, - ) { - let upstream_client = self.upstream_client(); - - let breakpoint_set = self.breakpoints.entry(project_path.clone()).or_default(); - - match edit_action { - BreakpointEditAction::Toggle => { - if !breakpoint_set.remove(&breakpoint) { - breakpoint_set.insert(breakpoint); - } - } - BreakpointEditAction::EditLogMessage(log_message) => { - if !log_message.is_empty() { - breakpoint.kind = BreakpointKind::Log(log_message.clone()); - breakpoint_set.remove(&breakpoint); - breakpoint_set.insert(breakpoint); - } else if matches!(&breakpoint.kind, BreakpointKind::Log(_)) { - breakpoint_set.remove(&breakpoint); - } - } - } - - if let Some((client, project_id)) = upstream_client.or(self.downstream_client.clone()) { - client - .send(client::proto::SynchronizeBreakpoints { - project_id, - project_path: Some(project_path.to_proto()), - breakpoints: breakpoint_set - .iter() - .filter_map(|breakpoint| breakpoint.to_proto()) - .collect(), - }) - .log_err(); - } - - if breakpoint_set.is_empty() { - self.breakpoints.remove(project_path); - } - - cx.emit(DapStoreEvent::BreakpointsChanged { - project_path: project_path.clone(), - source_changed: false, - }); - cx.notify(); - } - pub fn send_breakpoints( &self, client_id: DebugAdapterClientId, @@ -1602,7 +1403,7 @@ impl DapStore { mut breakpoints: Vec, ignore: bool, source_changed: bool, - cx: &Context, + cx: &App, ) -> Task> { let Some(client) = self .client_by_id(client_id, cx) @@ -1646,15 +1447,17 @@ impl DapStore { absolute_path: PathBuf, buffer_snapshot: Option, source_changed: bool, - cx: &Context, + cx: &App, ) -> Task> { let source_breakpoints = self + .breakpoint_store + .read(cx) .breakpoints .get(project_path) .cloned() .unwrap_or_default() .iter() - .map(|bp| bp.source_for_snapshot(buffer_snapshot.as_ref())) + .map(|breakpoint| breakpoint.source_for_snapshot(buffer_snapshot.as_ref())) .collect::>(); let mut tasks = Vec::new(); @@ -1694,19 +1497,6 @@ impl DapStore { _: &mut Context, ) { self.downstream_client = Some((downstream_client.clone(), project_id)); - - for (project_path, breakpoints) in self.breakpoints.iter() { - downstream_client - .send(proto::SynchronizeBreakpoints { - project_id, - project_path: Some(project_path.to_proto()), - breakpoints: breakpoints - .iter() - .filter_map(|breakpoint| breakpoint.to_proto()) - .collect(), - }) - .log_err(); - } } pub fn unshared(&mut self, cx: &mut Context) { @@ -1716,230 +1506,6 @@ impl DapStore { } } -type LogMessage = Arc; - -#[derive(Clone, Debug)] -pub enum BreakpointEditAction { - Toggle, - EditLogMessage(LogMessage), -} - -#[derive(Clone, Debug)] -pub enum BreakpointKind { - Standard, - Log(LogMessage), -} - -impl BreakpointKind { - pub fn to_int(&self) -> i32 { - match self { - BreakpointKind::Standard => 0, - BreakpointKind::Log(_) => 1, - } - } - - pub fn log_message(&self) -> Option { - match self { - BreakpointKind::Standard => None, - BreakpointKind::Log(message) => Some(message.clone()), - } - } -} - -impl PartialEq for BreakpointKind { - fn eq(&self, other: &Self) -> bool { - std::mem::discriminant(self) == std::mem::discriminant(other) - } -} - -impl Eq for BreakpointKind {} - -impl Hash for BreakpointKind { - fn hash(&self, state: &mut H) { - std::mem::discriminant(self).hash(state); - } -} - -#[derive(Clone, Debug)] -pub struct Breakpoint { - pub active_position: Option, - pub cached_position: u32, - pub kind: BreakpointKind, -} - -// Custom implementation for PartialEq, Eq, and Hash is done -// to get toggle breakpoint to solely be based on a breakpoint's -// location. Otherwise, a user can get in situation's where there's -// overlapping breakpoint's with them being aware. -impl PartialEq for Breakpoint { - fn eq(&self, other: &Self) -> bool { - match (&self.active_position, &other.active_position) { - (None, None) => self.cached_position == other.cached_position, - (None, Some(_)) => false, - (Some(_), None) => false, - (Some(self_position), Some(other_position)) => self_position == other_position, - } - } -} - -impl Eq for Breakpoint {} - -impl Hash for Breakpoint { - fn hash(&self, state: &mut H) { - if self.active_position.is_some() { - self.active_position.hash(state); - } else { - self.cached_position.hash(state); - } - } -} - -impl Breakpoint { - pub fn to_source_breakpoint(&self, buffer: &Buffer) -> SourceBreakpoint { - let line = self - .active_position - .map(|position| buffer.summary_for_anchor::(&position).row) - .unwrap_or(self.cached_position) as u64; - - let log_message = match &self.kind { - BreakpointKind::Standard => None, - BreakpointKind::Log(message) => Some(message.clone().to_string()), - }; - - SourceBreakpoint { - line, - condition: None, - hit_condition: None, - log_message, - column: None, - mode: None, - } - } - - pub fn set_active_position(&mut self, buffer: &Buffer) { - if self.active_position.is_none() { - self.active_position = - Some(buffer.breakpoint_anchor(Point::new(self.cached_position, 0))); - } - } - - pub fn point_for_buffer(&self, buffer: &Buffer) -> Point { - self.active_position - .map(|position| buffer.summary_for_anchor::(&position)) - .unwrap_or(Point::new(self.cached_position, 0)) - } - - pub fn point_for_buffer_snapshot(&self, buffer_snapshot: &BufferSnapshot) -> Point { - self.active_position - .map(|position| buffer_snapshot.summary_for_anchor::(&position)) - .unwrap_or(Point::new(self.cached_position, 0)) - } - - pub fn source_for_snapshot(&self, snapshot: Option<&BufferSnapshot>) -> SourceBreakpoint { - let line = match snapshot { - Some(snapshot) => self - .active_position - .map(|position| snapshot.summary_for_anchor::(&position).row) - .unwrap_or(self.cached_position) as u64, - None => self.cached_position as u64, - }; - - let log_message = match &self.kind { - BreakpointKind::Standard => None, - BreakpointKind::Log(log_message) => Some(log_message.clone().to_string()), - }; - - SourceBreakpoint { - line, - condition: None, - hit_condition: None, - log_message, - column: None, - mode: None, - } - } - - pub fn to_serialized(&self, buffer: Option<&Buffer>, path: Arc) -> SerializedBreakpoint { - match buffer { - Some(buffer) => SerializedBreakpoint { - position: self - .active_position - .map(|position| buffer.summary_for_anchor::(&position).row) - .unwrap_or(self.cached_position), - path, - kind: self.kind.clone(), - }, - None => SerializedBreakpoint { - position: self.cached_position, - path, - kind: self.kind.clone(), - }, - } - } - - pub fn to_proto(&self) -> Option { - Some(client::proto::Breakpoint { - position: if let Some(position) = &self.active_position { - Some(serialize_text_anchor(position)) - } else { - None - }, - cached_position: self.cached_position, - kind: match self.kind { - BreakpointKind::Standard => proto::BreakpointKind::Standard.into(), - BreakpointKind::Log(_) => proto::BreakpointKind::Log.into(), - }, - message: if let BreakpointKind::Log(message) = &self.kind { - Some(message.to_string()) - } else { - None - }, - }) - } - - pub fn from_proto(breakpoint: client::proto::Breakpoint) -> Option { - Some(Self { - active_position: if let Some(position) = breakpoint.position.clone() { - deserialize_anchor(position) - } else { - None - }, - cached_position: breakpoint.cached_position, - kind: match proto::BreakpointKind::from_i32(breakpoint.kind) { - Some(proto::BreakpointKind::Log) => { - BreakpointKind::Log(breakpoint.message.clone().unwrap_or_default().into()) - } - None | Some(proto::BreakpointKind::Standard) => BreakpointKind::Standard, - }, - }) - } -} - -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct SerializedBreakpoint { - pub position: u32, - pub path: Arc, - pub kind: BreakpointKind, -} - -impl SerializedBreakpoint { - pub fn to_source_breakpoint(&self) -> SourceBreakpoint { - let log_message = match &self.kind { - BreakpointKind::Standard => None, - BreakpointKind::Log(message) => Some(message.clone().to_string()), - }; - - SourceBreakpoint { - line: self.position as u64, - condition: None, - hit_condition: None, - log_message, - column: None, - mode: None, - } - } -} - #[derive(Clone)] pub struct DapAdapterDelegate { fs: Arc, diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index e41b355c03..50c4c4a49b 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -3128,8 +3128,12 @@ impl LspStore { if let Some(local) = self.as_local_mut() { local.initialize_buffer(buffer, cx); - local.dap_store.update(cx, |store, cx| { - store.sync_open_breakpoints_to_closed_breakpoints(buffer, cx); + local.dap_store.update(cx, |dap_store, cx| { + dap_store + .breakpoint_store() + .update(cx, |breakpoint_store, cx| { + breakpoint_store.sync_open_breakpoints_to_closed_breakpoints(buffer, cx); + }); }); } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 565a5a5bcb..7390614f29 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -49,8 +49,12 @@ use dap::{ use collections::{BTreeSet, HashMap, HashSet}; use debounced_delay::DebouncedDelay; -use debugger::dap_store::{ - Breakpoint, BreakpointEditAction, DapStore, DapStoreEvent, SerializedBreakpoint, +use debugger::{ + breakpoint_store::{ + Breakpoint, BreakpointEditAction, BreakpointStore, BreakpointStoreEvent, + SerializedBreakpoint, + }, + dap_store::{DapStore, DapStoreEvent}, }; pub use environment::ProjectEnvironment; use futures::{ @@ -175,6 +179,7 @@ pub struct Project { buffer_ordered_messages_tx: mpsc::UnboundedSender, languages: Arc, dap_store: Entity, + breakpoint_store: Entity, client: Arc, join_project_response_message_id: u32, task_store: Entity, @@ -687,6 +692,8 @@ impl Project { ) }); + let breakpoint_store = cx.new(|_| BreakpointStore::local()); + let dap_store = cx.new(|cx| { DapStore::new_local( client.http_client(), @@ -695,13 +702,16 @@ impl Project { languages.clone(), environment.clone(), toolchain_store.read(cx).as_language_toolchain_store(), + breakpoint_store.clone(), cx, ) }); cx.subscribe(&dap_store, Self::on_dap_store_event).detach(); + cx.subscribe(&breakpoint_store, Self::on_breakpoint_store_event) + .detach(); - let buffer_store = - cx.new(|cx| BufferStore::local(worktree_store.clone(), dap_store.clone(), cx)); + let buffer_store = cx + .new(|cx| BufferStore::local(worktree_store.clone(), breakpoint_store.clone(), cx)); cx.subscribe(&buffer_store, Self::on_buffer_store_event) .detach(); @@ -782,6 +792,7 @@ impl Project { settings_observer, fs, ssh_client: None, + breakpoint_store, dap_store, buffers_needing_diff: Default::default(), git_diff_debouncer: DebouncedDelay::new(), @@ -878,7 +889,16 @@ impl Project { }); cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach(); - let dap_store = cx.new(|_| DapStore::new_remote(SSH_PROJECT_ID, client.clone().into())); + let breakpoint_store = + cx.new(|_| BreakpointStore::remote(SSH_PROJECT_ID, client.clone().into())); + + let dap_store = cx.new(|_| { + DapStore::new_remote( + SSH_PROJECT_ID, + client.clone().into(), + breakpoint_store.clone(), + ) + }); let git_store = cx.new(|cx| { GitStore::new( @@ -900,6 +920,7 @@ impl Project { buffer_store, image_store, lsp_store, + breakpoint_store, dap_store, join_project_response_message_id: 0, client_state: ProjectClientState::Local, @@ -1059,10 +1080,16 @@ impl Project { let environment = cx.update(|cx| ProjectEnvironment::new(&worktree_store, None, cx))?; - let dap_store = cx.new(|cx| { - let mut dap_store = DapStore::new_remote(remote_id, client.clone().into()); + let breakpoint_store = cx.new(|cx| { + let mut bp_store = BreakpointStore::remote(SSH_PROJECT_ID, client.clone().into()); + bp_store.set_breakpoints_from_proto(response.payload.breakpoints, cx); + bp_store + })?; + + let dap_store = cx.new(|cx| { + let mut dap_store = + DapStore::new_remote(remote_id, client.clone().into(), breakpoint_store.clone()); - dap_store.set_breakpoints_from_proto(response.payload.breakpoints, cx); dap_store.request_active_debug_sessions(cx); dap_store })?; @@ -1138,6 +1165,8 @@ impl Project { .detach(); cx.subscribe(&dap_store, Self::on_dap_store_event).detach(); + cx.subscribe(&breakpoint_store, Self::on_breakpoint_store_event) + .detach(); let mut this = Self { buffer_ordered_messages_tx: tx, @@ -1164,6 +1193,7 @@ impl Project { remote_id, replica_id, }, + breakpoint_store, dap_store: dap_store.clone(), git_store, buffers_needing_diff: Default::default(), @@ -1272,8 +1302,7 @@ impl Project { ) -> HashMap, Vec> { let mut all_breakpoints: HashMap, Vec> = Default::default(); - let open_breakpoints = self.dap_store.read(cx).breakpoints(); - for (project_path, breakpoints) in open_breakpoints.iter() { + for (project_path, breakpoints) in &self.breakpoint_store.read(cx).breakpoints { let buffer = maybe!({ let buffer_store = self.buffer_store.read(cx); let buffer_id = buffer_store.buffer_id_for_project_path(project_path)?; @@ -1384,11 +1413,11 @@ impl Project { .read(cx) .abs_path(); - let breakpoints = self.dap_store.read(cx).breakpoints(); - Some(( worktree_path, - breakpoints + self.breakpoint_store + .read(cx) + .breakpoints .get(&project_path)? .iter() .map(|bp| bp.to_serialized(buffer, project_path.path.clone())) @@ -1412,10 +1441,9 @@ impl Project { return result; } - let breakpoints = self.dap_store.read(cx).breakpoints(); - for project_path in breakpoints.keys() { + for project_path in self.breakpoint_store.read(cx).breakpoints.keys() { if let Some((worktree_path, mut serialized_breakpoint)) = - self.serialize_breakpoints_for_project_path(&project_path, cx) + self.serialize_breakpoints_for_project_path(project_path, cx) { result .entry(worktree_path.clone()) @@ -1480,15 +1508,15 @@ impl Project { let mut tasks = Vec::new(); - for (project_path, breakpoints) in store.breakpoints() { + for (project_path, breakpoints) in &self.breakpoint_store.read(cx).breakpoints { let Some((buffer, buffer_path)) = maybe!({ let buffer = self .buffer_store - .read_with(cx, |store, cx| store.get_by_path(project_path, cx))?; + .read_with(cx, |store, cx| store.get_by_path(&project_path, cx))?; let buffer = buffer.read(cx); let project_path = buffer.project_path(cx)?; - let worktree = self.worktree_for_id(project_path.clone().worktree_id, cx)?; + let worktree = self.worktree_for_id(project_path.worktree_id, cx)?; Some(( buffer, worktree.read(cx).absolutize(&project_path.path).ok()?, @@ -1539,8 +1567,17 @@ impl Project { }; if let Some(project_path) = buffer.read(cx).project_path(cx) { - self.dap_store.update(cx, |store, cx| { - store.toggle_breakpoint_for_buffer(&project_path, breakpoint, edit_action, cx) + self.dap_store.update(cx, |dap_store, cx| { + dap_store + .breakpoint_store() + .update(cx, |breakpoint_store, cx| { + breakpoint_store.toggle_breakpoint_for_buffer( + &project_path, + breakpoint, + edit_action, + cx, + ) + }) }); } } @@ -1630,6 +1667,10 @@ impl Project { self.dap_store.clone() } + pub fn breakpoint_store(&self) -> Entity { + self.breakpoint_store.clone() + } + pub fn lsp_store(&self) -> Entity { self.lsp_store.clone() } @@ -2051,6 +2092,9 @@ impl Project { self.client .subscribe_to_entity(project_id)? .set_entity(&self.dap_store, &mut cx.to_async()), + self.client + .subscribe_to_entity(project_id)? + .set_entity(&self.breakpoint_store, &mut cx.to_async()), self.client .subscribe_to_entity(project_id)? .set_entity(&self.git_store, &mut cx.to_async()), @@ -2065,6 +2109,9 @@ impl Project { self.lsp_store.update(cx, |lsp_store, cx| { lsp_store.shared(project_id, self.client.clone().into(), cx) }); + self.breakpoint_store.update(cx, |breakpoint_store, _| { + breakpoint_store.shared(project_id, self.client.clone().into()) + }); self.dap_store.update(cx, |dap_store, cx| { dap_store.shared(project_id, self.client.clone().into(), cx); }); @@ -2123,8 +2170,8 @@ impl Project { self.lsp_store.update(cx, |lsp_store, _| { lsp_store.set_language_server_statuses_from_proto(message.language_servers) }); - self.dap_store.update(cx, |dap_store, cx| { - dap_store.set_breakpoints_from_proto(message.breakpoints, cx); + self.breakpoint_store.update(cx, |breakpoint_store, cx| { + breakpoint_store.set_breakpoints_from_proto(message.breakpoints, cx); }); self.enqueue_buffer_ordered_message(BufferOrderedMessage::Resync) .unwrap(); @@ -2158,6 +2205,9 @@ impl Project { self.task_store.update(cx, |task_store, cx| { task_store.unshared(cx); }); + self.breakpoint_store.update(cx, |breakpoint_store, cx| { + breakpoint_store.unshared(cx); + }); self.dap_store.update(cx, |dap_store, cx| { dap_store.unshared(cx); }); @@ -2664,6 +2714,44 @@ impl Project { } } + fn on_breakpoint_store_event( + &mut self, + _: Entity, + event: &BreakpointStoreEvent, + cx: &mut Context, + ) { + match event { + BreakpointStoreEvent::BreakpointsChanged { + project_path, + source_changed, + } => { + cx.notify(); // so the UI updates + + let buffer_snapshot = self + .buffer_store + .read(cx) + .get_by_path(&project_path, cx) + .map(|buffer| buffer.read(cx).snapshot()); + + let Some(absolute_path) = self.absolute_path(project_path, cx) else { + return; + }; + + self.dap_store.read_with(cx, |dap_store, cx| { + dap_store + .send_changed_breakpoints( + project_path, + absolute_path, + buffer_snapshot, + *source_changed, + cx, + ) + .detach_and_log_err(cx) + }); + } + } + } + fn on_dap_store_event( &mut self, _: Entity, @@ -2694,34 +2782,6 @@ impl Project { message: message.clone(), }); } - DapStoreEvent::BreakpointsChanged { - project_path, - source_changed, - } => { - cx.notify(); // so the UI updates - - let buffer_snapshot = self - .buffer_store - .read(cx) - .get_by_path(&project_path, cx) - .map(|buffer| buffer.read(cx).snapshot()); - - let Some(absolute_path) = self.absolute_path(project_path, cx) else { - return; - }; - - self.dap_store.update(cx, |store, cx| { - store - .send_changed_breakpoints( - project_path, - absolute_path, - buffer_snapshot, - *source_changed, - cx, - ) - .detach_and_log_err(cx); - }); - } DapStoreEvent::ActiveDebugLineChanged => { cx.emit(Event::ActiveDebugLineChanged); } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 1ac8337436..53d2d0de08 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1184,8 +1184,8 @@ impl ProjectPanel { path: new_entry.path.clone() }; - project.dap_store().update(cx, |dap_store, _| { - dap_store.on_file_rename(old_path, new_path); + project.breakpoint_store().update(cx, |breakpoint_store, cx| { + breakpoint_store.on_file_rename(old_path, new_path, cx); }); }); diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 2c84862c24..a320ad0748 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -9,7 +9,7 @@ use language::{proto::serialize_operation, Buffer, BufferEvent, LanguageRegistry use node_runtime::NodeRuntime; use project::{ buffer_store::{BufferStore, BufferStoreEvent}, - debugger::dap_store::DapStore, + debugger::breakpoint_store::BreakpointStore, git::GitStore, project_settings::SettingsObserver, search::SearchQuery, @@ -93,6 +93,8 @@ impl HeadlessProject { ) }); + let breakpoint_store = cx.new(|_| BreakpointStore::local()); + let dap_store = cx.new(|cx| { DapStore::new_local( http_client.clone(), @@ -101,13 +103,14 @@ impl HeadlessProject { languages.clone(), environment.clone(), toolchain_store.read(cx).as_language_toolchain_store(), + breakpoint_store.clone(), cx, ) }); let buffer_store = cx.new(|cx| { let mut buffer_store = - BufferStore::local(worktree_store.clone(), dap_store.clone(), cx); + BufferStore::local(worktree_store.clone(), breakpoint_store.clone(), cx); buffer_store.shared(SSH_PROJECT_ID, session.clone().into(), cx); buffer_store }); diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 0fadc801d8..575ff40beb 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -12,7 +12,7 @@ use client::DevServerProjectId; use collections::HashMap; use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql}; use gpui::{point, size, Axis, Bounds, WindowBounds, WindowId}; -use project::debugger::dap_store::{BreakpointKind, SerializedBreakpoint}; +use project::debugger::breakpoint_store::{BreakpointKind, SerializedBreakpoint}; use language::{LanguageName, Toolchain}; use project::WorktreeId; diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index 97005e5532..2847bfea19 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -11,7 +11,7 @@ use db::sqlez::{ }; use gpui::{AsyncWindowContext, Entity, WeakEntity}; use itertools::Itertools as _; -use project::debugger::dap_store::SerializedBreakpoint; +use project::debugger::breakpoint_store::SerializedBreakpoint; use project::Project; use remote::ssh_session::SshProjectId; use serde::{Deserialize, Serialize}; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 92a5f17f2d..053e9436e9 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4702,17 +4702,24 @@ impl Workspace { // Add unopened breakpoints to project before opening any items workspace.update(&mut cx, |workspace, cx| { workspace.project().update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - for worktree in project.worktrees(cx) { - let (worktree_id, worktree_path) = - worktree.read_with(cx, |tree, _cx| (tree.id(), tree.abs_path())); + project.dap_store().update(cx, |dap_store, cx| { + dap_store + .breakpoint_store() + .update(cx, |breakpoint_store, cx| { + for worktree in project.worktrees(cx) { + let (worktree_id, worktree_path) = worktree + .read_with(cx, |tree, _cx| (tree.id(), tree.abs_path())); - if let Some(serialized_breakpoints) = - serialized_workspace.breakpoints.remove(&worktree_path) - { - store.deserialize_breakpoints(worktree_id, serialized_breakpoints); - } - } + if let Some(serialized_breakpoints) = + serialized_workspace.breakpoints.remove(&worktree_path) + { + breakpoint_store.deserialize_breakpoints( + worktree_id, + serialized_breakpoints, + ); + } + } + }) }); }) })?;