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!(