From 1fe80040b16f7fa30db2b2fc524f6d2b175daf95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iago=20Berm=C3=BAdez?= <88665450+iagombermudez@users.noreply.github.com> Date: Thu, 5 Feb 2026 10:59:03 +0100 Subject: [PATCH] debugger: Fix breakpoint color only updating when mouse moves (#46427) The other day I was testing the debugging feature and I noticed a **very** small visual bug. When you click on the breakpoint circle to set a breakpoint, the breakpoint color remains in the wrong state until the mouse moves. After debugging for a while, I noticed that the issue was that the PhantomVariableIndicator variable attribute `collides_with_existing_breakpoint` was not being updated until the mouse moved. With the following change, the attribute is updated when the user clicks the breakpoint, toggleling tha attribute. ### Prior behaviour: https://github.com/user-attachments/assets/3dfa8e25-e970-49a3-8e87-0ecadabb3a3c As you can see in the video, prior to this change, the breakpoint would only change to the correct color when the mouse moved. ### New behaviour https://github.com/user-attachments/assets/356e84dc-42e5-4440-afab-0fbc4b3a1f0a With this new change, the breakpoint updates right after the user clicks, no need to move the mouse. ### Disclaimers - I'm new to this codebase, so any feedback is extremely welcomed! If you know of a better place where this could be handled, let me know and I'll explore further. https://github.com/user-attachments/assets/12d4b9df-e5d7-4955-b58d-dd63563c5c1b Release Notes: - N/A --------- Co-authored-by: Anthony Eid --- crates/editor/src/editor.rs | 28 ++++++++ crates/editor/src/editor_tests.rs | 102 ++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 02506a87278b315ed252b57e686e9d3a924c92cd..28c17bdcd3bfa2fdcdbc3ceacf99a6825da936cf 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -8984,6 +8984,7 @@ impl Editor { }; window.focus(&editor.focus_handle(cx), cx); + editor.update_breakpoint_collision_on_toggle(row, &edit_action); editor.edit_breakpoint_at_anchor( position, breakpoint.as_ref().clone(), @@ -11847,7 +11848,19 @@ impl Editor { return; } + let snapshot = self.snapshot(window, cx); for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) { + if self.gutter_breakpoint_indicator.0.is_some() { + let display_row = anchor + .to_point(snapshot.buffer_snapshot()) + .to_display_point(&snapshot.display_snapshot) + .row(); + self.update_breakpoint_collision_on_toggle( + display_row, + &BreakpointEditAction::Toggle, + ); + } + if let Some(breakpoint) = breakpoint { self.edit_breakpoint_at_anchor( anchor, @@ -11866,6 +11879,21 @@ impl Editor { } } + fn update_breakpoint_collision_on_toggle( + &mut self, + display_row: DisplayRow, + edit_action: &BreakpointEditAction, + ) { + if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 { + if breakpoint_indicator.display_row == display_row + && matches!(edit_action, BreakpointEditAction::Toggle) + { + breakpoint_indicator.collides_with_existing_breakpoint = + !breakpoint_indicator.collides_with_existing_breakpoint; + } + } + } + pub fn edit_breakpoint_at_anchor( &mut self, breakpoint_position: Anchor, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 9ebbf04d5e528195ed745d071c08119d686442a5..39d8f59d87b7ea212d55ec8f9d30e3618628cdf1 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -24836,6 +24836,108 @@ async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) { ); } +#[gpui::test] +async fn test_breakpoint_phantom_indicator_collision_on_toggle(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 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, rel_path("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, + ) + }); + + // Simulate hovering over row 0 with no existing breakpoint. + editor.update(cx, |editor, _cx| { + editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator { + display_row: DisplayRow(0), + is_active: true, + collides_with_existing_breakpoint: false, + }); + }); + + // Toggle breakpoint on the same row (row 0) — collision should flip to true. + editor.update_in(cx, |editor, window, cx| { + editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx); + }); + editor.update(cx, |editor, _cx| { + let indicator = editor.gutter_breakpoint_indicator.0.unwrap(); + assert!( + indicator.collides_with_existing_breakpoint, + "Adding a breakpoint on the hovered row should set collision to true" + ); + }); + + // Toggle again on the same row — breakpoint is removed, collision should flip back to false. + editor.update_in(cx, |editor, window, cx| { + editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx); + }); + editor.update(cx, |editor, _cx| { + let indicator = editor.gutter_breakpoint_indicator.0.unwrap(); + assert!( + !indicator.collides_with_existing_breakpoint, + "Removing a breakpoint on the hovered row should set collision to false" + ); + }); + + // Now move cursor to row 2 while phantom indicator stays on row 0. + editor.update_in(cx, |editor, window, cx| { + editor.move_down(&MoveDown, window, cx); + editor.move_down(&MoveDown, window, cx); + }); + + // Ensure phantom indicator is still on row 0, not colliding. + editor.update(cx, |editor, _cx| { + editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator { + display_row: DisplayRow(0), + is_active: true, + collides_with_existing_breakpoint: false, + }); + }); + + // Toggle breakpoint on row 2 (cursor row) — phantom on row 0 should NOT be affected. + editor.update_in(cx, |editor, window, cx| { + editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx); + }); + editor.update(cx, |editor, _cx| { + let indicator = editor.gutter_breakpoint_indicator.0.unwrap(); + assert!( + !indicator.collides_with_existing_breakpoint, + "Toggling a breakpoint on a different row should not affect the phantom indicator" + ); + }); +} + #[gpui::test] async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) { init_test(cx, |_| {});