diff --git a/assets/icons/debug_ignore_breakpoints.svg b/assets/icons/debug_ignore_breakpoints.svg
new file mode 100644
index 0000000000..ba7074e083
--- /dev/null
+++ b/assets/icons/debug_ignore_breakpoints.svg
@@ -0,0 +1 @@
+
diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs
index 47014ccfe8..121f56dfdf 100644
--- a/crates/debugger_ui/src/debugger_panel.rs
+++ b/crates/debugger_ui/src/debugger_panel.rs
@@ -32,6 +32,7 @@ use workspace::{
};
use workspace::{
pane, Continue, Disconnect, Pane, Pause, Restart, Start, StepInto, StepOut, StepOver, Stop,
+ ToggleIgnoreBreakpoints,
};
pub enum DebugPanelEvent {
@@ -174,6 +175,7 @@ impl DebugPanel {
TypeId::of::(),
TypeId::of::(),
TypeId::of::(),
+ TypeId::of::(),
];
if has_active_session {
diff --git a/crates/debugger_ui/src/debugger_panel_item.rs b/crates/debugger_ui/src/debugger_panel_item.rs
index 22aa393aa3..62168d1f23 100644
--- a/crates/debugger_ui/src/debugger_panel_item.rs
+++ b/crates/debugger_ui/src/debugger_panel_item.rs
@@ -477,6 +477,18 @@ impl DebugPanelItem {
.detach_and_log_err(cx);
});
}
+
+ pub fn toggle_ignore_breakpoints(&mut self, cx: &mut ViewContext) {
+ self.workspace
+ .update(cx, |workspace, cx| {
+ workspace.project().update(cx, |project, cx| {
+ project
+ .toggle_ignore_breakpoints(&self.client_id, cx)
+ .detach_and_log_err(cx);
+ })
+ })
+ .ok();
+ }
}
impl EventEmitter for DebugPanelItem {}
@@ -633,6 +645,25 @@ impl Render for DebugPanelItem {
|| thread_status == ThreadStatus::Ended,
)
.tooltip(move |cx| Tooltip::text("Disconnect", cx)),
+ )
+ .child(
+ IconButton::new(
+ "debug-ignore-breakpoints",
+ if self.dap_store.read(cx).ignore_breakpoints(&self.client_id) {
+ IconName::DebugIgnoreBreakpoints
+ } else {
+ IconName::DebugBreakpoint
+ },
+ )
+ .icon_size(IconSize::Small)
+ .on_click(cx.listener(|this, _, cx| {
+ this.toggle_ignore_breakpoints(cx);
+ }))
+ .disabled(
+ thread_status == ThreadStatus::Exited
+ || thread_status == ThreadStatus::Ended,
+ )
+ .tooltip(move |cx| Tooltip::text("Ignore breakpoints", cx)),
),
)
.child(
diff --git a/crates/debugger_ui/src/lib.rs b/crates/debugger_ui/src/lib.rs
index f9dbe505f1..7e05878aeb 100644
--- a/crates/debugger_ui/src/lib.rs
+++ b/crates/debugger_ui/src/lib.rs
@@ -5,7 +5,7 @@ use settings::Settings;
use ui::ViewContext;
use workspace::{
Continue, Pause, Restart, ShutdownDebugAdapters, Start, StepInto, StepOut, StepOver, Stop,
- Workspace,
+ ToggleIgnoreBreakpoints, Workspace,
};
mod attach_modal;
@@ -102,6 +102,19 @@ pub fn init(cx: &mut AppContext) {
active_item.update(cx, |item, cx| item.restart_client(cx))
});
})
+ .register_action(
+ |workspace: &mut Workspace, _: &ToggleIgnoreBreakpoints, cx| {
+ let debug_panel = workspace.panel::(cx).unwrap();
+
+ debug_panel.update(cx, |panel, cx| {
+ let Some(active_item) = panel.active_debug_panel_item(cx) else {
+ return;
+ };
+
+ active_item.update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))
+ });
+ },
+ )
.register_action(|workspace: &mut Workspace, _: &Pause, cx| {
let debug_panel = workspace.panel::(cx).unwrap();
diff --git a/crates/project/src/dap_store.rs b/crates/project/src/dap_store.rs
index bd7d24de41..53d0d57dc0 100644
--- a/crates/project/src/dap_store.rs
+++ b/crates/project/src/dap_store.rs
@@ -73,6 +73,7 @@ pub struct DebugPosition {
pub struct DapStore {
next_client_id: AtomicUsize,
delegate: DapAdapterDelegate,
+ ignore_breakpoints: HashSet,
breakpoints: BTreeMap>,
active_debug_line: Option<(ProjectPath, DebugPosition)>,
capabilities: HashMap,
@@ -103,6 +104,7 @@ impl DapStore {
breakpoints: Default::default(),
capabilities: HashMap::default(),
next_client_id: Default::default(),
+ ignore_breakpoints: Default::default(),
delegate: DapAdapterDelegate::new(
http_client.clone(),
node_runtime.clone(),
@@ -192,6 +194,16 @@ impl DapStore {
&self.breakpoints
}
+ pub fn ignore_breakpoints(&self, client_id: &DebugAdapterClientId) -> bool {
+ self.ignore_breakpoints.contains(client_id)
+ }
+
+ pub fn toggle_ignore_breakpoints(&mut self, client_id: &DebugAdapterClientId) {
+ if !self.ignore_breakpoints.remove(client_id) {
+ self.ignore_breakpoints.insert(*client_id);
+ }
+ }
+
pub fn breakpoint_at_row(
&self,
row: u32,
@@ -1024,6 +1036,7 @@ impl DapStore {
cx.emit(DapStoreEvent::DebugClientStopped(*client_id));
+ self.ignore_breakpoints.remove(client_id);
let capabilities = self.capabilities.remove(client_id);
cx.background_executor().spawn(async move {
@@ -1059,7 +1072,7 @@ impl DapStore {
buffer_snapshot: BufferSnapshot,
edit_action: BreakpointEditAction,
cx: &mut ModelContext,
- ) {
+ ) -> Task> {
let breakpoint_set = self.breakpoints.entry(project_path.clone()).or_default();
match edit_action {
@@ -1077,7 +1090,6 @@ impl DapStore {
cx.notify();
self.send_changed_breakpoints(project_path, buffer_path, buffer_snapshot, cx)
- .detach();
}
pub fn send_breakpoints(
@@ -1085,6 +1097,7 @@ impl DapStore {
client_id: &DebugAdapterClientId,
absolute_file_path: Arc,
mut breakpoints: Vec,
+ ignore: bool,
cx: &mut ModelContext,
) -> Task> {
let Some(client) = self.client_by_id(client_id) else {
@@ -1100,7 +1113,9 @@ impl DapStore {
.request::(SetBreakpointsArguments {
source: Source {
path: Some(String::from(absolute_file_path.to_string_lossy())),
- name: None,
+ name: absolute_file_path
+ .file_name()
+ .map(|name| name.to_string_lossy().to_string()),
source_reference: None,
presentation_hint: None,
origin: None,
@@ -1108,8 +1123,8 @@ impl DapStore {
adapter_data: None,
checksums: None,
},
- breakpoints: Some(breakpoints),
- source_modified: None,
+ breakpoints: Some(if ignore { Vec::default() } else { breakpoints }),
+ source_modified: Some(false),
lines: None,
})
.await?;
@@ -1124,15 +1139,15 @@ impl DapStore {
buffer_path: PathBuf,
buffer_snapshot: BufferSnapshot,
cx: &mut ModelContext,
- ) -> Task<()> {
+ ) -> Task> {
let clients = self.running_clients().collect::>();
if clients.is_empty() {
- return Task::ready(());
+ return Task::ready(Ok(()));
}
let Some(breakpoints) = self.breakpoints.get(project_path) else {
- return Task::ready(());
+ return Task::ready(Ok(()));
};
let source_breakpoints = breakpoints
@@ -1146,12 +1161,15 @@ impl DapStore {
&client.id(),
Arc::from(buffer_path.clone()),
source_breakpoints.clone(),
+ self.ignore_breakpoints(&client.id()),
cx,
))
}
cx.background_executor().spawn(async move {
- futures::future::join_all(tasks).await;
+ futures::future::try_join_all(tasks).await?;
+
+ Ok(())
})
}
}
diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs
index a6e7794f90..5a4547377f 100644
--- a/crates/project/src/project.rs
+++ b/crates/project/src/project.rs
@@ -1243,7 +1243,13 @@ impl Project {
.collect::>();
tasks.push(self.dap_store.update(cx, |store, cx| {
- store.send_breakpoints(client_id, abs_path, source_breakpoints, cx)
+ store.send_breakpoints(
+ client_id,
+ abs_path,
+ source_breakpoints,
+ store.ignore_breakpoints(client_id),
+ cx,
+ )
}));
}
@@ -1337,6 +1343,57 @@ impl Project {
result
}
+ pub fn toggle_ignore_breakpoints(
+ &self,
+ client_id: &DebugAdapterClientId,
+ cx: &mut ModelContext,
+ ) -> Task> {
+ let tasks = self.dap_store.update(cx, |store, cx| {
+ store.toggle_ignore_breakpoints(client_id);
+
+ let mut tasks = Vec::new();
+
+ for (project_path, breakpoints) in store.breakpoints() {
+ let Some((buffer, buffer_path)) = maybe!({
+ let buffer = self
+ .buffer_store
+ .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)?;
+ Some((
+ buffer,
+ worktree.read(cx).absolutize(&project_path.path).ok()?,
+ ))
+ }) else {
+ continue;
+ };
+
+ tasks.push(
+ store.send_breakpoints(
+ client_id,
+ Arc::from(buffer_path),
+ breakpoints
+ .into_iter()
+ .map(|breakpoint| breakpoint.to_source_breakpoint(buffer))
+ .collect::>(),
+ store.ignore_breakpoints(client_id),
+ cx,
+ ),
+ );
+ }
+
+ tasks
+ });
+
+ cx.background_executor().spawn(async move {
+ try_join_all(tasks).await?;
+
+ Ok(())
+ })
+ }
+
/// Sends updated breakpoint information of one file to all active debug adapters
///
/// This function is called whenever a breakpoint is toggled, and it doesn't need
@@ -1365,14 +1422,16 @@ impl Project {
};
self.dap_store.update(cx, |store, cx| {
- store.toggle_breakpoint_for_buffer(
- &project_path,
- breakpoint,
- buffer_path,
- buffer.read(cx).snapshot(),
- edit_action,
- cx,
- );
+ store
+ .toggle_breakpoint_for_buffer(
+ &project_path,
+ breakpoint,
+ buffer_path,
+ buffer.read(cx).snapshot(),
+ edit_action,
+ cx,
+ )
+ .detach_and_log_err(cx);
});
}
diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs
index d8be1c27f9..0b89970534 100644
--- a/crates/ui/src/components/icon.rs
+++ b/crates/ui/src/components/icon.rs
@@ -173,6 +173,7 @@ pub enum IconName {
TextSnippet,
Dash,
DebugBreakpoint,
+ DebugIgnoreBreakpoints,
DebugPause,
DebugContinue,
DebugStepOver,
diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs
index 0d277fff05..fbded0f7c4 100644
--- a/crates/workspace/src/workspace.rs
+++ b/crates/workspace/src/workspace.rs
@@ -130,7 +130,18 @@ actions!(assistant, [ShowConfiguration]);
actions!(
debugger,
- [Start, Continue, Disconnect, Pause, Restart, StepInto, StepOver, StepOut, Stop]
+ [
+ Start,
+ Continue,
+ Disconnect,
+ Pause,
+ Restart,
+ StepInto,
+ StepOver,
+ StepOut,
+ Stop,
+ ToggleIgnoreBreakpoints
+ ]
);
actions!(