Allow enabling/disabling breakpoints (#27280)

This PR adds the ability to enable/disable breakpoints. It also fixes a
bug where toggling a log breakpoint from the breakpoint context menu
would add a standard breakpoint on top of the log breakpoint instead of
deleting it.

todo: 
- [x] Add `BreakpointState` field Breakpoint that manages if a
breakpoint is active or not
- [x] Don't send disabled breakpoints to DAP servers - in progress
- [x] Half the opacity of disabled breakpoints - in progress
- [x] Add `BreakpointState` to database
- [x] Editor test for enabling/disabling breakpoints
- [ ] Integration Test to make sure we don't send disabled breakpoints
to DAP servers
- [x] Database test to make sure we properly serialize/deserialize
BreakpointState

Release Notes:

- N/A

---------

Co-authored-by: Piotr <piotr@zed.dev>
Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
This commit is contained in:
Anthony Eid 2025-03-26 02:06:08 -04:00 committed by GitHub
parent df583d73b9
commit d70ac64fe4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 583 additions and 172 deletions

View file

@ -256,6 +256,21 @@ impl BreakpointStore {
breakpoint_set.breakpoints.push(breakpoint.clone());
}
}
BreakpointEditAction::InvertState => {
if let Some((_, bp)) = breakpoint_set
.breakpoints
.iter_mut()
.find(|value| breakpoint == **value)
{
if bp.is_enabled() {
bp.state = BreakpointState::Disabled;
} else {
bp.state = BreakpointState::Enabled;
}
} else {
log::error!("Attempted to invert a breakpoint's state that doesn't exist ");
}
}
BreakpointEditAction::EditLogMessage(log_message) => {
if !log_message.is_empty() {
breakpoint.1.kind = BreakpointKind::Log(log_message.clone());
@ -351,7 +366,7 @@ impl BreakpointStore {
&'a self,
buffer: &'a Entity<Buffer>,
range: Option<Range<text::Anchor>>,
buffer_snapshot: BufferSnapshot,
buffer_snapshot: &'a BufferSnapshot,
cx: &App,
) -> impl Iterator<Item = &'a (text::Anchor, Breakpoint)> + 'a {
let abs_path = Self::abs_path_from_buffer(buffer, cx);
@ -361,11 +376,10 @@ impl BreakpointStore {
.flat_map(move |file_breakpoints| {
file_breakpoints.breakpoints.iter().filter({
let range = range.clone();
let buffer_snapshot = buffer_snapshot.clone();
move |(position, _)| {
if let Some(range) = &range {
position.cmp(&range.start, &buffer_snapshot).is_ge()
&& position.cmp(&range.end, &buffer_snapshot).is_le()
position.cmp(&range.start, buffer_snapshot).is_ge()
&& position.cmp(&range.end, buffer_snapshot).is_le()
} else {
true
}
@ -417,6 +431,7 @@ impl BreakpointStore {
position,
path: path.clone(),
kind: breakpoint.kind.clone(),
state: breakpoint.state,
}
})
.collect()
@ -439,6 +454,7 @@ impl BreakpointStore {
position,
path: path.clone(),
kind: breakpoint.kind.clone(),
state: breakpoint.state,
}
})
.collect(),
@ -487,9 +503,13 @@ impl BreakpointStore {
for bp in bps {
let position = snapshot.anchor_before(PointUtf16::new(bp.position, 0));
breakpoints_for_file
.breakpoints
.push((position, Breakpoint { kind: bp.kind }))
breakpoints_for_file.breakpoints.push((
position,
Breakpoint {
kind: bp.kind,
state: bp.state,
},
))
}
new_breakpoints.insert(path, breakpoints_for_file);
}
@ -530,6 +550,7 @@ type LogMessage = Arc<str>;
#[derive(Clone, Debug)]
pub enum BreakpointEditAction {
Toggle,
InvertState,
EditLogMessage(LogMessage),
}
@ -569,16 +590,60 @@ impl Hash for BreakpointKind {
}
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum BreakpointState {
Enabled,
Disabled,
}
impl BreakpointState {
#[inline]
pub fn is_enabled(&self) -> bool {
matches!(self, BreakpointState::Enabled)
}
#[inline]
pub fn is_disabled(&self) -> bool {
matches!(self, BreakpointState::Disabled)
}
#[inline]
pub fn to_int(&self) -> i32 {
match self {
BreakpointState::Enabled => 0,
BreakpointState::Disabled => 1,
}
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Breakpoint {
pub kind: BreakpointKind,
pub state: BreakpointState,
}
impl Breakpoint {
pub fn new_standard() -> Self {
Self {
kind: BreakpointKind::Standard,
state: BreakpointState::Enabled,
}
}
pub fn new_log(log_message: &str) -> Self {
Self {
kind: BreakpointKind::Log(log_message.to_owned().into()),
state: BreakpointState::Enabled,
}
}
fn to_proto(&self, _path: &Path, position: &text::Anchor) -> Option<client::proto::Breakpoint> {
Some(client::proto::Breakpoint {
position: Some(serialize_text_anchor(position)),
state: match self.state {
BreakpointState::Enabled => proto::BreakpointState::Enabled.into(),
BreakpointState::Disabled => proto::BreakpointState::Disabled.into(),
},
kind: match self.kind {
BreakpointKind::Standard => proto::BreakpointKind::Standard.into(),
BreakpointKind::Log(_) => proto::BreakpointKind::Log.into(),
@ -599,8 +664,22 @@ impl Breakpoint {
}
None | Some(proto::BreakpointKind::Standard) => BreakpointKind::Standard,
},
state: match proto::BreakpointState::from_i32(breakpoint.state) {
Some(proto::BreakpointState::Disabled) => BreakpointState::Disabled,
None | Some(proto::BreakpointState::Enabled) => BreakpointState::Enabled,
},
})
}
#[inline]
pub fn is_enabled(&self) -> bool {
self.state.is_enabled()
}
#[inline]
pub fn is_disabled(&self) -> bool {
self.state.is_disabled()
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
@ -608,6 +687,7 @@ pub struct SerializedBreakpoint {
pub position: u32,
pub path: Arc<Path>,
pub kind: BreakpointKind,
pub state: BreakpointState,
}
impl From<SerializedBreakpoint> for dap::SourceBreakpoint {

View file

@ -358,6 +358,7 @@ impl LocalMode {
.breakpoint_store
.read_with(cx, |store, cx| store.breakpoints_from_path(&abs_path, cx))
.into_iter()
.filter(|bp| bp.state.is_enabled())
.map(Into::into)
.collect();
@ -388,7 +389,11 @@ impl LocalMode {
let breakpoints = if ignore_breakpoints {
vec![]
} else {
breakpoints.into_iter().map(Into::into).collect()
breakpoints
.into_iter()
.filter(|bp| bp.state.is_enabled())
.map(Into::into)
.collect()
};
breakpoint_tasks.push(self.request(