debugger: Touchups to log breakpoints (#27675)

This is a slight refactor that flattens Breakpoint struct in
anticipation of condition/hit breakpoints. It also adds a slight delay
before breakpoints are shown on gutter hover to make breakpoints less
attention grabbing.
Release Notes:

- N/A
This commit is contained in:
Piotr Osiewicz 2025-03-29 02:16:44 +01:00 committed by GitHub
parent 8ecf553279
commit f86977e2a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 136 additions and 276 deletions

View file

@ -13,7 +13,7 @@ use client::DevServerProjectId;
use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
use gpui::{point, size, Axis, Bounds, WindowBounds, WindowId};
use itertools::Itertools;
use project::debugger::breakpoint_store::{BreakpointKind, BreakpointState, SerializedBreakpoint};
use project::debugger::breakpoint_store::{BreakpointState, SourceBreakpoint};
use language::{LanguageName, Toolchain};
use project::WorktreeId;
@ -147,7 +147,7 @@ impl Column for SerializedWindowBounds {
#[derive(Debug)]
pub struct Breakpoint {
pub position: u32,
pub kind: BreakpointKind,
pub message: Option<Arc<str>>,
pub state: BreakpointState,
}
@ -183,50 +183,6 @@ impl Column for BreakpointStateWrapper<'_> {
}
}
/// Wrapper for DB type of a breakpoint
#[derive(Debug)]
struct BreakpointKindWrapper<'a>(Cow<'a, BreakpointKind>);
impl From<BreakpointKind> for BreakpointKindWrapper<'static> {
fn from(kind: BreakpointKind) -> Self {
BreakpointKindWrapper(Cow::Owned(kind))
}
}
impl StaticColumnCount for BreakpointKindWrapper<'_> {
fn column_count() -> usize {
1
}
}
impl Bind for BreakpointKindWrapper<'_> {
fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
let next_index = statement.bind(&self.0.to_int(), start_index)?;
match self.0.as_ref() {
BreakpointKind::Standard => {
statement.bind_null(next_index)?;
Ok(next_index + 1)
}
BreakpointKind::Log(message) => statement.bind(&message.as_ref(), next_index),
}
}
}
impl Column for BreakpointKindWrapper<'_> {
fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
let kind = statement.column_int(start_index)?;
match kind {
0 => Ok((BreakpointKind::Standard.into(), start_index + 2)),
1 => {
let message = statement.column_text(start_index)?.to_string();
Ok((BreakpointKind::Log(message.into()).into(), start_index + 1))
}
_ => Err(anyhow::anyhow!("Invalid BreakpointKind discriminant")),
}
}
}
/// This struct is used to implement traits on Vec<breakpoint>
#[derive(Debug)]
#[allow(dead_code)]
@ -234,7 +190,7 @@ struct Breakpoints(Vec<Breakpoint>);
impl sqlez::bindable::StaticColumnCount for Breakpoint {
fn column_count() -> usize {
1 + BreakpointKindWrapper::column_count() + BreakpointStateWrapper::column_count()
2 + BreakpointStateWrapper::column_count()
}
}
@ -245,10 +201,7 @@ impl sqlez::bindable::Bind for Breakpoint {
start_index: i32,
) -> anyhow::Result<i32> {
let next_index = statement.bind(&self.position, start_index)?;
let next_index = statement.bind(
&BreakpointKindWrapper(Cow::Borrowed(&self.kind)),
next_index,
)?;
let next_index = statement.bind(&self.message, next_index)?;
statement.bind(
&BreakpointStateWrapper(Cow::Borrowed(&self.state)),
next_index,
@ -262,13 +215,13 @@ impl Column for Breakpoint {
.column_int(start_index)
.with_context(|| format!("Failed to read BreakPoint at index {start_index}"))?
as u32;
let (kind, next_index) = BreakpointKindWrapper::column(statement, start_index + 1)?;
let (message, next_index) = Option::<String>::column(statement, start_index + 1)?;
let (state, next_index) = BreakpointStateWrapper::column(statement, next_index)?;
Ok((
Breakpoint {
position,
kind: kind.0.into_owned(),
message: message.map(Arc::from),
state: state.0.into_owned(),
},
next_index,
@ -570,6 +523,9 @@ define_connection! {
),
sql!(
ALTER TABLE breakpoints ADD COLUMN state INTEGER DEFAULT(0) NOT NULL
),
sql!(
ALTER TABLE breakpoints DROP COLUMN kind
)
];
}
@ -720,13 +676,10 @@ impl WorkspaceDb {
})
}
fn breakpoints(
&self,
workspace_id: WorkspaceId,
) -> BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>> {
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, kind, log_message, state
SELECT path, breakpoint_location, log_message, state
FROM breakpoints
WHERE workspace_id = ?
})
@ -738,18 +691,16 @@ impl WorkspaceDb {
log::debug!("Breakpoints are empty after querying database for them");
}
let mut map: BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>> = Default::default();
let mut map: BTreeMap<Arc<Path>, Vec<SourceBreakpoint>> = Default::default();
for (path, breakpoint) in bp {
let path: Arc<Path> = path.into();
map.entry(path.clone())
.or_default()
.push(SerializedBreakpoint {
position: breakpoint.position,
path,
kind: breakpoint.kind,
state: breakpoint.state,
});
map.entry(path.clone()).or_default().push(SourceBreakpoint {
row: breakpoint.position,
path,
message: breakpoint.message,
state: breakpoint.state,
});
}
map
@ -775,17 +726,17 @@ 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 kind = BreakpointKindWrapper::from(bp.kind);
let message = bp.message;
let state = BreakpointStateWrapper::from(bp.state);
match conn.exec_bound(sql!(
INSERT INTO breakpoints (workspace_id, path, breakpoint_location, kind, log_message, state)
VALUES (?1, ?2, ?3, ?4, ?5, ?6);))?
INSERT INTO breakpoints (workspace_id, path, breakpoint_location, log_message, state)
VALUES (?1, ?2, ?3, ?4, ?5);))?
((
workspace.id,
path.as_ref(),
bp.position,
kind,
bp.row,
message,
state,
)) {
Ok(_) => {}
@ -1453,19 +1404,19 @@ mod tests {
let breakpoint = Breakpoint {
position: 123,
kind: BreakpointKind::Standard,
message: None,
state: BreakpointState::Enabled,
};
let log_breakpoint = Breakpoint {
position: 456,
kind: BreakpointKind::Log("Test log message".into()),
message: Some("Test log message".into()),
state: BreakpointState::Enabled,
};
let disable_breakpoint = Breakpoint {
position: 578,
kind: BreakpointKind::Standard,
message: None,
state: BreakpointState::Disabled,
};
@ -1482,22 +1433,22 @@ mod tests {
map.insert(
Arc::from(path),
vec![
SerializedBreakpoint {
position: breakpoint.position,
SourceBreakpoint {
row: breakpoint.position,
path: Arc::from(path),
kind: breakpoint.kind.clone(),
message: breakpoint.message.clone(),
state: breakpoint.state,
},
SerializedBreakpoint {
position: log_breakpoint.position,
SourceBreakpoint {
row: log_breakpoint.position,
path: Arc::from(path),
kind: log_breakpoint.kind.clone(),
message: log_breakpoint.message.clone(),
state: log_breakpoint.state,
},
SerializedBreakpoint {
position: disable_breakpoint.position,
SourceBreakpoint {
row: disable_breakpoint.position,
path: Arc::from(path),
kind: disable_breakpoint.kind.clone(),
message: disable_breakpoint.message.clone(),
state: disable_breakpoint.state,
},
],
@ -1515,18 +1466,18 @@ mod tests {
assert_eq!(loaded_breakpoints.len(), 3);
assert_eq!(loaded_breakpoints[0].position, breakpoint.position);
assert_eq!(loaded_breakpoints[0].kind, breakpoint.kind);
assert_eq!(loaded_breakpoints[0].row, breakpoint.position);
assert_eq!(loaded_breakpoints[0].message, breakpoint.message);
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].kind, log_breakpoint.kind);
assert_eq!(loaded_breakpoints[1].row, log_breakpoint.position);
assert_eq!(loaded_breakpoints[1].message, log_breakpoint.message);
assert_eq!(loaded_breakpoints[1].state, log_breakpoint.state);
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].row, disable_breakpoint.position);
assert_eq!(loaded_breakpoints[2].message, disable_breakpoint.message);
assert_eq!(loaded_breakpoints[2].state, disable_breakpoint.state);
assert_eq!(loaded_breakpoints[2].path, Arc::from(path));
}

View file

@ -10,7 +10,7 @@ use db::sqlez::{
};
use gpui::{AsyncWindowContext, Entity, WeakEntity};
use itertools::Itertools as _;
use project::{debugger::breakpoint_store::SerializedBreakpoint, Project};
use project::{debugger::breakpoint_store::SourceBreakpoint, Project};
use remote::ssh_session::SshProjectId;
use serde::{Deserialize, Serialize};
use std::{
@ -264,7 +264,7 @@ pub(crate) struct SerializedWorkspace {
pub(crate) display: Option<Uuid>,
pub(crate) docks: DockStructure,
pub(crate) session_id: Option<String>,
pub(crate) breakpoints: BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>>,
pub(crate) breakpoints: BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
pub(crate) window_id: Option<u64>,
}