Debugger: Add conditional and hit conditional breakpoint functionality (#27760)
This PR adds conditional and hit condition breakpoint functionality cc @osiewicz Co-authored-by: Remco Smits: <djsmits12@gmail.com> Release Notes: - N/A *or* Added/Fixed/Improved ... --------- Co-authored-by: Remco Smits <djsmits12@gmail.com>
This commit is contained in:
parent
dc64ec9cc8
commit
d517a212dc
6 changed files with 368 additions and 50 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
use feature_flags::{Debugger, FeatureFlagAppExt as _};
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, BackgroundExecutor, Entity, Focusable, FontWeight, ListSizingBehavior,
|
AnyElement, BackgroundExecutor, Entity, Focusable, FontWeight, ListSizingBehavior,
|
||||||
|
@ -992,6 +993,17 @@ impl CodeActionsMenu {
|
||||||
.iter()
|
.iter()
|
||||||
.skip(range.start)
|
.skip(range.start)
|
||||||
.take(range.end - range.start)
|
.take(range.end - range.start)
|
||||||
|
.filter(|action| {
|
||||||
|
if action
|
||||||
|
.as_task()
|
||||||
|
.map(|task| matches!(task.task_type(), task::TaskType::Debug(_)))
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
cx.has_flag::<Debugger>()
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(ix, action)| {
|
.map(|(ix, action)| {
|
||||||
let item_ix = range.start + ix;
|
let item_ix = range.start + ix;
|
||||||
|
|
|
@ -4798,6 +4798,8 @@ impl Editor {
|
||||||
Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
|
Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let debugger_flag = cx.has_flag::<Debugger>();
|
||||||
|
|
||||||
Some(cx.spawn_in(window, async move |editor, cx| {
|
Some(cx.spawn_in(window, async move |editor, cx| {
|
||||||
let task_context = match task_context {
|
let task_context = match task_context {
|
||||||
Some(task_context) => task_context.await,
|
Some(task_context) => task_context.await,
|
||||||
|
@ -4813,10 +4815,20 @@ impl Editor {
|
||||||
)),
|
)),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let spawn_straight_away = resolved_tasks
|
let spawn_straight_away = resolved_tasks.as_ref().map_or(false, |tasks| {
|
||||||
.as_ref()
|
tasks
|
||||||
.map_or(false, |tasks| tasks.templates.len() == 1)
|
.templates
|
||||||
&& code_actions
|
.iter()
|
||||||
|
.filter(|task| {
|
||||||
|
if matches!(task.1.task_type(), task::TaskType::Debug(_)) {
|
||||||
|
debugger_flag
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
== 1
|
||||||
|
}) && code_actions
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(true, |actions| actions.is_empty());
|
.map_or(true, |actions| actions.is_empty());
|
||||||
if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
|
if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
|
||||||
|
@ -6292,6 +6304,22 @@ impl Editor {
|
||||||
"Set Log Breakpoint"
|
"Set Log Breakpoint"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let condition_breakpoint_msg =
|
||||||
|
if breakpoint.as_ref().is_some_and(|bp| bp.condition.is_some()) {
|
||||||
|
"Edit Condition Breakpoint"
|
||||||
|
} else {
|
||||||
|
"Set Condition Breakpoint"
|
||||||
|
};
|
||||||
|
|
||||||
|
let hit_condition_breakpoint_msg = if breakpoint
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|bp| bp.hit_condition.is_some())
|
||||||
|
{
|
||||||
|
"Edit Hit Condition Breakpoint"
|
||||||
|
} else {
|
||||||
|
"Set Hit Condition Breakpoint"
|
||||||
|
};
|
||||||
|
|
||||||
let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
|
let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
|
||||||
"Unset Breakpoint"
|
"Unset Breakpoint"
|
||||||
} else {
|
} else {
|
||||||
|
@ -6303,12 +6331,7 @@ impl Editor {
|
||||||
BreakpointState::Disabled => Some("Enable"),
|
BreakpointState::Disabled => Some("Enable"),
|
||||||
});
|
});
|
||||||
|
|
||||||
let breakpoint = breakpoint.unwrap_or_else(|| {
|
let breakpoint = breakpoint.unwrap_or_else(|| Arc::new(Breakpoint::new_standard()));
|
||||||
Arc::new(Breakpoint {
|
|
||||||
state: BreakpointState::Enabled,
|
|
||||||
message: None,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
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(|| {}))
|
||||||
|
@ -6347,10 +6370,50 @@ impl Editor {
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.entry(log_breakpoint_msg, None, move |window, cx| {
|
.entry(log_breakpoint_msg, None, {
|
||||||
|
let breakpoint = breakpoint.clone();
|
||||||
|
let weak_editor = weak_editor.clone();
|
||||||
|
move |window, cx| {
|
||||||
weak_editor
|
weak_editor
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
this.add_edit_breakpoint_block(anchor, breakpoint.as_ref(), window, cx);
|
this.add_edit_breakpoint_block(
|
||||||
|
anchor,
|
||||||
|
breakpoint.as_ref(),
|
||||||
|
BreakpointPromptEditAction::Log,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.entry(condition_breakpoint_msg, None, {
|
||||||
|
let breakpoint = breakpoint.clone();
|
||||||
|
let weak_editor = weak_editor.clone();
|
||||||
|
move |window, cx| {
|
||||||
|
weak_editor
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.add_edit_breakpoint_block(
|
||||||
|
anchor,
|
||||||
|
breakpoint.as_ref(),
|
||||||
|
BreakpointPromptEditAction::Condition,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.entry(hit_condition_breakpoint_msg, None, move |window, cx| {
|
||||||
|
weak_editor
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.add_edit_breakpoint_block(
|
||||||
|
anchor,
|
||||||
|
breakpoint.as_ref(),
|
||||||
|
BreakpointPromptEditAction::HitCondition,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
})
|
})
|
||||||
|
@ -8597,12 +8660,20 @@ impl Editor {
|
||||||
&mut self,
|
&mut self,
|
||||||
anchor: Anchor,
|
anchor: Anchor,
|
||||||
breakpoint: &Breakpoint,
|
breakpoint: &Breakpoint,
|
||||||
|
edit_action: BreakpointPromptEditAction,
|
||||||
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 = cx.new(|cx| {
|
let bp_prompt = cx.new(|cx| {
|
||||||
BreakpointPromptEditor::new(weak_editor, anchor, breakpoint.clone(), window, cx)
|
BreakpointPromptEditor::new(
|
||||||
|
weak_editor,
|
||||||
|
anchor,
|
||||||
|
breakpoint.clone(),
|
||||||
|
edit_action,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let height = bp_prompt.update(cx, |this, cx| {
|
let height = bp_prompt.update(cx, |this, cx| {
|
||||||
|
@ -8721,11 +8792,13 @@ impl Editor {
|
||||||
Breakpoint {
|
Breakpoint {
|
||||||
message: None,
|
message: None,
|
||||||
state: BreakpointState::Enabled,
|
state: BreakpointState::Enabled,
|
||||||
|
condition: None,
|
||||||
|
hit_condition: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
self.add_edit_breakpoint_block(anchor, &bp, window, cx);
|
self.add_edit_breakpoint_block(anchor, &bp, BreakpointPromptEditAction::Log, window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enable_breakpoint(
|
pub fn enable_breakpoint(
|
||||||
|
@ -19861,11 +19934,18 @@ impl Global for KillRing {}
|
||||||
|
|
||||||
const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
|
const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
|
||||||
|
|
||||||
|
enum BreakpointPromptEditAction {
|
||||||
|
Log,
|
||||||
|
Condition,
|
||||||
|
HitCondition,
|
||||||
|
}
|
||||||
|
|
||||||
struct BreakpointPromptEditor {
|
struct BreakpointPromptEditor {
|
||||||
pub(crate) prompt: Entity<Editor>,
|
pub(crate) prompt: Entity<Editor>,
|
||||||
editor: WeakEntity<Editor>,
|
editor: WeakEntity<Editor>,
|
||||||
breakpoint_anchor: Anchor,
|
breakpoint_anchor: Anchor,
|
||||||
breakpoint: Breakpoint,
|
breakpoint: Breakpoint,
|
||||||
|
edit_action: BreakpointPromptEditAction,
|
||||||
block_ids: HashSet<CustomBlockId>,
|
block_ids: HashSet<CustomBlockId>,
|
||||||
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
|
@ -19878,19 +19958,19 @@ impl BreakpointPromptEditor {
|
||||||
editor: WeakEntity<Editor>,
|
editor: WeakEntity<Editor>,
|
||||||
breakpoint_anchor: Anchor,
|
breakpoint_anchor: Anchor,
|
||||||
breakpoint: Breakpoint,
|
breakpoint: Breakpoint,
|
||||||
|
edit_action: BreakpointPromptEditAction,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let buffer = cx.new(|cx| {
|
let base_text = match edit_action {
|
||||||
Buffer::local(
|
BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
|
||||||
breakpoint
|
BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
|
||||||
.message
|
BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
|
||||||
.as_ref()
|
}
|
||||||
.map(|msg| msg.to_string())
|
.map(|msg| msg.to_string())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default();
|
||||||
cx,
|
|
||||||
)
|
let buffer = cx.new(|cx| Buffer::local(base_text, cx));
|
||||||
});
|
|
||||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||||
|
|
||||||
let prompt = cx.new(|cx| {
|
let prompt = cx.new(|cx| {
|
||||||
|
@ -19906,7 +19986,11 @@ impl BreakpointPromptEditor {
|
||||||
prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
|
prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
|
||||||
prompt.set_show_cursor_when_unfocused(false, cx);
|
prompt.set_show_cursor_when_unfocused(false, cx);
|
||||||
prompt.set_placeholder_text(
|
prompt.set_placeholder_text(
|
||||||
"Message to log when breakpoint is hit. Expressions within {} are interpolated.",
|
match edit_action {
|
||||||
|
BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
|
||||||
|
BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
|
||||||
|
BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
|
||||||
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -19918,6 +20002,7 @@ impl BreakpointPromptEditor {
|
||||||
editor,
|
editor,
|
||||||
breakpoint_anchor,
|
breakpoint_anchor,
|
||||||
breakpoint,
|
breakpoint,
|
||||||
|
edit_action,
|
||||||
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![],
|
||||||
|
@ -19930,7 +20015,7 @@ impl BreakpointPromptEditor {
|
||||||
|
|
||||||
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
if let Some(editor) = self.editor.upgrade() {
|
if let Some(editor) = self.editor.upgrade() {
|
||||||
let log_message = self
|
let message = self
|
||||||
.prompt
|
.prompt
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.buffer
|
.buffer
|
||||||
|
@ -19945,7 +20030,17 @@ impl BreakpointPromptEditor {
|
||||||
editor.edit_breakpoint_at_anchor(
|
editor.edit_breakpoint_at_anchor(
|
||||||
self.breakpoint_anchor,
|
self.breakpoint_anchor,
|
||||||
self.breakpoint.clone(),
|
self.breakpoint.clone(),
|
||||||
BreakpointEditAction::EditLogMessage(log_message.into()),
|
match self.edit_action {
|
||||||
|
BreakpointPromptEditAction::Log => {
|
||||||
|
BreakpointEditAction::EditLogMessage(message.into())
|
||||||
|
}
|
||||||
|
BreakpointPromptEditAction::Condition => {
|
||||||
|
BreakpointEditAction::EditCondition(message.into())
|
||||||
|
}
|
||||||
|
BreakpointPromptEditAction::HitCondition => {
|
||||||
|
BreakpointEditAction::EditHitCondition(message.into())
|
||||||
|
}
|
||||||
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -17387,6 +17387,8 @@ fn assert_breakpoint(
|
||||||
Breakpoint {
|
Breakpoint {
|
||||||
message: breakpoint.message.clone(),
|
message: breakpoint.message.clone(),
|
||||||
state: breakpoint.state,
|
state: breakpoint.state,
|
||||||
|
condition: breakpoint.condition.clone(),
|
||||||
|
hit_condition: breakpoint.hit_condition.clone(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -17415,13 +17417,7 @@ fn add_log_breakpoint_at_cursor(
|
||||||
.buffer_snapshot
|
.buffer_snapshot
|
||||||
.anchor_before(Point::new(cursor_position.row, 0));
|
.anchor_before(Point::new(cursor_position.row, 0));
|
||||||
|
|
||||||
(
|
(breakpoint_position, Breakpoint::new_log(&log_message))
|
||||||
breakpoint_position,
|
|
||||||
Breakpoint {
|
|
||||||
message: Some(Arc::from(log_message)),
|
|
||||||
state: BreakpointState::Enabled,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.edit_breakpoint_at_anchor(
|
editor.edit_breakpoint_at_anchor(
|
||||||
|
|
|
@ -294,6 +294,60 @@ 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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(found_bp) = found_bp {
|
||||||
|
found_bp.hit_condition = Some(hit_condition.clone());
|
||||||
|
} else {
|
||||||
|
breakpoint.1.hit_condition = Some(hit_condition.clone());
|
||||||
|
// We did not remove any breakpoint, hence let's toggle one.
|
||||||
|
breakpoint_set.breakpoints.push(breakpoint.clone());
|
||||||
|
}
|
||||||
|
} else if breakpoint.1.hit_condition.is_some() {
|
||||||
|
breakpoint_set.breakpoints.retain(|(other_pos, other)| {
|
||||||
|
&breakpoint.0 != other_pos && other.hit_condition.is_none()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(found_bp) = found_bp {
|
||||||
|
found_bp.condition = Some(condition.clone());
|
||||||
|
} else {
|
||||||
|
breakpoint.1.condition = Some(condition.clone());
|
||||||
|
// We did not remove any breakpoint, hence let's toggle one.
|
||||||
|
breakpoint_set.breakpoints.push(breakpoint.clone());
|
||||||
|
}
|
||||||
|
} else if breakpoint.1.condition.is_some() {
|
||||||
|
breakpoint_set.breakpoints.retain(|(other_pos, other)| {
|
||||||
|
&breakpoint.0 != other_pos && other.condition.is_none()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if breakpoint_set.breakpoints.is_empty() {
|
if breakpoint_set.breakpoints.is_empty() {
|
||||||
|
@ -424,6 +478,8 @@ impl BreakpointStore {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
state: breakpoint.state,
|
state: breakpoint.state,
|
||||||
message: breakpoint.message.clone(),
|
message: breakpoint.message.clone(),
|
||||||
|
condition: breakpoint.condition.clone(),
|
||||||
|
hit_condition: breakpoint.hit_condition.clone(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -447,6 +503,8 @@ impl BreakpointStore {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
message: breakpoint.message.clone(),
|
message: breakpoint.message.clone(),
|
||||||
state: breakpoint.state,
|
state: breakpoint.state,
|
||||||
|
hit_condition: breakpoint.hit_condition.clone(),
|
||||||
|
condition: breakpoint.condition.clone(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
@ -500,6 +558,8 @@ impl BreakpointStore {
|
||||||
Breakpoint {
|
Breakpoint {
|
||||||
message: bp.message,
|
message: bp.message,
|
||||||
state: bp.state,
|
state: bp.state,
|
||||||
|
condition: bp.condition,
|
||||||
|
hit_condition: bp.hit_condition,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -537,13 +597,15 @@ pub enum BreakpointStoreEvent {
|
||||||
|
|
||||||
impl EventEmitter<BreakpointStoreEvent> for BreakpointStore {}
|
impl EventEmitter<BreakpointStoreEvent> for BreakpointStore {}
|
||||||
|
|
||||||
type LogMessage = Arc<str>;
|
type BreakpointMessage = Arc<str>;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum BreakpointEditAction {
|
pub enum BreakpointEditAction {
|
||||||
Toggle,
|
Toggle,
|
||||||
InvertState,
|
InvertState,
|
||||||
EditLogMessage(LogMessage),
|
EditLogMessage(BreakpointMessage),
|
||||||
|
EditCondition(BreakpointMessage),
|
||||||
|
EditHitCondition(BreakpointMessage),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
|
@ -574,7 +636,10 @@ impl BreakpointState {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct Breakpoint {
|
pub struct Breakpoint {
|
||||||
pub message: Option<Arc<str>>,
|
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 condition: Option<BreakpointMessage>,
|
||||||
pub state: BreakpointState,
|
pub state: BreakpointState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -582,6 +647,17 @@ impl Breakpoint {
|
||||||
pub fn new_standard() -> Self {
|
pub fn new_standard() -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: BreakpointState::Enabled,
|
state: BreakpointState::Enabled,
|
||||||
|
hit_condition: None,
|
||||||
|
condition: None,
|
||||||
|
message: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_condition(hit_condition: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
state: BreakpointState::Enabled,
|
||||||
|
condition: None,
|
||||||
|
hit_condition: Some(hit_condition.into()),
|
||||||
message: None,
|
message: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -589,6 +665,8 @@ impl Breakpoint {
|
||||||
pub fn new_log(log_message: &str) -> Self {
|
pub fn new_log(log_message: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: BreakpointState::Enabled,
|
state: BreakpointState::Enabled,
|
||||||
|
hit_condition: None,
|
||||||
|
condition: None,
|
||||||
message: Some(log_message.into()),
|
message: Some(log_message.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -601,6 +679,11 @@ impl Breakpoint {
|
||||||
BreakpointState::Disabled => proto::BreakpointState::Disabled.into(),
|
BreakpointState::Disabled => proto::BreakpointState::Disabled.into(),
|
||||||
},
|
},
|
||||||
message: self.message.as_ref().map(|s| String::from(s.as_ref())),
|
message: self.message.as_ref().map(|s| String::from(s.as_ref())),
|
||||||
|
condition: self.condition.as_ref().map(|s| String::from(s.as_ref())),
|
||||||
|
hit_condition: self
|
||||||
|
.hit_condition
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| String::from(s.as_ref())),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -610,7 +693,9 @@ impl Breakpoint {
|
||||||
Some(proto::BreakpointState::Disabled) => BreakpointState::Disabled,
|
Some(proto::BreakpointState::Disabled) => BreakpointState::Disabled,
|
||||||
None | Some(proto::BreakpointState::Enabled) => BreakpointState::Enabled,
|
None | Some(proto::BreakpointState::Enabled) => BreakpointState::Enabled,
|
||||||
},
|
},
|
||||||
message: breakpoint.message.map(|message| message.into()),
|
message: breakpoint.message.map(Into::into),
|
||||||
|
condition: breakpoint.condition.map(Into::into),
|
||||||
|
hit_condition: breakpoint.hit_condition.map(Into::into),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -631,6 +716,8 @@ pub struct SourceBreakpoint {
|
||||||
pub row: u32,
|
pub row: u32,
|
||||||
pub path: Arc<Path>,
|
pub path: Arc<Path>,
|
||||||
pub message: Option<Arc<str>>,
|
pub message: Option<Arc<str>>,
|
||||||
|
pub condition: Option<Arc<str>>,
|
||||||
|
pub hit_condition: Option<Arc<str>>,
|
||||||
pub state: BreakpointState,
|
pub state: BreakpointState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -639,8 +726,12 @@ impl From<SourceBreakpoint> for dap::SourceBreakpoint {
|
||||||
Self {
|
Self {
|
||||||
line: bp.row as u64 + 1,
|
line: bp.row as u64 + 1,
|
||||||
column: None,
|
column: None,
|
||||||
condition: None,
|
condition: bp
|
||||||
hit_condition: None,
|
.condition
|
||||||
|
.map(|condition| String::from(condition.as_ref())),
|
||||||
|
hit_condition: bp
|
||||||
|
.hit_condition
|
||||||
|
.map(|hit_condition| String::from(hit_condition.as_ref())),
|
||||||
log_message: bp.message.map(|message| String::from(message.as_ref())),
|
log_message: bp.message.map(|message| String::from(message.as_ref())),
|
||||||
mode: None,
|
mode: None,
|
||||||
}
|
}
|
||||||
|
|
|
@ -2673,6 +2673,8 @@ message Breakpoint {
|
||||||
BreakpointState state = 2;
|
BreakpointState state = 2;
|
||||||
reserved 3;
|
reserved 3;
|
||||||
optional string message = 4;
|
optional string message = 4;
|
||||||
|
optional string condition = 5;
|
||||||
|
optional string hit_condition = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BreakpointsForFile {
|
message BreakpointsForFile {
|
||||||
|
|
|
@ -148,6 +148,8 @@ impl Column for SerializedWindowBounds {
|
||||||
pub struct Breakpoint {
|
pub struct Breakpoint {
|
||||||
pub position: u32,
|
pub position: u32,
|
||||||
pub message: Option<Arc<str>>,
|
pub message: Option<Arc<str>>,
|
||||||
|
pub condition: Option<Arc<str>>,
|
||||||
|
pub hit_condition: Option<Arc<str>>,
|
||||||
pub state: BreakpointState,
|
pub state: BreakpointState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +192,8 @@ struct Breakpoints(Vec<Breakpoint>);
|
||||||
|
|
||||||
impl sqlez::bindable::StaticColumnCount for Breakpoint {
|
impl sqlez::bindable::StaticColumnCount for Breakpoint {
|
||||||
fn column_count() -> usize {
|
fn column_count() -> usize {
|
||||||
2 + BreakpointStateWrapper::column_count()
|
// Position, log message, condition message, and hit condition message
|
||||||
|
4 + BreakpointStateWrapper::column_count()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,6 +205,8 @@ impl sqlez::bindable::Bind for Breakpoint {
|
||||||
) -> anyhow::Result<i32> {
|
) -> anyhow::Result<i32> {
|
||||||
let next_index = statement.bind(&self.position, start_index)?;
|
let next_index = statement.bind(&self.position, start_index)?;
|
||||||
let next_index = statement.bind(&self.message, next_index)?;
|
let next_index = statement.bind(&self.message, next_index)?;
|
||||||
|
let next_index = statement.bind(&self.condition, next_index)?;
|
||||||
|
let next_index = statement.bind(&self.hit_condition, next_index)?;
|
||||||
statement.bind(
|
statement.bind(
|
||||||
&BreakpointStateWrapper(Cow::Borrowed(&self.state)),
|
&BreakpointStateWrapper(Cow::Borrowed(&self.state)),
|
||||||
next_index,
|
next_index,
|
||||||
|
@ -216,12 +221,16 @@ 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 (message, next_index) = Option::<String>::column(statement, start_index + 1)?;
|
let (message, next_index) = Option::<String>::column(statement, start_index + 1)?;
|
||||||
|
let (condition, next_index) = Option::<String>::column(statement, next_index)?;
|
||||||
|
let (hit_condition, next_index) = Option::<String>::column(statement, next_index)?;
|
||||||
let (state, next_index) = BreakpointStateWrapper::column(statement, next_index)?;
|
let (state, next_index) = BreakpointStateWrapper::column(statement, next_index)?;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
Breakpoint {
|
Breakpoint {
|
||||||
position,
|
position,
|
||||||
message: message.map(Arc::from),
|
message: message.map(Arc::from),
|
||||||
|
condition: condition.map(Arc::from),
|
||||||
|
hit_condition: hit_condition.map(Arc::from),
|
||||||
state: state.0.into_owned(),
|
state: state.0.into_owned(),
|
||||||
},
|
},
|
||||||
next_index,
|
next_index,
|
||||||
|
@ -527,7 +536,11 @@ define_connection! {
|
||||||
sql!(
|
sql!(
|
||||||
ALTER TABLE breakpoints DROP COLUMN kind
|
ALTER TABLE breakpoints DROP COLUMN kind
|
||||||
),
|
),
|
||||||
sql!(ALTER TABLE toolchains ADD COLUMN relative_worktree_path TEXT DEFAULT "" NOT NULL)
|
sql!(ALTER TABLE toolchains ADD COLUMN relative_worktree_path TEXT DEFAULT "" NOT NULL),
|
||||||
|
sql!(
|
||||||
|
ALTER TABLE breakpoints ADD COLUMN condition TEXT;
|
||||||
|
ALTER TABLE breakpoints ADD COLUMN hit_condition TEXT;
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -680,7 +693,7 @@ impl WorkspaceDb {
|
||||||
fn breakpoints(&self, workspace_id: WorkspaceId) -> BTreeMap<Arc<Path>, Vec<SourceBreakpoint>> {
|
fn breakpoints(&self, workspace_id: WorkspaceId) -> BTreeMap<Arc<Path>, Vec<SourceBreakpoint>> {
|
||||||
let breakpoints: Result<Vec<(PathBuf, Breakpoint)>> = self
|
let breakpoints: Result<Vec<(PathBuf, Breakpoint)>> = self
|
||||||
.select_bound(sql! {
|
.select_bound(sql! {
|
||||||
SELECT path, breakpoint_location, log_message, state
|
SELECT path, breakpoint_location, log_message, condition, hit_condition, state
|
||||||
FROM breakpoints
|
FROM breakpoints
|
||||||
WHERE workspace_id = ?
|
WHERE workspace_id = ?
|
||||||
})
|
})
|
||||||
|
@ -700,10 +713,20 @@ impl WorkspaceDb {
|
||||||
row: breakpoint.position,
|
row: breakpoint.position,
|
||||||
path,
|
path,
|
||||||
message: breakpoint.message,
|
message: breakpoint.message,
|
||||||
|
condition: breakpoint.condition,
|
||||||
|
hit_condition: breakpoint.hit_condition,
|
||||||
state: breakpoint.state,
|
state: breakpoint.state,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (path, bps) in map.iter() {
|
||||||
|
log::debug!(
|
||||||
|
"Got {} breakpoints from path: {}",
|
||||||
|
bps.len(),
|
||||||
|
path.to_string_lossy()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
map
|
map
|
||||||
}
|
}
|
||||||
Err(msg) => {
|
Err(msg) => {
|
||||||
|
@ -727,20 +750,23 @@ impl WorkspaceDb {
|
||||||
conn.exec_bound(sql!(DELETE FROM breakpoints WHERE workspace_id = ?1 AND path = ?2))?((workspace.id, path.as_ref()))
|
conn.exec_bound(sql!(DELETE FROM breakpoints WHERE workspace_id = ?1 AND path = ?2))?((workspace.id, path.as_ref()))
|
||||||
.context("Clearing old breakpoints")?;
|
.context("Clearing old breakpoints")?;
|
||||||
for bp in breakpoints {
|
for bp in breakpoints {
|
||||||
let message = bp.message;
|
|
||||||
let state = BreakpointStateWrapper::from(bp.state);
|
let state = BreakpointStateWrapper::from(bp.state);
|
||||||
match conn.exec_bound(sql!(
|
match conn.exec_bound(sql!(
|
||||||
INSERT INTO breakpoints (workspace_id, path, breakpoint_location, log_message, state)
|
INSERT INTO breakpoints (workspace_id, path, breakpoint_location, log_message, condition, hit_condition, state)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5);))?
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7);))?
|
||||||
|
|
||||||
((
|
((
|
||||||
workspace.id,
|
workspace.id,
|
||||||
path.as_ref(),
|
path.as_ref(),
|
||||||
bp.row,
|
bp.row,
|
||||||
message,
|
bp.message,
|
||||||
|
bp.condition,
|
||||||
|
bp.hit_condition,
|
||||||
state,
|
state,
|
||||||
)) {
|
)) {
|
||||||
Ok(_) => {}
|
Ok(_) => {
|
||||||
|
log::debug!("Stored breakpoint at row: {} in path: {}", bp.row, path.to_string_lossy())
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log::error!("{err}");
|
log::error!("{err}");
|
||||||
continue;
|
continue;
|
||||||
|
@ -1409,18 +1435,40 @@ mod tests {
|
||||||
position: 123,
|
position: 123,
|
||||||
message: None,
|
message: None,
|
||||||
state: BreakpointState::Enabled,
|
state: BreakpointState::Enabled,
|
||||||
|
condition: None,
|
||||||
|
hit_condition: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let log_breakpoint = Breakpoint {
|
let log_breakpoint = Breakpoint {
|
||||||
position: 456,
|
position: 456,
|
||||||
message: Some("Test log message".into()),
|
message: Some("Test log message".into()),
|
||||||
state: BreakpointState::Enabled,
|
state: BreakpointState::Enabled,
|
||||||
|
condition: None,
|
||||||
|
hit_condition: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let disable_breakpoint = Breakpoint {
|
let disable_breakpoint = Breakpoint {
|
||||||
position: 578,
|
position: 578,
|
||||||
message: None,
|
message: None,
|
||||||
state: BreakpointState::Disabled,
|
state: BreakpointState::Disabled,
|
||||||
|
condition: None,
|
||||||
|
hit_condition: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let condition_breakpoint = Breakpoint {
|
||||||
|
position: 789,
|
||||||
|
message: None,
|
||||||
|
state: BreakpointState::Enabled,
|
||||||
|
condition: Some("x > 5".into()),
|
||||||
|
hit_condition: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let hit_condition_breakpoint = Breakpoint {
|
||||||
|
position: 999,
|
||||||
|
message: None,
|
||||||
|
state: BreakpointState::Enabled,
|
||||||
|
condition: None,
|
||||||
|
hit_condition: Some(">= 3".into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let workspace = SerializedWorkspace {
|
let workspace = SerializedWorkspace {
|
||||||
|
@ -1441,18 +1489,40 @@ mod tests {
|
||||||
path: Arc::from(path),
|
path: Arc::from(path),
|
||||||
message: breakpoint.message.clone(),
|
message: breakpoint.message.clone(),
|
||||||
state: breakpoint.state,
|
state: breakpoint.state,
|
||||||
|
condition: breakpoint.condition.clone(),
|
||||||
|
hit_condition: breakpoint.hit_condition.clone(),
|
||||||
},
|
},
|
||||||
SourceBreakpoint {
|
SourceBreakpoint {
|
||||||
row: log_breakpoint.position,
|
row: log_breakpoint.position,
|
||||||
path: Arc::from(path),
|
path: Arc::from(path),
|
||||||
message: log_breakpoint.message.clone(),
|
message: log_breakpoint.message.clone(),
|
||||||
state: log_breakpoint.state,
|
state: log_breakpoint.state,
|
||||||
|
condition: log_breakpoint.condition.clone(),
|
||||||
|
hit_condition: log_breakpoint.hit_condition.clone(),
|
||||||
},
|
},
|
||||||
SourceBreakpoint {
|
SourceBreakpoint {
|
||||||
row: disable_breakpoint.position,
|
row: disable_breakpoint.position,
|
||||||
path: Arc::from(path),
|
path: Arc::from(path),
|
||||||
message: disable_breakpoint.message.clone(),
|
message: disable_breakpoint.message.clone(),
|
||||||
state: disable_breakpoint.state,
|
state: disable_breakpoint.state,
|
||||||
|
condition: disable_breakpoint.condition.clone(),
|
||||||
|
hit_condition: disable_breakpoint.hit_condition.clone(),
|
||||||
|
},
|
||||||
|
SourceBreakpoint {
|
||||||
|
row: condition_breakpoint.position,
|
||||||
|
path: Arc::from(path),
|
||||||
|
message: condition_breakpoint.message.clone(),
|
||||||
|
state: condition_breakpoint.state,
|
||||||
|
condition: condition_breakpoint.condition.clone(),
|
||||||
|
hit_condition: condition_breakpoint.hit_condition.clone(),
|
||||||
|
},
|
||||||
|
SourceBreakpoint {
|
||||||
|
row: hit_condition_breakpoint.position,
|
||||||
|
path: Arc::from(path),
|
||||||
|
message: hit_condition_breakpoint.message.clone(),
|
||||||
|
state: hit_condition_breakpoint.state,
|
||||||
|
condition: hit_condition_breakpoint.condition.clone(),
|
||||||
|
hit_condition: hit_condition_breakpoint.hit_condition.clone(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -1467,22 +1537,74 @@ 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(), 3);
|
assert_eq!(loaded_breakpoints.len(), 5);
|
||||||
|
|
||||||
|
// normal breakpoint
|
||||||
assert_eq!(loaded_breakpoints[0].row, breakpoint.position);
|
assert_eq!(loaded_breakpoints[0].row, breakpoint.position);
|
||||||
assert_eq!(loaded_breakpoints[0].message, breakpoint.message);
|
assert_eq!(loaded_breakpoints[0].message, breakpoint.message);
|
||||||
|
assert_eq!(loaded_breakpoints[0].condition, breakpoint.condition);
|
||||||
|
assert_eq!(
|
||||||
|
loaded_breakpoints[0].hit_condition,
|
||||||
|
breakpoint.hit_condition
|
||||||
|
);
|
||||||
assert_eq!(loaded_breakpoints[0].state, breakpoint.state);
|
assert_eq!(loaded_breakpoints[0].state, breakpoint.state);
|
||||||
assert_eq!(loaded_breakpoints[0].path, Arc::from(path));
|
assert_eq!(loaded_breakpoints[0].path, Arc::from(path));
|
||||||
|
|
||||||
|
// enabled breakpoint
|
||||||
assert_eq!(loaded_breakpoints[1].row, log_breakpoint.position);
|
assert_eq!(loaded_breakpoints[1].row, log_breakpoint.position);
|
||||||
assert_eq!(loaded_breakpoints[1].message, log_breakpoint.message);
|
assert_eq!(loaded_breakpoints[1].message, log_breakpoint.message);
|
||||||
|
assert_eq!(loaded_breakpoints[1].condition, log_breakpoint.condition);
|
||||||
|
assert_eq!(
|
||||||
|
loaded_breakpoints[1].hit_condition,
|
||||||
|
log_breakpoint.hit_condition
|
||||||
|
);
|
||||||
assert_eq!(loaded_breakpoints[1].state, log_breakpoint.state);
|
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));
|
||||||
|
|
||||||
|
// disable breakpoint
|
||||||
assert_eq!(loaded_breakpoints[2].row, disable_breakpoint.position);
|
assert_eq!(loaded_breakpoints[2].row, disable_breakpoint.position);
|
||||||
assert_eq!(loaded_breakpoints[2].message, disable_breakpoint.message);
|
assert_eq!(loaded_breakpoints[2].message, disable_breakpoint.message);
|
||||||
|
assert_eq!(
|
||||||
|
loaded_breakpoints[2].condition,
|
||||||
|
disable_breakpoint.condition
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
loaded_breakpoints[2].hit_condition,
|
||||||
|
disable_breakpoint.hit_condition
|
||||||
|
);
|
||||||
assert_eq!(loaded_breakpoints[2].state, disable_breakpoint.state);
|
assert_eq!(loaded_breakpoints[2].state, disable_breakpoint.state);
|
||||||
assert_eq!(loaded_breakpoints[2].path, Arc::from(path));
|
assert_eq!(loaded_breakpoints[2].path, Arc::from(path));
|
||||||
|
|
||||||
|
// condition breakpoint
|
||||||
|
assert_eq!(loaded_breakpoints[3].row, condition_breakpoint.position);
|
||||||
|
assert_eq!(loaded_breakpoints[3].message, condition_breakpoint.message);
|
||||||
|
assert_eq!(
|
||||||
|
loaded_breakpoints[3].condition,
|
||||||
|
condition_breakpoint.condition
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
loaded_breakpoints[3].hit_condition,
|
||||||
|
condition_breakpoint.hit_condition
|
||||||
|
);
|
||||||
|
assert_eq!(loaded_breakpoints[3].state, condition_breakpoint.state);
|
||||||
|
assert_eq!(loaded_breakpoints[3].path, Arc::from(path));
|
||||||
|
|
||||||
|
// hit condition breakpoint
|
||||||
|
assert_eq!(loaded_breakpoints[4].row, hit_condition_breakpoint.position);
|
||||||
|
assert_eq!(
|
||||||
|
loaded_breakpoints[4].message,
|
||||||
|
hit_condition_breakpoint.message
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
loaded_breakpoints[4].condition,
|
||||||
|
hit_condition_breakpoint.condition
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
loaded_breakpoints[4].hit_condition,
|
||||||
|
hit_condition_breakpoint.hit_condition
|
||||||
|
);
|
||||||
|
assert_eq!(loaded_breakpoints[4].state, hit_condition_breakpoint.state);
|
||||||
|
assert_eq!(loaded_breakpoints[4].path, Arc::from(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue