Detailed changes
@@ -2517,7 +2517,7 @@ async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
.clone()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
@@ -2526,7 +2526,7 @@ async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
.clone()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
@@ -2550,7 +2550,7 @@ async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
.clone()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
@@ -2559,7 +2559,7 @@ async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
.clone()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
@@ -2583,7 +2583,7 @@ async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
.clone()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
@@ -2592,7 +2592,7 @@ async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
.clone()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
@@ -2616,7 +2616,7 @@ async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
.clone()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
let breakpoints_b = editor_b.update(cx_b, |editor, cx| {
@@ -2625,7 +2625,7 @@ async fn test_add_breakpoints(cx_a: &mut TestAppContext, cx_b: &mut TestAppConte
.clone()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
@@ -148,7 +148,7 @@ impl Render for BreakpointList {
cx: &mut ui::Context<Self>,
) -> impl ui::IntoElement {
let old_len = self.breakpoints.len();
- let breakpoints = self.breakpoint_store.read(cx).all_breakpoints(cx);
+ let breakpoints = self.breakpoint_store.read(cx).all_source_breakpoints(cx);
self.breakpoints.clear();
let weak = cx.weak_entity();
let breakpoints = breakpoints.into_iter().flat_map(|(path, mut breakpoints)| {
@@ -122,10 +122,11 @@ use markdown::Markdown;
use mouse_context_menu::MouseContextMenu;
use persistence::DB;
use project::{
- ProjectPath,
+ BreakpointWithPosition, ProjectPath,
debugger::{
breakpoint_store::{
- BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
+ BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
+ BreakpointStoreEvent,
},
session::{Session, SessionEvent},
},
@@ -198,7 +199,7 @@ use theme::{
};
use ui::{
ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
- IconSize, Key, Tooltip, h_flex, prelude::*,
+ IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
};
use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
use workspace::{
@@ -6997,7 +6998,7 @@ impl Editor {
range: Range<DisplayRow>,
window: &mut Window,
cx: &mut Context<Self>,
- ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
+ ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
let mut breakpoint_display_points = HashMap::default();
let Some(breakpoint_store) = self.breakpoint_store.clone() else {
@@ -7031,15 +7032,17 @@ impl Editor {
buffer_snapshot,
cx,
);
- for (anchor, breakpoint) in breakpoints {
+ for (breakpoint, state) in breakpoints {
let multi_buffer_anchor =
- Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
+ Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
let position = multi_buffer_anchor
.to_point(&multi_buffer_snapshot)
.to_display_point(&snapshot);
- breakpoint_display_points
- .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
+ breakpoint_display_points.insert(
+ position.row(),
+ (multi_buffer_anchor, breakpoint.bp.clone(), state),
+ );
}
}
@@ -7214,8 +7217,10 @@ impl Editor {
position: Anchor,
row: DisplayRow,
breakpoint: &Breakpoint,
+ state: Option<BreakpointSessionState>,
cx: &mut Context<Self>,
) -> IconButton {
+ let is_rejected = state.is_some_and(|s| !s.verified);
// Is it a breakpoint that shows up when hovering over gutter?
let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
(false, false),
@@ -7241,6 +7246,8 @@ impl Editor {
let color = if is_phantom {
Color::Hint
+ } else if is_rejected {
+ Color::Disabled
} else {
Color::Debugger
};
@@ -7268,9 +7275,18 @@ impl Editor {
}
let primary_text = SharedString::from(primary_text);
let focus_handle = self.focus_handle.clone();
+
+ let meta = if is_rejected {
+ "No executable code is associated with this line."
+ } else {
+ "Right-click for more options."
+ };
IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
.icon_size(IconSize::XSmall)
.size(ui::ButtonSize::None)
+ .when(is_rejected, |this| {
+ this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
+ })
.icon_color(color)
.style(ButtonStyle::Transparent)
.on_click(cx.listener({
@@ -7302,14 +7318,7 @@ impl Editor {
);
}))
.tooltip(move |window, cx| {
- Tooltip::with_meta_in(
- primary_text.clone(),
- None,
- "Right-click for more options",
- &focus_handle,
- window,
- cx,
- )
+ Tooltip::with_meta_in(primary_text.clone(), None, meta, &focus_handle, window, cx)
})
}
@@ -7449,11 +7458,11 @@ impl Editor {
_style: &EditorStyle,
is_active: bool,
row: DisplayRow,
- breakpoint: Option<(Anchor, Breakpoint)>,
+ breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
cx: &mut Context<Self>,
) -> IconButton {
let color = Color::Muted;
- let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
+ let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
.shape(ui::IconButtonShape::Square)
@@ -9633,16 +9642,16 @@ impl Editor {
cx,
)
.next()
- .and_then(|(anchor, bp)| {
+ .and_then(|(bp, _)| {
let breakpoint_row = buffer_snapshot
- .summary_for_anchor::<text::PointUtf16>(anchor)
+ .summary_for_anchor::<text::PointUtf16>(&bp.position)
.row;
if breakpoint_row == row {
snapshot
.buffer_snapshot
- .anchor_in_excerpt(enclosing_excerpt, *anchor)
- .map(|anchor| (anchor, bp.clone()))
+ .anchor_in_excerpt(enclosing_excerpt, bp.position)
+ .map(|position| (position, bp.bp.clone()))
} else {
None
}
@@ -9805,7 +9814,10 @@ impl Editor {
breakpoint_store.update(cx, |breakpoint_store, cx| {
breakpoint_store.toggle_breakpoint(
buffer,
- (breakpoint_position.text_anchor, breakpoint),
+ BreakpointWithPosition {
+ position: breakpoint_position.text_anchor,
+ bp: breakpoint,
+ },
edit_action,
cx,
);
@@ -18716,7 +18716,7 @@ async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
.as_ref()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
@@ -18741,7 +18741,7 @@ async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
.as_ref()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
@@ -18763,7 +18763,7 @@ async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
.as_ref()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
@@ -18830,7 +18830,7 @@ async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
.as_ref()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
@@ -18851,7 +18851,7 @@ async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
.as_ref()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
@@ -18871,7 +18871,7 @@ async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
.as_ref()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
@@ -18894,7 +18894,7 @@ async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
.as_ref()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
@@ -18917,7 +18917,7 @@ async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
.as_ref()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
@@ -19010,7 +19010,7 @@ async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
.as_ref()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
@@ -19042,7 +19042,7 @@ async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
.as_ref()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
@@ -19078,7 +19078,7 @@ async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
.as_ref()
.unwrap()
.read(cx)
- .all_breakpoints(cx)
+ .all_source_breakpoints(cx)
.clone()
});
@@ -62,7 +62,7 @@ use multi_buffer::{
use project::{
ProjectPath,
- debugger::breakpoint_store::Breakpoint,
+ debugger::breakpoint_store::{Breakpoint, BreakpointSessionState},
project_settings::{GitGutterSetting, GitHunkStyleSetting, ProjectSettings},
};
use settings::Settings;
@@ -2317,7 +2317,7 @@ impl EditorElement {
gutter_hitbox: &Hitbox,
display_hunks: &[(DisplayDiffHunk, Option<Hitbox>)],
snapshot: &EditorSnapshot,
- breakpoints: HashMap<DisplayRow, (Anchor, Breakpoint)>,
+ breakpoints: HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)>,
row_infos: &[RowInfo],
window: &mut Window,
cx: &mut App,
@@ -2325,7 +2325,7 @@ impl EditorElement {
self.editor.update(cx, |editor, cx| {
breakpoints
.into_iter()
- .filter_map(|(display_row, (text_anchor, bp))| {
+ .filter_map(|(display_row, (text_anchor, bp, state))| {
if row_infos
.get((display_row.0.saturating_sub(range.start.0)) as usize)
.is_some_and(|row_info| {
@@ -2348,7 +2348,7 @@ impl EditorElement {
return None;
}
- let button = editor.render_breakpoint(text_anchor, display_row, &bp, cx);
+ let button = editor.render_breakpoint(text_anchor, display_row, &bp, state, cx);
let button = prepaint_gutter_button(
button,
@@ -2378,7 +2378,7 @@ impl EditorElement {
gutter_hitbox: &Hitbox,
display_hunks: &[(DisplayDiffHunk, Option<Hitbox>)],
snapshot: &EditorSnapshot,
- breakpoints: &mut HashMap<DisplayRow, (Anchor, Breakpoint)>,
+ breakpoints: &mut HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)>,
window: &mut Window,
cx: &mut App,
) -> Vec<AnyElement> {
@@ -7437,8 +7437,10 @@ impl Element for EditorElement {
editor.active_breakpoints(start_row..end_row, window, cx)
});
if cx.has_flag::<DebuggerFeatureFlag>() {
- for display_row in breakpoint_rows.keys() {
- active_rows.entry(*display_row).or_default().breakpoint = true;
+ for (display_row, (_, bp, state)) in &breakpoint_rows {
+ if bp.is_enabled() && state.is_none_or(|s| s.verified) {
+ active_rows.entry(*display_row).or_default().breakpoint = true;
+ }
}
}
@@ -7478,7 +7480,7 @@ impl Element for EditorElement {
let breakpoint = Breakpoint::new_standard();
phantom_breakpoint.collides_with_existing_breakpoint =
false;
- (position, breakpoint)
+ (position, breakpoint, None)
});
}
})
@@ -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 => {
@@ -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},
@@ -3,7 +3,6 @@ fn main() {
build
.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]")
.type_attribute("ProjectPath", "#[derive(Hash, Eq)]")
- .type_attribute("Breakpoint", "#[derive(Hash, Eq)]")
.type_attribute("Anchor", "#[derive(Hash, Eq)]")
.compile_protos(&["proto/zed.proto"], &["proto"])
.unwrap();
@@ -16,6 +16,12 @@ message Breakpoint {
optional string message = 4;
optional string condition = 5;
optional string hit_condition = 6;
+ map<uint64, BreakpointSessionState> session_state = 7;
+}
+
+message BreakpointSessionState {
+ uint64 id = 1;
+ bool verified = 2;
}
message BreakpointsForFile {
@@ -30,63 +36,6 @@ message ToggleBreakpoint {
Breakpoint breakpoint = 3;
}
-enum DebuggerThreadItem {
- Console = 0;
- LoadedSource = 1;
- Modules = 2;
- Variables = 3;
-}
-
-message DebuggerSetVariableState {
- string name = 1;
- DapScope scope = 2;
- string value = 3;
- uint64 stack_frame_id = 4;
- optional string evaluate_name = 5;
- uint64 parent_variables_reference = 6;
-}
-
-message VariableListOpenEntry {
- oneof entry {
- DebuggerOpenEntryScope scope = 1;
- DebuggerOpenEntryVariable variable = 2;
- }
-}
-
-message DebuggerOpenEntryScope {
- string name = 1;
-}
-
-message DebuggerOpenEntryVariable {
- string scope_name = 1;
- string name = 2;
- uint64 depth = 3;
-}
-
-message VariableListEntrySetState {
- uint64 depth = 1;
- DebuggerSetVariableState state = 2;
-}
-
-message VariableListEntryVariable {
- uint64 depth = 1;
- DapScope scope = 2;
- DapVariable variable = 3;
- bool has_children = 4;
- uint64 container_reference = 5;
-}
-
-message DebuggerScopeVariableIndex {
- repeated uint64 fetched_ids = 1;
- repeated DebuggerVariableContainer variables = 2;
-}
-
-message DebuggerVariableContainer {
- uint64 container_reference = 1;
- DapVariable variable = 2;
- uint64 depth = 3;
-}
-
enum DapThreadStatus {
Running = 0;
Stopped = 1;
@@ -94,18 +43,6 @@ enum DapThreadStatus {
Ended = 3;
}
-message VariableListScopes {
- uint64 stack_frame_id = 1;
- repeated DapScope scopes = 2;
-}
-
-message VariableListVariables {
- uint64 stack_frame_id = 1;
- uint64 scope_id = 2;
- DebuggerScopeVariableIndex variables = 3;
-}
-
-
enum VariablesArgumentsFilter {
Indexed = 0;
Named = 1;
@@ -4999,7 +4999,10 @@ impl Workspace {
if let Some(location) = self.serialize_workspace_location(cx) {
let breakpoints = self.project.update(cx, |project, cx| {
- project.breakpoint_store().read(cx).all_breakpoints(cx)
+ project
+ .breakpoint_store()
+ .read(cx)
+ .all_source_breakpoints(cx)
});
let center_group = build_serialized_pane_group(&self.center.root, window, cx);