diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index d22bfa6ee1bbf..897089cf9b650 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -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 }); diff --git a/crates/diagnostics/src/diagnostics_tests.rs b/crates/diagnostics/src/diagnostics_tests.rs index ec9d86f3d526f..804be2e6b88ac 100644 --- a/crates/diagnostics/src/diagnostics_tests.rs +++ b/crates/diagnostics/src/diagnostics_tests.rs @@ -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 ) ); } diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 7ec189395673d..0b429fb8c797c 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -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> { + self.block_snapshot.sticky_header_excerpt(row.0) + } + pub fn block_for_id(&self, id: BlockId) -> Option { self.block_snapshot.block_for_id(id) } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 00c87c84cb2dd..33242e6c8c53d 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1411,6 +1411,66 @@ impl BlockSnapshot { }) } + pub fn sticky_header_excerpt(&self, top_row: u32) -> Option> { + let mut cursor = self.transforms.cursor::(&()); + 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 { 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, +} + impl<'a> Iterator for BlockChunks<'a> { type Item = Chunk<'a>; diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 1f8925e48a4b3..af89945f4f0d1 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -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, selections: &[Selection], is_row_soft_wrapped: impl Copy + Fn(usize) -> bool, + sticky_header_excerpt_id: Option, cx: &mut WindowContext, ) -> (AnyElement, Size) { - 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], is_row_soft_wrapped: impl Copy + Fn(usize) -> bool, + sticky_header_excerpt_id: Option, cx: &mut WindowContext, ) -> Result, HashMap> { 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, tab_invisible: ShapedLine, space_invisible: ShapedLine, + sticky_buffer_header: Option, } impl EditorLayout {