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 gpui::{
|
||||
AnyElement, BackgroundExecutor, Entity, Focusable, FontWeight, ListSizingBehavior,
|
||||
|
@ -992,6 +993,17 @@ impl CodeActionsMenu {
|
|||
.iter()
|
||||
.skip(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()
|
||||
.map(|(ix, action)| {
|
||||
let item_ix = range.start + ix;
|
||||
|
|
|
@ -4798,6 +4798,8 @@ impl Editor {
|
|||
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| {
|
||||
let task_context = match task_context {
|
||||
Some(task_context) => task_context.await,
|
||||
|
@ -4813,12 +4815,22 @@ impl Editor {
|
|||
)),
|
||||
})
|
||||
});
|
||||
let spawn_straight_away = resolved_tasks
|
||||
let spawn_straight_away = resolved_tasks.as_ref().map_or(false, |tasks| {
|
||||
tasks
|
||||
.templates
|
||||
.iter()
|
||||
.filter(|task| {
|
||||
if matches!(task.1.task_type(), task::TaskType::Debug(_)) {
|
||||
debugger_flag
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.count()
|
||||
== 1
|
||||
}) && code_actions
|
||||
.as_ref()
|
||||
.map_or(false, |tasks| tasks.templates.len() == 1)
|
||||
&& code_actions
|
||||
.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| {
|
||||
*editor.context_menu.borrow_mut() =
|
||||
Some(CodeContextMenu::CodeActions(CodeActionsMenu {
|
||||
|
@ -6292,6 +6304,22 @@ impl Editor {
|
|||
"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() {
|
||||
"Unset Breakpoint"
|
||||
} else {
|
||||
|
@ -6303,12 +6331,7 @@ impl Editor {
|
|||
BreakpointState::Disabled => Some("Enable"),
|
||||
});
|
||||
|
||||
let breakpoint = breakpoint.unwrap_or_else(|| {
|
||||
Arc::new(Breakpoint {
|
||||
state: BreakpointState::Enabled,
|
||||
message: None,
|
||||
})
|
||||
});
|
||||
let breakpoint = breakpoint.unwrap_or_else(|| Arc::new(Breakpoint::new_standard()));
|
||||
|
||||
ui::ContextMenu::build(window, cx, |menu, _, _cx| {
|
||||
menu.on_blur_subscription(Subscription::new(|| {}))
|
||||
|
@ -6347,10 +6370,50 @@ impl Editor {
|
|||
.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
|
||||
.update(cx, |this, 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(), window, cx);
|
||||
this.add_edit_breakpoint_block(
|
||||
anchor,
|
||||
breakpoint.as_ref(),
|
||||
BreakpointPromptEditAction::HitCondition,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
|
@ -8597,12 +8660,20 @@ impl Editor {
|
|||
&mut self,
|
||||
anchor: Anchor,
|
||||
breakpoint: &Breakpoint,
|
||||
edit_action: BreakpointPromptEditAction,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let weak_editor = cx.weak_entity();
|
||||
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| {
|
||||
|
@ -8721,11 +8792,13 @@ impl Editor {
|
|||
Breakpoint {
|
||||
message: None,
|
||||
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(
|
||||
|
@ -19861,11 +19934,18 @@ impl Global for KillRing {}
|
|||
|
||||
const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
|
||||
|
||||
enum BreakpointPromptEditAction {
|
||||
Log,
|
||||
Condition,
|
||||
HitCondition,
|
||||
}
|
||||
|
||||
struct BreakpointPromptEditor {
|
||||
pub(crate) prompt: Entity<Editor>,
|
||||
editor: WeakEntity<Editor>,
|
||||
breakpoint_anchor: Anchor,
|
||||
breakpoint: Breakpoint,
|
||||
edit_action: BreakpointPromptEditAction,
|
||||
block_ids: HashSet<CustomBlockId>,
|
||||
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
|
@ -19878,19 +19958,19 @@ impl BreakpointPromptEditor {
|
|||
editor: WeakEntity<Editor>,
|
||||
breakpoint_anchor: Anchor,
|
||||
breakpoint: Breakpoint,
|
||||
edit_action: BreakpointPromptEditAction,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let buffer = cx.new(|cx| {
|
||||
Buffer::local(
|
||||
breakpoint
|
||||
.message
|
||||
.as_ref()
|
||||
.map(|msg| msg.to_string())
|
||||
.unwrap_or_default(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let base_text = match edit_action {
|
||||
BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
|
||||
BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
|
||||
BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
|
||||
}
|
||||
.map(|msg| msg.to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
let buffer = cx.new(|cx| Buffer::local(base_text, cx));
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, 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_show_cursor_when_unfocused(false, cx);
|
||||
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,
|
||||
);
|
||||
|
||||
|
@ -19918,6 +20002,7 @@ impl BreakpointPromptEditor {
|
|||
editor,
|
||||
breakpoint_anchor,
|
||||
breakpoint,
|
||||
edit_action,
|
||||
gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
|
||||
block_ids: Default::default(),
|
||||
_subscriptions: vec![],
|
||||
|
@ -19930,7 +20015,7 @@ impl BreakpointPromptEditor {
|
|||
|
||||
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(editor) = self.editor.upgrade() {
|
||||
let log_message = self
|
||||
let message = self
|
||||
.prompt
|
||||
.read(cx)
|
||||
.buffer
|
||||
|
@ -19945,7 +20030,17 @@ impl BreakpointPromptEditor {
|
|||
editor.edit_breakpoint_at_anchor(
|
||||
self.breakpoint_anchor,
|
||||
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,
|
||||
);
|
||||
|
||||
|
|
|
@ -17387,6 +17387,8 @@ fn assert_breakpoint(
|
|||
Breakpoint {
|
||||
message: breakpoint.message.clone(),
|
||||
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
|
||||
.anchor_before(Point::new(cursor_position.row, 0));
|
||||
|
||||
(
|
||||
breakpoint_position,
|
||||
Breakpoint {
|
||||
message: Some(Arc::from(log_message)),
|
||||
state: BreakpointState::Enabled,
|
||||
},
|
||||
)
|
||||
(breakpoint_position, Breakpoint::new_log(&log_message))
|
||||
});
|
||||
|
||||
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() {
|
||||
|
@ -424,6 +478,8 @@ impl BreakpointStore {
|
|||
path: path.clone(),
|
||||
state: breakpoint.state,
|
||||
message: breakpoint.message.clone(),
|
||||
condition: breakpoint.condition.clone(),
|
||||
hit_condition: breakpoint.hit_condition.clone(),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
@ -447,6 +503,8 @@ impl BreakpointStore {
|
|||
path: path.clone(),
|
||||
message: breakpoint.message.clone(),
|
||||
state: breakpoint.state,
|
||||
hit_condition: breakpoint.hit_condition.clone(),
|
||||
condition: breakpoint.condition.clone(),
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
|
@ -500,6 +558,8 @@ impl BreakpointStore {
|
|||
Breakpoint {
|
||||
message: bp.message,
|
||||
state: bp.state,
|
||||
condition: bp.condition,
|
||||
hit_condition: bp.hit_condition,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
@ -537,13 +597,15 @@ pub enum BreakpointStoreEvent {
|
|||
|
||||
impl EventEmitter<BreakpointStoreEvent> for BreakpointStore {}
|
||||
|
||||
type LogMessage = Arc<str>;
|
||||
type BreakpointMessage = Arc<str>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum BreakpointEditAction {
|
||||
Toggle,
|
||||
InvertState,
|
||||
EditLogMessage(LogMessage),
|
||||
EditLogMessage(BreakpointMessage),
|
||||
EditCondition(BreakpointMessage),
|
||||
EditHitCondition(BreakpointMessage),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
|
@ -574,7 +636,10 @@ impl BreakpointState {
|
|||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
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,
|
||||
}
|
||||
|
||||
|
@ -582,6 +647,17 @@ impl Breakpoint {
|
|||
pub fn new_standard() -> Self {
|
||||
Self {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
@ -589,6 +665,8 @@ impl Breakpoint {
|
|||
pub fn new_log(log_message: &str) -> Self {
|
||||
Self {
|
||||
state: BreakpointState::Enabled,
|
||||
hit_condition: None,
|
||||
condition: None,
|
||||
message: Some(log_message.into()),
|
||||
}
|
||||
}
|
||||
|
@ -601,6 +679,11 @@ impl Breakpoint {
|
|||
BreakpointState::Disabled => proto::BreakpointState::Disabled.into(),
|
||||
},
|
||||
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,
|
||||
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 path: Arc<Path>,
|
||||
pub message: Option<Arc<str>>,
|
||||
pub condition: Option<Arc<str>>,
|
||||
pub hit_condition: Option<Arc<str>>,
|
||||
pub state: BreakpointState,
|
||||
}
|
||||
|
||||
|
@ -639,8 +726,12 @@ impl From<SourceBreakpoint> for dap::SourceBreakpoint {
|
|||
Self {
|
||||
line: bp.row as u64 + 1,
|
||||
column: None,
|
||||
condition: None,
|
||||
hit_condition: None,
|
||||
condition: bp
|
||||
.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())),
|
||||
mode: None,
|
||||
}
|
||||
|
|
|
@ -2673,6 +2673,8 @@ message Breakpoint {
|
|||
BreakpointState state = 2;
|
||||
reserved 3;
|
||||
optional string message = 4;
|
||||
optional string condition = 5;
|
||||
optional string hit_condition = 6;
|
||||
}
|
||||
|
||||
message BreakpointsForFile {
|
||||
|
|
|
@ -148,6 +148,8 @@ impl Column for SerializedWindowBounds {
|
|||
pub struct Breakpoint {
|
||||
pub position: u32,
|
||||
pub message: Option<Arc<str>>,
|
||||
pub condition: Option<Arc<str>>,
|
||||
pub hit_condition: Option<Arc<str>>,
|
||||
pub state: BreakpointState,
|
||||
}
|
||||
|
||||
|
@ -190,7 +192,8 @@ struct Breakpoints(Vec<Breakpoint>);
|
|||
|
||||
impl sqlez::bindable::StaticColumnCount for Breakpoint {
|
||||
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> {
|
||||
let next_index = statement.bind(&self.position, start_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(
|
||||
&BreakpointStateWrapper(Cow::Borrowed(&self.state)),
|
||||
next_index,
|
||||
|
@ -216,12 +221,16 @@ impl Column for Breakpoint {
|
|||
.with_context(|| format!("Failed to read BreakPoint at index {start_index}"))?
|
||||
as u32;
|
||||
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)?;
|
||||
|
||||
Ok((
|
||||
Breakpoint {
|
||||
position,
|
||||
message: message.map(Arc::from),
|
||||
condition: condition.map(Arc::from),
|
||||
hit_condition: hit_condition.map(Arc::from),
|
||||
state: state.0.into_owned(),
|
||||
},
|
||||
next_index,
|
||||
|
@ -527,7 +536,11 @@ define_connection! {
|
|||
sql!(
|
||||
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>> {
|
||||
let breakpoints: Result<Vec<(PathBuf, Breakpoint)>> = self
|
||||
.select_bound(sql! {
|
||||
SELECT path, breakpoint_location, log_message, state
|
||||
SELECT path, breakpoint_location, log_message, condition, hit_condition, state
|
||||
FROM breakpoints
|
||||
WHERE workspace_id = ?
|
||||
})
|
||||
|
@ -700,10 +713,20 @@ impl WorkspaceDb {
|
|||
row: breakpoint.position,
|
||||
path,
|
||||
message: breakpoint.message,
|
||||
condition: breakpoint.condition,
|
||||
hit_condition: breakpoint.hit_condition,
|
||||
state: breakpoint.state,
|
||||
});
|
||||
}
|
||||
|
||||
for (path, bps) in map.iter() {
|
||||
log::debug!(
|
||||
"Got {} breakpoints from path: {}",
|
||||
bps.len(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
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()))
|
||||
.context("Clearing old breakpoints")?;
|
||||
for bp in breakpoints {
|
||||
let message = bp.message;
|
||||
let state = BreakpointStateWrapper::from(bp.state);
|
||||
match conn.exec_bound(sql!(
|
||||
INSERT INTO breakpoints (workspace_id, path, breakpoint_location, log_message, state)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5);))?
|
||||
INSERT INTO breakpoints (workspace_id, path, breakpoint_location, log_message, condition, hit_condition, state)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7);))?
|
||||
|
||||
((
|
||||
workspace.id,
|
||||
path.as_ref(),
|
||||
bp.row,
|
||||
message,
|
||||
bp.message,
|
||||
bp.condition,
|
||||
bp.hit_condition,
|
||||
state,
|
||||
)) {
|
||||
Ok(_) => {}
|
||||
Ok(_) => {
|
||||
log::debug!("Stored breakpoint at row: {} in path: {}", bp.row, path.to_string_lossy())
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("{err}");
|
||||
continue;
|
||||
|
@ -1409,18 +1435,40 @@ mod tests {
|
|||
position: 123,
|
||||
message: None,
|
||||
state: BreakpointState::Enabled,
|
||||
condition: None,
|
||||
hit_condition: None,
|
||||
};
|
||||
|
||||
let log_breakpoint = Breakpoint {
|
||||
position: 456,
|
||||
message: Some("Test log message".into()),
|
||||
state: BreakpointState::Enabled,
|
||||
condition: None,
|
||||
hit_condition: None,
|
||||
};
|
||||
|
||||
let disable_breakpoint = Breakpoint {
|
||||
position: 578,
|
||||
message: None,
|
||||
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 {
|
||||
|
@ -1441,18 +1489,40 @@ mod tests {
|
|||
path: Arc::from(path),
|
||||
message: breakpoint.message.clone(),
|
||||
state: breakpoint.state,
|
||||
condition: breakpoint.condition.clone(),
|
||||
hit_condition: breakpoint.hit_condition.clone(),
|
||||
},
|
||||
SourceBreakpoint {
|
||||
row: log_breakpoint.position,
|
||||
path: Arc::from(path),
|
||||
message: log_breakpoint.message.clone(),
|
||||
state: log_breakpoint.state,
|
||||
condition: log_breakpoint.condition.clone(),
|
||||
hit_condition: log_breakpoint.hit_condition.clone(),
|
||||
},
|
||||
SourceBreakpoint {
|
||||
row: disable_breakpoint.position,
|
||||
path: Arc::from(path),
|
||||
message: disable_breakpoint.message.clone(),
|
||||
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_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].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].path, Arc::from(path));
|
||||
|
||||
// enabled breakpoint
|
||||
assert_eq!(loaded_breakpoints[1].row, log_breakpoint.position);
|
||||
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].path, Arc::from(path));
|
||||
|
||||
// disable breakpoint
|
||||
assert_eq!(loaded_breakpoints[2].row, disable_breakpoint.position);
|
||||
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].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]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue