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:
parent
df583d73b9
commit
d70ac64fe4
9 changed files with 583 additions and 172 deletions
|
@ -414,6 +414,8 @@ actions!(
|
||||||
Tab,
|
Tab,
|
||||||
Backtab,
|
Backtab,
|
||||||
ToggleBreakpoint,
|
ToggleBreakpoint,
|
||||||
|
DisableBreakpoint,
|
||||||
|
EnableBreakpoint,
|
||||||
EditLogBreakpoint,
|
EditLogBreakpoint,
|
||||||
ToggleAutoSignatureHelp,
|
ToggleAutoSignatureHelp,
|
||||||
ToggleGitBlameInline,
|
ToggleGitBlameInline,
|
||||||
|
|
|
@ -116,7 +116,9 @@ use linked_editing_ranges::refresh_linked_ranges;
|
||||||
use mouse_context_menu::MouseContextMenu;
|
use mouse_context_menu::MouseContextMenu;
|
||||||
use persistence::DB;
|
use persistence::DB;
|
||||||
use project::{
|
use project::{
|
||||||
debugger::breakpoint_store::{BreakpointEditAction, BreakpointStore, BreakpointStoreEvent},
|
debugger::breakpoint_store::{
|
||||||
|
BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
|
||||||
|
},
|
||||||
ProjectPath,
|
ProjectPath,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6019,13 +6021,7 @@ impl Editor {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Option<IconButton> {
|
) -> Option<IconButton> {
|
||||||
let color = Color::Muted;
|
let color = Color::Muted;
|
||||||
|
|
||||||
let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
|
let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
|
||||||
let bp_kind = Arc::new(
|
|
||||||
breakpoint
|
|
||||||
.map(|(_, bp)| bp.kind.clone())
|
|
||||||
.unwrap_or(BreakpointKind::Standard),
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.available_code_actions.is_some() {
|
if self.available_code_actions.is_some() {
|
||||||
Some(
|
Some(
|
||||||
|
@ -6062,7 +6058,6 @@ impl Editor {
|
||||||
editor.set_breakpoint_context_menu(
|
editor.set_breakpoint_context_menu(
|
||||||
row,
|
row,
|
||||||
position,
|
position,
|
||||||
bp_kind.clone(),
|
|
||||||
event.down.position,
|
event.down.position,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
@ -6115,7 +6110,7 @@ impl Editor {
|
||||||
for breakpoint in
|
for breakpoint in
|
||||||
breakpoint_store
|
breakpoint_store
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.breakpoints(&buffer, None, buffer_snapshot.clone(), cx)
|
.breakpoints(&buffer, None, &buffer_snapshot, cx)
|
||||||
{
|
{
|
||||||
let point = buffer_snapshot.summary_for_anchor::<Point>(&breakpoint.0);
|
let point = buffer_snapshot.summary_for_anchor::<Point>(&breakpoint.0);
|
||||||
let mut anchor = multi_buffer_snapshot.anchor_before(point);
|
let mut anchor = multi_buffer_snapshot.anchor_before(point);
|
||||||
|
@ -6140,49 +6135,33 @@ impl Editor {
|
||||||
|
|
||||||
let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
|
let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
|
||||||
..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
|
..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
|
||||||
for excerpt_boundary in multi_buffer_snapshot.excerpt_boundaries_in_range(range) {
|
|
||||||
let info = excerpt_boundary.next;
|
|
||||||
|
|
||||||
let Some(excerpt_ranges) = multi_buffer_snapshot.range_for_excerpt(info.id) else {
|
for (buffer_snapshot, range, excerpt_id) in
|
||||||
|
multi_buffer_snapshot.range_to_buffer_ranges(range)
|
||||||
|
{
|
||||||
|
let Some(buffer) = project.read_with(cx, |this, cx| {
|
||||||
|
this.buffer_for_id(buffer_snapshot.remote_id(), cx)
|
||||||
|
}) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(buffer) =
|
|
||||||
project.read_with(cx, |this, cx| this.buffer_for_id(info.buffer_id, cx))
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if buffer.read(cx).file().is_none() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let breakpoints = breakpoint_store.read(cx).breakpoints(
|
let breakpoints = breakpoint_store.read(cx).breakpoints(
|
||||||
&buffer,
|
&buffer,
|
||||||
Some(info.range.context.start..info.range.context.end),
|
Some(
|
||||||
info.buffer.clone(),
|
buffer_snapshot.anchor_before(range.start)
|
||||||
|
..buffer_snapshot.anchor_after(range.end),
|
||||||
|
),
|
||||||
|
buffer_snapshot,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
// To translate a breakpoint's position within a singular buffer to a multi buffer
|
|
||||||
// position we need to know it's excerpt starting location, it's position within
|
|
||||||
// the singular buffer, and if that position is within the excerpt's range.
|
|
||||||
let excerpt_head = excerpt_ranges
|
|
||||||
.start
|
|
||||||
.to_display_point(&snapshot.display_snapshot);
|
|
||||||
|
|
||||||
let buffer_start = info
|
|
||||||
.buffer
|
|
||||||
.summary_for_anchor::<Point>(&info.range.context.start);
|
|
||||||
|
|
||||||
for (anchor, breakpoint) in breakpoints {
|
for (anchor, breakpoint) in breakpoints {
|
||||||
let as_row = info.buffer.summary_for_anchor::<Point>(&anchor).row;
|
let multi_buffer_anchor =
|
||||||
let delta = as_row - buffer_start.row;
|
Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
|
||||||
|
let position = multi_buffer_anchor
|
||||||
|
.to_point(&multi_buffer_snapshot)
|
||||||
|
.to_display_point(&snapshot);
|
||||||
|
|
||||||
let position = excerpt_head + DisplayPoint::new(DisplayRow(delta), 0);
|
breakpoint_display_points
|
||||||
|
.insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
|
||||||
let anchor = snapshot.display_point_to_anchor(position, Bias::Left);
|
|
||||||
|
|
||||||
breakpoint_display_points.insert(position.row(), (anchor, breakpoint.clone()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6192,30 +6171,80 @@ impl Editor {
|
||||||
fn breakpoint_context_menu(
|
fn breakpoint_context_menu(
|
||||||
&self,
|
&self,
|
||||||
anchor: Anchor,
|
anchor: Anchor,
|
||||||
kind: Arc<BreakpointKind>,
|
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Entity<ui::ContextMenu> {
|
) -> Entity<ui::ContextMenu> {
|
||||||
let weak_editor = cx.weak_entity();
|
let weak_editor = cx.weak_entity();
|
||||||
let focus_handle = self.focus_handle(cx);
|
let focus_handle = self.focus_handle(cx);
|
||||||
|
|
||||||
let second_entry_msg = if kind.log_message().is_some() {
|
let row = self
|
||||||
|
.buffer
|
||||||
|
.read(cx)
|
||||||
|
.snapshot(cx)
|
||||||
|
.summary_for_anchor::<Point>(&anchor)
|
||||||
|
.row;
|
||||||
|
|
||||||
|
let breakpoint = self
|
||||||
|
.breakpoint_at_row(row, window, cx)
|
||||||
|
.map(|(_, bp)| Arc::from(bp));
|
||||||
|
|
||||||
|
let log_breakpoint_msg = if breakpoint
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|bp| bp.kind.log_message().is_some())
|
||||||
|
{
|
||||||
"Edit Log Breakpoint"
|
"Edit Log Breakpoint"
|
||||||
} else {
|
} else {
|
||||||
"Add Log Breakpoint"
|
"Set Log Breakpoint"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
|
||||||
|
"Unset Breakpoint"
|
||||||
|
} else {
|
||||||
|
"Set Breakpoint"
|
||||||
|
};
|
||||||
|
|
||||||
|
let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.state {
|
||||||
|
BreakpointState::Enabled => Some("Disable"),
|
||||||
|
BreakpointState::Disabled => Some("Enable"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let breakpoint = breakpoint.unwrap_or_else(|| {
|
||||||
|
Arc::new(Breakpoint {
|
||||||
|
state: BreakpointState::Enabled,
|
||||||
|
kind: BreakpointKind::Standard,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
ui::ContextMenu::build(window, cx, |menu, _, _cx| {
|
ui::ContextMenu::build(window, cx, |menu, _, _cx| {
|
||||||
menu.on_blur_subscription(Subscription::new(|| {}))
|
menu.on_blur_subscription(Subscription::new(|| {}))
|
||||||
.context(focus_handle)
|
.context(focus_handle)
|
||||||
.entry("Toggle Breakpoint", None, {
|
.when_some(toggle_state_msg, |this, msg| {
|
||||||
|
this.entry(msg, None, {
|
||||||
|
let weak_editor = weak_editor.clone();
|
||||||
|
let breakpoint = breakpoint.clone();
|
||||||
|
move |_window, cx| {
|
||||||
|
weak_editor
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.edit_breakpoint_at_anchor(
|
||||||
|
anchor,
|
||||||
|
breakpoint.as_ref().clone(),
|
||||||
|
BreakpointEditAction::InvertState,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.entry(set_breakpoint_msg, None, {
|
||||||
let weak_editor = weak_editor.clone();
|
let weak_editor = weak_editor.clone();
|
||||||
|
let breakpoint = breakpoint.clone();
|
||||||
move |_window, cx| {
|
move |_window, cx| {
|
||||||
weak_editor
|
weak_editor
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
this.edit_breakpoint_at_anchor(
|
this.edit_breakpoint_at_anchor(
|
||||||
anchor,
|
anchor,
|
||||||
BreakpointKind::Standard,
|
breakpoint.as_ref().clone(),
|
||||||
BreakpointEditAction::Toggle,
|
BreakpointEditAction::Toggle,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -6223,10 +6252,10 @@ impl Editor {
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.entry(second_entry_msg, None, move |window, cx| {
|
.entry(log_breakpoint_msg, None, move |window, cx| {
|
||||||
weak_editor
|
weak_editor
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
this.add_edit_breakpoint_block(anchor, kind.as_ref(), window, cx);
|
this.add_edit_breakpoint_block(anchor, breakpoint.as_ref(), window, cx);
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
})
|
})
|
||||||
|
@ -6237,44 +6266,51 @@ impl Editor {
|
||||||
&self,
|
&self,
|
||||||
position: Anchor,
|
position: Anchor,
|
||||||
row: DisplayRow,
|
row: DisplayRow,
|
||||||
kind: &BreakpointKind,
|
breakpoint: &Breakpoint,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> IconButton {
|
) -> IconButton {
|
||||||
let color = if self
|
let (color, icon) = {
|
||||||
.gutter_breakpoint_indicator
|
let color = if self
|
||||||
.is_some_and(|gutter_bp| gutter_bp.row() == row)
|
.gutter_breakpoint_indicator
|
||||||
{
|
.is_some_and(|point| point.row() == row)
|
||||||
Color::Hint
|
{
|
||||||
} else {
|
Color::Hint
|
||||||
Color::Debugger
|
} else if breakpoint.is_disabled() {
|
||||||
|
Color::Custom(Color::Debugger.color(cx).opacity(0.5))
|
||||||
|
} else {
|
||||||
|
Color::Debugger
|
||||||
|
};
|
||||||
|
let icon = match &breakpoint.kind {
|
||||||
|
BreakpointKind::Standard => ui::IconName::DebugBreakpoint,
|
||||||
|
BreakpointKind::Log(_) => ui::IconName::DebugLogBreakpoint,
|
||||||
|
};
|
||||||
|
(color, icon)
|
||||||
};
|
};
|
||||||
|
|
||||||
let icon = match &kind {
|
let breakpoint = Arc::from(breakpoint.clone());
|
||||||
BreakpointKind::Standard => ui::IconName::DebugBreakpoint,
|
|
||||||
BreakpointKind::Log(_) => ui::IconName::DebugLogBreakpoint,
|
|
||||||
};
|
|
||||||
let arc_kind = Arc::new(kind.clone());
|
|
||||||
let arc_kind2 = arc_kind.clone();
|
|
||||||
|
|
||||||
IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
|
IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
|
||||||
.icon_size(IconSize::XSmall)
|
.icon_size(IconSize::XSmall)
|
||||||
.size(ui::ButtonSize::None)
|
.size(ui::ButtonSize::None)
|
||||||
.icon_color(color)
|
.icon_color(color)
|
||||||
.style(ButtonStyle::Transparent)
|
.style(ButtonStyle::Transparent)
|
||||||
.on_click(cx.listener(move |editor, _e, window, cx| {
|
.on_click(cx.listener({
|
||||||
window.focus(&editor.focus_handle(cx));
|
let breakpoint = breakpoint.clone();
|
||||||
editor.edit_breakpoint_at_anchor(
|
|
||||||
position,
|
move |editor, _e, window, cx| {
|
||||||
arc_kind.as_ref().clone(),
|
window.focus(&editor.focus_handle(cx));
|
||||||
BreakpointEditAction::Toggle,
|
editor.edit_breakpoint_at_anchor(
|
||||||
cx,
|
position,
|
||||||
);
|
breakpoint.as_ref().clone(),
|
||||||
|
BreakpointEditAction::Toggle,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
.on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
|
.on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
|
||||||
editor.set_breakpoint_context_menu(
|
editor.set_breakpoint_context_menu(
|
||||||
row,
|
row,
|
||||||
Some(position),
|
Some(position),
|
||||||
arc_kind2.clone(),
|
|
||||||
event.down.position,
|
event.down.position,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
@ -6422,13 +6458,7 @@ impl Editor {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> IconButton {
|
) -> IconButton {
|
||||||
let color = Color::Muted;
|
let color = Color::Muted;
|
||||||
|
|
||||||
let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
|
let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
|
||||||
let bp_kind = Arc::new(
|
|
||||||
breakpoint
|
|
||||||
.map(|(_, bp)| bp.kind)
|
|
||||||
.unwrap_or(BreakpointKind::Standard),
|
|
||||||
);
|
|
||||||
|
|
||||||
IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
|
IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
|
||||||
.shape(ui::IconButtonShape::Square)
|
.shape(ui::IconButtonShape::Square)
|
||||||
|
@ -6446,14 +6476,7 @@ impl Editor {
|
||||||
);
|
);
|
||||||
}))
|
}))
|
||||||
.on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
|
.on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
|
||||||
editor.set_breakpoint_context_menu(
|
editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
|
||||||
row,
|
|
||||||
position,
|
|
||||||
bp_kind.clone(),
|
|
||||||
event.down.position,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8430,9 +8453,8 @@ impl Editor {
|
||||||
|
|
||||||
fn set_breakpoint_context_menu(
|
fn set_breakpoint_context_menu(
|
||||||
&mut self,
|
&mut self,
|
||||||
row: DisplayRow,
|
display_row: DisplayRow,
|
||||||
position: Option<Anchor>,
|
position: Option<Anchor>,
|
||||||
kind: Arc<BreakpointKind>,
|
|
||||||
clicked_point: gpui::Point<Pixels>,
|
clicked_point: gpui::Point<Pixels>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
|
@ -8444,10 +8466,9 @@ impl Editor {
|
||||||
.buffer
|
.buffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.snapshot(cx)
|
.snapshot(cx)
|
||||||
.anchor_before(Point::new(row.0, 0u32));
|
.anchor_before(Point::new(display_row.0, 0u32));
|
||||||
|
|
||||||
let context_menu =
|
let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
|
||||||
self.breakpoint_context_menu(position.unwrap_or(source), kind, window, cx);
|
|
||||||
|
|
||||||
self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
|
self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
|
||||||
self,
|
self,
|
||||||
|
@ -8462,13 +8483,14 @@ impl Editor {
|
||||||
fn add_edit_breakpoint_block(
|
fn add_edit_breakpoint_block(
|
||||||
&mut self,
|
&mut self,
|
||||||
anchor: Anchor,
|
anchor: Anchor,
|
||||||
kind: &BreakpointKind,
|
breakpoint: &Breakpoint,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let weak_editor = cx.weak_entity();
|
let weak_editor = cx.weak_entity();
|
||||||
let bp_prompt =
|
let bp_prompt = cx.new(|cx| {
|
||||||
cx.new(|cx| BreakpointPromptEditor::new(weak_editor, anchor, kind.clone(), window, cx));
|
BreakpointPromptEditor::new(weak_editor, anchor, breakpoint.clone(), window, cx)
|
||||||
|
});
|
||||||
|
|
||||||
let height = bp_prompt.update(cx, |this, cx| {
|
let height = bp_prompt.update(cx, |this, cx| {
|
||||||
this.prompt
|
this.prompt
|
||||||
|
@ -8495,36 +8517,45 @@ impl Editor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn breakpoint_at_cursor_head(
|
fn breakpoint_at_cursor_head(
|
||||||
&self,
|
&self,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Option<(Anchor, Breakpoint)> {
|
) -> Option<(Anchor, Breakpoint)> {
|
||||||
let cursor_position: Point = self.selections.newest(cx).head();
|
let cursor_position: Point = self.selections.newest(cx).head();
|
||||||
|
self.breakpoint_at_row(cursor_position.row, window, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn breakpoint_at_row(
|
||||||
|
&self,
|
||||||
|
row: u32,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Option<(Anchor, Breakpoint)> {
|
||||||
let snapshot = self.snapshot(window, cx);
|
let snapshot = self.snapshot(window, cx);
|
||||||
// We Set the column position to zero so this function interacts correctly
|
let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
|
||||||
// between calls by clicking on the gutter & using an action to toggle a
|
|
||||||
// breakpoint. Otherwise, toggling a breakpoint through an action wouldn't
|
|
||||||
// untoggle a breakpoint that was added through clicking on the gutter
|
|
||||||
let cursor_position = snapshot
|
|
||||||
.display_snapshot
|
|
||||||
.buffer_snapshot
|
|
||||||
.anchor_before(Point::new(cursor_position.row, 0));
|
|
||||||
|
|
||||||
let project = self.project.clone();
|
let project = self.project.clone()?;
|
||||||
|
|
||||||
let buffer_id = cursor_position.text_anchor.buffer_id?;
|
let buffer_id = breakpoint_position.buffer_id.or_else(|| {
|
||||||
let enclosing_excerpt = snapshot
|
snapshot
|
||||||
.buffer_snapshot
|
.buffer_snapshot
|
||||||
.excerpt_ids_for_range(cursor_position..cursor_position)
|
.buffer_id_for_excerpt(breakpoint_position.excerpt_id)
|
||||||
.next()?;
|
})?;
|
||||||
let buffer = project?.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
|
|
||||||
|
let enclosing_excerpt = breakpoint_position.excerpt_id;
|
||||||
|
let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
|
||||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||||
|
|
||||||
let row = buffer_snapshot
|
let row = buffer_snapshot
|
||||||
.summary_for_anchor::<text::PointUtf16>(&cursor_position.text_anchor)
|
.summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
|
||||||
.row;
|
.row;
|
||||||
|
|
||||||
|
let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
|
||||||
|
let anchor_end = snapshot
|
||||||
|
.buffer_snapshot
|
||||||
|
.anchor_before(Point::new(row, line_len));
|
||||||
|
|
||||||
let bp = self
|
let bp = self
|
||||||
.breakpoint_store
|
.breakpoint_store
|
||||||
.as_ref()?
|
.as_ref()?
|
||||||
|
@ -8532,12 +8563,12 @@ impl Editor {
|
||||||
breakpoint_store
|
breakpoint_store
|
||||||
.breakpoints(
|
.breakpoints(
|
||||||
&buffer,
|
&buffer,
|
||||||
Some(cursor_position.text_anchor..(text::Anchor::MAX)),
|
Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
|
||||||
buffer_snapshot.clone(),
|
&buffer_snapshot,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.next()
|
.next()
|
||||||
.and_then(move |(anchor, bp)| {
|
.and_then(|(anchor, bp)| {
|
||||||
let breakpoint_row = buffer_snapshot
|
let breakpoint_row = buffer_snapshot
|
||||||
.summary_for_anchor::<text::PointUtf16>(anchor)
|
.summary_for_anchor::<text::PointUtf16>(anchor)
|
||||||
.row;
|
.row;
|
||||||
|
@ -8576,11 +8607,48 @@ impl Editor {
|
||||||
breakpoint_position,
|
breakpoint_position,
|
||||||
Breakpoint {
|
Breakpoint {
|
||||||
kind: BreakpointKind::Standard,
|
kind: BreakpointKind::Standard,
|
||||||
|
state: BreakpointState::Enabled,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
self.add_edit_breakpoint_block(anchor, &bp.kind, window, cx);
|
self.add_edit_breakpoint_block(anchor, &bp, window, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_breakpoint(
|
||||||
|
&mut self,
|
||||||
|
_: &crate::actions::EnableBreakpoint,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
|
||||||
|
if breakpoint.is_disabled() {
|
||||||
|
self.edit_breakpoint_at_anchor(
|
||||||
|
anchor,
|
||||||
|
breakpoint,
|
||||||
|
BreakpointEditAction::InvertState,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable_breakpoint(
|
||||||
|
&mut self,
|
||||||
|
_: &crate::actions::DisableBreakpoint,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
|
||||||
|
if breakpoint.is_enabled() {
|
||||||
|
self.edit_breakpoint_at_anchor(
|
||||||
|
anchor,
|
||||||
|
breakpoint,
|
||||||
|
BreakpointEditAction::InvertState,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_breakpoint(
|
pub fn toggle_breakpoint(
|
||||||
|
@ -8592,7 +8660,7 @@ impl Editor {
|
||||||
let edit_action = BreakpointEditAction::Toggle;
|
let edit_action = BreakpointEditAction::Toggle;
|
||||||
|
|
||||||
if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
|
if let Some((anchor, breakpoint)) = self.breakpoint_at_cursor_head(window, cx) {
|
||||||
self.edit_breakpoint_at_anchor(anchor, breakpoint.kind, edit_action, cx);
|
self.edit_breakpoint_at_anchor(anchor, breakpoint, edit_action, cx);
|
||||||
} else {
|
} else {
|
||||||
let cursor_position: Point = self.selections.newest(cx).head();
|
let cursor_position: Point = self.selections.newest(cx).head();
|
||||||
|
|
||||||
|
@ -8604,7 +8672,7 @@ impl Editor {
|
||||||
|
|
||||||
self.edit_breakpoint_at_anchor(
|
self.edit_breakpoint_at_anchor(
|
||||||
breakpoint_position,
|
breakpoint_position,
|
||||||
BreakpointKind::Standard,
|
Breakpoint::new_standard(),
|
||||||
edit_action,
|
edit_action,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -8614,7 +8682,7 @@ impl Editor {
|
||||||
pub fn edit_breakpoint_at_anchor(
|
pub fn edit_breakpoint_at_anchor(
|
||||||
&mut self,
|
&mut self,
|
||||||
breakpoint_position: Anchor,
|
breakpoint_position: Anchor,
|
||||||
kind: BreakpointKind,
|
breakpoint: Breakpoint,
|
||||||
edit_action: BreakpointEditAction,
|
edit_action: BreakpointEditAction,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
|
@ -8643,7 +8711,7 @@ impl Editor {
|
||||||
breakpoint_store.update(cx, |breakpoint_store, cx| {
|
breakpoint_store.update(cx, |breakpoint_store, cx| {
|
||||||
breakpoint_store.toggle_breakpoint(
|
breakpoint_store.toggle_breakpoint(
|
||||||
buffer,
|
buffer,
|
||||||
(breakpoint_position.text_anchor, Breakpoint { kind }),
|
(breakpoint_position.text_anchor, breakpoint),
|
||||||
edit_action,
|
edit_action,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -19605,7 +19673,7 @@ struct BreakpointPromptEditor {
|
||||||
pub(crate) prompt: Entity<Editor>,
|
pub(crate) prompt: Entity<Editor>,
|
||||||
editor: WeakEntity<Editor>,
|
editor: WeakEntity<Editor>,
|
||||||
breakpoint_anchor: Anchor,
|
breakpoint_anchor: Anchor,
|
||||||
kind: BreakpointKind,
|
breakpoint: Breakpoint,
|
||||||
block_ids: HashSet<CustomBlockId>,
|
block_ids: HashSet<CustomBlockId>,
|
||||||
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
|
@ -19617,13 +19685,15 @@ impl BreakpointPromptEditor {
|
||||||
fn new(
|
fn new(
|
||||||
editor: WeakEntity<Editor>,
|
editor: WeakEntity<Editor>,
|
||||||
breakpoint_anchor: Anchor,
|
breakpoint_anchor: Anchor,
|
||||||
kind: BreakpointKind,
|
breakpoint: Breakpoint,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let buffer = cx.new(|cx| {
|
let buffer = cx.new(|cx| {
|
||||||
Buffer::local(
|
Buffer::local(
|
||||||
kind.log_message()
|
breakpoint
|
||||||
|
.kind
|
||||||
|
.log_message()
|
||||||
.map(|msg| msg.to_string())
|
.map(|msg| msg.to_string())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
cx,
|
cx,
|
||||||
|
@ -19655,7 +19725,7 @@ impl BreakpointPromptEditor {
|
||||||
prompt,
|
prompt,
|
||||||
editor,
|
editor,
|
||||||
breakpoint_anchor,
|
breakpoint_anchor,
|
||||||
kind,
|
breakpoint,
|
||||||
gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
|
gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
|
||||||
block_ids: Default::default(),
|
block_ids: Default::default(),
|
||||||
_subscriptions: vec![],
|
_subscriptions: vec![],
|
||||||
|
@ -19682,7 +19752,7 @@ impl BreakpointPromptEditor {
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.edit_breakpoint_at_anchor(
|
editor.edit_breakpoint_at_anchor(
|
||||||
self.breakpoint_anchor,
|
self.breakpoint_anchor,
|
||||||
self.kind.clone(),
|
self.breakpoint.clone(),
|
||||||
BreakpointEditAction::EditLogMessage(log_message.into()),
|
BreakpointEditAction::EditLogMessage(log_message.into()),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
|
@ -32,7 +32,7 @@ use multi_buffer::{IndentGuide, PathKey};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use pretty_assertions::{assert_eq, assert_ne};
|
use pretty_assertions::{assert_eq, assert_ne};
|
||||||
use project::{
|
use project::{
|
||||||
debugger::breakpoint_store::{BreakpointKind, SerializedBreakpoint},
|
debugger::breakpoint_store::{BreakpointKind, BreakpointState, SerializedBreakpoint},
|
||||||
project_settings::{LspSettings, ProjectSettings},
|
project_settings::{LspSettings, ProjectSettings},
|
||||||
FakeFs,
|
FakeFs,
|
||||||
};
|
};
|
||||||
|
@ -17392,7 +17392,7 @@ async fn assert_highlighted_edits(
|
||||||
fn assert_breakpoint(
|
fn assert_breakpoint(
|
||||||
breakpoints: &BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>>,
|
breakpoints: &BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>>,
|
||||||
path: &Arc<Path>,
|
path: &Arc<Path>,
|
||||||
expected: Vec<(u32, BreakpointKind)>,
|
expected: Vec<(u32, Breakpoint)>,
|
||||||
) {
|
) {
|
||||||
if expected.len() == 0usize {
|
if expected.len() == 0usize {
|
||||||
assert!(!breakpoints.contains_key(path));
|
assert!(!breakpoints.contains_key(path));
|
||||||
|
@ -17401,7 +17401,15 @@ fn assert_breakpoint(
|
||||||
.get(path)
|
.get(path)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|breakpoint| (breakpoint.position, breakpoint.kind.clone()))
|
.map(|breakpoint| {
|
||||||
|
(
|
||||||
|
breakpoint.position,
|
||||||
|
Breakpoint {
|
||||||
|
kind: breakpoint.kind.clone(),
|
||||||
|
state: breakpoint.state,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
|
breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
|
||||||
|
@ -17429,12 +17437,18 @@ fn add_log_breakpoint_at_cursor(
|
||||||
|
|
||||||
let kind = BreakpointKind::Log(Arc::from(log_message));
|
let kind = BreakpointKind::Log(Arc::from(log_message));
|
||||||
|
|
||||||
(breakpoint_position, Breakpoint { kind })
|
(
|
||||||
|
breakpoint_position,
|
||||||
|
Breakpoint {
|
||||||
|
kind,
|
||||||
|
state: BreakpointState::Enabled,
|
||||||
|
},
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.edit_breakpoint_at_anchor(
|
editor.edit_breakpoint_at_anchor(
|
||||||
anchor,
|
anchor,
|
||||||
bp.kind,
|
bp,
|
||||||
BreakpointEditAction::EditLogMessage(log_message.into()),
|
BreakpointEditAction::EditLogMessage(log_message.into()),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -17522,7 +17536,10 @@ async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
|
||||||
assert_breakpoint(
|
assert_breakpoint(
|
||||||
&breakpoints,
|
&breakpoints,
|
||||||
&abs_path,
|
&abs_path,
|
||||||
vec![(0, BreakpointKind::Standard), (3, BreakpointKind::Standard)],
|
vec![
|
||||||
|
(0, Breakpoint::new_standard()),
|
||||||
|
(3, Breakpoint::new_standard()),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
editor.update_in(cx, |editor, window, cx| {
|
editor.update_in(cx, |editor, window, cx| {
|
||||||
|
@ -17541,7 +17558,11 @@ async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(1, breakpoints.len());
|
assert_eq!(1, breakpoints.len());
|
||||||
assert_breakpoint(&breakpoints, &abs_path, vec![(3, BreakpointKind::Standard)]);
|
assert_breakpoint(
|
||||||
|
&breakpoints,
|
||||||
|
&abs_path,
|
||||||
|
vec![(3, Breakpoint::new_standard())],
|
||||||
|
);
|
||||||
|
|
||||||
editor.update_in(cx, |editor, window, cx| {
|
editor.update_in(cx, |editor, window, cx| {
|
||||||
editor.move_to_end(&MoveToEnd, window, cx);
|
editor.move_to_end(&MoveToEnd, window, cx);
|
||||||
|
@ -17628,7 +17649,7 @@ async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
|
||||||
assert_breakpoint(
|
assert_breakpoint(
|
||||||
&breakpoints,
|
&breakpoints,
|
||||||
&abs_path,
|
&abs_path,
|
||||||
vec![(0, BreakpointKind::Log("hello world".into()))],
|
vec![(0, Breakpoint::new_log("hello world"))],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Removing a log message from a log breakpoint should remove it
|
// Removing a log message from a log breakpoint should remove it
|
||||||
|
@ -17669,7 +17690,10 @@ async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
|
||||||
assert_breakpoint(
|
assert_breakpoint(
|
||||||
&breakpoints,
|
&breakpoints,
|
||||||
&abs_path,
|
&abs_path,
|
||||||
vec![(0, BreakpointKind::Standard), (3, BreakpointKind::Standard)],
|
vec![
|
||||||
|
(0, Breakpoint::new_standard()),
|
||||||
|
(3, Breakpoint::new_standard()),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
editor.update_in(cx, |editor, window, cx| {
|
editor.update_in(cx, |editor, window, cx| {
|
||||||
|
@ -17690,8 +17714,8 @@ async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
|
||||||
&breakpoints,
|
&breakpoints,
|
||||||
&abs_path,
|
&abs_path,
|
||||||
vec![
|
vec![
|
||||||
(0, BreakpointKind::Standard),
|
(0, Breakpoint::new_standard()),
|
||||||
(3, BreakpointKind::Log("hello world".into())),
|
(3, Breakpoint::new_log("hello world")),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -17713,8 +17737,167 @@ async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
|
||||||
&breakpoints,
|
&breakpoints,
|
||||||
&abs_path,
|
&abs_path,
|
||||||
vec![
|
vec![
|
||||||
(0, BreakpointKind::Standard),
|
(0, Breakpoint::new_standard()),
|
||||||
(3, BreakpointKind::Log("hello Earth !!".into())),
|
(3, Breakpoint::new_log("hello Earth !!")),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This also tests that Editor::breakpoint_at_cursor_head is working properly
|
||||||
|
/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
|
||||||
|
/// or when breakpoints were placed out of order. This tests for a regression too
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
|
let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
fs.insert_tree(
|
||||||
|
path!("/a"),
|
||||||
|
json!({
|
||||||
|
"main.rs": sample_text,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
|
||||||
|
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||||
|
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.executor());
|
||||||
|
fs.insert_tree(
|
||||||
|
path!("/a"),
|
||||||
|
json!({
|
||||||
|
"main.rs": sample_text,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
|
||||||
|
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||||
|
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
|
||||||
|
let worktree_id = workspace
|
||||||
|
.update(cx, |workspace, _window, cx| {
|
||||||
|
workspace.project().update(cx, |project, cx| {
|
||||||
|
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let buffer = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.open_buffer((worktree_id, "main.rs"), cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (editor, cx) = cx.add_window_view(|window, cx| {
|
||||||
|
Editor::new(
|
||||||
|
EditorMode::Full,
|
||||||
|
MultiBuffer::build_from_buffer(buffer, cx),
|
||||||
|
Some(project.clone()),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
|
||||||
|
let abs_path = project.read_with(cx, |project, cx| {
|
||||||
|
project
|
||||||
|
.absolute_path(&project_path, cx)
|
||||||
|
.map(|path_buf| Arc::from(path_buf.to_owned()))
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
// assert we can add breakpoint on the first line
|
||||||
|
editor.update_in(cx, |editor, window, cx| {
|
||||||
|
editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
|
||||||
|
editor.move_to_end(&MoveToEnd, window, cx);
|
||||||
|
editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
|
||||||
|
editor.move_up(&MoveUp, window, cx);
|
||||||
|
editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
let breakpoints = editor.update(cx, |editor, cx| {
|
||||||
|
editor
|
||||||
|
.breakpoint_store()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.read(cx)
|
||||||
|
.all_breakpoints(cx)
|
||||||
|
.clone()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(1, breakpoints.len());
|
||||||
|
assert_breakpoint(
|
||||||
|
&breakpoints,
|
||||||
|
&abs_path,
|
||||||
|
vec![
|
||||||
|
(0, Breakpoint::new_standard()),
|
||||||
|
(2, Breakpoint::new_standard()),
|
||||||
|
(3, Breakpoint::new_standard()),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
editor.update_in(cx, |editor, window, cx| {
|
||||||
|
editor.move_to_beginning(&MoveToBeginning, window, cx);
|
||||||
|
editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
|
||||||
|
editor.move_to_end(&MoveToEnd, window, cx);
|
||||||
|
editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
let breakpoints = editor.update(cx, |editor, cx| {
|
||||||
|
editor
|
||||||
|
.breakpoint_store()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.read(cx)
|
||||||
|
.all_breakpoints(cx)
|
||||||
|
.clone()
|
||||||
|
});
|
||||||
|
|
||||||
|
let disable_breakpoint = {
|
||||||
|
let mut bp = Breakpoint::new_standard();
|
||||||
|
bp.state = BreakpointState::Disabled;
|
||||||
|
bp
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(1, breakpoints.len());
|
||||||
|
assert_breakpoint(
|
||||||
|
&breakpoints,
|
||||||
|
&abs_path,
|
||||||
|
vec![
|
||||||
|
(0, disable_breakpoint.clone()),
|
||||||
|
(2, Breakpoint::new_standard()),
|
||||||
|
(3, disable_breakpoint.clone()),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
editor.update_in(cx, |editor, window, cx| {
|
||||||
|
editor.move_to_beginning(&MoveToBeginning, window, cx);
|
||||||
|
editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
|
||||||
|
editor.move_to_end(&MoveToEnd, window, cx);
|
||||||
|
editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
|
||||||
|
editor.move_up(&MoveUp, window, cx);
|
||||||
|
editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
let breakpoints = editor.update(cx, |editor, cx| {
|
||||||
|
editor
|
||||||
|
.breakpoint_store()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.read(cx)
|
||||||
|
.all_breakpoints(cx)
|
||||||
|
.clone()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(1, breakpoints.len());
|
||||||
|
assert_breakpoint(
|
||||||
|
&breakpoints,
|
||||||
|
&abs_path,
|
||||||
|
vec![
|
||||||
|
(0, Breakpoint::new_standard()),
|
||||||
|
(2, disable_breakpoint),
|
||||||
|
(3, Breakpoint::new_standard()),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ use multi_buffer::{
|
||||||
MultiBufferRow, RowInfo,
|
MultiBufferRow, RowInfo,
|
||||||
};
|
};
|
||||||
use project::{
|
use project::{
|
||||||
debugger::breakpoint_store::{Breakpoint, BreakpointKind},
|
debugger::breakpoint_store::Breakpoint,
|
||||||
project_settings::{self, GitGutterSetting, GitHunkStyleSetting, ProjectSettings},
|
project_settings::{self, GitGutterSetting, GitHunkStyleSetting, ProjectSettings},
|
||||||
};
|
};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
@ -525,6 +525,8 @@ impl EditorElement {
|
||||||
if cx.has_flag::<Debugger>() {
|
if cx.has_flag::<Debugger>() {
|
||||||
register_action(editor, window, Editor::toggle_breakpoint);
|
register_action(editor, window, Editor::toggle_breakpoint);
|
||||||
register_action(editor, window, Editor::edit_log_breakpoint);
|
register_action(editor, window, Editor::edit_log_breakpoint);
|
||||||
|
register_action(editor, window, Editor::enable_breakpoint);
|
||||||
|
register_action(editor, window, Editor::disable_breakpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1950,8 +1952,6 @@ impl EditorElement {
|
||||||
breakpoints
|
breakpoints
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|(display_row, (text_anchor, bp))| {
|
.filter_map(|(display_row, (text_anchor, bp))| {
|
||||||
let row = MultiBufferRow { 0: display_row.0 };
|
|
||||||
|
|
||||||
if row_infos
|
if row_infos
|
||||||
.get((display_row.0.saturating_sub(range.start.0)) as usize)
|
.get((display_row.0.saturating_sub(range.start.0)) as usize)
|
||||||
.is_some_and(|row_info| row_info.expand_info.is_some())
|
.is_some_and(|row_info| row_info.expand_info.is_some())
|
||||||
|
@ -1963,11 +1963,13 @@ impl EditorElement {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let row =
|
||||||
|
MultiBufferRow(DisplayPoint::new(display_row, 0).to_point(&snapshot).row);
|
||||||
if snapshot.is_line_folded(row) {
|
if snapshot.is_line_folded(row) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let button = editor.render_breakpoint(text_anchor, display_row, &bp.kind, cx);
|
let button = editor.render_breakpoint(text_anchor, display_row, &bp, cx);
|
||||||
|
|
||||||
let button = prepaint_gutter_button(
|
let button = prepaint_gutter_button(
|
||||||
button,
|
button,
|
||||||
|
@ -2065,6 +2067,7 @@ impl EditorElement {
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let button = editor.render_run_indicator(
|
let button = editor.render_run_indicator(
|
||||||
&self.style,
|
&self.style,
|
||||||
Some(display_row) == active_task_indicator_row,
|
Some(display_row) == active_task_indicator_row,
|
||||||
|
@ -6827,9 +6830,7 @@ impl Element for EditorElement {
|
||||||
gutter_breakpoint_point,
|
gutter_breakpoint_point,
|
||||||
Bias::Left,
|
Bias::Left,
|
||||||
);
|
);
|
||||||
let breakpoint = Breakpoint {
|
let breakpoint = Breakpoint::new_standard();
|
||||||
kind: BreakpointKind::Standard,
|
|
||||||
};
|
|
||||||
|
|
||||||
(position, breakpoint)
|
(position, breakpoint)
|
||||||
});
|
});
|
||||||
|
|
|
@ -256,6 +256,21 @@ impl BreakpointStore {
|
||||||
breakpoint_set.breakpoints.push(breakpoint.clone());
|
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) => {
|
BreakpointEditAction::EditLogMessage(log_message) => {
|
||||||
if !log_message.is_empty() {
|
if !log_message.is_empty() {
|
||||||
breakpoint.1.kind = BreakpointKind::Log(log_message.clone());
|
breakpoint.1.kind = BreakpointKind::Log(log_message.clone());
|
||||||
|
@ -351,7 +366,7 @@ impl BreakpointStore {
|
||||||
&'a self,
|
&'a self,
|
||||||
buffer: &'a Entity<Buffer>,
|
buffer: &'a Entity<Buffer>,
|
||||||
range: Option<Range<text::Anchor>>,
|
range: Option<Range<text::Anchor>>,
|
||||||
buffer_snapshot: BufferSnapshot,
|
buffer_snapshot: &'a BufferSnapshot,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> impl Iterator<Item = &'a (text::Anchor, Breakpoint)> + 'a {
|
) -> impl Iterator<Item = &'a (text::Anchor, Breakpoint)> + 'a {
|
||||||
let abs_path = Self::abs_path_from_buffer(buffer, cx);
|
let abs_path = Self::abs_path_from_buffer(buffer, cx);
|
||||||
|
@ -361,11 +376,10 @@ impl BreakpointStore {
|
||||||
.flat_map(move |file_breakpoints| {
|
.flat_map(move |file_breakpoints| {
|
||||||
file_breakpoints.breakpoints.iter().filter({
|
file_breakpoints.breakpoints.iter().filter({
|
||||||
let range = range.clone();
|
let range = range.clone();
|
||||||
let buffer_snapshot = buffer_snapshot.clone();
|
|
||||||
move |(position, _)| {
|
move |(position, _)| {
|
||||||
if let Some(range) = &range {
|
if let Some(range) = &range {
|
||||||
position.cmp(&range.start, &buffer_snapshot).is_ge()
|
position.cmp(&range.start, buffer_snapshot).is_ge()
|
||||||
&& position.cmp(&range.end, &buffer_snapshot).is_le()
|
&& position.cmp(&range.end, buffer_snapshot).is_le()
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -417,6 +431,7 @@ impl BreakpointStore {
|
||||||
position,
|
position,
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
kind: breakpoint.kind.clone(),
|
kind: breakpoint.kind.clone(),
|
||||||
|
state: breakpoint.state,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -439,6 +454,7 @@ impl BreakpointStore {
|
||||||
position,
|
position,
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
kind: breakpoint.kind.clone(),
|
kind: breakpoint.kind.clone(),
|
||||||
|
state: breakpoint.state,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
@ -487,9 +503,13 @@ impl BreakpointStore {
|
||||||
|
|
||||||
for bp in bps {
|
for bp in bps {
|
||||||
let position = snapshot.anchor_before(PointUtf16::new(bp.position, 0));
|
let position = snapshot.anchor_before(PointUtf16::new(bp.position, 0));
|
||||||
breakpoints_for_file
|
breakpoints_for_file.breakpoints.push((
|
||||||
.breakpoints
|
position,
|
||||||
.push((position, Breakpoint { kind: bp.kind }))
|
Breakpoint {
|
||||||
|
kind: bp.kind,
|
||||||
|
state: bp.state,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
new_breakpoints.insert(path, breakpoints_for_file);
|
new_breakpoints.insert(path, breakpoints_for_file);
|
||||||
}
|
}
|
||||||
|
@ -530,6 +550,7 @@ type LogMessage = Arc<str>;
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum BreakpointEditAction {
|
pub enum BreakpointEditAction {
|
||||||
Toggle,
|
Toggle,
|
||||||
|
InvertState,
|
||||||
EditLogMessage(LogMessage),
|
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)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct Breakpoint {
|
pub struct Breakpoint {
|
||||||
pub kind: BreakpointKind,
|
pub kind: BreakpointKind,
|
||||||
|
pub state: BreakpointState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Breakpoint {
|
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> {
|
fn to_proto(&self, _path: &Path, position: &text::Anchor) -> Option<client::proto::Breakpoint> {
|
||||||
Some(client::proto::Breakpoint {
|
Some(client::proto::Breakpoint {
|
||||||
position: Some(serialize_text_anchor(position)),
|
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 {
|
kind: match self.kind {
|
||||||
BreakpointKind::Standard => proto::BreakpointKind::Standard.into(),
|
BreakpointKind::Standard => proto::BreakpointKind::Standard.into(),
|
||||||
BreakpointKind::Log(_) => proto::BreakpointKind::Log.into(),
|
BreakpointKind::Log(_) => proto::BreakpointKind::Log.into(),
|
||||||
|
@ -599,8 +664,22 @@ impl Breakpoint {
|
||||||
}
|
}
|
||||||
None | Some(proto::BreakpointKind::Standard) => BreakpointKind::Standard,
|
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)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
@ -608,6 +687,7 @@ pub struct SerializedBreakpoint {
|
||||||
pub position: u32,
|
pub position: u32,
|
||||||
pub path: Arc<Path>,
|
pub path: Arc<Path>,
|
||||||
pub kind: BreakpointKind,
|
pub kind: BreakpointKind,
|
||||||
|
pub state: BreakpointState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SerializedBreakpoint> for dap::SourceBreakpoint {
|
impl From<SerializedBreakpoint> for dap::SourceBreakpoint {
|
||||||
|
|
|
@ -358,6 +358,7 @@ impl LocalMode {
|
||||||
.breakpoint_store
|
.breakpoint_store
|
||||||
.read_with(cx, |store, cx| store.breakpoints_from_path(&abs_path, cx))
|
.read_with(cx, |store, cx| store.breakpoints_from_path(&abs_path, cx))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
.filter(|bp| bp.state.is_enabled())
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -388,7 +389,11 @@ impl LocalMode {
|
||||||
let breakpoints = if ignore_breakpoints {
|
let breakpoints = if ignore_breakpoints {
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} 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(
|
breakpoint_tasks.push(self.request(
|
||||||
|
|
|
@ -2640,9 +2640,15 @@ enum BreakpointKind {
|
||||||
Log = 1;
|
Log = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum BreakpointState {
|
||||||
|
Enabled = 0;
|
||||||
|
Disabled = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
message Breakpoint {
|
message Breakpoint {
|
||||||
Anchor position = 1;
|
Anchor position = 1;
|
||||||
|
BreakpointState state = 2;
|
||||||
BreakpointKind kind = 3;
|
BreakpointKind kind = 3;
|
||||||
optional string message = 4;
|
optional string message = 4;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use crate::connection::Connection;
|
||||||
pub struct Statement<'a> {
|
pub struct Statement<'a> {
|
||||||
/// vector of pointers to the raw SQLite statement objects.
|
/// vector of pointers to the raw SQLite statement objects.
|
||||||
/// it holds the actual prepared statements that will be executed.
|
/// it holds the actual prepared statements that will be executed.
|
||||||
raw_statements: Vec<*mut sqlite3_stmt>,
|
pub raw_statements: Vec<*mut sqlite3_stmt>,
|
||||||
/// Index of the current statement being executed from the `raw_statements` vector.
|
/// Index of the current statement being executed from the `raw_statements` vector.
|
||||||
current_statement: usize,
|
current_statement: usize,
|
||||||
/// A reference to the database connection.
|
/// A reference to the database connection.
|
||||||
|
|
|
@ -13,7 +13,7 @@ use client::DevServerProjectId;
|
||||||
use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
|
use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
|
||||||
use gpui::{point, size, Axis, Bounds, WindowBounds, WindowId};
|
use gpui::{point, size, Axis, Bounds, WindowBounds, WindowId};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use project::debugger::breakpoint_store::{BreakpointKind, SerializedBreakpoint};
|
use project::debugger::breakpoint_store::{BreakpointKind, BreakpointState, SerializedBreakpoint};
|
||||||
|
|
||||||
use language::{LanguageName, Toolchain};
|
use language::{LanguageName, Toolchain};
|
||||||
use project::WorktreeId;
|
use project::WorktreeId;
|
||||||
|
@ -148,9 +148,43 @@ impl Column for SerializedWindowBounds {
|
||||||
pub struct Breakpoint {
|
pub struct Breakpoint {
|
||||||
pub position: u32,
|
pub position: u32,
|
||||||
pub kind: BreakpointKind,
|
pub kind: BreakpointKind,
|
||||||
|
pub state: BreakpointState,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper for DB type of a breakpoint
|
/// Wrapper for DB type of a breakpoint
|
||||||
|
struct BreakpointStateWrapper<'a>(Cow<'a, BreakpointState>);
|
||||||
|
|
||||||
|
impl From<BreakpointState> for BreakpointStateWrapper<'static> {
|
||||||
|
fn from(kind: BreakpointState) -> Self {
|
||||||
|
BreakpointStateWrapper(Cow::Owned(kind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl StaticColumnCount for BreakpointStateWrapper<'_> {
|
||||||
|
fn column_count() -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bind for BreakpointStateWrapper<'_> {
|
||||||
|
fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
|
||||||
|
statement.bind(&self.0.to_int(), start_index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Column for BreakpointStateWrapper<'_> {
|
||||||
|
fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
|
||||||
|
let state = statement.column_int(start_index)?;
|
||||||
|
|
||||||
|
match state {
|
||||||
|
0 => Ok((BreakpointState::Enabled.into(), start_index + 1)),
|
||||||
|
1 => Ok((BreakpointState::Disabled.into(), start_index + 1)),
|
||||||
|
_ => Err(anyhow::anyhow!("Invalid BreakpointState discriminant")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper for DB type of a breakpoint
|
||||||
|
#[derive(Debug)]
|
||||||
struct BreakpointKindWrapper<'a>(Cow<'a, BreakpointKind>);
|
struct BreakpointKindWrapper<'a>(Cow<'a, BreakpointKind>);
|
||||||
|
|
||||||
impl From<BreakpointKind> for BreakpointKindWrapper<'static> {
|
impl From<BreakpointKind> for BreakpointKindWrapper<'static> {
|
||||||
|
@ -200,7 +234,7 @@ struct Breakpoints(Vec<Breakpoint>);
|
||||||
|
|
||||||
impl sqlez::bindable::StaticColumnCount for Breakpoint {
|
impl sqlez::bindable::StaticColumnCount for Breakpoint {
|
||||||
fn column_count() -> usize {
|
fn column_count() -> usize {
|
||||||
1 + BreakpointKindWrapper::column_count()
|
1 + BreakpointKindWrapper::column_count() + BreakpointStateWrapper::column_count()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,9 +245,13 @@ impl sqlez::bindable::Bind for Breakpoint {
|
||||||
start_index: i32,
|
start_index: i32,
|
||||||
) -> anyhow::Result<i32> {
|
) -> anyhow::Result<i32> {
|
||||||
let next_index = statement.bind(&self.position, start_index)?;
|
let next_index = statement.bind(&self.position, start_index)?;
|
||||||
statement.bind(
|
let next_index = statement.bind(
|
||||||
&BreakpointKindWrapper(Cow::Borrowed(&self.kind)),
|
&BreakpointKindWrapper(Cow::Borrowed(&self.kind)),
|
||||||
next_index,
|
next_index,
|
||||||
|
)?;
|
||||||
|
statement.bind(
|
||||||
|
&BreakpointStateWrapper(Cow::Borrowed(&self.state)),
|
||||||
|
next_index,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,11 +263,13 @@ impl Column for Breakpoint {
|
||||||
.with_context(|| format!("Failed to read BreakPoint at index {start_index}"))?
|
.with_context(|| format!("Failed to read BreakPoint at index {start_index}"))?
|
||||||
as u32;
|
as u32;
|
||||||
let (kind, next_index) = BreakpointKindWrapper::column(statement, start_index + 1)?;
|
let (kind, next_index) = BreakpointKindWrapper::column(statement, start_index + 1)?;
|
||||||
|
let (state, next_index) = BreakpointStateWrapper::column(statement, next_index)?;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
Breakpoint {
|
Breakpoint {
|
||||||
position,
|
position,
|
||||||
kind: kind.0.into_owned(),
|
kind: kind.0.into_owned(),
|
||||||
|
state: state.0.into_owned(),
|
||||||
},
|
},
|
||||||
next_index,
|
next_index,
|
||||||
))
|
))
|
||||||
|
@ -245,16 +285,9 @@ impl Column for Breakpoints {
|
||||||
match statement.column_type(index) {
|
match statement.column_type(index) {
|
||||||
Ok(SqlType::Null) => break,
|
Ok(SqlType::Null) => break,
|
||||||
_ => {
|
_ => {
|
||||||
let position = statement
|
let (breakpoint, next_index) = Breakpoint::column(statement, index)?;
|
||||||
.column_int(index)
|
|
||||||
.with_context(|| format!("Failed to read BreakPoint at index {index}"))?
|
|
||||||
as u32;
|
|
||||||
let (kind, next_index) = BreakpointKindWrapper::column(statement, index + 1)?;
|
|
||||||
|
|
||||||
breakpoints.push(Breakpoint {
|
breakpoints.push(breakpoint);
|
||||||
position,
|
|
||||||
kind: kind.0.into_owned(),
|
|
||||||
});
|
|
||||||
index = next_index;
|
index = next_index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -535,6 +568,9 @@ define_connection! {
|
||||||
CREATE UNIQUE INDEX local_paths_array_uq ON workspaces(local_paths_array);
|
CREATE UNIQUE INDEX local_paths_array_uq ON workspaces(local_paths_array);
|
||||||
ALTER TABLE workspaces ADD COLUMN local_paths_order_array TEXT;
|
ALTER TABLE workspaces ADD COLUMN local_paths_order_array TEXT;
|
||||||
),
|
),
|
||||||
|
sql!(
|
||||||
|
ALTER TABLE breakpoints ADD COLUMN state INTEGER DEFAULT(0) NOT NULL
|
||||||
|
)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -690,7 +726,7 @@ impl WorkspaceDb {
|
||||||
) -> BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>> {
|
) -> BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>> {
|
||||||
let breakpoints: Result<Vec<(PathBuf, Breakpoint)>> = self
|
let breakpoints: Result<Vec<(PathBuf, Breakpoint)>> = self
|
||||||
.select_bound(sql! {
|
.select_bound(sql! {
|
||||||
SELECT path, breakpoint_location, kind
|
SELECT path, breakpoint_location, kind, log_message, state
|
||||||
FROM breakpoints
|
FROM breakpoints
|
||||||
WHERE workspace_id = ?
|
WHERE workspace_id = ?
|
||||||
})
|
})
|
||||||
|
@ -712,6 +748,7 @@ impl WorkspaceDb {
|
||||||
position: breakpoint.position,
|
position: breakpoint.position,
|
||||||
path,
|
path,
|
||||||
kind: breakpoint.kind,
|
kind: breakpoint.kind,
|
||||||
|
state: breakpoint.state,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -739,15 +776,17 @@ impl WorkspaceDb {
|
||||||
.context("Clearing old breakpoints")?;
|
.context("Clearing old breakpoints")?;
|
||||||
for bp in breakpoints {
|
for bp in breakpoints {
|
||||||
let kind = BreakpointKindWrapper::from(bp.kind);
|
let kind = BreakpointKindWrapper::from(bp.kind);
|
||||||
|
let state = BreakpointStateWrapper::from(bp.state);
|
||||||
match conn.exec_bound(sql!(
|
match conn.exec_bound(sql!(
|
||||||
INSERT INTO breakpoints (workspace_id, path, breakpoint_location, kind, log_message)
|
INSERT INTO breakpoints (workspace_id, path, breakpoint_location, kind, log_message, state)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5);))?
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6);))?
|
||||||
|
|
||||||
((
|
((
|
||||||
workspace.id,
|
workspace.id,
|
||||||
path.as_ref(),
|
path.as_ref(),
|
||||||
bp.position,
|
bp.position,
|
||||||
kind,
|
kind,
|
||||||
|
state,
|
||||||
)) {
|
)) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -1415,11 +1454,19 @@ mod tests {
|
||||||
let breakpoint = Breakpoint {
|
let breakpoint = Breakpoint {
|
||||||
position: 123,
|
position: 123,
|
||||||
kind: BreakpointKind::Standard,
|
kind: BreakpointKind::Standard,
|
||||||
|
state: BreakpointState::Enabled,
|
||||||
};
|
};
|
||||||
|
|
||||||
let log_breakpoint = Breakpoint {
|
let log_breakpoint = Breakpoint {
|
||||||
position: 456,
|
position: 456,
|
||||||
kind: BreakpointKind::Log("Test log message".into()),
|
kind: BreakpointKind::Log("Test log message".into()),
|
||||||
|
state: BreakpointState::Enabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
let disable_breakpoint = Breakpoint {
|
||||||
|
position: 578,
|
||||||
|
kind: BreakpointKind::Standard,
|
||||||
|
state: BreakpointState::Disabled,
|
||||||
};
|
};
|
||||||
|
|
||||||
let workspace = SerializedWorkspace {
|
let workspace = SerializedWorkspace {
|
||||||
|
@ -1439,11 +1486,19 @@ mod tests {
|
||||||
position: breakpoint.position,
|
position: breakpoint.position,
|
||||||
path: Arc::from(path),
|
path: Arc::from(path),
|
||||||
kind: breakpoint.kind.clone(),
|
kind: breakpoint.kind.clone(),
|
||||||
|
state: breakpoint.state,
|
||||||
},
|
},
|
||||||
SerializedBreakpoint {
|
SerializedBreakpoint {
|
||||||
position: log_breakpoint.position,
|
position: log_breakpoint.position,
|
||||||
path: Arc::from(path),
|
path: Arc::from(path),
|
||||||
kind: log_breakpoint.kind.clone(),
|
kind: log_breakpoint.kind.clone(),
|
||||||
|
state: log_breakpoint.state,
|
||||||
|
},
|
||||||
|
SerializedBreakpoint {
|
||||||
|
position: disable_breakpoint.position,
|
||||||
|
path: Arc::from(path),
|
||||||
|
kind: disable_breakpoint.kind.clone(),
|
||||||
|
state: disable_breakpoint.state,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -1458,13 +1513,22 @@ mod tests {
|
||||||
let loaded = db.workspace_for_roots(&["/tmp"]).unwrap();
|
let loaded = db.workspace_for_roots(&["/tmp"]).unwrap();
|
||||||
let loaded_breakpoints = loaded.breakpoints.get(&Arc::from(path)).unwrap();
|
let loaded_breakpoints = loaded.breakpoints.get(&Arc::from(path)).unwrap();
|
||||||
|
|
||||||
assert_eq!(loaded_breakpoints.len(), 2);
|
assert_eq!(loaded_breakpoints.len(), 3);
|
||||||
|
|
||||||
assert_eq!(loaded_breakpoints[0].position, breakpoint.position);
|
assert_eq!(loaded_breakpoints[0].position, breakpoint.position);
|
||||||
assert_eq!(loaded_breakpoints[0].kind, breakpoint.kind);
|
assert_eq!(loaded_breakpoints[0].kind, breakpoint.kind);
|
||||||
|
assert_eq!(loaded_breakpoints[0].state, breakpoint.state);
|
||||||
|
assert_eq!(loaded_breakpoints[0].path, Arc::from(path));
|
||||||
|
|
||||||
assert_eq!(loaded_breakpoints[1].position, log_breakpoint.position);
|
assert_eq!(loaded_breakpoints[1].position, log_breakpoint.position);
|
||||||
assert_eq!(loaded_breakpoints[1].kind, log_breakpoint.kind);
|
assert_eq!(loaded_breakpoints[1].kind, log_breakpoint.kind);
|
||||||
assert_eq!(loaded_breakpoints[0].path, Arc::from(path));
|
assert_eq!(loaded_breakpoints[1].state, log_breakpoint.state);
|
||||||
assert_eq!(loaded_breakpoints[1].path, Arc::from(path));
|
assert_eq!(loaded_breakpoints[1].path, Arc::from(path));
|
||||||
|
|
||||||
|
assert_eq!(loaded_breakpoints[2].position, disable_breakpoint.position);
|
||||||
|
assert_eq!(loaded_breakpoints[2].kind, disable_breakpoint.kind);
|
||||||
|
assert_eq!(loaded_breakpoints[2].state, disable_breakpoint.state);
|
||||||
|
assert_eq!(loaded_breakpoints[2].path, Arc::from(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue