editor: Show tooltips on breakpoints (#29523)
Closes #ISSUE Release Notes: - N/A
This commit is contained in:
parent
9bd0828303
commit
ddfeb202a3
2 changed files with 113 additions and 36 deletions
|
@ -815,6 +815,16 @@ struct InlineBlamePopover {
|
||||||
popover_state: InlineBlamePopoverState,
|
popover_state: InlineBlamePopoverState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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)]
|
||||||
|
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,
|
||||||
|
}
|
||||||
/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
|
/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
|
||||||
///
|
///
|
||||||
/// See the [module level documentation](self) for more information.
|
/// See the [module level documentation](self) for more information.
|
||||||
|
@ -963,10 +973,7 @@ pub struct Editor {
|
||||||
tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
|
tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
|
||||||
tasks_update_task: Option<Task<()>>,
|
tasks_update_task: Option<Task<()>>,
|
||||||
breakpoint_store: Option<Entity<BreakpointStore>>,
|
breakpoint_store: Option<Entity<BreakpointStore>>,
|
||||||
/// Allow's a user to create a breakpoint by selecting this indicator
|
gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
|
||||||
/// It should be None while a user is not hovering over the gutter
|
|
||||||
/// Otherwise it represents the point that the breakpoint will be shown
|
|
||||||
gutter_breakpoint_indicator: (Option<(DisplayPoint, bool)>, Option<Task<()>>),
|
|
||||||
in_project_search: bool,
|
in_project_search: bool,
|
||||||
previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
|
previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
|
||||||
breadcrumb_header: Option<String>,
|
breadcrumb_header: Option<String>,
|
||||||
|
@ -6965,6 +6972,21 @@ impl Editor {
|
||||||
breakpoint: &Breakpoint,
|
breakpoint: &Breakpoint,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> IconButton {
|
) -> IconButton {
|
||||||
|
// 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 (color, icon) = {
|
||||||
let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
|
let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
|
||||||
(false, false) => ui::IconName::DebugBreakpoint,
|
(false, false) => ui::IconName::DebugBreakpoint,
|
||||||
|
@ -6973,11 +6995,7 @@ impl Editor {
|
||||||
(true, true) => ui::IconName::DebugDisabledLogBreakpoint,
|
(true, true) => ui::IconName::DebugDisabledLogBreakpoint,
|
||||||
};
|
};
|
||||||
|
|
||||||
let color = if self
|
let color = if is_phantom {
|
||||||
.gutter_breakpoint_indicator
|
|
||||||
.0
|
|
||||||
.is_some_and(|(point, is_visible)| is_visible && point.row() == row)
|
|
||||||
{
|
|
||||||
Color::Hint
|
Color::Hint
|
||||||
} else {
|
} else {
|
||||||
Color::Debugger
|
Color::Debugger
|
||||||
|
@ -6988,6 +7006,24 @@ impl Editor {
|
||||||
|
|
||||||
let breakpoint = Arc::from(breakpoint.clone());
|
let breakpoint = Arc::from(breakpoint.clone());
|
||||||
|
|
||||||
|
let alt_as_text = gpui::Keystroke {
|
||||||
|
modifiers: Modifiers::secondary_key(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let primary_action_text = if breakpoint.is_disabled() {
|
||||||
|
"enable"
|
||||||
|
} else if is_phantom && !collides_with_existing {
|
||||||
|
"set"
|
||||||
|
} else {
|
||||||
|
"unset"
|
||||||
|
};
|
||||||
|
let mut primary_text = format!("Click to {primary_action_text}");
|
||||||
|
if collides_with_existing && !breakpoint.is_disabled() {
|
||||||
|
use std::fmt::Write;
|
||||||
|
write!(primary_text, ", {alt_as_text}-click to disable").ok();
|
||||||
|
}
|
||||||
|
let primary_text = SharedString::from(primary_text);
|
||||||
|
let focus_handle = self.focus_handle.clone();
|
||||||
IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
|
IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
|
||||||
.icon_size(IconSize::XSmall)
|
.icon_size(IconSize::XSmall)
|
||||||
.size(ui::ButtonSize::None)
|
.size(ui::ButtonSize::None)
|
||||||
|
@ -7021,6 +7057,16 @@ impl Editor {
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}))
|
}))
|
||||||
|
.tooltip(move |window, cx| {
|
||||||
|
Tooltip::with_meta_in(
|
||||||
|
primary_text.clone(),
|
||||||
|
None,
|
||||||
|
"Right-click for more options",
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_tasks_context(
|
fn build_tasks_context(
|
||||||
|
|
|
@ -7,8 +7,8 @@ use crate::{
|
||||||
FILE_HEADER_HEIGHT, FocusedBlock, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput,
|
FILE_HEADER_HEIGHT, FocusedBlock, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput,
|
||||||
HoveredCursor, InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineHighlight,
|
HoveredCursor, InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineHighlight,
|
||||||
LineUp, MAX_LINE_LEN, MIN_LINE_NUMBER_DIGITS, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, OpenExcerpts,
|
LineUp, MAX_LINE_LEN, MIN_LINE_NUMBER_DIGITS, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, OpenExcerpts,
|
||||||
PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection,
|
PageDown, PageUp, PhantomBreakpointIndicator, Point, RowExt, RowRangeExt, SelectPhase,
|
||||||
SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold,
|
SelectedTextHighlight, Selection, SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold,
|
||||||
code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP},
|
code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP},
|
||||||
display_map::{
|
display_map::{
|
||||||
Block, BlockContext, BlockStyle, DisplaySnapshot, FoldId, HighlightedChunk, ToDisplayPoint,
|
Block, BlockContext, BlockStyle, DisplaySnapshot, FoldId, HighlightedChunk, ToDisplayPoint,
|
||||||
|
@ -59,6 +59,7 @@ use multi_buffer::{
|
||||||
MultiBufferRow, RowInfo,
|
MultiBufferRow, RowInfo,
|
||||||
};
|
};
|
||||||
use project::{
|
use project::{
|
||||||
|
ProjectPath,
|
||||||
debugger::breakpoint_store::Breakpoint,
|
debugger::breakpoint_store::Breakpoint,
|
||||||
project_settings::{self, GitGutterSetting, GitHunkStyleSetting, ProjectSettings},
|
project_settings::{self, GitGutterSetting, GitHunkStyleSetting, ProjectSettings},
|
||||||
};
|
};
|
||||||
|
@ -946,18 +947,45 @@ impl EditorElement {
|
||||||
.snapshot
|
.snapshot
|
||||||
.display_point_to_anchor(new_point, Bias::Left);
|
.display_point_to_anchor(new_point, Bias::Left);
|
||||||
|
|
||||||
if position_map
|
if let Some((buffer_snapshot, file)) = position_map
|
||||||
.snapshot
|
.snapshot
|
||||||
.buffer_snapshot
|
.buffer_snapshot
|
||||||
.buffer_for_excerpt(buffer_anchor.excerpt_id)
|
.buffer_for_excerpt(buffer_anchor.excerpt_id)
|
||||||
.is_some_and(|buffer| buffer.file().is_some())
|
.and_then(|buffer| buffer.file().map(|file| (buffer, file)))
|
||||||
{
|
{
|
||||||
let was_hovered = editor.gutter_breakpoint_indicator.0.is_some();
|
let was_hovered = editor.gutter_breakpoint_indicator.0.is_some();
|
||||||
|
let as_point = text::ToPoint::to_point(&buffer_anchor.text_anchor, buffer_snapshot);
|
||||||
|
|
||||||
let is_visible = editor
|
let is_visible = editor
|
||||||
.gutter_breakpoint_indicator
|
.gutter_breakpoint_indicator
|
||||||
.0
|
.0
|
||||||
.map_or(false, |(_, is_active)| is_active);
|
.map_or(false, |indicator| indicator.is_active);
|
||||||
editor.gutter_breakpoint_indicator.0 = Some((new_point, is_visible));
|
|
||||||
|
let has_existing_breakpoint =
|
||||||
|
editor.breakpoint_store.as_ref().map_or(false, |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()
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator {
|
||||||
|
display_row: new_point.row(),
|
||||||
|
is_active: is_visible,
|
||||||
|
collides_with_existing_breakpoint: has_existing_breakpoint,
|
||||||
|
});
|
||||||
|
|
||||||
editor.gutter_breakpoint_indicator.1.get_or_insert_with(|| {
|
editor.gutter_breakpoint_indicator.1.get_or_insert_with(|| {
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
|
@ -968,10 +996,8 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
if let Some((_, is_active)) =
|
if let Some(indicator) = this.gutter_breakpoint_indicator.0.as_mut() {
|
||||||
this.gutter_breakpoint_indicator.0.as_mut()
|
indicator.is_active = true;
|
||||||
{
|
|
||||||
*is_active = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
@ -7063,23 +7089,28 @@ impl Element for EditorElement {
|
||||||
// line numbers so we don't paint a line number debug accent color if a user
|
// 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
|
// has their mouse over that line when a breakpoint isn't there
|
||||||
if cx.has_flag::<DebuggerFeatureFlag>() {
|
if cx.has_flag::<DebuggerFeatureFlag>() {
|
||||||
let gutter_breakpoint_indicator =
|
self.editor.update(cx, |editor, _| {
|
||||||
self.editor.read(cx).gutter_breakpoint_indicator.0;
|
if let Some(phantom_breakpoint) = &mut editor
|
||||||
if let Some((gutter_breakpoint_point, _)) =
|
.gutter_breakpoint_indicator
|
||||||
gutter_breakpoint_indicator.filter(|(_, is_active)| *is_active)
|
.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
|
breakpoint_rows
|
||||||
.entry(gutter_breakpoint_point.row())
|
.entry(phantom_breakpoint.display_row)
|
||||||
.or_insert_with(|| {
|
.or_insert_with(|| {
|
||||||
let position = snapshot.display_point_to_anchor(
|
let position = snapshot.display_point_to_anchor(
|
||||||
gutter_breakpoint_point,
|
DisplayPoint::new(phantom_breakpoint.display_row, 0),
|
||||||
Bias::Right,
|
Bias::Right,
|
||||||
);
|
);
|
||||||
let breakpoint = Breakpoint::new_standard();
|
let breakpoint = Breakpoint::new_standard();
|
||||||
|
phantom_breakpoint.collides_with_existing_breakpoint =
|
||||||
|
false;
|
||||||
(position, breakpoint)
|
(position, breakpoint)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut expand_toggles =
|
let mut expand_toggles =
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue