Skip to content

Commit

Permalink
Improve multibuffer excerpt affordances (#22167)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
5 people authored Dec 30, 2024
1 parent 3f33ca0 commit ad51df7
Show file tree
Hide file tree
Showing 6 changed files with 510 additions and 313 deletions.
111 changes: 61 additions & 50 deletions crates/diagnostics/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
div()
.top(px(0.))
.absolute()
.w_full()
.h_px()
.bg(color.border_variant),
)
.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))
}
}),
)
})
.block_mouse_down()
.h(2. * cx.line_height())
.pl_10()
.pr_5()
.w_full()
.justify_between()
.gap_2()
.child(
h_flex()
.gap_1()
.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(
StyledText::new(message.clone()).with_highlights(
&cx.text_style(),
code_ranges
.iter()
.map(|range| (range.clone(), highlight_style)),
),
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),
)
.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()
})
Expand Down
1 change: 1 addition & 0 deletions crates/editor/src/display_map/block_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
}

Expand Down
44 changes: 32 additions & 12 deletions crates/editor/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit ad51df7

Please sign in to comment.