Sticky multibuffer headers (#22391)
https://github.com/user-attachments/assets/92cc5ff7-d8be-4e4b-ac6e-68eb310fffce Release Notes: - Multibuffer headers will now stick to the top of the viewport as you scroll - Added support for expanding diagnostic excerpts --------- Co-authored-by: Michael <michael@zed.dev>
This commit is contained in:
parent
4c84600630
commit
45c714110e
5 changed files with 268 additions and 69 deletions
|
@ -166,7 +166,7 @@ impl ProjectDiagnosticsEditor {
|
|||
let excerpts = cx.new_model(|cx| MultiBuffer::new(project_handle.read(cx).capability()));
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor =
|
||||
Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), false, cx);
|
||||
Editor::for_multibuffer(excerpts.clone(), Some(project_handle.clone()), true, cx);
|
||||
editor.set_vertical_scroll_margin(5, cx);
|
||||
editor
|
||||
});
|
||||
|
|
|
@ -167,10 +167,10 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(DisplayRow(0), FILE_HEADER.into()),
|
||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(15), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(16), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(25), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(16), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(18), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(27), EXCERPT_HEADER.into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
|
@ -184,6 +184,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
" let x = vec![];\n",
|
||||
" let y = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
|
@ -195,6 +196,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
" c(y);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" d(x);\n",
|
||||
"\n", // expand
|
||||
"\n", // context ellipsis
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
|
@ -206,11 +208,13 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
" a(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" b(y);\n",
|
||||
"\n", // expand
|
||||
"\n", // context ellipsis
|
||||
" c(y);\n",
|
||||
" d(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
"}"
|
||||
"}",
|
||||
"\n", // expand
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -218,7 +222,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.selections.display_ranges(cx),
|
||||
[DisplayPoint::new(DisplayRow(12), 6)..DisplayPoint::new(DisplayRow(12), 6)]
|
||||
[DisplayPoint::new(DisplayRow(13), 6)..DisplayPoint::new(DisplayRow(13), 6)]
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -253,12 +257,12 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(DisplayRow(0), FILE_HEADER.into()),
|
||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(7), FILE_HEADER.into()),
|
||||
(DisplayRow(9), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(22), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(23), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(32), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(8), FILE_HEADER.into()),
|
||||
(DisplayRow(12), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(25), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(27), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(36), EXCERPT_HEADER.into()),
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -273,6 +277,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"const a: i32 = 'a';\n",
|
||||
"\n", // supporting diagnostic
|
||||
"const b: i32 = c;\n",
|
||||
|
@ -284,6 +289,8 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"\n", // expand
|
||||
" let x = vec![];\n",
|
||||
" let y = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
|
@ -299,6 +306,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // filename
|
||||
"\n", // expand
|
||||
"fn main() {\n",
|
||||
" let x = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
|
@ -306,11 +314,13 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
" a(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" b(y);\n",
|
||||
"\n", // expand
|
||||
"\n", // context ellipsis
|
||||
" c(y);\n",
|
||||
" d(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
"}"
|
||||
"}",
|
||||
"\n", // expand
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -318,7 +328,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.selections.display_ranges(cx),
|
||||
[DisplayPoint::new(DisplayRow(19), 6)..DisplayPoint::new(DisplayRow(19), 6)]
|
||||
[DisplayPoint::new(DisplayRow(22), 6)..DisplayPoint::new(DisplayRow(22), 6)]
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -366,14 +376,14 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(DisplayRow(0), FILE_HEADER.into()),
|
||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(7), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(8), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(13), FILE_HEADER.into()),
|
||||
(DisplayRow(15), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(28), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(29), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(38), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(8), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(10), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(15), FILE_HEADER.into()),
|
||||
(DisplayRow(19), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(32), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(34), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(43), EXCERPT_HEADER.into()),
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -388,6 +398,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"const a: i32 = 'a';\n",
|
||||
"\n", // supporting diagnostic
|
||||
"const b: i32 = c;\n",
|
||||
|
@ -395,6 +406,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"const a: i32 = 'a';\n",
|
||||
"const b: i32 = c;\n",
|
||||
"\n", // supporting diagnostic
|
||||
|
@ -406,6 +418,8 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"\n", // expand
|
||||
" let x = vec![];\n",
|
||||
" let y = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
|
@ -421,6 +435,7 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // filename
|
||||
"\n", // expand
|
||||
"fn main() {\n",
|
||||
" let x = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
|
@ -428,11 +443,13 @@ async fn test_diagnostics(cx: &mut TestAppContext) {
|
|||
" a(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" b(y);\n",
|
||||
"\n", // expand
|
||||
"\n", // context ellipsis
|
||||
" c(y);\n",
|
||||
" d(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
"}"
|
||||
"}",
|
||||
"\n", // expand
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -513,7 +530,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
|||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(DisplayRow(0), FILE_HEADER.into()),
|
||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
|
@ -524,8 +541,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
|||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"a();\n", //
|
||||
"b();",
|
||||
"b();", "\n", // expand
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -561,9 +579,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
|||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(DisplayRow(0), FILE_HEADER.into()),
|
||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(6), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(7), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(7), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(9), DIAGNOSTIC_HEADER.into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
|
@ -574,8 +592,10 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
|||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"a();\n", // location
|
||||
"b();\n", //
|
||||
"\n", // expand
|
||||
"\n", // collapsed context
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
|
@ -583,6 +603,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
|||
"a();\n", // context
|
||||
"b();\n", //
|
||||
"c();", // context
|
||||
"\n", // expand
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -629,9 +650,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
|||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(DisplayRow(0), FILE_HEADER.into()),
|
||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(7), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(8), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(8), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(10), DIAGNOSTIC_HEADER.into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
|
@ -642,9 +663,11 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
|||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"a();\n", // location
|
||||
"b();\n", //
|
||||
"c();\n", // context
|
||||
"\n", // expand
|
||||
"\n", // collapsed context
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
|
@ -652,6 +675,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
|||
"b();\n", // context
|
||||
"c();\n", //
|
||||
"d();", // context
|
||||
"\n", // expand
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -687,9 +711,9 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
|||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(DisplayRow(0), FILE_HEADER.into()),
|
||||
(DisplayRow(2), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(7), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(8), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(3), DIAGNOSTIC_HEADER.into()),
|
||||
(DisplayRow(8), EXCERPT_HEADER.into()),
|
||||
(DisplayRow(10), DIAGNOSTIC_HEADER.into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
|
@ -700,9 +724,11 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
|||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"\n", // expand
|
||||
"b();\n", // location
|
||||
"c();\n", //
|
||||
"d();\n", // context
|
||||
"\n", // expand
|
||||
"\n", // collapsed context
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
|
@ -710,6 +736,7 @@ async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
|||
"c();\n", // context
|
||||
"d();\n", //
|
||||
"e();", // context
|
||||
"\n", // expand
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ use crate::{
|
|||
pub use block_map::{
|
||||
Block, BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap,
|
||||
BlockPlacement, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
|
||||
StickyHeaderExcerpt,
|
||||
};
|
||||
use block_map::{BlockRow, BlockSnapshot};
|
||||
use collections::{HashMap, HashSet};
|
||||
|
@ -1105,6 +1106,10 @@ impl DisplaySnapshot {
|
|||
.map(|(row, block)| (DisplayRow(row), block))
|
||||
}
|
||||
|
||||
pub fn sticky_header_excerpt(&self, row: DisplayRow) -> Option<StickyHeaderExcerpt<'_>> {
|
||||
self.block_snapshot.sticky_header_excerpt(row.0)
|
||||
}
|
||||
|
||||
pub fn block_for_id(&self, id: BlockId) -> Option<Block> {
|
||||
self.block_snapshot.block_for_id(id)
|
||||
}
|
||||
|
|
|
@ -1411,6 +1411,66 @@ impl BlockSnapshot {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn sticky_header_excerpt(&self, top_row: u32) -> Option<StickyHeaderExcerpt<'_>> {
|
||||
let mut cursor = self.transforms.cursor::<BlockRow>(&());
|
||||
cursor.seek(&BlockRow(top_row), Bias::Left, &());
|
||||
|
||||
while let Some(transform) = cursor.item() {
|
||||
let start = cursor.start().0;
|
||||
let end = cursor.end(&()).0;
|
||||
|
||||
match &transform.block {
|
||||
Some(Block::ExcerptBoundary {
|
||||
prev_excerpt,
|
||||
next_excerpt,
|
||||
starts_new_buffer,
|
||||
show_excerpt_controls,
|
||||
..
|
||||
}) => {
|
||||
let matches_start = if *show_excerpt_controls && prev_excerpt.is_some() {
|
||||
start < top_row
|
||||
} else {
|
||||
start <= top_row
|
||||
};
|
||||
|
||||
if matches_start && top_row <= end {
|
||||
return next_excerpt.as_ref().map(|excerpt| StickyHeaderExcerpt {
|
||||
next_buffer_row: None,
|
||||
next_excerpt_controls_present: *show_excerpt_controls,
|
||||
excerpt,
|
||||
});
|
||||
}
|
||||
|
||||
let next_buffer_row = if *starts_new_buffer { Some(end) } else { None };
|
||||
|
||||
return prev_excerpt.as_ref().map(|excerpt| StickyHeaderExcerpt {
|
||||
excerpt,
|
||||
next_buffer_row,
|
||||
next_excerpt_controls_present: *show_excerpt_controls,
|
||||
});
|
||||
}
|
||||
Some(Block::FoldedBuffer {
|
||||
prev_excerpt: Some(excerpt),
|
||||
..
|
||||
}) if top_row <= start => {
|
||||
return Some(StickyHeaderExcerpt {
|
||||
next_buffer_row: Some(end),
|
||||
next_excerpt_controls_present: false,
|
||||
excerpt,
|
||||
});
|
||||
}
|
||||
Some(Block::FoldedBuffer { .. }) | Some(Block::Custom(_)) | None => {}
|
||||
}
|
||||
|
||||
// This is needed to iterate past None / FoldedBuffer / Custom blocks. For FoldedBuffer,
|
||||
// if scrolled slightly past the header of a folded block, the next block is needed for
|
||||
// the sticky header.
|
||||
cursor.next(&());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
|
||||
let buffer = self.wrap_snapshot.buffer_snapshot();
|
||||
let wrap_point = match block_id {
|
||||
|
@ -1694,6 +1754,12 @@ impl<'a> BlockChunks<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct StickyHeaderExcerpt<'a> {
|
||||
pub excerpt: &'a ExcerptInfo,
|
||||
pub next_excerpt_controls_present: bool,
|
||||
pub next_buffer_row: Option<u32>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BlockChunks<'a> {
|
||||
type Item = Chunk<'a>;
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ use crate::{
|
|||
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
|
||||
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, InlineCompletion, JumpData, LineDown,
|
||||
LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection,
|
||||
SoftWrap, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
|
||||
SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
|
||||
GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
||||
};
|
||||
use client::ParticipantIndex;
|
||||
|
@ -30,14 +30,14 @@ use collections::{BTreeMap, HashMap, HashSet};
|
|||
use file_icons::FileIcons;
|
||||
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
|
||||
use gpui::{
|
||||
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
|
||||
transparent_black, Action, AnyElement, AvailableSpace, Axis, Bounds, ClickEvent, ClipboardItem,
|
||||
ContentMask, Corner, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler,
|
||||
Entity, FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
|
||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
|
||||
ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
|
||||
StatefulInteractiveElement, Style, Styled, Subscription, TextRun, TextStyleRefinement, View,
|
||||
ViewContext, WeakView, WindowContext,
|
||||
anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad,
|
||||
relative, size, svg, transparent_black, Action, AnyElement, AvailableSpace, Axis, Bounds,
|
||||
ClickEvent, ClipboardItem, ContentMask, Corner, Corners, CursorStyle, DispatchPhase, Edges,
|
||||
Element, ElementInputHandler, Entity, FontId, GlobalElementId, Hitbox, Hsla,
|
||||
InteractiveElement, IntoElement, Length, ModifiersChangedEvent, MouseButton, MouseDownEvent,
|
||||
MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent,
|
||||
ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled, Subscription,
|
||||
TextRun, TextStyleRefinement, View, ViewContext, WeakView, WindowContext,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
|
@ -2210,9 +2210,9 @@ impl EditorElement {
|
|||
resized_blocks: &mut HashMap<CustomBlockId, u32>,
|
||||
selections: &[Selection<Point>],
|
||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||
sticky_header_excerpt_id: Option<ExcerptId>,
|
||||
cx: &mut WindowContext,
|
||||
) -> (AnyElement, Size<Pixels>) {
|
||||
let header_padding = px(6.0);
|
||||
let mut element = match block {
|
||||
Block::Custom(block) => {
|
||||
let block_start = block.start().to_point(&snapshot.buffer_snapshot);
|
||||
|
@ -2305,14 +2305,7 @@ impl EditorElement {
|
|||
|
||||
let jump_data = jump_data(snapshot, block_row_start, *height, first_excerpt, cx);
|
||||
result
|
||||
.child(self.render_buffer_header(
|
||||
first_excerpt,
|
||||
header_padding,
|
||||
true,
|
||||
selected,
|
||||
jump_data,
|
||||
cx,
|
||||
))
|
||||
.child(self.render_buffer_header(first_excerpt, true, selected, jump_data, cx))
|
||||
.into_any_element()
|
||||
}
|
||||
Block::ExcerptBoundary {
|
||||
|
@ -2347,14 +2340,19 @@ impl EditorElement {
|
|||
if let Some(next_excerpt) = next_excerpt {
|
||||
let jump_data = jump_data(snapshot, block_row_start, *height, next_excerpt, cx);
|
||||
if *starts_new_buffer {
|
||||
result = result.child(self.render_buffer_header(
|
||||
next_excerpt,
|
||||
header_padding,
|
||||
false,
|
||||
false,
|
||||
jump_data,
|
||||
cx,
|
||||
));
|
||||
if sticky_header_excerpt_id != Some(next_excerpt.id) {
|
||||
result = result.child(self.render_buffer_header(
|
||||
next_excerpt,
|
||||
false,
|
||||
false,
|
||||
jump_data,
|
||||
cx,
|
||||
));
|
||||
} else {
|
||||
result =
|
||||
result.child(div().h(FILE_HEADER_HEIGHT as f32 * cx.line_height()));
|
||||
}
|
||||
|
||||
if *show_excerpt_controls {
|
||||
result = result.child(
|
||||
h_flex()
|
||||
|
@ -2507,7 +2505,6 @@ impl EditorElement {
|
|||
fn render_buffer_header(
|
||||
&self,
|
||||
for_excerpt: &ExcerptInfo,
|
||||
header_padding: Pixels,
|
||||
is_folded: bool,
|
||||
is_selected: bool,
|
||||
jump_data: JumpData,
|
||||
|
@ -2531,8 +2528,8 @@ impl EditorElement {
|
|||
let focus_handle = self.editor.focus_handle(cx);
|
||||
|
||||
div()
|
||||
.px(header_padding)
|
||||
.pt(header_padding)
|
||||
.px_2()
|
||||
.pt_2()
|
||||
.w_full()
|
||||
.h(FILE_HEADER_HEIGHT as f32 * cx.line_height())
|
||||
.child(
|
||||
|
@ -2686,6 +2683,7 @@ impl EditorElement {
|
|||
line_layouts: &[LineWithInvisibles],
|
||||
selections: &[Selection<Point>],
|
||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||
sticky_header_excerpt_id: Option<ExcerptId>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Result<Vec<BlockLayout>, HashMap<CustomBlockId, u32>> {
|
||||
let (fixed_blocks, non_fixed_blocks) = snapshot
|
||||
|
@ -2724,6 +2722,7 @@ impl EditorElement {
|
|||
&mut resized_blocks,
|
||||
selections,
|
||||
is_row_soft_wrapped,
|
||||
sticky_header_excerpt_id,
|
||||
cx,
|
||||
);
|
||||
fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
|
||||
|
@ -2735,6 +2734,7 @@ impl EditorElement {
|
|||
style: BlockStyle::Fixed,
|
||||
});
|
||||
}
|
||||
|
||||
for (row, block) in non_fixed_blocks {
|
||||
let style = block.style();
|
||||
let width = match style {
|
||||
|
@ -2770,6 +2770,7 @@ impl EditorElement {
|
|||
&mut resized_blocks,
|
||||
selections,
|
||||
is_row_soft_wrapped,
|
||||
sticky_header_excerpt_id,
|
||||
cx,
|
||||
);
|
||||
|
||||
|
@ -2817,6 +2818,7 @@ impl EditorElement {
|
|||
&mut resized_blocks,
|
||||
selections,
|
||||
is_row_soft_wrapped,
|
||||
sticky_header_excerpt_id,
|
||||
cx,
|
||||
);
|
||||
|
||||
|
@ -2883,6 +2885,71 @@ impl EditorElement {
|
|||
}
|
||||
}
|
||||
|
||||
fn layout_sticky_buffer_header(
|
||||
&self,
|
||||
StickyHeaderExcerpt {
|
||||
excerpt,
|
||||
next_excerpt_controls_present,
|
||||
next_buffer_row,
|
||||
}: StickyHeaderExcerpt<'_>,
|
||||
scroll_position: f32,
|
||||
line_height: Pixels,
|
||||
snapshot: &EditorSnapshot,
|
||||
hitbox: &Hitbox,
|
||||
cx: &mut WindowContext,
|
||||
) -> AnyElement {
|
||||
let jump_data = jump_data(snapshot, DisplayRow(0), FILE_HEADER_HEIGHT, excerpt, cx);
|
||||
|
||||
let editor_bg_color = cx.theme().colors().editor_background;
|
||||
|
||||
let mut header = v_flex()
|
||||
.relative()
|
||||
.child(
|
||||
div()
|
||||
.w(hitbox.bounds.size.width)
|
||||
.h(FILE_HEADER_HEIGHT as f32 * line_height)
|
||||
.bg(linear_gradient(
|
||||
0.,
|
||||
linear_color_stop(editor_bg_color.opacity(0.), 0.),
|
||||
linear_color_stop(editor_bg_color, 0.6),
|
||||
))
|
||||
.absolute()
|
||||
.top_0(),
|
||||
)
|
||||
.child(
|
||||
self.render_buffer_header(excerpt, false, false, jump_data, cx)
|
||||
.into_any_element(),
|
||||
)
|
||||
.into_any_element();
|
||||
|
||||
let mut origin = hitbox.origin;
|
||||
|
||||
if let Some(next_buffer_row) = next_buffer_row {
|
||||
// Push up the sticky header when the excerpt is getting close to the top of the viewport
|
||||
|
||||
let mut max_row = next_buffer_row - FILE_HEADER_HEIGHT * 2;
|
||||
|
||||
if next_excerpt_controls_present {
|
||||
max_row -= MULTI_BUFFER_EXCERPT_HEADER_HEIGHT;
|
||||
}
|
||||
|
||||
let offset = scroll_position - max_row as f32;
|
||||
|
||||
if offset > 0.0 {
|
||||
origin.y -= Pixels(offset) * line_height;
|
||||
}
|
||||
}
|
||||
|
||||
let size = size(
|
||||
AvailableSpace::Definite(hitbox.size.width),
|
||||
AvailableSpace::MinContent,
|
||||
);
|
||||
|
||||
header.prepaint_as_root(origin, size, cx);
|
||||
|
||||
header
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_context_menu(
|
||||
&self,
|
||||
|
@ -4945,11 +5012,14 @@ fn jump_data(
|
|||
let excerpt_start_row = language::ToPoint::to_point(&jump_anchor, buffer).row;
|
||||
jump_position.row - excerpt_start_row
|
||||
};
|
||||
let line_offset_from_top = block_row_start.0 + height + offset_from_excerpt_start
|
||||
- snapshot
|
||||
.scroll_anchor
|
||||
.scroll_position(&snapshot.display_snapshot)
|
||||
.y as u32;
|
||||
let line_offset_from_top = block_row_start.0
|
||||
+ height
|
||||
+ offset_from_excerpt_start.saturating_sub(
|
||||
snapshot
|
||||
.scroll_anchor
|
||||
.scroll_position(&snapshot.display_snapshot)
|
||||
.y as u32,
|
||||
);
|
||||
JumpData {
|
||||
excerpt_id: for_excerpt.id,
|
||||
anchor: jump_anchor,
|
||||
|
@ -6096,6 +6166,14 @@ impl Element for EditorElement {
|
|||
let scroll_range_bounds = scrollbar_range_data.scroll_range;
|
||||
let mut scroll_width = scroll_range_bounds.size.width;
|
||||
|
||||
let sticky_header_excerpt = if snapshot.buffer_snapshot.show_headers() {
|
||||
snapshot.sticky_header_excerpt(start_row)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let sticky_header_excerpt_id =
|
||||
sticky_header_excerpt.as_ref().map(|top| top.excerpt.id);
|
||||
|
||||
let blocks = cx.with_element_namespace("blocks", |cx| {
|
||||
self.render_blocks(
|
||||
start_row..end_row,
|
||||
|
@ -6111,6 +6189,7 @@ impl Element for EditorElement {
|
|||
&line_layouts,
|
||||
&local_selections,
|
||||
is_row_soft_wrapped,
|
||||
sticky_header_excerpt_id,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -6124,6 +6203,19 @@ impl Element for EditorElement {
|
|||
}
|
||||
};
|
||||
|
||||
let sticky_buffer_header = sticky_header_excerpt.map(|sticky_header_excerpt| {
|
||||
cx.with_element_namespace("blocks", |cx| {
|
||||
self.layout_sticky_buffer_header(
|
||||
sticky_header_excerpt,
|
||||
scroll_position.y,
|
||||
line_height,
|
||||
&snapshot,
|
||||
&hitbox,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
let start_buffer_row =
|
||||
MultiBufferRow(start_anchor.to_point(&snapshot.buffer_snapshot).row);
|
||||
let end_buffer_row =
|
||||
|
@ -6251,6 +6343,7 @@ impl Element for EditorElement {
|
|||
);
|
||||
|
||||
let mut block_start_rows = HashSet::default();
|
||||
|
||||
cx.with_element_namespace("blocks", |cx| {
|
||||
self.layout_blocks(
|
||||
&mut blocks,
|
||||
|
@ -6542,6 +6635,7 @@ impl Element for EditorElement {
|
|||
crease_trailers,
|
||||
tab_invisible,
|
||||
space_invisible,
|
||||
sticky_buffer_header,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -6623,6 +6717,12 @@ impl Element for EditorElement {
|
|||
});
|
||||
}
|
||||
|
||||
cx.with_element_namespace("blocks", |cx| {
|
||||
if let Some(mut sticky_header) = layout.sticky_buffer_header.take() {
|
||||
sticky_header.paint(cx)
|
||||
}
|
||||
});
|
||||
|
||||
self.paint_scrollbars(layout, cx);
|
||||
self.paint_inline_completion_popover(layout, cx);
|
||||
self.paint_mouse_context_menu(layout, cx);
|
||||
|
@ -6730,6 +6830,7 @@ pub struct EditorLayout {
|
|||
mouse_context_menu: Option<AnyElement>,
|
||||
tab_invisible: ShapedLine,
|
||||
space_invisible: ShapedLine,
|
||||
sticky_buffer_header: Option<AnyElement>,
|
||||
}
|
||||
|
||||
impl EditorLayout {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue