debugger: Surface validity of breakpoints (#30380)

We now show on the breakpoint itself whether it can ever be hit.

![image](https://github.com/user-attachments/assets/148d7712-53c9-4a0a-9fc0-4ff80dec5fb1)

Release Notes:

- N/A

---------

Signed-off-by: Umesh Yadav <git@umesh.dev>
Co-authored-by: Anthony <anthony@zed.dev>
Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Michael Sloan <michael@zed.dev>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
Co-authored-by: Ben Kunkle <ben@zed.dev>
Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
Co-authored-by: Agus Zubiaga <hi@aguz.me>
Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
Co-authored-by: Agus Zubiaga <agus@zed.dev>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: Richard Feldman <oss@rtfeldman.com>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
Co-authored-by: peppidesu <bakker.pepijn@gmail.com>
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
Co-authored-by: Ben Kunkle <ben.kunkle@gmail.com>
Co-authored-by: Jens Krause <47693+sectore@users.noreply.github.com>
Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
Co-authored-by: Max Nordlund <max.nordlund@gmail.com>
Co-authored-by: Finn Evers <dev@bahn.sh>
Co-authored-by: tidely <43219534+tidely@users.noreply.github.com>
Co-authored-by: Sergei Kartsev <kartsevsb@gmail.com>
Co-authored-by: Shardul Vaidya <31039336+5herlocked@users.noreply.github.com>
Co-authored-by: Chris Kelly <amateurhuman@gmail.com>
Co-authored-by: Peter Tripp <peter@zed.dev>
Co-authored-by: Umesh Yadav <23421535+imumesh18@users.noreply.github.com>
Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
Co-authored-by: Cole Miller <m@cole-miller.net>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: william341 <wwokwilliam@gmail.com>
Co-authored-by: Liam <33645555+lj3954@users.noreply.github.com>
Co-authored-by: AidanV <aidanvanduyne@gmail.com>
Co-authored-by: imumesh18 <umesh4257@gmail.com>
Co-authored-by: d1y <chenhonzhou@gmail.com>
Co-authored-by: AidanV <84053180+AidanV@users.noreply.github.com>
Co-authored-by: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com>
Co-authored-by: 张小白 <364772080@qq.com>
Co-authored-by: THELOSTSOUL <1095533751@qq.com>
Co-authored-by: Ron Harel <55725807+ronharel02@users.noreply.github.com>
Co-authored-by: Tristan Hume <tristan@anthropic.com>
Co-authored-by: Stanislav Alekseev <43210583+WeetHet@users.noreply.github.com>
Co-authored-by: Joseph T. Lyons <JosephTLyons@gmail.com>
Co-authored-by: Remco Smits <djsmits12@gmail.com>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Oleksiy Syvokon <oleksiy@zed.dev>
Co-authored-by: Thomas David Baker <bakert@gmail.com>
Co-authored-by: Nate Butler <iamnbutler@gmail.com>
Co-authored-by: Mikayla Maki <mikayla@zed.dev>
Co-authored-by: Rob McBroom <github@skurfer.com>
Co-authored-by: CharlesChen0823 <yongchen0823@gmail.com>
This commit is contained in:
Piotr Osiewicz 2025-05-20 17:56:15 +02:00 committed by GitHub
parent 36ae564b61
commit 17cf04558b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 439 additions and 237 deletions

View file

@ -2,8 +2,9 @@
//!
//! Breakpoints are separate from a session because they're not associated with any particular debug session. They can also be set up without a session running.
use anyhow::{Result, anyhow};
use breakpoints_in_file::BreakpointsInFile;
use collections::BTreeMap;
pub use breakpoints_in_file::{BreakpointSessionState, BreakpointWithPosition};
use breakpoints_in_file::{BreakpointsInFile, StatefulBreakpoint};
use collections::{BTreeMap, HashMap};
use dap::{StackFrameId, client::SessionId};
use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, Subscription, Task};
use itertools::Itertools;
@ -14,21 +15,54 @@ use rpc::{
};
use std::{hash::Hash, ops::Range, path::Path, sync::Arc, u32};
use text::{Point, PointUtf16};
use util::maybe;
use crate::{Project, ProjectPath, buffer_store::BufferStore, worktree_store::WorktreeStore};
use super::session::ThreadId;
mod breakpoints_in_file {
use collections::HashMap;
use language::{BufferEvent, DiskState};
use super::*;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BreakpointWithPosition {
pub position: text::Anchor,
pub bp: Breakpoint,
}
/// A breakpoint with per-session data about it's state (as seen by the Debug Adapter).
#[derive(Clone, Debug)]
pub struct StatefulBreakpoint {
pub bp: BreakpointWithPosition,
pub session_state: HashMap<SessionId, BreakpointSessionState>,
}
impl StatefulBreakpoint {
pub(super) fn new(bp: BreakpointWithPosition) -> Self {
Self {
bp,
session_state: Default::default(),
}
}
pub(super) fn position(&self) -> &text::Anchor {
&self.bp.position
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct BreakpointSessionState {
/// Session-specific identifier for the breakpoint, as assigned by Debug Adapter.
pub id: u64,
pub verified: bool,
}
#[derive(Clone)]
pub(super) struct BreakpointsInFile {
pub(super) buffer: Entity<Buffer>,
// TODO: This is.. less than ideal, as it's O(n) and does not return entries in order. We'll have to change TreeMap to support passing in the context for comparisons
pub(super) breakpoints: Vec<(text::Anchor, Breakpoint)>,
pub(super) breakpoints: Vec<StatefulBreakpoint>,
_subscription: Arc<Subscription>,
}
@ -199,9 +233,26 @@ impl BreakpointStore {
.breakpoints
.into_iter()
.filter_map(|breakpoint| {
let anchor = language::proto::deserialize_anchor(breakpoint.position.clone()?)?;
let position =
language::proto::deserialize_anchor(breakpoint.position.clone()?)?;
let session_state = breakpoint
.session_state
.iter()
.map(|(session_id, state)| {
let state = BreakpointSessionState {
id: state.id,
verified: state.verified,
};
(SessionId::from_proto(*session_id), state)
})
.collect();
let breakpoint = Breakpoint::from_proto(breakpoint)?;
Some((anchor, breakpoint))
let bp = BreakpointWithPosition {
position,
bp: breakpoint,
};
Some(StatefulBreakpoint { bp, session_state })
})
.collect();
@ -231,7 +282,7 @@ impl BreakpointStore {
.payload
.breakpoint
.ok_or_else(|| anyhow!("Breakpoint not present in RPC payload"))?;
let anchor = language::proto::deserialize_anchor(
let position = language::proto::deserialize_anchor(
breakpoint
.position
.clone()
@ -244,7 +295,10 @@ impl BreakpointStore {
breakpoints.update(&mut cx, |this, cx| {
this.toggle_breakpoint(
buffer,
(anchor, breakpoint),
BreakpointWithPosition {
position,
bp: breakpoint,
},
BreakpointEditAction::Toggle,
cx,
);
@ -261,13 +315,76 @@ impl BreakpointStore {
breakpoints: breakpoint_set
.breakpoints
.iter()
.filter_map(|(anchor, bp)| bp.to_proto(&path, anchor))
.filter_map(|breakpoint| {
breakpoint.bp.bp.to_proto(
&path,
&breakpoint.position(),
&breakpoint.session_state,
)
})
.collect(),
});
}
}
}
pub(crate) fn update_session_breakpoint(
&mut self,
session_id: SessionId,
_: dap::BreakpointEventReason,
breakpoint: dap::Breakpoint,
) {
maybe!({
let event_id = breakpoint.id?;
let state = self
.breakpoints
.values_mut()
.find_map(|breakpoints_in_file| {
breakpoints_in_file
.breakpoints
.iter_mut()
.find_map(|state| {
let state = state.session_state.get_mut(&session_id)?;
if state.id == event_id {
Some(state)
} else {
None
}
})
})?;
state.verified = breakpoint.verified;
Some(())
});
}
pub(super) fn mark_breakpoints_verified(
&mut self,
session_id: SessionId,
abs_path: &Path,
it: impl Iterator<Item = (BreakpointWithPosition, BreakpointSessionState)>,
) {
maybe!({
let breakpoints = self.breakpoints.get_mut(abs_path)?;
for (breakpoint, state) in it {
if let Some(to_update) = breakpoints
.breakpoints
.iter_mut()
.find(|bp| *bp.position() == breakpoint.position)
{
to_update
.session_state
.entry(session_id)
.insert_entry(state);
}
}
Some(())
});
}
pub fn abs_path_from_buffer(buffer: &Entity<Buffer>, cx: &App) -> Option<Arc<Path>> {
worktree::File::from_dyn(buffer.read(cx).file())
.and_then(|file| file.worktree.read(cx).absolutize(&file.path).ok())
@ -277,7 +394,7 @@ impl BreakpointStore {
pub fn toggle_breakpoint(
&mut self,
buffer: Entity<Buffer>,
mut breakpoint: (text::Anchor, Breakpoint),
mut breakpoint: BreakpointWithPosition,
edit_action: BreakpointEditAction,
cx: &mut Context<Self>,
) {
@ -295,54 +412,57 @@ impl BreakpointStore {
let len_before = breakpoint_set.breakpoints.len();
breakpoint_set
.breakpoints
.retain(|value| &breakpoint != value);
.retain(|value| breakpoint != value.bp);
if len_before == breakpoint_set.breakpoints.len() {
// We did not remove any breakpoint, hence let's toggle one.
breakpoint_set.breakpoints.push(breakpoint.clone());
breakpoint_set
.breakpoints
.push(StatefulBreakpoint::new(breakpoint.clone()));
}
}
BreakpointEditAction::InvertState => {
if let Some((_, bp)) = breakpoint_set
if let Some(bp) = breakpoint_set
.breakpoints
.iter_mut()
.find(|value| breakpoint == **value)
.find(|value| breakpoint == value.bp)
{
let bp = &mut bp.bp.bp;
if bp.is_enabled() {
bp.state = BreakpointState::Disabled;
} else {
bp.state = BreakpointState::Enabled;
}
} else {
breakpoint.1.state = BreakpointState::Disabled;
breakpoint_set.breakpoints.push(breakpoint.clone());
breakpoint.bp.state = BreakpointState::Disabled;
breakpoint_set
.breakpoints
.push(StatefulBreakpoint::new(breakpoint.clone()));
}
}
BreakpointEditAction::EditLogMessage(log_message) => {
if !log_message.is_empty() {
let found_bp =
breakpoint_set
.breakpoints
.iter_mut()
.find_map(|(other_pos, other_bp)| {
if breakpoint.0 == *other_pos {
Some(other_bp)
} else {
None
}
});
let found_bp = breakpoint_set.breakpoints.iter_mut().find_map(|bp| {
if breakpoint.position == *bp.position() {
Some(&mut bp.bp.bp)
} else {
None
}
});
if let Some(found_bp) = found_bp {
found_bp.message = Some(log_message.clone());
} else {
breakpoint.1.message = Some(log_message.clone());
breakpoint.bp.message = Some(log_message.clone());
// We did not remove any breakpoint, hence let's toggle one.
breakpoint_set.breakpoints.push(breakpoint.clone());
breakpoint_set
.breakpoints
.push(StatefulBreakpoint::new(breakpoint.clone()));
}
} else if breakpoint.1.message.is_some() {
} else if breakpoint.bp.message.is_some() {
if let Some(position) = breakpoint_set
.breakpoints
.iter()
.find_position(|(pos, bp)| &breakpoint.0 == pos && bp == &breakpoint.1)
.find_position(|other| breakpoint == other.bp)
.map(|res| res.0)
{
breakpoint_set.breakpoints.remove(position);
@ -353,30 +473,28 @@ impl BreakpointStore {
}
BreakpointEditAction::EditHitCondition(hit_condition) => {
if !hit_condition.is_empty() {
let found_bp =
breakpoint_set
.breakpoints
.iter_mut()
.find_map(|(other_pos, other_bp)| {
if breakpoint.0 == *other_pos {
Some(other_bp)
} else {
None
}
});
let found_bp = breakpoint_set.breakpoints.iter_mut().find_map(|other| {
if breakpoint.position == *other.position() {
Some(&mut other.bp.bp)
} else {
None
}
});
if let Some(found_bp) = found_bp {
found_bp.hit_condition = Some(hit_condition.clone());
} else {
breakpoint.1.hit_condition = Some(hit_condition.clone());
breakpoint.bp.hit_condition = Some(hit_condition.clone());
// We did not remove any breakpoint, hence let's toggle one.
breakpoint_set.breakpoints.push(breakpoint.clone());
breakpoint_set
.breakpoints
.push(StatefulBreakpoint::new(breakpoint.clone()))
}
} else if breakpoint.1.hit_condition.is_some() {
} else if breakpoint.bp.hit_condition.is_some() {
if let Some(position) = breakpoint_set
.breakpoints
.iter()
.find_position(|(pos, bp)| &breakpoint.0 == pos && bp == &breakpoint.1)
.find_position(|bp| breakpoint == bp.bp)
.map(|res| res.0)
{
breakpoint_set.breakpoints.remove(position);
@ -387,30 +505,28 @@ impl BreakpointStore {
}
BreakpointEditAction::EditCondition(condition) => {
if !condition.is_empty() {
let found_bp =
breakpoint_set
.breakpoints
.iter_mut()
.find_map(|(other_pos, other_bp)| {
if breakpoint.0 == *other_pos {
Some(other_bp)
} else {
None
}
});
let found_bp = breakpoint_set.breakpoints.iter_mut().find_map(|other| {
if breakpoint.position == *other.position() {
Some(&mut other.bp.bp)
} else {
None
}
});
if let Some(found_bp) = found_bp {
found_bp.condition = Some(condition.clone());
} else {
breakpoint.1.condition = Some(condition.clone());
breakpoint.bp.condition = Some(condition.clone());
// We did not remove any breakpoint, hence let's toggle one.
breakpoint_set.breakpoints.push(breakpoint.clone());
breakpoint_set
.breakpoints
.push(StatefulBreakpoint::new(breakpoint.clone()));
}
} else if breakpoint.1.condition.is_some() {
} else if breakpoint.bp.condition.is_some() {
if let Some(position) = breakpoint_set
.breakpoints
.iter()
.find_position(|(pos, bp)| &breakpoint.0 == pos && bp == &breakpoint.1)
.find_position(|bp| breakpoint == bp.bp)
.map(|res| res.0)
{
breakpoint_set.breakpoints.remove(position);
@ -425,7 +541,11 @@ impl BreakpointStore {
self.breakpoints.remove(&abs_path);
}
if let BreakpointStoreMode::Remote(remote) = &self.mode {
if let Some(breakpoint) = breakpoint.1.to_proto(&abs_path, &breakpoint.0) {
if let Some(breakpoint) =
breakpoint
.bp
.to_proto(&abs_path, &breakpoint.position, &HashMap::default())
{
cx.background_spawn(remote.upstream_client.request(proto::ToggleBreakpoint {
project_id: remote._upstream_project_id,
path: abs_path.to_str().map(ToOwned::to_owned).unwrap(),
@ -441,7 +561,11 @@ impl BreakpointStore {
breakpoint_set
.breakpoints
.iter()
.filter_map(|(anchor, bp)| bp.to_proto(&abs_path, anchor))
.filter_map(|bp| {
bp.bp
.bp
.to_proto(&abs_path, bp.position(), &bp.session_state)
})
.collect()
})
.unwrap_or_default();
@ -485,21 +609,31 @@ impl BreakpointStore {
range: Option<Range<text::Anchor>>,
buffer_snapshot: &'a BufferSnapshot,
cx: &App,
) -> impl Iterator<Item = &'a (text::Anchor, Breakpoint)> + 'a {
) -> impl Iterator<Item = (&'a BreakpointWithPosition, Option<BreakpointSessionState>)> + 'a
{
let abs_path = Self::abs_path_from_buffer(buffer, cx);
let active_session_id = self
.active_stack_frame
.as_ref()
.map(|frame| frame.session_id);
abs_path
.and_then(|path| self.breakpoints.get(&path))
.into_iter()
.flat_map(move |file_breakpoints| {
file_breakpoints.breakpoints.iter().filter({
file_breakpoints.breakpoints.iter().filter_map({
let range = range.clone();
move |(position, _)| {
move |bp| {
if let Some(range) = &range {
position.cmp(&range.start, buffer_snapshot).is_ge()
&& position.cmp(&range.end, buffer_snapshot).is_le()
} else {
true
if bp.position().cmp(&range.start, buffer_snapshot).is_lt()
|| bp.position().cmp(&range.end, buffer_snapshot).is_gt()
{
return None;
}
}
let session_state = active_session_id
.and_then(|id| bp.session_state.get(&id))
.copied();
Some((&bp.bp, session_state))
}
})
})
@ -549,34 +683,46 @@ impl BreakpointStore {
path: &Path,
row: u32,
cx: &App,
) -> Option<(Entity<Buffer>, (text::Anchor, Breakpoint))> {
) -> Option<(Entity<Buffer>, BreakpointWithPosition)> {
self.breakpoints.get(path).and_then(|breakpoints| {
let snapshot = breakpoints.buffer.read(cx).text_snapshot();
breakpoints
.breakpoints
.iter()
.find(|(anchor, _)| anchor.summary::<Point>(&snapshot).row == row)
.map(|breakpoint| (breakpoints.buffer.clone(), breakpoint.clone()))
.find(|bp| bp.position().summary::<Point>(&snapshot).row == row)
.map(|breakpoint| (breakpoints.buffer.clone(), breakpoint.bp.clone()))
})
}
pub fn breakpoints_from_path(&self, path: &Arc<Path>, cx: &App) -> Vec<SourceBreakpoint> {
pub fn breakpoints_from_path(&self, path: &Arc<Path>) -> Vec<BreakpointWithPosition> {
self.breakpoints
.get(path)
.map(|bp| bp.breakpoints.iter().map(|bp| bp.bp.clone()).collect())
.unwrap_or_default()
}
pub fn source_breakpoints_from_path(
&self,
path: &Arc<Path>,
cx: &App,
) -> Vec<SourceBreakpoint> {
self.breakpoints
.get(path)
.map(|bp| {
let snapshot = bp.buffer.read(cx).snapshot();
bp.breakpoints
.iter()
.map(|(position, breakpoint)| {
let position = snapshot.summary_for_anchor::<PointUtf16>(position).row;
.map(|bp| {
let position = snapshot.summary_for_anchor::<PointUtf16>(bp.position()).row;
let bp = &bp.bp;
SourceBreakpoint {
row: position,
path: path.clone(),
state: breakpoint.state,
message: breakpoint.message.clone(),
condition: breakpoint.condition.clone(),
hit_condition: breakpoint.hit_condition.clone(),
state: bp.bp.state,
message: bp.bp.message.clone(),
condition: bp.bp.condition.clone(),
hit_condition: bp.bp.hit_condition.clone(),
}
})
.collect()
@ -584,7 +730,18 @@ impl BreakpointStore {
.unwrap_or_default()
}
pub fn all_breakpoints(&self, cx: &App) -> BTreeMap<Arc<Path>, Vec<SourceBreakpoint>> {
pub fn all_breakpoints(&self) -> BTreeMap<Arc<Path>, Vec<BreakpointWithPosition>> {
self.breakpoints
.iter()
.map(|(path, bp)| {
(
path.clone(),
bp.breakpoints.iter().map(|bp| bp.bp.clone()).collect(),
)
})
.collect()
}
pub fn all_source_breakpoints(&self, cx: &App) -> BTreeMap<Arc<Path>, Vec<SourceBreakpoint>> {
self.breakpoints
.iter()
.map(|(path, bp)| {
@ -593,15 +750,18 @@ impl BreakpointStore {
path.clone(),
bp.breakpoints
.iter()
.map(|(position, breakpoint)| {
let position = snapshot.summary_for_anchor::<PointUtf16>(position).row;
.map(|breakpoint| {
let position = snapshot
.summary_for_anchor::<PointUtf16>(&breakpoint.position())
.row;
let breakpoint = &breakpoint.bp;
SourceBreakpoint {
row: position,
path: path.clone(),
message: breakpoint.message.clone(),
state: breakpoint.state,
hit_condition: breakpoint.hit_condition.clone(),
condition: breakpoint.condition.clone(),
message: breakpoint.bp.message.clone(),
state: breakpoint.bp.state,
hit_condition: breakpoint.bp.hit_condition.clone(),
condition: breakpoint.bp.condition.clone(),
}
})
.collect(),
@ -656,15 +816,17 @@ impl BreakpointStore {
continue;
}
let position = snapshot.anchor_after(point);
breakpoints_for_file.breakpoints.push((
position,
Breakpoint {
message: bp.message,
state: bp.state,
condition: bp.condition,
hit_condition: bp.hit_condition,
},
))
breakpoints_for_file
.breakpoints
.push(StatefulBreakpoint::new(BreakpointWithPosition {
position,
bp: Breakpoint {
message: bp.message,
state: bp.state,
condition: bp.condition,
hit_condition: bp.hit_condition,
},
}))
}
new_breakpoints.insert(path, breakpoints_for_file);
}
@ -755,7 +917,7 @@ impl BreakpointState {
pub struct Breakpoint {
pub message: Option<BreakpointMessage>,
/// How many times do we hit the breakpoint until we actually stop at it e.g. (2 = 2 times of the breakpoint action)
pub hit_condition: Option<BreakpointMessage>,
pub hit_condition: Option<Arc<str>>,
pub condition: Option<BreakpointMessage>,
pub state: BreakpointState,
}
@ -788,7 +950,12 @@ impl Breakpoint {
}
}
fn to_proto(&self, _path: &Path, position: &text::Anchor) -> Option<client::proto::Breakpoint> {
fn to_proto(
&self,
_path: &Path,
position: &text::Anchor,
session_states: &HashMap<SessionId, BreakpointSessionState>,
) -> Option<client::proto::Breakpoint> {
Some(client::proto::Breakpoint {
position: Some(serialize_text_anchor(position)),
state: match self.state {
@ -801,6 +968,18 @@ impl Breakpoint {
.hit_condition
.as_ref()
.map(|s| String::from(s.as_ref())),
session_state: session_states
.iter()
.map(|(session_id, state)| {
(
session_id.to_proto(),
proto::BreakpointSessionState {
id: state.id,
verified: state.verified,
},
)
})
.collect(),
})
}

View file

@ -1,3 +1,5 @@
use crate::debugger::breakpoint_store::BreakpointSessionState;
use super::breakpoint_store::{
BreakpointStore, BreakpointStoreEvent, BreakpointUpdatedReason, SourceBreakpoint,
};
@ -218,25 +220,55 @@ impl LocalMode {
breakpoint_store: &Entity<BreakpointStore>,
cx: &mut App,
) -> Task<()> {
let breakpoints = breakpoint_store
.read_with(cx, |store, cx| store.breakpoints_from_path(&abs_path, cx))
let breakpoints =
breakpoint_store
.read_with(cx, |store, cx| {
store.source_breakpoints_from_path(&abs_path, cx)
})
.into_iter()
.filter(|bp| bp.state.is_enabled())
.chain(self.tmp_breakpoint.iter().filter_map(|breakpoint| {
breakpoint.path.eq(&abs_path).then(|| breakpoint.clone())
}))
.map(Into::into)
.collect();
let raw_breakpoints = breakpoint_store
.read(cx)
.breakpoints_from_path(&abs_path)
.into_iter()
.filter(|bp| bp.state.is_enabled())
.chain(self.tmp_breakpoint.clone())
.map(Into::into)
.collect();
.filter(|bp| bp.bp.state.is_enabled())
.collect::<Vec<_>>();
let task = self.request(dap_command::SetBreakpoints {
source: client_source(&abs_path),
source_modified: Some(matches!(reason, BreakpointUpdatedReason::FileSaved)),
breakpoints,
});
cx.background_spawn(async move {
match task.await {
Ok(_) => {}
Err(err) => log::warn!("Set breakpoints request failed for path: {}", err),
let session_id = self.client.id();
let breakpoint_store = breakpoint_store.downgrade();
cx.spawn(async move |cx| match cx.background_spawn(task).await {
Ok(breakpoints) => {
let breakpoints =
breakpoints
.into_iter()
.zip(raw_breakpoints)
.filter_map(|(dap_bp, zed_bp)| {
Some((
zed_bp,
BreakpointSessionState {
id: dap_bp.id?,
verified: dap_bp.verified,
},
))
});
breakpoint_store
.update(cx, |this, _| {
this.mark_breakpoints_verified(session_id, &abs_path, breakpoints);
})
.ok();
}
Err(err) => log::warn!("Set breakpoints request failed for path: {}", err),
})
}
@ -271,8 +303,11 @@ impl LocalMode {
cx: &App,
) -> Task<HashMap<Arc<Path>, anyhow::Error>> {
let mut breakpoint_tasks = Vec::new();
let breakpoints = breakpoint_store.read_with(cx, |store, cx| store.all_breakpoints(cx));
let breakpoints =
breakpoint_store.read_with(cx, |store, cx| store.all_source_breakpoints(cx));
let mut raw_breakpoints = breakpoint_store.read_with(cx, |this, _| this.all_breakpoints());
debug_assert_eq!(raw_breakpoints.len(), breakpoints.len());
let session_id = self.client.id();
for (path, breakpoints) in breakpoints {
let breakpoints = if ignore_breakpoints {
vec![]
@ -284,14 +319,46 @@ impl LocalMode {
.collect()
};
breakpoint_tasks.push(
self.request(dap_command::SetBreakpoints {
let raw_breakpoints = raw_breakpoints
.remove(&path)
.unwrap_or_default()
.into_iter()
.filter(|bp| bp.bp.state.is_enabled());
let error_path = path.clone();
let send_request = self
.request(dap_command::SetBreakpoints {
source: client_source(&path),
source_modified: Some(false),
breakpoints,
})
.map(|result| result.map_err(|e| (path, e))),
);
.map(|result| result.map_err(move |e| (error_path, e)));
let task = cx.spawn({
let breakpoint_store = breakpoint_store.downgrade();
async move |cx| {
let breakpoints = cx.background_spawn(send_request).await?;
let breakpoints = breakpoints.into_iter().zip(raw_breakpoints).filter_map(
|(dap_bp, zed_bp)| {
Some((
zed_bp,
BreakpointSessionState {
id: dap_bp.id?,
verified: dap_bp.verified,
},
))
},
);
breakpoint_store
.update(cx, |this, _| {
this.mark_breakpoints_verified(session_id, &path, breakpoints);
})
.ok();
Ok(())
}
});
breakpoint_tasks.push(task);
}
cx.background_spawn(async move {
@ -1204,7 +1271,9 @@ impl Session {
self.output_token.0 += 1;
cx.notify();
}
Events::Breakpoint(_) => {}
Events::Breakpoint(event) => self.breakpoint_store.update(cx, |store, _| {
store.update_session_breakpoint(self.session_id(), event.reason, event.breakpoint);
}),
Events::Module(event) => {
match event.reason {
dap::ModuleEventReason::New => {

View file

@ -47,6 +47,7 @@ use dap::{DapRegistry, client::DebugAdapterClient};
use collections::{BTreeSet, HashMap, HashSet};
use debounced_delay::DebouncedDelay;
pub use debugger::breakpoint_store::BreakpointWithPosition;
use debugger::{
breakpoint_store::{ActiveStackFrame, BreakpointStore},
dap_store::{DapStore, DapStoreEvent},