Improve multibuffer excerpt affordances (#22167)
Changes: - [x] Increase expand affordance surface area - [x] Ensure expand buttons have tooltips with keybindings - [x] Make line numbers clickable to jump you to location (only in multibuffers) - [x] Hide the "Jump To File" element in not-focused excerpts Before merging it: - [x] Fix off-by-one header focus styles glitch Improvements to consider for follow-up PRs: 1. Experiment with increasing the width of the clickable surface area for line numbers 2. Don't show (or disable) the "expand excerpt" button when at the top or bottom edge of the file 3. Once you jump to location, centralize the cursor scroll position Release Notes: - Improved multibuffer's "expand excerpt" affordance - Fixed "jump to file/location" and "expand excerpt" keybinding display - Made clicking on line numbers in multibuffers jump you to cursor location in file --------- Co-authored-by: Thorsten Ball <mrnugget@gmail.com> Co-authored-by: Agus Zubiaga <hi@aguz.me> Co-authored-by: Kirill Bulatov <kirill@zed.dev> Co-authored-by: Agus Zubiaga <agus@zed.dev>
This commit is contained in:
parent
3f33ca01a8
commit
ad51df7644
6 changed files with 513 additions and 316 deletions
|
@ -836,65 +836,76 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
|
|||
let (message, code_ranges) = highlight_diagnostic_message(&diagnostic, None);
|
||||
let message: SharedString = message;
|
||||
Arc::new(move |cx| {
|
||||
let highlight_style: HighlightStyle = cx.theme().colors().text_accent.into();
|
||||
let color = cx.theme().colors();
|
||||
let highlight_style: HighlightStyle = color.text_accent.into();
|
||||
|
||||
h_flex()
|
||||
.id(DIAGNOSTIC_HEADER)
|
||||
.block_mouse_down()
|
||||
.h(2. * cx.line_height())
|
||||
.pl_10()
|
||||
.pr_5()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.relative()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_3()
|
||||
.map(|stack| {
|
||||
stack.child(
|
||||
svg()
|
||||
.size(cx.text_style().font_size)
|
||||
.flex_none()
|
||||
.map(|icon| {
|
||||
if diagnostic.severity == DiagnosticSeverity::ERROR {
|
||||
icon.path(IconName::XCircle.path())
|
||||
.text_color(Color::Error.color(cx))
|
||||
} else {
|
||||
icon.path(IconName::Warning.path())
|
||||
.text_color(Color::Warning.color(cx))
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
StyledText::new(message.clone()).with_highlights(
|
||||
&cx.text_style(),
|
||||
code_ranges
|
||||
.iter()
|
||||
.map(|range| (range.clone(), highlight_style)),
|
||||
),
|
||||
)
|
||||
.when_some(diagnostic.code.as_ref(), |stack, code| {
|
||||
stack.child(
|
||||
div()
|
||||
.child(SharedString::from(format!("({code})")))
|
||||
.text_color(cx.theme().colors().text_muted),
|
||||
)
|
||||
}),
|
||||
),
|
||||
div()
|
||||
.top(px(0.))
|
||||
.absolute()
|
||||
.w_full()
|
||||
.h_px()
|
||||
.bg(color.border_variant),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.when_some(diagnostic.source.as_ref(), |stack, source| {
|
||||
stack.child(
|
||||
div()
|
||||
.child(SharedString::from(source.clone()))
|
||||
.text_color(cx.theme().colors().text_muted),
|
||||
)
|
||||
}),
|
||||
.block_mouse_down()
|
||||
.h(2. * cx.line_height())
|
||||
.pl_10()
|
||||
.pr_5()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_3()
|
||||
.map(|stack| {
|
||||
stack.child(svg().size(cx.text_style().font_size).flex_none().map(
|
||||
|icon| {
|
||||
if diagnostic.severity == DiagnosticSeverity::ERROR {
|
||||
icon.path(IconName::XCircle.path())
|
||||
.text_color(Color::Error.color(cx))
|
||||
} else {
|
||||
icon.path(IconName::Warning.path())
|
||||
.text_color(Color::Warning.color(cx))
|
||||
}
|
||||
},
|
||||
))
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
StyledText::new(message.clone()).with_highlights(
|
||||
&cx.text_style(),
|
||||
code_ranges
|
||||
.iter()
|
||||
.map(|range| (range.clone(), highlight_style)),
|
||||
),
|
||||
)
|
||||
.when_some(diagnostic.code.as_ref(), |stack, code| {
|
||||
stack.child(
|
||||
div()
|
||||
.child(SharedString::from(format!("({code})")))
|
||||
.text_color(cx.theme().colors().text_muted),
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(h_flex().gap_1().when_some(
|
||||
diagnostic.source.as_ref(),
|
||||
|stack, source| {
|
||||
stack.child(
|
||||
div()
|
||||
.child(SharedString::from(source.clone()))
|
||||
.text_color(cx.theme().colors().text_muted),
|
||||
)
|
||||
},
|
||||
)),
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
|
|
|
@ -1757,6 +1757,7 @@ impl<'a> BlockChunks<'a> {
|
|||
pub struct StickyHeaderExcerpt<'a> {
|
||||
pub excerpt: &'a ExcerptInfo,
|
||||
pub next_excerpt_controls_present: bool,
|
||||
// TODO az remove option
|
||||
pub next_buffer_row: Option<u32>,
|
||||
}
|
||||
|
||||
|
|
|
@ -992,12 +992,14 @@ pub(crate) struct FocusedBlock {
|
|||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct JumpData {
|
||||
excerpt_id: ExcerptId,
|
||||
position: Point,
|
||||
anchor: text::Anchor,
|
||||
path: Option<project::ProjectPath>,
|
||||
line_offset_from_top: u32,
|
||||
enum JumpData {
|
||||
MultiBufferRow(MultiBufferRow),
|
||||
MultiBufferPoint {
|
||||
excerpt_id: ExcerptId,
|
||||
position: Point,
|
||||
anchor: text::Anchor,
|
||||
line_offset_from_top: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
|
@ -12458,28 +12460,46 @@ impl Editor {
|
|||
|
||||
let mut new_selections_by_buffer = HashMap::default();
|
||||
match &jump_data {
|
||||
Some(jump_data) => {
|
||||
Some(JumpData::MultiBufferPoint {
|
||||
excerpt_id,
|
||||
position,
|
||||
anchor,
|
||||
line_offset_from_top,
|
||||
}) => {
|
||||
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
if let Some(buffer) = multi_buffer_snapshot
|
||||
.buffer_id_for_excerpt(jump_data.excerpt_id)
|
||||
.buffer_id_for_excerpt(*excerpt_id)
|
||||
.and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
|
||||
{
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let jump_to_point = if buffer_snapshot.can_resolve(&jump_data.anchor) {
|
||||
language::ToPoint::to_point(&jump_data.anchor, &buffer_snapshot)
|
||||
let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
|
||||
language::ToPoint::to_point(anchor, &buffer_snapshot)
|
||||
} else {
|
||||
buffer_snapshot.clip_point(jump_data.position, Bias::Left)
|
||||
buffer_snapshot.clip_point(*position, Bias::Left)
|
||||
};
|
||||
let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
|
||||
new_selections_by_buffer.insert(
|
||||
buffer,
|
||||
(
|
||||
vec![jump_to_offset..jump_to_offset],
|
||||
Some(jump_data.line_offset_from_top),
|
||||
Some(*line_offset_from_top),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Some(JumpData::MultiBufferRow(row)) => {
|
||||
let point = MultiBufferPoint::new(row.0, 0);
|
||||
if let Some((buffer, buffer_point, _)) =
|
||||
self.buffer.read(cx).point_to_buffer_point(point, cx)
|
||||
{
|
||||
let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
|
||||
new_selections_by_buffer
|
||||
.entry(buffer)
|
||||
.or_insert((Vec::new(), None))
|
||||
.0
|
||||
.push(buffer_offset..buffer_offset)
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let selections = self.selections.all::<usize>(cx);
|
||||
let buffer = self.buffer.read(cx);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3449,6 +3449,24 @@ impl MultiBufferSnapshot {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn buffer_ids_in_selected_rows(
|
||||
&self,
|
||||
selection: Selection<Point>,
|
||||
) -> impl Iterator<Item = BufferId> + '_ {
|
||||
let mut cursor = self.excerpts.cursor::<Point>(&());
|
||||
cursor.seek(&Point::new(selection.start.row, 0), Bias::Right, &());
|
||||
cursor.prev(&());
|
||||
|
||||
iter::from_fn(move || {
|
||||
cursor.next(&());
|
||||
if cursor.start().row <= selection.end.row {
|
||||
cursor.item().map(|item| item.buffer_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn excerpts(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (ExcerptId, &BufferSnapshot, ExcerptRange<text::Anchor>)> {
|
||||
|
|
|
@ -49,6 +49,7 @@ impl Tooltip {
|
|||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn with_meta(
|
||||
title: impl Into<SharedString>,
|
||||
action: Option<&dyn Action>,
|
||||
|
@ -63,6 +64,22 @@ impl Tooltip {
|
|||
.into()
|
||||
}
|
||||
|
||||
pub fn with_meta_in(
|
||||
title: impl Into<SharedString>,
|
||||
action: Option<&dyn Action>,
|
||||
meta: impl Into<SharedString>,
|
||||
focus_handle: &FocusHandle,
|
||||
cx: &mut WindowContext,
|
||||
) -> AnyView {
|
||||
cx.new_view(|cx| Self {
|
||||
title: title.into(),
|
||||
meta: Some(meta.into()),
|
||||
key_binding: action
|
||||
.and_then(|action| KeyBinding::for_action_in(action, focus_handle, cx)),
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn new(title: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
title: title.into(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue