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:
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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)
|
||||
})?
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod breakpoint_store;
|
||||
pub mod dap_command;
|
||||
pub mod dap_session;
|
||||
pub mod dap_store;
|
||||
|
||||
548
crates/project/src/debugger/breakpoint_store.rs
Normal file
548
crates/project/src/debugger/breakpoint_store.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
})
|
||||
})?;
|
||||
|
||||
Reference in New Issue
Block a user