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 <djsmits12@gmail.com>
This commit is contained in:
Anthony Eid
2025-02-14 07:55:03 -05:00
committed by GitHub
parent ecfc0ef12d
commit 91e60d79bb
14 changed files with 742 additions and 544 deletions

View File

@@ -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::<Point>(&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,
});
});
});
}

View File

@@ -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};

View File

@@ -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<ProjectPath, BufferId>,
local_buffer_ids_by_entry_id: HashMap<ProjectEntryId, BufferId>,
dap_store: Entity<DapStore>,
breakpoint_store: Entity<BreakpointStore>,
worktree_store: Entity<WorktreeStore>,
_subscription: Subscription,
}
@@ -1242,14 +1242,14 @@ impl BufferStore {
/// Creates a buffer store, optionally retaining its buffers.
pub fn local(
worktree_store: Entity<WorktreeStore>,
dap_store: Entity<DapStore>,
breakpoint_store: Entity<BreakpointStore>,
cx: &mut Context<Self>,
) -> 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<Buffer>,
cx: &mut Context<Self>,
) {
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<Self>,
) -> Task<Result<Entity<Buffer>>> {
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)
})?
})

View File

@@ -1,3 +1,4 @@
pub mod breakpoint_store;
pub mod dap_command;
pub mod dap_session;
pub mod dap_store;

View File

@@ -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<AnyProtoClient>,
upstream_project_id: u64,
}
enum BreakpointMode {
Local,
Remote(RemoteBreakpointStore),
}
pub struct BreakpointStore {
pub breakpoints: BTreeMap<ProjectPath, HashSet<Breakpoint>>,
downstream_client: Option<(AnyProtoClient, u64)>,
mode: BreakpointMode,
}
pub enum BreakpointStoreEvent {
BreakpointsChanged {
project_path: ProjectPath,
source_changed: bool,
},
}
impl EventEmitter<BreakpointStoreEvent> 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>) {
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<proto::SynchronizeBreakpoints>,
cx: &mut Context<Self>,
) {
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::<HashSet<_>>(),
);
}
std::mem::swap(&mut self.breakpoints, &mut new_breakpoints);
cx.notify();
}
pub fn on_open_buffer(
&mut self,
project_path: &ProjectPath,
buffer: &Entity<Buffer>,
cx: &mut Context<Self>,
) {
let entry = self.breakpoints.remove(project_path).unwrap_or_default();
let mut set_bp: HashSet<Breakpoint> = 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<Self>,
) {
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<Buffer>,
cx: &mut Context<Self>,
) {
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::<HashSet<_>>(),
);
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<Breakpoint> {
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<Self>,
) {
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<SerializedBreakpoint>,
) {
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<Self>,
envelope: TypedEnvelope<proto::SynchronizeBreakpoints>,
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::<HashSet<_>>();
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<str>;
#[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<LogMessage> {
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<H: Hasher>(&self, state: &mut H) {
std::mem::discriminant(self).hash(state);
}
}
#[derive(Clone, Debug)]
pub struct Breakpoint {
pub active_position: Option<text::Anchor>,
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<H: Hasher>(&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::<Point>(&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::<Point>(&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::<Point>(&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::<Point>(&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<Path>) -> SerializedBreakpoint {
match buffer {
Some(buffer) => SerializedBreakpoint {
position: self
.active_position
.map(|position| buffer.summary_for_anchor::<Point>(&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<client::proto::Breakpoint> {
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<Self> {
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<Path>,
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,
}
}
}

View File

@@ -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,
);

View File

@@ -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<ProjectPath, HashSet<Breakpoint>>,
breakpoint_store: Entity<BreakpointStore>,
active_debug_line: Option<(DebugAdapterClientId, ProjectPath, u32)>,
sessions: HashMap<DebugSessionId, Entity<DebugSession>>,
client_by_session: HashMap<DebugAdapterClientId, DebugSessionId>,
@@ -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<LanguageRegistry>,
environment: Entity<ProjectEnvironment>,
toolchain_store: Arc<dyn LanguageToolchainStore>,
breakpoint_store: Entity<BreakpointStore>,
cx: &mut Context<Self>,
) -> 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<BreakpointStore>,
) -> 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<DebugAdapterClientId>,
cx: &Context<Self>,
cx: &App,
) -> Option<(Entity<DebugSession>, Entity<dap_session::Client>)> {
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<ProjectPath, HashSet<Breakpoint>> {
&self.breakpoints
pub fn breakpoint_store(&self) -> &Entity<BreakpointStore> {
&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<Breakpoint> {
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<Buffer>,
cx: &mut Context<Self>,
) {
let entry = self.breakpoints.remove(project_path).unwrap_or_default();
let mut set_bp: HashSet<Breakpoint> = 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<SerializedBreakpoint>,
) {
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<Buffer>,
cx: &mut Context<Self>,
) {
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::<HashSet<_>>());
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<proto::SynchronizeBreakpoints>,
cx: &mut Context<Self>,
) {
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::<HashSet<_>>(),
);
}
std::mem::swap(&mut self.breakpoints, &mut new_breakpoints);
cx.notify();
}
async fn handle_shutdown_session_request(
this: Entity<Self>,
envelope: TypedEnvelope<proto::DapShutdownSession>,
@@ -1407,41 +1295,6 @@ impl DapStore {
// Ok(T::response_to_proto(&client_id, response))
// }
async fn handle_synchronize_breakpoints(
this: Entity<Self>,
envelope: TypedEnvelope<proto::SynchronizeBreakpoints>,
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::<HashSet<_>>();
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<Self>,
envelope: TypedEnvelope<proto::SetDebuggerPanelItem>,
@@ -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<Self>,
) {
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<SourceBreakpoint>,
ignore: bool,
source_changed: bool,
cx: &Context<Self>,
cx: &App,
) -> Task<Result<()>> {
let Some(client) = self
.client_by_id(client_id, cx)
@@ -1646,15 +1447,17 @@ impl DapStore {
absolute_path: PathBuf,
buffer_snapshot: Option<BufferSnapshot>,
source_changed: bool,
cx: &Context<Self>,
cx: &App,
) -> Task<Result<()>> {
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::<Vec<_>>();
let mut tasks = Vec::new();
@@ -1694,19 +1497,6 @@ impl DapStore {
_: &mut Context<Self>,
) {
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>) {
@@ -1716,230 +1506,6 @@ impl DapStore {
}
}
type LogMessage = Arc<str>;
#[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<LogMessage> {
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<H: Hasher>(&self, state: &mut H) {
std::mem::discriminant(self).hash(state);
}
}
#[derive(Clone, Debug)]
pub struct Breakpoint {
pub active_position: Option<text::Anchor>,
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<H: Hasher>(&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::<Point>(&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::<Point>(&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::<Point>(&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::<Point>(&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<Path>) -> SerializedBreakpoint {
match buffer {
Some(buffer) => SerializedBreakpoint {
position: self
.active_position
.map(|position| buffer.summary_for_anchor::<Point>(&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<client::proto::Breakpoint> {
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<Self> {
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<Path>,
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<dyn Fs>,

View File

@@ -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);
});
});
}

View File

@@ -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<BufferOrderedMessage>,
languages: Arc<LanguageRegistry>,
dap_store: Entity<DapStore>,
breakpoint_store: Entity<BreakpointStore>,
client: Arc<client::Client>,
join_project_response_message_id: u32,
task_store: Entity<TaskStore>,
@@ -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<Arc<Path>, Vec<SerializedBreakpoint>> {
let mut all_breakpoints: HashMap<Arc<Path>, Vec<SerializedBreakpoint>> = 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<BreakpointStore> {
self.breakpoint_store.clone()
}
pub fn lsp_store(&self) -> Entity<LspStore> {
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<BreakpointStore>,
event: &BreakpointStoreEvent,
cx: &mut Context<Self>,
) {
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<DapStore>,
@@ -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);
}

View File

@@ -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);
});
});

View File

@@ -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
});

View File

@@ -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;

View File

@@ -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};

View File

@@ -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,
);
}
}
})
});
})
})?;