diff --git a/assets/icons/bookmark.svg b/assets/icons/bookmark.svg
deleted file mode 100644
index 999c9b72a1ead51fe9e899d6a6946e7840bfe742..0000000000000000000000000000000000000000
--- a/assets/icons/bookmark.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/assets/settings/default.json b/assets/settings/default.json
index 335dc7543ccd171d0cc91deee364f945186d3868..584c4c4d49d573be8ca600edde638c428bace3e6 100644
--- a/assets/settings/default.json
+++ b/assets/settings/default.json
@@ -614,8 +614,6 @@
"line_numbers": true,
// Whether to show runnables buttons in the gutter.
"runnables": true,
- // Whether to show bookmarks in the gutter.
- "bookmarks": true,
// Whether to show breakpoints in the gutter.
"breakpoints": true,
// Whether to show fold buttons in the gutter.
diff --git a/codebook.toml b/codebook.toml
deleted file mode 100644
index 57cdd2569c350bdf35a27195aa6ab86a1b08ab83..0000000000000000000000000000000000000000
--- a/codebook.toml
+++ /dev/null
@@ -1 +0,0 @@
-words = ["breakpoint"]
diff --git a/crates/agent_ui/src/entry_view_state.rs b/crates/agent_ui/src/entry_view_state.rs
index 8543b3c96199e7b303971b476ffaade9484384a7..415cd1f3db19df29895d7dd984e7ac4fb4a7b47b 100644
--- a/crates/agent_ui/src/entry_view_state.rs
+++ b/crates/agent_ui/src/entry_view_state.rs
@@ -458,7 +458,6 @@ fn create_editor_diff(
editor.set_show_indent_guides(false, cx);
editor.set_read_only(true);
editor.set_delegate_open_excerpts(true);
- editor.set_show_bookmarks(false, cx);
editor.set_show_breakpoints(false, cx);
editor.set_show_code_actions(false, cx);
editor.set_show_git_diff_gutter(false, cx);
diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs
index f4aadb7433f143ba0bcd18e494e4b838da1f5894..48dbd43dd449911a0c40f9e943ad917843941c02 100644
--- a/crates/collab_ui/src/channel_view.rs
+++ b/crates/collab_ui/src/channel_view.rs
@@ -216,9 +216,6 @@ impl ChannelView {
})
}))
});
- editor.set_show_bookmarks(false, cx);
- editor.set_show_breakpoints(false, cx);
- editor.set_show_runnables(false, cx);
editor
});
let _editor_event_subscription =
diff --git a/crates/debugger_tools/src/dap_log.rs b/crates/debugger_tools/src/dap_log.rs
index c364cdd244752afdda203911653e8a60e54b7871..2c653217716b0218cff0b60eb2bce4ac1ce02e5d 100644
--- a/crates/debugger_tools/src/dap_log.rs
+++ b/crates/debugger_tools/src/dap_log.rs
@@ -761,7 +761,6 @@ impl DapLogView {
editor.set_text(log_contents, window, cx);
editor.move_to_end(&editor::actions::MoveToEnd, window, cx);
editor.set_show_code_actions(false, cx);
- editor.set_show_bookmarks(false, cx);
editor.set_show_breakpoints(false, cx);
editor.set_show_git_diff_gutter(false, cx);
editor.set_show_runnables(false, cx);
diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs
index d1c53203329d738bfd96b2fc0ac89446d7cdcc54..c541257b6d219b56a611f8a3711da287109ef48d 100644
--- a/crates/debugger_ui/src/session/running/console.rs
+++ b/crates/debugger_ui/src/session/running/console.rs
@@ -73,7 +73,6 @@ impl Console {
editor.disable_scrollbars_and_minimap(window, cx);
editor.set_show_gutter(false, cx);
editor.set_show_runnables(false, cx);
- editor.set_show_bookmarks(false, cx);
editor.set_show_breakpoints(false, cx);
editor.set_show_code_actions(false, cx);
editor.set_show_line_numbers(false, cx);
diff --git a/crates/edit_prediction_ui/src/rate_prediction_modal.rs b/crates/edit_prediction_ui/src/rate_prediction_modal.rs
index 6ff7c0e2e46efe1142414e9e5717e1607323636c..eb071bf955cede173e74993c93ab5cd294338474 100644
--- a/crates/edit_prediction_ui/src/rate_prediction_modal.rs
+++ b/crates/edit_prediction_ui/src/rate_prediction_modal.rs
@@ -439,7 +439,6 @@ impl RatePredictionsModal {
editor.set_show_git_diff_gutter(false, cx);
editor.set_show_code_actions(false, cx);
editor.set_show_runnables(false, cx);
- editor.set_show_bookmarks(false, cx);
editor.set_show_breakpoints(false, cx);
editor.set_show_wrap_guides(false, cx);
editor.set_show_indent_guides(false, cx);
diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs
index 7524c5b01bf090be3661d1af03f918aa3a7449fb..4c0623006953c0aae9c718b2e894ba85b26074e0 100644
--- a/crates/editor/src/actions.rs
+++ b/crates/editor/src/actions.rs
@@ -576,14 +576,10 @@ actions!(
GoToImplementation,
/// Goes to implementation in a split pane.
GoToImplementationSplit,
- /// Goes to the next bookmark in the file.
- GoToNextBookmark,
/// Goes to the next change in the file.
GoToNextChange,
/// Goes to the parent module of the current file.
GoToParentModule,
- /// Goes to the previous bookmark in the file.
- GoToPreviousBookmark,
/// Goes to the previous change in the file.
GoToPreviousChange,
/// Goes to the next symbol.
@@ -674,8 +670,6 @@ actions!(
NextScreen,
/// Goes to the next snippet tabstop if one exists.
NextSnippetTabstop,
- /// Opens a view of all bookmarks in the project.
- ViewBookmarks,
/// Opens the context menu at cursor position.
OpenContextMenu,
/// Opens excerpts from the current file.
@@ -825,8 +819,6 @@ actions!(
Tab,
/// Removes a tab character or outdents.
Backtab,
- /// Toggles a bookmark at the current line.
- ToggleBookmark,
/// Toggles a breakpoint at the current line.
ToggleBreakpoint,
/// Toggles the case of selected text.
diff --git a/crates/editor/src/bookmarks.rs b/crates/editor/src/bookmarks.rs
deleted file mode 100644
index fe047e7fa18c22f4dc90e665f1a24c1ee53f1fda..0000000000000000000000000000000000000000
--- a/crates/editor/src/bookmarks.rs
+++ /dev/null
@@ -1,243 +0,0 @@
-use std::ops::Range;
-
-use gpui::Entity;
-use multi_buffer::{Anchor, MultiBufferOffset, MultiBufferSnapshot, ToOffset as _};
-use project::{Project, bookmark_store::BookmarkStore};
-use rope::Point;
-use text::Bias;
-use ui::{Context, Window};
-use util::ResultExt as _;
-use workspace::{Workspace, searchable::Direction};
-
-use crate::display_map::DisplayRow;
-use crate::{
- Editor, GoToNextBookmark, GoToPreviousBookmark, MultibufferSelectionMode, SelectionEffects,
- ToggleBookmark, ViewBookmarks, scroll::Autoscroll,
-};
-
-impl Editor {
- pub fn set_show_bookmarks(&mut self, show_bookmarks: bool, cx: &mut Context) {
- self.show_bookmarks = Some(show_bookmarks);
- cx.notify();
- }
-
- pub fn toggle_bookmark(
- &mut self,
- _: &ToggleBookmark,
- window: &mut Window,
- cx: &mut Context,
- ) {
- let Some(bookmark_store) = self.bookmark_store.clone() else {
- return;
- };
- let Some(project) = self.project() else {
- return;
- };
-
- let snapshot = self.snapshot(window, cx);
- let multi_buffer_snapshot = snapshot.buffer_snapshot();
-
- let mut selections = self.selections.all::(&snapshot.display_snapshot);
- selections.sort_by_key(|s| s.head());
- selections.dedup_by_key(|s| s.head().row);
-
- for selection in &selections {
- let head = selection.head();
- let multibuffer_anchor = multi_buffer_snapshot.anchor_before(Point::new(head.row, 0));
-
- if let Some((buffer_anchor, _)) =
- multi_buffer_snapshot.anchor_to_buffer_anchor(multibuffer_anchor)
- {
- let buffer_id = buffer_anchor.buffer_id;
- if let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) {
- bookmark_store.update(cx, |store, cx| {
- store.toggle_bookmark(buffer, buffer_anchor, cx);
- });
- }
- }
- }
-
- cx.notify();
- }
-
- pub fn toggle_bookmark_at_row(&mut self, row: DisplayRow, cx: &mut Context) {
- let Some(bookmark_store) = &self.bookmark_store else {
- return;
- };
- let display_snapshot = self.display_snapshot(cx);
- let point = display_snapshot.display_point_to_point(row.as_display_point(), Bias::Left);
- let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
- let anchor = buffer_snapshot.anchor_before(point);
-
- let Some((position, _)) = buffer_snapshot.anchor_to_buffer_anchor(anchor) else {
- return;
- };
- let Some(buffer) = self.buffer.read(cx).buffer(position.buffer_id) else {
- return;
- };
-
- bookmark_store.update(cx, |bookmark_store, cx| {
- bookmark_store.toggle_bookmark(buffer, position, cx);
- });
-
- cx.notify();
- }
-
- pub fn toggle_bookmark_at_anchor(&mut self, anchor: Anchor, cx: &mut Context) {
- let Some(bookmark_store) = &self.bookmark_store else {
- return;
- };
- let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
- let Some((position, _)) = buffer_snapshot.anchor_to_buffer_anchor(anchor) else {
- return;
- };
- let Some(buffer) = self.buffer.read(cx).buffer(position.buffer_id) else {
- return;
- };
-
- bookmark_store.update(cx, |bookmark_store, cx| {
- bookmark_store.toggle_bookmark(buffer, position, cx);
- });
-
- cx.notify();
- }
-
- pub fn go_to_next_bookmark(
- &mut self,
- _: &GoToNextBookmark,
- window: &mut Window,
- cx: &mut Context,
- ) {
- self.go_to_bookmark_impl(Direction::Next, window, cx);
- }
-
- pub fn go_to_previous_bookmark(
- &mut self,
- _: &GoToPreviousBookmark,
- window: &mut Window,
- cx: &mut Context,
- ) {
- self.go_to_bookmark_impl(Direction::Prev, window, cx);
- }
-
- fn go_to_bookmark_impl(
- &mut self,
- direction: Direction,
- window: &mut Window,
- cx: &mut Context,
- ) {
- let Some(project) = &self.project else {
- return;
- };
- let Some(bookmark_store) = &self.bookmark_store else {
- return;
- };
-
- let selection = self
- .selections
- .newest::(&self.display_snapshot(cx));
- let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
-
- let mut all_bookmarks = Self::bookmarks_in_range(
- MultiBufferOffset(0)..multi_buffer_snapshot.len(),
- &multi_buffer_snapshot,
- project,
- bookmark_store,
- cx,
- );
- all_bookmarks.sort_by_key(|a| a.to_offset(&multi_buffer_snapshot));
-
- let anchor = match direction {
- Direction::Next => all_bookmarks
- .iter()
- .find(|anchor| anchor.to_offset(&multi_buffer_snapshot) > selection.head())
- .or_else(|| all_bookmarks.first()),
- Direction::Prev => all_bookmarks
- .iter()
- .rfind(|anchor| anchor.to_offset(&multi_buffer_snapshot) < selection.head())
- .or_else(|| all_bookmarks.last()),
- }
- .cloned();
-
- if let Some(anchor) = anchor {
- self.unfold_ranges(&[anchor..anchor], true, false, cx);
- self.change_selections(
- SelectionEffects::scroll(Autoscroll::center()),
- window,
- cx,
- |s| {
- s.select_anchor_ranges([anchor..anchor]);
- },
- );
- }
- }
-
- pub fn view_bookmarks(
- workspace: &mut Workspace,
- _: &ViewBookmarks,
- window: &mut Window,
- cx: &mut Context,
- ) {
- let bookmark_store = workspace.project().read(cx).bookmark_store();
- cx.spawn_in(window, async move |workspace, cx| {
- let Some(locations) = BookmarkStore::all_bookmark_locations(bookmark_store, cx)
- .await
- .log_err()
- else {
- return;
- };
-
- workspace
- .update_in(cx, |workspace, window, cx| {
- Editor::open_locations_in_multibuffer(
- workspace,
- locations,
- "Bookmarks".into(),
- false,
- false,
- MultibufferSelectionMode::First,
- window,
- cx,
- );
- })
- .log_err();
- })
- .detach();
- }
-
- fn bookmarks_in_range(
- range: Range,
- multi_buffer_snapshot: &MultiBufferSnapshot,
- project: &Entity,
- bookmark_store: &Entity,
- cx: &mut Context,
- ) -> Vec {
- multi_buffer_snapshot
- .range_to_buffer_ranges(range)
- .into_iter()
- .flat_map(|(buffer_snapshot, buffer_range, _excerpt_range)| {
- let Some(buffer) = project
- .read(cx)
- .buffer_for_id(buffer_snapshot.remote_id(), cx)
- else {
- return Vec::new();
- };
- bookmark_store
- .update(cx, |store, cx| {
- store.bookmarks_for_buffer(
- buffer,
- buffer_snapshot.anchor_before(buffer_range.start)
- ..buffer_snapshot.anchor_after(buffer_range.end),
- &buffer_snapshot,
- cx,
- )
- })
- .into_iter()
- .filter_map(|bookmark| {
- multi_buffer_snapshot.anchor_in_buffer(bookmark.anchor())
- })
- .collect::>()
- })
- .collect()
- }
-}
diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs
index 784c05371630d8201396cab3b797cfab037258f0..f3dfef45783240042675667af4207797ae65b743 100644
--- a/crates/editor/src/editor.rs
+++ b/crates/editor/src/editor.rs
@@ -43,7 +43,6 @@ pub mod semantic_tokens;
mod split;
pub mod split_editor_view;
-mod bookmarks;
#[cfg(test)]
mod code_completion_tests;
#[cfg(test)]
@@ -163,7 +162,6 @@ use project::{
CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
ProjectItem, ProjectPath, ProjectTransaction,
- bookmark_store::BookmarkStore,
debugger::{
breakpoint_store::{
Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
@@ -353,7 +351,6 @@ pub fn init(cx: &mut App) {
workspace.register_action(Editor::new_file_horizontal);
workspace.register_action(Editor::cancel_language_server_work);
workspace.register_action(Editor::toggle_focus);
- workspace.register_action(Editor::view_bookmarks);
},
)
.detach();
@@ -1029,14 +1026,15 @@ enum ColumnarSelectionState {
},
}
-/// Represents a button that shows up when hovering over lines in the gutter that don't have
-/// any button on them already (like a bookmark, breakpoint or run indicator).
+/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
+/// a breakpoint on them.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-struct GutterHoverButton {
+struct PhantomBreakpointIndicator {
display_row: DisplayRow,
/// There's a small debounce between hovering over the line and showing the indicator.
/// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
is_active: bool,
+ collides_with_existing_breakpoint: bool,
}
/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
@@ -1192,7 +1190,6 @@ pub struct Editor {
show_git_diff_gutter: Option,
show_code_actions: Option,
show_runnables: Option,
- show_bookmarks: Option,
show_breakpoints: Option,
show_diff_review_button: bool,
show_wrap_guides: Option,
@@ -1300,9 +1297,8 @@ pub struct Editor {
last_position_map: Option>,
expect_bounds_change: Option>,
runnables: RunnableData,
- bookmark_store: Option>,
breakpoint_store: Option>,
- gutter_hover_button: (Option, Option>),
+ gutter_breakpoint_indicator: (Option, Option>),
pub(crate) gutter_diff_review_indicator: (Option, Option>),
pub(crate) diff_review_drag_state: Option,
/// Active diff review overlays. Multiple overlays can be open simultaneously
@@ -1408,7 +1404,6 @@ pub struct EditorSnapshot {
show_code_actions: Option,
show_runnables: Option,
show_breakpoints: Option,
- show_bookmarks: Option,
git_blame_gutter_max_author_length: Option,
pub display_snapshot: DisplaySnapshot,
pub placeholder_display_snapshot: Option,
@@ -2370,11 +2365,6 @@ impl Editor {
None
};
- let bookmark_store = match (&mode, project.as_ref()) {
- (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).bookmark_store()),
- _ => None,
- };
-
let breakpoint_store = match (&mode, project.as_ref()) {
(EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
_ => None,
@@ -2452,7 +2442,6 @@ impl Editor {
show_git_diff_gutter: None,
show_code_actions: None,
show_runnables: None,
- show_bookmarks: None,
show_breakpoints: None,
show_diff_review_button: false,
show_wrap_guides: None,
@@ -2555,9 +2544,8 @@ impl Editor {
blame: None,
blame_subscription: None,
- bookmark_store,
breakpoint_store,
- gutter_hover_button: (None, None),
+ gutter_breakpoint_indicator: (None, None),
gutter_diff_review_indicator: (None, None),
diff_review_drag_state: None,
diff_review_overlays: Vec::new(),
@@ -3335,7 +3323,6 @@ impl Editor {
semantic_tokens_enabled: self.semantic_token_state.enabled(),
show_code_actions: self.show_code_actions,
show_runnables: self.show_runnables,
- show_bookmarks: self.show_bookmarks,
show_breakpoints: self.show_breakpoints,
git_blame_gutter_max_author_length,
scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
@@ -8636,9 +8623,6 @@ impl Editor {
let mouse_position = window.mouse_position();
if !position_map.text_hitbox.is_hovered(window) {
- if self.gutter_hover_button.0.is_some() {
- cx.notify();
- }
return;
}
@@ -9021,138 +9005,6 @@ impl Editor {
Some(self.edit_prediction_provider.as_ref()?.provider.clone())
}
- fn active_run_indicators(
- &mut self,
- range: Range,
- window: &mut Window,
- cx: &mut Context,
- ) -> HashSet {
- let snapshot = self.snapshot(window, cx);
-
- let offset_range_start =
- snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left);
-
- let offset_range_end =
- snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
-
- self.runnables
- .all_runnables()
- .filter_map(|tasks| {
- let multibuffer_point = tasks.offset.to_point(&snapshot.buffer_snapshot());
- if multibuffer_point < offset_range_start || multibuffer_point > offset_range_end {
- return None;
- }
- let multibuffer_row = MultiBufferRow(multibuffer_point.row);
- let buffer_folded = snapshot
- .buffer_snapshot()
- .buffer_line_for_row(multibuffer_row)
- .map(|(buffer_snapshot, _)| buffer_snapshot.remote_id())
- .map(|buffer_id| self.is_buffer_folded(buffer_id, cx))
- .unwrap_or(false);
- if buffer_folded {
- return None;
- }
-
- if snapshot.is_line_folded(multibuffer_row) {
- // Skip folded indicators, unless it's the starting line of a fold.
- if multibuffer_row
- .0
- .checked_sub(1)
- .is_some_and(|previous_row| {
- snapshot.is_line_folded(MultiBufferRow(previous_row))
- })
- {
- return None;
- }
- }
-
- let display_row = multibuffer_point.to_display_point(&snapshot).row();
- Some(display_row)
- })
- .collect()
- }
-
- fn active_bookmarks(
- &self,
- range: Range,
- window: &mut Window,
- cx: &mut Context,
- ) -> HashSet {
- let mut bookmark_display_points = HashSet::default();
-
- let Some(bookmark_store) = self.bookmark_store.clone() else {
- return bookmark_display_points;
- };
-
- let snapshot = self.snapshot(window, cx);
-
- let multi_buffer_snapshot = snapshot.buffer_snapshot();
- let Some(project) = self.project() else {
- return bookmark_display_points;
- };
-
- let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
- ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
-
- for (buffer_snapshot, range, _excerpt_range) in
- multi_buffer_snapshot.range_to_buffer_ranges(range.start..range.end)
- {
- let Some(buffer) = project
- .read(cx)
- .buffer_for_id(buffer_snapshot.remote_id(), cx)
- else {
- continue;
- };
- let bookmarks = bookmark_store.update(cx, |store, cx| {
- store.bookmarks_for_buffer(
- buffer,
- buffer_snapshot.anchor_before(range.start)
- ..buffer_snapshot.anchor_after(range.end),
- &buffer_snapshot,
- cx,
- )
- });
- for bookmark in bookmarks {
- let Some(multi_buffer_anchor) =
- multi_buffer_snapshot.anchor_in_buffer(bookmark.anchor())
- else {
- continue;
- };
- let position = multi_buffer_anchor
- .to_point(&multi_buffer_snapshot)
- .to_display_point(&snapshot);
-
- bookmark_display_points.insert(position.row());
- }
- }
-
- bookmark_display_points
- }
-
- fn render_bookmark(&self, row: DisplayRow, cx: &mut Context) -> IconButton {
- let focus_handle = self.focus_handle.clone();
- IconButton::new(("bookmark indicator", row.0 as usize), IconName::Bookmark)
- .icon_size(IconSize::XSmall)
- .size(ui::ButtonSize::None)
- .icon_color(Color::Info)
- .style(ButtonStyle::Transparent)
- .on_click(cx.listener(move |editor, _, _, cx| {
- editor.toggle_bookmark_at_row(row, cx);
- }))
- .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
- editor.set_gutter_context_menu(row, None, event.position(), window, cx);
- }))
- .tooltip(move |_window, cx| {
- Tooltip::with_meta_in(
- "Remove bookmark",
- Some(&ToggleBookmark),
- SharedString::from("Right-click for more options."),
- &focus_handle,
- cx,
- )
- })
- }
-
/// Get all display points of breakpoints that will be rendered within editor
///
/// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
@@ -9212,7 +9064,7 @@ impl Editor {
breakpoint_display_points
}
- fn gutter_context_menu(
+ fn breakpoint_context_menu(
&self,
anchor: Anchor,
window: &mut Window,
@@ -9262,14 +9114,6 @@ impl Editor {
"Set Breakpoint"
};
- let bookmark = self.bookmark_at_row(row, window, cx);
-
- let set_bookmark_msg = if bookmark.as_ref().is_some() {
- "Remove Bookmark"
- } else {
- "Add Bookmark"
- };
-
let run_to_cursor = window.is_action_available(&RunToCursor, cx);
let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
@@ -9369,28 +9213,16 @@ impl Editor {
.log_err();
}
})
- .entry(hit_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::HitCondition,
- window,
- cx,
- );
- })
- .log_err();
- }
- })
- .separator()
- .entry(set_bookmark_msg, None, move |_window, cx| {
+ .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
weak_editor
.update(cx, |this, cx| {
- this.toggle_bookmark_at_anchor(anchor, cx);
+ this.add_edit_breakpoint_block(
+ anchor,
+ breakpoint.as_ref(),
+ BreakpointPromptEditAction::HitCondition,
+ window,
+ cx,
+ );
})
.log_err();
})
@@ -9406,6 +9238,20 @@ impl Editor {
cx: &mut Context,
) -> IconButton {
let is_rejected = state.is_some_and(|s| !s.verified);
+ // Is it a breakpoint that shows up when hovering over gutter?
+ let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
+ (false, false),
+ |PhantomBreakpointIndicator {
+ is_active,
+ display_row,
+ collides_with_existing_breakpoint,
+ }| {
+ (
+ is_active && display_row == row,
+ collides_with_existing_breakpoint,
+ )
+ },
+ );
let (color, icon) = {
let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
@@ -9415,7 +9261,19 @@ impl Editor {
(true, true) => ui::IconName::DebugDisabledLogBreakpoint,
};
- let color = if is_rejected {
+ let theme_colors = cx.theme().colors();
+
+ let color = if is_phantom {
+ if collides_with_existing {
+ Color::Custom(
+ theme_colors
+ .debugger_accent
+ .blend(theme_colors.text.opacity(0.6)),
+ )
+ } else {
+ Color::Hint
+ }
+ } else if is_rejected {
Color::Disabled
} else {
Color::Debugger
@@ -9430,14 +9288,20 @@ impl Editor {
modifiers: Modifiers::secondary_key(),
..Default::default()
};
- let primary_action_text = "Unset breakpoint";
+ let primary_action_text = if breakpoint.is_disabled() {
+ "Enable breakpoint"
+ } else if is_phantom && !collides_with_existing {
+ "Set breakpoint"
+ } else {
+ "Unset breakpoint"
+ };
let focus_handle = self.focus_handle.clone();
let meta = if is_rejected {
SharedString::from("No executable code is associated with this line.")
- } else if !breakpoint.is_disabled() {
+ } else if collides_with_existing && !breakpoint.is_disabled() {
SharedString::from(format!(
- "{alt_as_text}click to disable,\nright-click for more options."
+ "{alt_as_text}-click to disable,\nright-click for more options."
))
} else {
SharedString::from("Right-click for more options.")
@@ -9459,6 +9323,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(),
@@ -9468,7 +9333,13 @@ impl Editor {
}
}))
.on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
- editor.set_gutter_context_menu(row, Some(position), event.position(), window, cx);
+ editor.set_breakpoint_context_menu(
+ row,
+ Some(position),
+ event.position(),
+ window,
+ cx,
+ );
}))
.tooltip(move |_window, cx| {
Tooltip::with_meta_in(
@@ -9481,117 +9352,6 @@ impl Editor {
})
}
- fn render_gutter_hover_button(
- &self,
- position: Anchor,
- row: DisplayRow,
- window: &mut Window,
- cx: &mut Context,
- ) -> IconButton {
- #[derive(Clone, Copy)]
- enum Intent {
- SetBookmark,
- SetBreakpoint,
- }
-
- impl Intent {
- fn as_str(&self) -> &'static str {
- match self {
- Intent::SetBookmark => "Set bookmark",
- Intent::SetBreakpoint => "Set breakpoint",
- }
- }
-
- fn icon(&self) -> ui::IconName {
- match self {
- Intent::SetBookmark => ui::IconName::Bookmark,
- Intent::SetBreakpoint => ui::IconName::DebugBreakpoint,
- }
- }
-
- fn color(&self) -> Color {
- match self {
- Intent::SetBookmark => Color::Info,
- Intent::SetBreakpoint => Color::Hint,
- }
- }
-
- fn secondary_and_options(&self) -> String {
- let alt_as_text = gpui::Keystroke {
- modifiers: Modifiers::secondary_key(),
- ..Default::default()
- };
- match self {
- Intent::SetBookmark => format!(
- "{alt_as_text}click to add a breakpoint,\nright-click for more options."
- ),
- Intent::SetBreakpoint => format!(
- "{alt_as_text}click to add a bookmark,\nright-click for more options."
- ),
- }
- }
- }
-
- let gutter_settings = EditorSettings::get_global(cx).gutter;
- let show_bookmarks = self.show_bookmarks.unwrap_or(gutter_settings.bookmarks);
- let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
-
- let [primary, secondary] = match [show_breakpoints, show_bookmarks] {
- [true, true] => [Intent::SetBreakpoint, Intent::SetBookmark],
- [true, false] => [Intent::SetBreakpoint; 2],
- [false, true] => [Intent::SetBookmark; 2],
- [false, false] => {
- log::error!("Trying to place gutter_hover without anything enabled!!");
- [Intent::SetBookmark; 2]
- }
- };
-
- let intent = if window.modifiers().secondary() {
- secondary
- } else {
- primary
- };
-
- let focus_handle = self.focus_handle.clone();
- IconButton::new(("add_breakpoint_button", row.0 as usize), intent.icon())
- .icon_size(IconSize::XSmall)
- .size(ui::ButtonSize::None)
- .icon_color(intent.color())
- .style(ButtonStyle::Transparent)
- .on_click(cx.listener({
- move |editor, _: &ClickEvent, window, cx| {
- window.focus(&editor.focus_handle(cx), cx);
- let intent = if window.modifiers().secondary() {
- secondary
- } else {
- primary
- };
-
- match intent {
- Intent::SetBookmark => editor.toggle_bookmark_at_row(row, cx),
- Intent::SetBreakpoint => editor.edit_breakpoint_at_anchor(
- position,
- Breakpoint::new_standard(),
- BreakpointEditAction::Toggle,
- cx,
- ),
- }
- }
- }))
- .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
- editor.set_gutter_context_menu(row, Some(position), event.position(), window, cx);
- }))
- .tooltip(move |_window, cx| {
- Tooltip::with_meta_in(
- intent.as_str(),
- Some(&ToggleBreakpoint),
- intent.secondary_and_options(),
- &focus_handle,
- cx,
- )
- })
- }
-
fn build_tasks_context(
project: &Entity,
buffer: &Entity,
@@ -12164,7 +11924,7 @@ impl Editor {
}
}
- fn set_gutter_context_menu(
+ fn set_breakpoint_context_menu(
&mut self,
display_row: DisplayRow,
position: Option,
@@ -12178,7 +11938,7 @@ impl Editor {
.snapshot(cx)
.anchor_before(Point::new(display_row.0, 0u32));
- let context_menu = self.gutter_context_menu(position.unwrap_or(source), window, cx);
+ let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
self,
@@ -12295,65 +12055,6 @@ impl Editor {
})
}
- pub(crate) fn bookmark_at_row(
- &self,
- row: u32,
- window: &mut Window,
- cx: &mut Context,
- ) -> Option {
- let snapshot = self.snapshot(window, cx);
- let bookmark_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
-
- self.bookmark_at_anchor(bookmark_position, &snapshot, cx)
- }
-
- pub(crate) fn bookmark_at_anchor(
- &self,
- bookmark_position: Anchor,
- snapshot: &EditorSnapshot,
- cx: &mut Context,
- ) -> Option {
- let (bookmark_position, _) = snapshot
- .buffer_snapshot()
- .anchor_to_buffer_anchor(bookmark_position)?;
- let buffer = self.buffer.read(cx).buffer(bookmark_position.buffer_id)?;
-
- let buffer_snapshot = buffer.read(cx).snapshot();
-
- let row = buffer_snapshot
- .summary_for_anchor::(&bookmark_position)
- .row;
-
- let line_len = buffer_snapshot.line_len(row);
- let anchor_end = buffer_snapshot.anchor_after(Point::new(row, line_len));
-
- self.bookmark_store
- .as_ref()?
- .update(cx, |bookmark_store, cx| {
- bookmark_store
- .bookmarks_for_buffer(
- buffer,
- bookmark_position..anchor_end,
- &buffer_snapshot,
- cx,
- )
- .first()
- .and_then(|bookmark| {
- let bookmark_row = buffer_snapshot
- .summary_for_anchor::(&bookmark.anchor())
- .row;
-
- if bookmark_row == row {
- snapshot
- .buffer_snapshot()
- .anchor_in_excerpt(bookmark.anchor())
- } else {
- None
- }
- })
- })
- }
-
pub fn edit_log_breakpoint(
&mut self,
_: &EditLogBreakpoint,
@@ -12565,7 +12266,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,
@@ -12584,6 +12297,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,
@@ -28460,7 +28188,6 @@ impl EditorSnapshot {
let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
- let show_bookmarks = self.show_bookmarks.unwrap_or(gutter_settings.bookmarks);
let git_blame_entries_width =
self.git_blame_gutter_max_author_length
@@ -28481,20 +28208,18 @@ impl EditorSnapshot {
let is_singleton = self.buffer_snapshot().is_singleton();
- let left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO)
- + if !is_singleton {
- ch_width * 4.0
- // runnables, breakpoints and bookmarks are shown in the same place
- // if all three are there only the runnable is shown
- } else if show_runnables || show_breakpoints || show_bookmarks {
- ch_width * 3.0
- } else if show_git_gutter && show_line_numbers {
- ch_width * 2.0
- } else if show_git_gutter || show_line_numbers {
- ch_width
- } else {
- px(0.)
- };
+ let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
+ left_padding += if !is_singleton {
+ ch_width * 4.0
+ } else if show_runnables || show_breakpoints {
+ ch_width * 3.0
+ } else if show_git_gutter && show_line_numbers {
+ ch_width * 2.0
+ } else if show_git_gutter || show_line_numbers {
+ ch_width
+ } else {
+ px(0.)
+ };
let shows_folds = is_singleton && gutter_settings.folds;
diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs
index e70dd137ba382049b59691d4252e76ae75cb66d0..548053da7d794de83d99afdfddb098e4cfb2b18e 100644
--- a/crates/editor/src/editor_settings.rs
+++ b/crates/editor/src/editor_settings.rs
@@ -133,7 +133,6 @@ pub struct Gutter {
pub line_numbers: bool,
pub runnables: bool,
pub breakpoints: bool,
- pub bookmarks: bool,
pub folds: bool,
}
@@ -249,7 +248,6 @@ impl Settings for EditorSettings {
min_line_number_digits: gutter.min_line_number_digits.unwrap(),
line_numbers: gutter.line_numbers.unwrap(),
runnables: gutter.runnables.unwrap(),
- bookmarks: gutter.bookmarks.unwrap(),
breakpoints: gutter.breakpoints.unwrap(),
folds: gutter.folds.unwrap(),
},
diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs
index 20eb17a2da9ba848030f1fbf26d22bf72498d68b..38f4259b4d4c87f3243e49dec8f35991bd82f246 100644
--- a/crates/editor/src/editor_tests.rs
+++ b/crates/editor/src/editor_tests.rs
@@ -41,7 +41,6 @@ use parking_lot::Mutex;
use pretty_assertions::{assert_eq, assert_ne};
use project::{
FakeFs, Project,
- bookmark_store::SerializedBookmark,
debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
project_settings::LspSettings,
trusted_worktrees::{PathTrust, TrustedWorktrees},
@@ -27571,554 +27570,107 @@ async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
);
}
-struct BookmarkTestContext {
- project: Entity,
- editor: Entity,
- cx: VisualTestContext,
-}
-
-impl BookmarkTestContext {
- async fn new(sample_text: &str, cx: &mut TestAppContext) -> BookmarkTestContext {
- init_test(cx, |_| {});
-
- 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 window =
- cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
- let workspace = window
- .read_with(cx, |mw, _| mw.workspace().clone())
- .unwrap();
- let mut visual_cx = VisualTestContext::from_window(*window, cx);
- let worktree_id = workspace.update_in(&mut visual_cx, |workspace, _window, cx| {
- workspace.project().update(cx, |project, cx| {
- project.worktrees(cx).next().unwrap().read(cx).id()
- })
- });
-
- let buffer = project
- .update(&mut visual_cx, |project, cx| {
- project.open_buffer((worktree_id, rel_path("main.rs")), cx)
- })
- .await
- .unwrap();
-
- let (editor, editor_cx) = cx.add_window_view(|window, cx| {
- Editor::new(
- EditorMode::full(),
- MultiBuffer::build_from_buffer(buffer, cx),
- Some(project.clone()),
- window,
- cx,
- )
- });
- let cx = editor_cx.clone();
-
- BookmarkTestContext {
- project,
- editor,
- cx,
- }
- }
-
- fn abs_path(&self) -> Arc {
- let project_path = self
- .editor
- .read_with(&self.cx, |editor, cx| editor.project_path(cx).unwrap());
- self.project.read_with(&self.cx, |project, cx| {
- project
- .absolute_path(&project_path, cx)
- .map(Arc::from)
- .unwrap()
- })
- }
-
- fn all_bookmarks(&self) -> BTreeMap, Vec> {
- self.project.read_with(&self.cx, |project, cx| {
- project
- .bookmark_store()
- .read(cx)
- .all_serialized_bookmarks(cx)
- })
- }
-
- fn assert_bookmark_rows(&self, expected_rows: Vec) {
- let abs_path = self.abs_path();
- let bookmarks = self.all_bookmarks();
- if expected_rows.is_empty() {
- assert!(
- !bookmarks.contains_key(&abs_path),
- "Expected no bookmarks for {}",
- abs_path.display()
- );
- } else {
- let mut rows: Vec = bookmarks
- .get(&abs_path)
- .unwrap()
- .iter()
- .map(|b| b.0)
- .collect();
- rows.sort();
- assert_eq!(expected_rows, rows);
- }
- }
-
- fn cursor_row(&mut self) -> u32 {
- self.editor.update(&mut self.cx, |editor, cx| {
- let snapshot = editor.display_snapshot(cx);
- editor.selections.newest::(&snapshot).head().row
- })
- }
-
- fn cursor_point(&mut self) -> Point {
- self.editor.update(&mut self.cx, |editor, cx| {
- let snapshot = editor.display_snapshot(cx);
- editor.selections.newest::(&snapshot).head()
- })
- }
-
- fn move_to_row(&mut self, row: u32) {
- self.editor
- .update_in(&mut self.cx, |editor: &mut Editor, window, cx| {
- editor.move_to_beginning(&MoveToBeginning, window, cx);
- for _ in 0..row {
- editor.move_down(&MoveDown, window, cx);
- }
- });
- }
-
- fn toggle_bookmark(&mut self) {
- self.editor
- .update_in(&mut self.cx, |editor: &mut Editor, window, cx| {
- editor.toggle_bookmark(&actions::ToggleBookmark, window, cx);
- });
- }
-
- fn toggle_bookmarks_at_rows(&mut self, rows: &[u32]) {
- for &row in rows {
- self.move_to_row(row);
- self.toggle_bookmark();
- }
- }
-
- fn go_to_next_bookmark(&mut self) {
- self.editor
- .update_in(&mut self.cx, |editor: &mut Editor, window, cx| {
- editor.go_to_next_bookmark(&actions::GoToNextBookmark, window, cx);
- });
- }
-
- fn go_to_previous_bookmark(&mut self) {
- self.editor
- .update_in(&mut self.cx, |editor: &mut Editor, window, cx| {
- editor.go_to_previous_bookmark(&actions::GoToPreviousBookmark, window, cx);
- });
- }
-}
-
-#[gpui::test]
-async fn test_bookmark_toggling(cx: &mut TestAppContext) {
- let mut ctx =
- BookmarkTestContext::new("First line\nSecond line\nThird line\nFourth line", cx).await;
-
- ctx.editor
- .update_in(&mut ctx.cx, |editor: &mut Editor, window, cx| {
- editor.toggle_bookmark(&actions::ToggleBookmark, window, cx);
- editor.move_to_end(&MoveToEnd, window, cx);
- editor.toggle_bookmark(&actions::ToggleBookmark, window, cx);
- });
-
- assert_eq!(1, ctx.all_bookmarks().len());
- ctx.assert_bookmark_rows(vec![0, 3]);
-
- ctx.editor
- .update_in(&mut ctx.cx, |editor: &mut Editor, window, cx| {
- editor.move_to_beginning(&MoveToBeginning, window, cx);
- editor.toggle_bookmark(&actions::ToggleBookmark, window, cx);
- });
-
- assert_eq!(1, ctx.all_bookmarks().len());
- ctx.assert_bookmark_rows(vec![3]);
-
- ctx.editor
- .update_in(&mut ctx.cx, |editor: &mut Editor, window, cx| {
- editor.move_to_end(&MoveToEnd, window, cx);
- editor.toggle_bookmark(&actions::ToggleBookmark, window, cx);
- });
-
- assert_eq!(0, ctx.all_bookmarks().len());
- ctx.assert_bookmark_rows(vec![]);
-}
-
-#[gpui::test]
-async fn test_bookmark_toggling_with_multiple_selections(cx: &mut TestAppContext) {
- let mut ctx =
- BookmarkTestContext::new("First line\nSecond line\nThird line\nFourth line", cx).await;
-
- ctx.editor
- .update_in(&mut ctx.cx, |editor: &mut Editor, window, cx| {
- editor.move_to_beginning(&MoveToBeginning, window, cx);
- editor.add_selection_below(&Default::default(), window, cx);
- editor.add_selection_below(&Default::default(), window, cx);
- editor.add_selection_below(&Default::default(), window, cx);
- });
-
- ctx.toggle_bookmark();
-
- assert_eq!(1, ctx.all_bookmarks().len());
- ctx.assert_bookmark_rows(vec![0, 1, 2, 3]);
-
- ctx.editor
- .update_in(&mut ctx.cx, |editor: &mut Editor, window, cx| {
- editor.move_to_beginning(&MoveToBeginning, window, cx);
- editor.add_selection_below(&Default::default(), window, cx);
- editor.add_selection_below(&Default::default(), window, cx);
- editor.add_selection_below(&Default::default(), window, cx);
- editor.toggle_bookmark(&actions::ToggleBookmark, window, cx);
- });
-
- assert_eq!(0, ctx.all_bookmarks().len());
- ctx.assert_bookmark_rows(vec![]);
-}
-
-#[gpui::test]
-async fn test_bookmark_toggle_deduplicates_by_row(cx: &mut TestAppContext) {
- let mut ctx =
- BookmarkTestContext::new("First line\nSecond line\nThird line\nFourth line", cx).await;
-
- ctx.editor
- .update_in(&mut ctx.cx, |editor: &mut Editor, window, cx| {
- editor.move_to_beginning(&MoveToBeginning, window, cx);
- editor.toggle_bookmark(&actions::ToggleBookmark, window, cx);
- });
-
- ctx.assert_bookmark_rows(vec![0]);
-
- ctx.editor
- .update_in(&mut ctx.cx, |editor: &mut Editor, window, cx| {
- editor.move_to_end_of_line(
- &MoveToEndOfLine {
- stop_at_soft_wraps: true,
- },
- window,
- cx,
- );
- editor.toggle_bookmark(&actions::ToggleBookmark, window, cx);
- });
-
- ctx.assert_bookmark_rows(vec![]);
-}
-
#[gpui::test]
-async fn test_bookmark_survives_edits(cx: &mut TestAppContext) {
- let mut ctx =
- BookmarkTestContext::new("First line\nSecond line\nThird line\nFourth line", cx).await;
-
- ctx.move_to_row(2);
- ctx.toggle_bookmark();
- ctx.assert_bookmark_rows(vec![2]);
-
- ctx.editor
- .update_in(&mut ctx.cx, |editor: &mut Editor, window, cx| {
- editor.move_to_beginning(&MoveToBeginning, window, cx);
- editor.newline(&Newline, window, cx);
- });
-
- ctx.assert_bookmark_rows(vec![3]);
-
- ctx.move_to_row(3);
- ctx.toggle_bookmark();
- ctx.assert_bookmark_rows(vec![]);
-}
+async fn test_breakpoint_phantom_indicator_collision_on_toggle(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
-#[gpui::test]
-async fn test_active_bookmarks(cx: &mut TestAppContext) {
- let mut ctx = BookmarkTestContext::new(
- "Line 0\nLine 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nLine 8\nLine 9",
- 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 window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
+ let workspace = window
+ .read_with(cx, |mw, _| mw.workspace().clone())
+ .unwrap();
+ let cx = &mut VisualTestContext::from_window(*window, cx);
+ let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
+ workspace.project().update(cx, |project, cx| {
+ project.worktrees(cx).next().unwrap().read(cx).id()
+ })
+ });
- ctx.toggle_bookmarks_at_rows(&[1, 3, 5, 8]);
-
- let active = ctx
- .editor
- .update_in(&mut ctx.cx, |editor: &mut Editor, window, cx| {
- editor.active_bookmarks(DisplayRow(0)..DisplayRow(10), window, cx)
- });
- assert!(active.contains(&DisplayRow(1)));
- assert!(active.contains(&DisplayRow(3)));
- assert!(active.contains(&DisplayRow(5)));
- assert!(active.contains(&DisplayRow(8)));
- assert!(!active.contains(&DisplayRow(0)));
- assert!(!active.contains(&DisplayRow(2)));
- assert!(!active.contains(&DisplayRow(9)));
-
- let active = ctx
- .editor
- .update_in(&mut ctx.cx, |editor: &mut Editor, window, cx| {
- editor.active_bookmarks(DisplayRow(2)..DisplayRow(6), window, cx)
- });
- assert!(active.contains(&DisplayRow(3)));
- assert!(active.contains(&DisplayRow(5)));
- assert!(!active.contains(&DisplayRow(1)));
- assert!(!active.contains(&DisplayRow(8)));
-}
+ let buffer = project
+ .update(cx, |project, cx| {
+ project.open_buffer((worktree_id, rel_path("main.rs")), cx)
+ })
+ .await
+ .unwrap();
-#[gpui::test]
-async fn test_bookmark_not_available_in_single_line_editor(cx: &mut TestAppContext) {
- init_test(cx, |_| {});
+ let (editor, cx) = cx.add_window_view(|window, cx| {
+ Editor::new(
+ EditorMode::full(),
+ MultiBuffer::build_from_buffer(buffer, cx),
+ Some(project.clone()),
+ window,
+ cx,
+ )
+ });
- let (editor, _cx) = cx.add_window_view(|window, cx| Editor::single_line(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!(
- editor.bookmark_store.is_none(),
- "Single-line editors should not have a bookmark store"
+ indicator.collides_with_existing_breakpoint,
+ "Adding a breakpoint on the hovered row should set collision to true"
);
});
-}
-
-#[gpui::test]
-async fn test_bookmark_navigation_lands_at_column_zero(cx: &mut TestAppContext) {
- let mut ctx =
- BookmarkTestContext::new("First line\nSecond line\nThird line\nFourth line", cx).await;
-
- ctx.editor
- .update_in(&mut ctx.cx, |editor: &mut Editor, window, cx| {
- editor.move_to_beginning(&MoveToBeginning, window, cx);
- editor.move_down(&MoveDown, window, cx);
- editor.move_to_end_of_line(
- &MoveToEndOfLine {
- stop_at_soft_wraps: true,
- },
- window,
- cx,
- );
- });
-
- let column_before_toggle = ctx.cursor_point().column;
- assert_eq!(
- column_before_toggle, 11,
- "Cursor should be at the 11th column before toggling bookmark, got column {column_before_toggle}"
- );
-
- ctx.toggle_bookmark();
-
- ctx.editor
- .update_in(&mut ctx.cx, |editor: &mut Editor, window, cx| {
- editor.move_to_beginning(&MoveToBeginning, window, cx);
- });
-
- ctx.go_to_next_bookmark();
-
- let cursor = ctx.cursor_point();
- assert_eq!(cursor.row, 1, "Should navigate to the bookmarked row");
- assert_eq!(
- cursor.column, 0,
- "Bookmark navigation should always land at column 0"
- );
-}
-
-#[gpui::test]
-async fn test_bookmark_set_from_nonzero_column_toggles_off_from_column_zero(
- cx: &mut TestAppContext,
-) {
- let mut ctx =
- BookmarkTestContext::new("First line\nSecond line\nThird line\nFourth line", cx).await;
-
- ctx.editor
- .update_in(&mut ctx.cx, |editor: &mut Editor, window, cx| {
- editor.move_to_beginning(&MoveToBeginning, window, cx);
- editor.move_down(&MoveDown, window, cx);
- editor.move_to_end_of_line(
- &MoveToEndOfLine {
- stop_at_soft_wraps: true,
- },
- window,
- cx,
- );
- editor.toggle_bookmark(&actions::ToggleBookmark, window, cx);
- });
-
- ctx.assert_bookmark_rows(vec![1]);
-
- ctx.editor
- .update_in(&mut ctx.cx, |editor: &mut Editor, window, cx| {
- editor.move_to_beginning_of_line(
- &MoveToBeginningOfLine {
- stop_at_soft_wraps: true,
- stop_at_indent: false,
- },
- window,
- cx,
- );
- editor.toggle_bookmark(&actions::ToggleBookmark, window, cx);
- });
-
- ctx.assert_bookmark_rows(vec![]);
-}
-
-#[gpui::test]
-async fn test_go_to_next_bookmark(cx: &mut TestAppContext) {
- let mut ctx = BookmarkTestContext::new(
- "Line 0\nLine 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nLine 8\nLine 9",
- cx,
- )
- .await;
-
- ctx.toggle_bookmarks_at_rows(&[2, 5, 8]);
-
- ctx.move_to_row(0);
-
- ctx.go_to_next_bookmark();
- assert_eq!(
- ctx.cursor_row(),
- 2,
- "First next-bookmark should go to row 2"
- );
-
- ctx.go_to_next_bookmark();
- assert_eq!(
- ctx.cursor_row(),
- 5,
- "Second next-bookmark should go to row 5"
- );
-
- ctx.go_to_next_bookmark();
- assert_eq!(
- ctx.cursor_row(),
- 8,
- "Third next-bookmark should go to row 8"
- );
-
- ctx.go_to_next_bookmark();
- assert_eq!(
- ctx.cursor_row(),
- 2,
- "Next-bookmark should wrap around to row 2"
- );
-}
-#[gpui::test]
-async fn test_go_to_previous_bookmark(cx: &mut TestAppContext) {
- let mut ctx = BookmarkTestContext::new(
- "Line 0\nLine 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nLine 8\nLine 9",
- cx,
- )
- .await;
+ // 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"
+ );
+ });
- ctx.toggle_bookmarks_at_rows(&[2, 5, 8]);
+ // 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);
+ });
- ctx.editor
- .update_in(&mut ctx.cx, |editor: &mut Editor, window, cx| {
- editor.move_to_end(&MoveToEnd, 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,
});
+ });
- ctx.go_to_previous_bookmark();
- assert_eq!(
- ctx.cursor_row(),
- 8,
- "First prev-bookmark should go to row 8"
- );
-
- ctx.go_to_previous_bookmark();
- assert_eq!(
- ctx.cursor_row(),
- 5,
- "Second prev-bookmark should go to row 5"
- );
-
- ctx.go_to_previous_bookmark();
- assert_eq!(
- ctx.cursor_row(),
- 2,
- "Third prev-bookmark should go to row 2"
- );
-
- ctx.go_to_previous_bookmark();
- assert_eq!(
- ctx.cursor_row(),
- 8,
- "Prev-bookmark should wrap around to row 8"
- );
-}
-
-#[gpui::test]
-async fn test_go_to_bookmark_when_cursor_on_bookmarked_line(cx: &mut TestAppContext) {
- let mut ctx = BookmarkTestContext::new(
- "Line 0\nLine 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nLine 8\nLine 9",
- cx,
- )
- .await;
-
- ctx.toggle_bookmarks_at_rows(&[3, 7]);
-
- ctx.move_to_row(3);
-
- ctx.go_to_next_bookmark();
- assert_eq!(
- ctx.cursor_row(),
- 7,
- "Next from bookmarked row 3 should go to row 7"
- );
-
- ctx.go_to_previous_bookmark();
- assert_eq!(
- ctx.cursor_row(),
- 3,
- "Previous from bookmarked row 7 should go to row 3"
- );
-
- ctx.go_to_next_bookmark();
- assert_eq!(ctx.cursor_row(), 7, "Next from row 3 should go to row 7");
-
- ctx.go_to_next_bookmark();
- assert_eq!(ctx.cursor_row(), 3, "Next from row 7 should wrap to row 3");
-}
-
-#[gpui::test]
-async fn test_go_to_bookmark_with_out_of_order_bookmarks(cx: &mut TestAppContext) {
- let mut ctx = BookmarkTestContext::new(
- "Line 0\nLine 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\nLine 7\nLine 8\nLine 9",
- cx,
- )
- .await;
-
- ctx.toggle_bookmarks_at_rows(&[8, 1, 5]);
-
- ctx.move_to_row(0);
-
- ctx.go_to_next_bookmark();
- assert_eq!(ctx.cursor_row(), 1, "First next should go to row 1");
-
- ctx.go_to_next_bookmark();
- assert_eq!(ctx.cursor_row(), 5, "Second next should go to row 5");
-
- ctx.go_to_next_bookmark();
- assert_eq!(ctx.cursor_row(), 8, "Third next should go to row 8");
-
- ctx.go_to_next_bookmark();
- assert_eq!(ctx.cursor_row(), 1, "Fourth next should wrap to row 1");
-
- ctx.go_to_previous_bookmark();
- assert_eq!(
- ctx.cursor_row(),
- 8,
- "Prev from row 1 should wrap around to row 8"
- );
-
- ctx.go_to_previous_bookmark();
- assert_eq!(ctx.cursor_row(), 5, "Prev from row 8 should go to row 5");
-
- ctx.go_to_previous_bookmark();
- assert_eq!(ctx.cursor_row(), 1, "Prev from row 5 should go to row 1");
+ // 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]
diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs
index 5d246b7f6ba241d437cbca25b913f300359a2786..392a67bb0d495c3d49063bd4aa6ec87ea6bfd610 100644
--- a/crates/editor/src/element.rs
+++ b/crates/editor/src/element.rs
@@ -4,12 +4,12 @@ use crate::{
ConflictsOuter, ConflictsTheirs, ConflictsTheirsMarker, ContextMenuPlacement, CursorShape,
CustomBlockId, DisplayDiffHunk, DisplayPoint, DisplayRow, EditDisplayMode, EditPrediction,
Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, FILE_HEADER_HEIGHT,
- FocusedBlock, GutterDimensions, GutterHoverButton, HalfPageDown, HalfPageUp, HandleInput,
- HoveredCursor, InlayHintRefreshReason, JumpData, LineDown, LineHighlight, LineUp, MAX_LINE_LEN,
+ FocusedBlock, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
+ InlayHintRefreshReason, JumpData, LineDown, LineHighlight, LineUp, MAX_LINE_LEN,
MINIMAP_FONT_SIZE, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, OpenExcerpts, PageDown, PageUp,
- PhantomDiffReviewIndicator, Point, RowExt, RowRangeExt, SelectPhase, Selection,
- SelectionDragState, SelectionEffects, SizingBehavior, SoftWrap, StickyHeaderExcerpt, ToPoint,
- ToggleFold, ToggleFoldAll,
+ PhantomBreakpointIndicator, PhantomDiffReviewIndicator, Point, RowExt, RowRangeExt,
+ SelectPhase, Selection, SelectionDragState, SelectionEffects, SizingBehavior, SoftWrap,
+ StickyHeaderExcerpt, ToPoint, ToggleFold, ToggleFoldAll,
code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP},
column_pixels,
display_map::{
@@ -34,7 +34,7 @@ use crate::{
},
};
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
-use collections::{BTreeMap, HashMap, HashSet};
+use collections::{BTreeMap, HashMap};
use feature_flags::{DiffReviewFeatureFlag, FeatureFlagAppExt as _};
use file_icons::FileIcons;
use git::{Oid, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
@@ -62,7 +62,7 @@ use multi_buffer::{
};
use project::{
- DisableAiSettings, Entry,
+ DisableAiSettings, Entry, ProjectPath,
debugger::breakpoint_store::{Breakpoint, BreakpointSessionState},
project_settings::ProjectSettings,
};
@@ -652,9 +652,6 @@ impl EditorElement {
register_action(editor, window, Editor::insert_uuid_v4);
register_action(editor, window, Editor::insert_uuid_v7);
register_action(editor, window, Editor::open_selections_in_multibuffer);
- register_action(editor, window, Editor::toggle_bookmark);
- register_action(editor, window, Editor::go_to_next_bookmark);
- register_action(editor, window, Editor::go_to_previous_bookmark);
register_action(editor, window, Editor::toggle_breakpoint);
register_action(editor, window, Editor::edit_log_breakpoint);
register_action(editor, window, Editor::enable_breakpoint);
@@ -893,11 +890,9 @@ impl EditorElement {
let gutter_right_padding = editor.gutter_dimensions.right_padding;
let hitbox = &position_map.gutter_hitbox;
- if event.position.x <= hitbox.bounds.right() - gutter_right_padding
- && editor.collaboration_hub.is_none()
- {
+ if event.position.x <= hitbox.bounds.right() - gutter_right_padding {
let point_for_position = position_map.point_for_position(event.position);
- editor.set_gutter_context_menu(
+ editor.set_breakpoint_context_menu(
point_for_position.previous_valid.row(),
None,
event.position,
@@ -1401,26 +1396,49 @@ impl EditorElement {
.snapshot
.display_point_to_anchor(valid_point, Bias::Left);
- if position_map
+ if let Some((buffer_anchor, buffer_snapshot)) = position_map
.snapshot
.buffer_snapshot()
.anchor_to_buffer_anchor(buffer_anchor)
- .is_some()
+ && let Some(file) = buffer_snapshot.file()
{
+ let as_point = text::ToPoint::to_point(&buffer_anchor, buffer_snapshot);
+
let is_visible = editor
- .gutter_hover_button
+ .gutter_breakpoint_indicator
.0
.is_some_and(|indicator| indicator.is_active);
+ let has_existing_breakpoint =
+ editor.breakpoint_store.as_ref().is_some_and(|store| {
+ let Some(project) = &editor.project else {
+ return false;
+ };
+ let Some(abs_path) = project.read(cx).absolute_path(
+ &ProjectPath {
+ path: file.path().clone(),
+ worktree_id: file.worktree_id(cx),
+ },
+ cx,
+ ) else {
+ return false;
+ };
+ store
+ .read(cx)
+ .breakpoint_at_row(&abs_path, as_point.row, cx)
+ .is_some()
+ });
+
if !is_visible {
- editor.gutter_hover_button.1.get_or_insert_with(|| {
+ editor.gutter_breakpoint_indicator.1.get_or_insert_with(|| {
cx.spawn(async move |this, cx| {
cx.background_executor()
.timer(Duration::from_millis(200))
.await;
this.update(cx, |this, cx| {
- if let Some(indicator) = this.gutter_hover_button.0.as_mut() {
+ if let Some(indicator) = this.gutter_breakpoint_indicator.0.as_mut()
+ {
indicator.is_active = true;
cx.notify();
}
@@ -1430,21 +1448,22 @@ impl EditorElement {
});
}
- Some(GutterHoverButton {
+ Some(PhantomBreakpointIndicator {
display_row: valid_point.row(),
is_active: is_visible,
+ collides_with_existing_breakpoint: has_existing_breakpoint,
})
} else {
- editor.gutter_hover_button.1 = None;
+ editor.gutter_breakpoint_indicator.1 = None;
None
}
} else {
- editor.gutter_hover_button.1 = None;
+ editor.gutter_breakpoint_indicator.1 = None;
None
};
- if &breakpoint_indicator != &editor.gutter_hover_button.0 {
- editor.gutter_hover_button.0 = breakpoint_indicator;
+ if &breakpoint_indicator != &editor.gutter_breakpoint_indicator.0 {
+ editor.gutter_breakpoint_indicator.0 = breakpoint_indicator;
cx.notify();
}
@@ -3114,10 +3133,16 @@ impl EditorElement {
(offset_y, length, row_range)
}
- fn layout_bookmarks(
+ fn layout_breakpoints(
&self,
- gutter: &Gutter<'_>,
- bookmarks: &HashSet,
+ line_height: Pixels,
+ range: Range,
+ scroll_position: gpui::Point,
+ gutter_dimensions: &GutterDimensions,
+ gutter_hitbox: &Hitbox,
+ snapshot: &EditorSnapshot,
+ breakpoints: HashMap)>,
+ row_infos: &[RowInfo],
window: &mut Window,
cx: &mut App,
) -> Vec {
@@ -3126,71 +3151,44 @@ impl EditorElement {
}
self.editor.update(cx, |editor, cx| {
- bookmarks
- .iter()
- .filter_map(|row| {
- gutter.layout_item_skipping_folds(
- *row,
- |cx, _| editor.render_bookmark(*row, cx).into_any_element(),
- window,
- cx,
- )
- })
- .collect_vec()
- })
- }
+ breakpoints
+ .into_iter()
+ .filter_map(|(display_row, (text_anchor, bp, state))| {
+ if row_infos
+ .get((display_row.0.saturating_sub(range.start.0)) as usize)
+ .is_some_and(|row_info| {
+ row_info.expand_info.is_some()
+ || row_info
+ .diff_status
+ .is_some_and(|status| status.is_deleted())
+ })
+ {
+ return None;
+ }
- fn layout_gutter_hover_button(
- &self,
- gutter: &Gutter,
- position: Anchor,
- row: DisplayRow,
- window: &mut Window,
- cx: &mut App,
- ) -> Option {
- if self.split_side == Some(SplitSide::Left) {
- return None;
- }
+ if range.start > display_row || range.end < display_row {
+ return None;
+ }
- self.editor.update(cx, |editor, cx| {
- gutter.layout_item_skipping_folds(
- row,
- |cx, window| {
- editor
- .render_gutter_hover_button(position, row, window, cx)
- .into_any_element()
- },
- window,
- cx,
- )
- })
- }
+ let row =
+ MultiBufferRow(DisplayPoint::new(display_row, 0).to_point(snapshot).row);
+ if snapshot.is_line_folded(row) {
+ return None;
+ }
- fn layout_breakpoints(
- &self,
- gutter: &Gutter,
- breakpoints: &HashMap)>,
- window: &mut Window,
- cx: &mut App,
- ) -> Vec {
- if self.split_side == Some(SplitSide::Left) {
- return Vec::new();
- }
+ let button = editor.render_breakpoint(text_anchor, display_row, &bp, state, cx);
- self.editor.update(cx, |editor, cx| {
- breakpoints
- .iter()
- .filter_map(|(row, (text_anchor, bp, state))| {
- gutter.layout_item_skipping_folds(
- *row,
- |cx, _| {
- editor
- .render_breakpoint(*text_anchor, *row, &bp, *state, cx)
- .into_any_element()
- },
+ let button = prepaint_gutter_button(
+ button.into_any_element(),
+ display_row,
+ line_height,
+ gutter_dimensions,
+ scroll_position,
+ gutter_hitbox,
window,
cx,
- )
+ );
+ Some(button)
})
.collect_vec()
})
@@ -3242,11 +3240,17 @@ impl EditorElement {
Some((display_row, buffer_row))
}
+ #[allow(clippy::too_many_arguments)]
fn layout_run_indicators(
&self,
- gutter: &Gutter,
- run_indicators: &HashSet,
- breakpoints: &HashMap)>,
+ line_height: Pixels,
+ range: Range,
+ row_infos: &[RowInfo],
+ scroll_position: gpui::Point,
+ gutter_dimensions: &GutterDimensions,
+ gutter_hitbox: &Hitbox,
+ snapshot: &EditorSnapshot,
+ breakpoints: &mut HashMap)>,
window: &mut Window,
cx: &mut App,
) -> Vec {
@@ -3265,7 +3269,7 @@ impl EditorElement {
{
actions
.tasks()
- .map(|tasks| tasks.position.to_display_point(gutter.snapshot).row())
+ .map(|tasks| tasks.position.to_display_point(snapshot).row())
.or_else(|| match deployed_from {
Some(CodeActionSource::Indicator(row)) => Some(*row),
_ => None,
@@ -3274,25 +3278,77 @@ impl EditorElement {
None
};
- run_indicators
- .iter()
- .filter_map(|display_row| {
- gutter.layout_item(
- *display_row,
- |cx, _| {
- editor
- .render_run_indicator(
- &self.style,
- Some(*display_row) == active_task_indicator_row,
- breakpoints.get(&display_row).map(|(anchor, _, _)| *anchor),
- *display_row,
- cx,
- )
- .into_any_element()
- },
+ let offset_range_start =
+ snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left);
+
+ let offset_range_end =
+ snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
+
+ editor
+ .runnables
+ .all_runnables()
+ .filter_map(|tasks| {
+ let multibuffer_point = tasks.offset.to_point(&snapshot.buffer_snapshot());
+ if multibuffer_point < offset_range_start
+ || multibuffer_point > offset_range_end
+ {
+ return None;
+ }
+ let multibuffer_row = MultiBufferRow(multibuffer_point.row);
+ let buffer_folded = snapshot
+ .buffer_snapshot()
+ .buffer_line_for_row(multibuffer_row)
+ .map(|(buffer_snapshot, _)| buffer_snapshot.remote_id())
+ .map(|buffer_id| editor.is_buffer_folded(buffer_id, cx))
+ .unwrap_or(false);
+ if buffer_folded {
+ return None;
+ }
+
+ if snapshot.is_line_folded(multibuffer_row) {
+ // Skip folded indicators, unless it's the starting line of a fold.
+ if multibuffer_row
+ .0
+ .checked_sub(1)
+ .is_some_and(|previous_row| {
+ snapshot.is_line_folded(MultiBufferRow(previous_row))
+ })
+ {
+ return None;
+ }
+ }
+
+ let display_row = multibuffer_point.to_display_point(snapshot).row();
+ if !range.contains(&display_row) {
+ return None;
+ }
+ if row_infos
+ .get((display_row - range.start).0 as usize)
+ .is_some_and(|row_info| row_info.expand_info.is_some())
+ {
+ return None;
+ }
+
+ let removed_breakpoint = breakpoints.remove(&display_row);
+ let button = editor.render_run_indicator(
+ &self.style,
+ Some(display_row) == active_task_indicator_row,
+ display_row,
+ removed_breakpoint,
+ cx,
+ );
+
+ let button = prepaint_gutter_button(
+ button.into_any_element(),
+ display_row,
+ line_height,
+ gutter_dimensions,
+ scroll_position,
+ gutter_hitbox,
window,
cx,
- )
+ );
+ Some(button)
})
.collect_vec()
})
@@ -3392,14 +3448,19 @@ impl EditorElement {
fn layout_line_numbers(
&self,
- gutter: &Gutter<'_>,
+ gutter_hitbox: Option<&Hitbox>,
+ gutter_dimensions: GutterDimensions,
+ line_height: Pixels,
+ scroll_position: gpui::Point,
+ rows: Range,
+ buffer_rows: &[RowInfo],
active_rows: &BTreeMap,
current_selection_head: Option,
+ snapshot: &EditorSnapshot,
window: &mut Window,
cx: &mut App,
) -> Arc> {
- let include_line_numbers = gutter
- .snapshot
+ let include_line_numbers = snapshot
.show_line_numbers
.unwrap_or_else(|| EditorSettings::get_global(cx).gutter.line_numbers);
if !include_line_numbers {
@@ -3412,8 +3473,8 @@ impl EditorElement {
let relative_rows = if relative_line_numbers_enabled
&& let Some(current_selection_head) = current_selection_head
{
- gutter.snapshot.calculate_relative_line_numbers(
- &gutter.range,
+ snapshot.calculate_relative_line_numbers(
+ &rows,
current_selection_head,
relative.wrapped(),
)
@@ -3422,79 +3483,72 @@ impl EditorElement {
};
let mut line_number = String::new();
- let segments = gutter
- .row_infos
- .iter()
- .enumerate()
- .flat_map(|(ix, row_info)| {
- let display_row = DisplayRow(gutter.range.start.0 + ix as u32);
- line_number.clear();
- let non_relative_number = if relative.wrapped() {
- row_info.buffer_row.or(row_info.wrapped_buffer_row)? + 1
- } else {
- row_info.buffer_row? + 1
- };
- let relative_number = relative_rows.get(&display_row);
- if !(relative_line_numbers_enabled && relative_number.is_some())
- && !gutter.snapshot.number_deleted_lines
- && row_info
- .diff_status
- .is_some_and(|status| status.is_deleted())
- {
- return None;
- }
+ let segments = buffer_rows.iter().enumerate().flat_map(|(ix, row_info)| {
+ let display_row = DisplayRow(rows.start.0 + ix as u32);
+ line_number.clear();
+ let non_relative_number = if relative.wrapped() {
+ row_info.buffer_row.or(row_info.wrapped_buffer_row)? + 1
+ } else {
+ row_info.buffer_row? + 1
+ };
+ let relative_number = relative_rows.get(&display_row);
+ if !(relative_line_numbers_enabled && relative_number.is_some())
+ && !snapshot.number_deleted_lines
+ && row_info
+ .diff_status
+ .is_some_and(|status| status.is_deleted())
+ {
+ return None;
+ }
- let number = relative_number.unwrap_or(&non_relative_number);
- write!(&mut line_number, "{number}").unwrap();
+ let number = relative_number.unwrap_or(&non_relative_number);
+ write!(&mut line_number, "{number}").unwrap();
- let color = active_rows
- .get(&display_row)
- .map(|spec| {
- if spec.breakpoint {
- cx.theme().colors().debugger_accent
- } else {
- cx.theme().colors().editor_active_line_number
- }
- })
- .unwrap_or_else(|| cx.theme().colors().editor_line_number);
- let shaped_line =
- self.shape_line_number(SharedString::from(&line_number), color, window);
- let scroll_top =
- gutter.scroll_position.y * ScrollPixelOffset::from(gutter.line_height);
- let line_origin = gutter.hitbox.origin
+ let color = active_rows
+ .get(&display_row)
+ .map(|spec| {
+ if spec.breakpoint {
+ cx.theme().colors().debugger_accent
+ } else {
+ cx.theme().colors().editor_active_line_number
+ }
+ })
+ .unwrap_or_else(|| cx.theme().colors().editor_line_number);
+ let shaped_line =
+ self.shape_line_number(SharedString::from(&line_number), color, window);
+ let scroll_top = scroll_position.y * ScrollPixelOffset::from(line_height);
+ let line_origin = gutter_hitbox.map(|hitbox| {
+ hitbox.origin
+ point(
- gutter.hitbox.size.width
- - shaped_line.width
- - gutter.dimensions.right_padding,
- ix as f32 * gutter.line_height
- - Pixels::from(
- scroll_top % ScrollPixelOffset::from(gutter.line_height),
- ),
- );
+ hitbox.size.width - shaped_line.width - gutter_dimensions.right_padding,
+ ix as f32 * line_height
+ - Pixels::from(scroll_top % ScrollPixelOffset::from(line_height)),
+ )
+ });
- #[cfg(not(test))]
- let hitbox = Some(window.insert_hitbox(
- Bounds::new(line_origin, size(shaped_line.width, gutter.line_height)),
+ #[cfg(not(test))]
+ let hitbox = line_origin.map(|line_origin| {
+ window.insert_hitbox(
+ Bounds::new(line_origin, size(shaped_line.width, line_height)),
HitboxBehavior::Normal,
- ));
- #[cfg(test)]
- let hitbox = {
- let _ = line_origin;
- None
- };
+ )
+ });
+ #[cfg(test)]
+ let hitbox = {
+ let _ = line_origin;
+ None
+ };
- let segment = LineNumberSegment {
- shaped_line,
- hitbox,
- };
+ let segment = LineNumberSegment {
+ shaped_line,
+ hitbox,
+ };
- let buffer_row = DisplayPoint::new(display_row, 0)
- .to_point(gutter.snapshot)
- .row;
- let multi_buffer_row = MultiBufferRow(buffer_row);
+ let buffer_row = DisplayPoint::new(display_row, 0).to_point(snapshot).row;
+ let multi_buffer_row = MultiBufferRow(buffer_row);
- Some((multi_buffer_row, segment))
- });
+ Some((multi_buffer_row, segment))
+ });
let mut line_numbers: HashMap = HashMap::default();
for (buffer_row, segment) in segments {
@@ -6373,10 +6427,6 @@ impl EditorElement {
}
});
- for bookmark in layout.bookmarks.iter_mut() {
- bookmark.paint(window, cx);
- }
-
for breakpoint in layout.breakpoints.iter_mut() {
breakpoint.paint(window, cx);
}
@@ -7914,96 +7964,6 @@ impl EditorElement {
}
}
-struct Gutter<'a> {
- line_height: Pixels,
- range: Range,
- scroll_position: gpui::Point,
- dimensions: &'a GutterDimensions,
- hitbox: &'a Hitbox,
- snapshot: &'a EditorSnapshot,
- row_infos: &'a [RowInfo],
-}
-
-impl Gutter<'_> {
- fn layout_item_skipping_folds(
- &self,
- display_row: DisplayRow,
- render_item: impl Fn(&mut Context<'_, Editor>, &mut Window) -> AnyElement,
- window: &mut Window,
- cx: &mut Context<'_, Editor>,
- ) -> Option {
- let row = MultiBufferRow(
- DisplayPoint::new(display_row, 0)
- .to_point(self.snapshot)
- .row,
- );
- if self.snapshot.is_line_folded(row) {
- return None;
- }
-
- self.layout_item(display_row, render_item, window, cx)
- }
-
- fn layout_item(
- &self,
- display_row: DisplayRow,
- render_item: impl Fn(&mut Context<'_, Editor>, &mut Window) -> AnyElement,
- window: &mut Window,
- cx: &mut Context<'_, Editor>,
- ) -> Option {
- if !self.range.contains(&display_row) {
- return None;
- }
-
- if self
- .row_infos
- .get((display_row.0.saturating_sub(self.range.start.0)) as usize)
- .is_some_and(|row_info| {
- row_info.expand_info.is_some()
- || row_info
- .diff_status
- .is_some_and(|status| status.is_deleted())
- })
- {
- return None;
- }
-
- let button = self.prepaint_button(render_item(cx, window), display_row, window, cx);
- Some(button)
- }
-
- fn prepaint_button(
- &self,
- mut button: AnyElement,
- row: DisplayRow,
- window: &mut Window,
- cx: &mut App,
- ) -> AnyElement {
- let available_space = size(
- AvailableSpace::MinContent,
- AvailableSpace::Definite(self.line_height),
- );
- let indicator_size = button.layout_as_root(available_space, window, cx);
- let git_gutter_width = EditorElement::gutter_strip_width(self.line_height)
- + self.dimensions.git_blame_entries_width.unwrap_or_default();
-
- let x = git_gutter_width + px(2.);
-
- let mut y = Pixels::from(
- (row.as_f64() - self.scroll_position.y) * ScrollPixelOffset::from(self.line_height),
- );
- y += (self.line_height - indicator_size.height) / 2.;
-
- button.prepaint_as_root(
- self.hitbox.origin + point(x, y),
- available_space,
- window,
- cx,
- );
- button
- }
-}
-
pub fn render_breadcrumb_text(
mut segments: Vec,
breadcrumb_font: Option,
@@ -8681,6 +8641,41 @@ pub(crate) fn render_buffer_header(
})
}
+fn prepaint_gutter_button(
+ mut button: AnyElement,
+ row: DisplayRow,
+ line_height: Pixels,
+ gutter_dimensions: &GutterDimensions,
+ scroll_position: gpui::Point,
+ gutter_hitbox: &Hitbox,
+ window: &mut Window,
+ cx: &mut App,
+) -> AnyElement {
+ let available_space = size(
+ AvailableSpace::MinContent,
+ AvailableSpace::Definite(line_height),
+ );
+ let indicator_size = button.layout_as_root(available_space, window, cx);
+ let git_gutter_width = EditorElement::gutter_strip_width(line_height)
+ + gutter_dimensions
+ .git_blame_entries_width
+ .unwrap_or_default();
+
+ let x = git_gutter_width + px(2.);
+
+ let mut y =
+ Pixels::from((row.as_f64() - scroll_position.y) * ScrollPixelOffset::from(line_height));
+ y += (line_height - indicator_size.height) / 2.;
+
+ button.prepaint_as_root(
+ gutter_hitbox.origin + point(x, y),
+ available_space,
+ window,
+ cx,
+ );
+ button
+}
+
fn render_inline_blame_entry(
blame_entry: BlameEntry,
style: &EditorStyle,
@@ -10127,38 +10122,54 @@ impl Element for EditorElement {
})
});
- let run_indicator_rows = self.editor.update(cx, |editor, cx| {
- editor.active_run_indicators(start_row..end_row, window, cx)
- });
-
let mut breakpoint_rows = self.editor.update(cx, |editor, cx| {
editor.active_breakpoints(start_row..end_row, window, cx)
});
-
for (display_row, (_, bp, state)) in &breakpoint_rows {
if bp.is_enabled() && state.is_none_or(|s| s.verified) {
active_rows.entry(*display_row).or_default().breakpoint = true;
}
}
- let gutter = Gutter {
+ let line_numbers = self.layout_line_numbers(
+ Some(&gutter_hitbox),
+ gutter_dimensions,
line_height,
- range: start_row..end_row,
scroll_position,
- dimensions: &gutter_dimensions,
- hitbox: &gutter_hitbox,
- snapshot: &snapshot,
- row_infos: &row_infos,
- };
-
- let line_numbers = self.layout_line_numbers(
- &gutter,
+ start_row..end_row,
+ &row_infos,
&active_rows,
current_selection_head,
+ &snapshot,
window,
cx,
);
+ // We add the gutter breakpoint indicator to breakpoint_rows after painting
+ // line numbers so we don't paint a line number debug accent color if a user
+ // has their mouse over that line when a breakpoint isn't there
+ self.editor.update(cx, |editor, _| {
+ if let Some(phantom_breakpoint) = &mut editor
+ .gutter_breakpoint_indicator
+ .0
+ .filter(|phantom_breakpoint| phantom_breakpoint.is_active)
+ {
+ // Is there a non-phantom breakpoint on this line?
+ phantom_breakpoint.collides_with_existing_breakpoint = true;
+ breakpoint_rows
+ .entry(phantom_breakpoint.display_row)
+ .or_insert_with(|| {
+ let position = snapshot.display_point_to_anchor(
+ DisplayPoint::new(phantom_breakpoint.display_row, 0),
+ Bias::Right,
+ );
+ let breakpoint = Breakpoint::new_standard();
+ phantom_breakpoint.collides_with_existing_breakpoint = false;
+ (position, breakpoint, None)
+ });
+ }
+ });
+
let mut expand_toggles =
window.with_element_namespace("expand_toggles", |window| {
self.layout_expand_toggles(
@@ -10741,9 +10752,14 @@ impl Element for EditorElement {
let test_indicators = if gutter_settings.runnables {
self.layout_run_indicators(
- &gutter,
- &run_indicator_rows,
- &breakpoint_rows,
+ line_height,
+ start_row..end_row,
+ &row_infos,
+ scroll_position,
+ &gutter_dimensions,
+ &gutter_hitbox,
+ &snapshot,
+ &mut breakpoint_rows,
window,
cx,
)
@@ -10751,54 +10767,26 @@ impl Element for EditorElement {
Vec::new()
};
- let show_bookmarks =
- snapshot.show_bookmarks.unwrap_or(gutter_settings.bookmarks);
-
- let bookmark_rows = self.editor.update(cx, |editor, cx| {
- let mut rows = editor.active_bookmarks(start_row..end_row, window, cx);
- rows.retain(|k| !run_indicator_rows.contains(k));
- rows.retain(|k| !breakpoint_rows.contains_key(k));
- rows
- });
-
- let bookmarks = if show_bookmarks {
- self.layout_bookmarks(&gutter, &bookmark_rows, window, cx)
- } else {
- Vec::new()
- };
-
let show_breakpoints = snapshot
.show_breakpoints
.unwrap_or(gutter_settings.breakpoints);
-
- breakpoint_rows.retain(|k, _| !run_indicator_rows.contains(k));
- let mut breakpoints = if show_breakpoints {
- self.layout_breakpoints(&gutter, &breakpoint_rows, window, cx)
+ let breakpoints = if show_breakpoints {
+ self.layout_breakpoints(
+ line_height,
+ start_row..end_row,
+ scroll_position,
+ &gutter_dimensions,
+ &gutter_hitbox,
+ &snapshot,
+ breakpoint_rows,
+ &row_infos,
+ window,
+ cx,
+ )
} else {
Vec::new()
};
- let gutter_hover_button = self
- .editor
- .read(cx)
- .gutter_hover_button
- .0
- .filter(|phantom| phantom.is_active)
- .map(|phantom| phantom.display_row);
-
- if let Some(row) = gutter_hover_button
- && !breakpoint_rows.contains_key(&row)
- && !run_indicator_rows.contains(&row)
- && !bookmark_rows.contains(&row)
- && (show_bookmarks || show_breakpoints)
- {
- let position = snapshot
- .display_point_to_anchor(DisplayPoint::new(row, 0), Bias::Right);
- breakpoints.extend(
- self.layout_gutter_hover_button(&gutter, position, row, window, cx),
- );
- }
-
let git_gutter_width = Self::gutter_strip_width(line_height)
+ gutter_dimensions
.git_blame_entries_width
@@ -10842,7 +10830,16 @@ impl Element for EditorElement {
.render_diff_review_button(display_row, button_width, cx)
.into_any_element()
});
- gutter.prepaint_button(button, display_row, window, cx)
+ prepaint_gutter_button(
+ button,
+ display_row,
+ line_height,
+ &gutter_dimensions,
+ scroll_position,
+ &gutter_hitbox,
+ window,
+ cx,
+ )
});
self.layout_signature_help(
@@ -11078,7 +11075,6 @@ impl Element for EditorElement {
diff_hunk_controls,
mouse_context_menu,
test_indicators,
- bookmarks,
breakpoints,
diff_review_button,
crease_toggles,
@@ -11267,7 +11263,6 @@ pub struct EditorLayout {
visible_cursors: Vec,
selections: Vec<(PlayerColor, Vec)>,
test_indicators: Vec,
- bookmarks: Vec,
breakpoints: Vec,
diff_review_button: Option,
crease_toggles: Vec