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

@ -147,7 +147,7 @@ use multi_buffer::{
};
use parking_lot::Mutex;
use project::{
debugger::breakpoint_store::{Breakpoint, BreakpointKind},
debugger::breakpoint_store::Breakpoint,
lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
project_settings::{GitGutterSetting, ProjectSettings},
CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
@ -785,11 +785,11 @@ pub struct Editor {
expect_bounds_change: Option<Bounds<Pixels>>,
tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
tasks_update_task: Option<Task<()>>,
pub breakpoint_store: Option<Entity<BreakpointStore>>,
breakpoint_store: Option<Entity<BreakpointStore>>,
/// Allow's a user to create a breakpoint by selecting this indicator
/// It should be None while a user is not hovering over the gutter
/// Otherwise it represents the point that the breakpoint will be shown
pub gutter_breakpoint_indicator: Option<DisplayPoint>,
gutter_breakpoint_indicator: (Option<(DisplayPoint, bool)>, Option<Task<()>>),
in_project_search: bool,
previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
breadcrumb_header: Option<String>,
@ -1549,7 +1549,7 @@ impl Editor {
tasks: Default::default(),
breakpoint_store,
gutter_breakpoint_indicator: None,
gutter_breakpoint_indicator: (None, None),
_subscriptions: vec![
cx.observe(&buffer, Self::on_buffer_changed),
cx.subscribe_in(&buffer, window, Self::on_buffer_event),
@ -6226,10 +6226,7 @@ impl Editor {
.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())
{
let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.message.is_some()) {
"Edit Log Breakpoint"
} else {
"Set Log Breakpoint"
@ -6249,7 +6246,7 @@ impl Editor {
let breakpoint = breakpoint.unwrap_or_else(|| {
Arc::new(Breakpoint {
state: BreakpointState::Enabled,
kind: BreakpointKind::Standard,
message: None,
})
});
@ -6308,16 +6305,17 @@ impl Editor {
cx: &mut Context<Self>,
) -> IconButton {
let (color, icon) = {
let icon = match (&breakpoint.kind, breakpoint.is_disabled()) {
(BreakpointKind::Standard, false) => ui::IconName::DebugBreakpoint,
(BreakpointKind::Log(_), false) => ui::IconName::DebugLogBreakpoint,
(BreakpointKind::Standard, true) => ui::IconName::DebugDisabledBreakpoint,
(BreakpointKind::Log(_), true) => ui::IconName::DebugDisabledLogBreakpoint,
let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
(false, false) => ui::IconName::DebugBreakpoint,
(true, false) => ui::IconName::DebugLogBreakpoint,
(false, true) => ui::IconName::DebugDisabledBreakpoint,
(true, true) => ui::IconName::DebugDisabledLogBreakpoint,
};
let color = if self
.gutter_breakpoint_indicator
.is_some_and(|point| point.row() == row)
.0
.is_some_and(|(point, is_visible)| is_visible && point.row() == row)
{
Color::Hint
} else {
@ -8654,7 +8652,7 @@ impl Editor {
(
breakpoint_position,
Breakpoint {
kind: BreakpointKind::Standard,
message: None,
state: BreakpointState::Enabled,
},
)
@ -19758,8 +19756,8 @@ impl BreakpointPromptEditor {
let buffer = cx.new(|cx| {
Buffer::local(
breakpoint
.kind
.log_message()
.message
.as_ref()
.map(|msg| msg.to_string())
.unwrap_or_default(),
cx,

View file

@ -32,7 +32,7 @@ use multi_buffer::{IndentGuide, PathKey};
use parking_lot::Mutex;
use pretty_assertions::{assert_eq, assert_ne};
use project::{
debugger::breakpoint_store::{BreakpointKind, BreakpointState, SerializedBreakpoint},
debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
project_settings::{LspSettings, ProjectSettings},
FakeFs,
};
@ -17390,12 +17390,12 @@ async fn assert_highlighted_edits(
#[track_caller]
fn assert_breakpoint(
breakpoints: &BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>>,
breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
path: &Arc<Path>,
expected: Vec<(u32, Breakpoint)>,
) {
if expected.len() == 0usize {
assert!(!breakpoints.contains_key(path));
assert!(!breakpoints.contains_key(path), "{}", path.display());
} else {
let mut breakpoint = breakpoints
.get(path)
@ -17403,9 +17403,9 @@ fn assert_breakpoint(
.into_iter()
.map(|breakpoint| {
(
breakpoint.position,
breakpoint.row,
Breakpoint {
kind: breakpoint.kind.clone(),
message: breakpoint.message.clone(),
state: breakpoint.state,
},
)
@ -17435,12 +17435,10 @@ fn add_log_breakpoint_at_cursor(
.buffer_snapshot
.anchor_before(Point::new(cursor_position.row, 0));
let kind = BreakpointKind::Log(Arc::from(log_message));
(
breakpoint_position,
Breakpoint {
kind,
message: Some(Arc::from(log_message)),
state: BreakpointState::Enabled,
},
)
@ -17738,7 +17736,7 @@ async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
&abs_path,
vec![
(0, Breakpoint::new_standard()),
(3, Breakpoint::new_log("hello Earth !!")),
(3, Breakpoint::new_log("hello Earth!!")),
],
);
}

View file

@ -71,6 +71,7 @@ use std::{
ops::{Deref, Range},
rc::Rc,
sync::Arc,
time::Duration,
};
use sum_tree::Bias;
use text::BufferId;
@ -907,7 +908,6 @@ impl EditorElement {
let modifiers = event.modifiers;
let gutter_hovered = gutter_hitbox.is_hovered(window);
editor.set_gutter_hovered(gutter_hovered, cx);
editor.gutter_breakpoint_indicator = None;
editor.mouse_cursor_hidden = false;
if gutter_hovered {
@ -924,8 +924,38 @@ impl EditorElement {
.buffer_for_excerpt(buffer_anchor.excerpt_id)
.is_some_and(|buffer| buffer.file().is_some())
{
editor.gutter_breakpoint_indicator = Some(new_point);
let was_hovered = editor.gutter_breakpoint_indicator.0.is_some();
let is_visible = editor
.gutter_breakpoint_indicator
.0
.map_or(false, |(_, is_active)| is_active);
editor.gutter_breakpoint_indicator.0 = Some((new_point, is_visible));
editor.gutter_breakpoint_indicator.1.get_or_insert_with(|| {
cx.spawn(async move |this, cx| {
if !was_hovered {
cx.background_executor()
.timer(Duration::from_millis(200))
.await;
}
this.update(cx, |this, cx| {
if let Some((_, is_active)) =
this.gutter_breakpoint_indicator.0.as_mut()
{
*is_active = true;
}
cx.notify();
})
.ok();
})
});
} else {
editor.gutter_breakpoint_indicator = (None, None);
}
} else {
editor.gutter_breakpoint_indicator = (None, None);
}
cx.notify();
@ -6851,8 +6881,10 @@ impl Element for EditorElement {
// has their mouse over that line when a breakpoint isn't there
if cx.has_flag::<Debugger>() {
let gutter_breakpoint_indicator =
self.editor.read(cx).gutter_breakpoint_indicator;
if let Some(gutter_breakpoint_point) = gutter_breakpoint_indicator {
self.editor.read(cx).gutter_breakpoint_indicator.0;
if let Some((gutter_breakpoint_point, _)) =
gutter_breakpoint_indicator.filter(|(_, is_active)| *is_active)
{
breakpoint_rows
.entry(gutter_breakpoint_point.row())
.or_insert_with(|| {