debugger: Surface validity of breakpoints (#30380)
We now show on the breakpoint itself whether it can ever be hit.  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:
co-authored by
Anthony
Cole Miller
Michael Sloan
Marshall Bowers
Ben Kunkle
Danilo Leal
Agus Zubiaga
Ben Brandt
Agus Zubiaga
Danilo Leal
Richard Feldman
Max Brunsfeld
Smit Barmase
peppidesu
Kirill Bulatov
Ben Kunkle
Jens Krause
Bennet Bo Fenner
Max Nordlund
Finn Evers
tidely
Sergei Kartsev
Shardul Vaidya
Chris Kelly
Peter Tripp
Umesh Yadav
Julia Ryan
Cole Miller
Conrad Irwin
william341
Liam
AidanV
imumesh18
d1y
AidanV
Anthony Eid
张小白
THELOSTSOUL
Ron Harel
Tristan Hume
Stanislav Alekseev
Joseph T. Lyons
Remco Smits
Anthony Eid
Oleksiy Syvokon
Thomas David Baker
Nate Butler
Mikayla Maki
Rob McBroom
CharlesChen0823
parent
36ae564b61
commit
17cf04558b
@@ -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(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
Reference in New Issue
Block a user