Allow users to toggle ignore breakpoints (#62)

* Add allow users to ignore breakpoints

* Add different colors for both states

* Add source name for breakpoints

* Move ignore breakpoints to dap store

* Change icon instead of color

* Add action for ignore breakpoints for a client

* Remove spacing

* Fix compile error

* Return task instead of detaching itself
This commit is contained in:
Remco Smits
2024-11-10 18:20:16 +01:00
committed by GitHub
parent 055ffc17cd
commit c19c86085b
8 changed files with 156 additions and 20 deletions

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-off"><path d="m2 2 20 20"/><path d="M8.35 2.69A10 10 0 0 1 21.3 15.65"/><path d="M19.08 19.08A10 10 0 1 1 4.92 4.92"/></svg>

After

Width:  |  Height:  |  Size: 334 B

View File

@@ -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::<Disconnect>(),
TypeId::of::<Pause>(),
TypeId::of::<Restart>(),
TypeId::of::<ToggleIgnoreBreakpoints>(),
];
if has_active_session {

View File

@@ -477,6 +477,18 @@ impl DebugPanelItem {
.detach_and_log_err(cx);
});
}
pub fn toggle_ignore_breakpoints(&mut self, cx: &mut ViewContext<Self>) {
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<DebugPanelItemEvent> 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(

View File

@@ -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::<DebugPanel>(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::<DebugPanel>(cx).unwrap();

View File

@@ -73,6 +73,7 @@ pub struct DebugPosition {
pub struct DapStore {
next_client_id: AtomicUsize,
delegate: DapAdapterDelegate,
ignore_breakpoints: HashSet<DebugAdapterClientId>,
breakpoints: BTreeMap<ProjectPath, HashSet<Breakpoint>>,
active_debug_line: Option<(ProjectPath, DebugPosition)>,
capabilities: HashMap<DebugAdapterClientId, Capabilities>,
@@ -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<Self>,
) {
) -> Task<Result<()>> {
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<Path>,
mut breakpoints: Vec<SourceBreakpoint>,
ignore: bool,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let Some(client) = self.client_by_id(client_id) else {
@@ -1100,7 +1113,9 @@ impl DapStore {
.request::<SetBreakpoints>(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<Self>,
) -> Task<()> {
) -> Task<Result<()>> {
let clients = self.running_clients().collect::<Vec<_>>();
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(())
})
}
}

View File

@@ -1243,7 +1243,13 @@ impl Project {
.collect::<Vec<_>>();
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<Self>,
) -> Task<Result<()>> {
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::<Vec<_>>(),
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);
});
}

View File

@@ -173,6 +173,7 @@ pub enum IconName {
TextSnippet,
Dash,
DebugBreakpoint,
DebugIgnoreBreakpoints,
DebugPause,
DebugContinue,
DebugStepOver,

View File

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