Fix sticky header in last buffer of a multibuffer (#26944)

This also simplifies our code to stop generating a last excerpt boundary
that we always ignore.

Closes #ISSUE

Release Notes:

- N/A
This commit is contained in:
Conrad Irwin 2025-03-17 12:39:57 -06:00 committed by GitHub
parent 94b63808e0
commit 25772b8777
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 138 additions and 236 deletions

View file

@ -240,7 +240,7 @@ pub struct BlockContext<'a, 'b> {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum BlockId { pub enum BlockId {
ExcerptBoundary(Option<ExcerptId>), ExcerptBoundary(ExcerptId),
FoldedBuffer(ExcerptId), FoldedBuffer(ExcerptId),
Custom(CustomBlockId), Custom(CustomBlockId),
} }
@ -249,10 +249,9 @@ impl From<BlockId> for ElementId {
fn from(value: BlockId) -> Self { fn from(value: BlockId) -> Self {
match value { match value {
BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(), BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(),
BlockId::ExcerptBoundary(next_excerpt) => match next_excerpt { BlockId::ExcerptBoundary(excerpt_id) => {
Some(id) => ("ExcerptBoundary", EntityId::from(id)).into(), ("ExcerptBoundary", EntityId::from(excerpt_id)).into()
None => "LastExcerptBoundary".into(), }
},
BlockId::FoldedBuffer(id) => ("FoldedBuffer", EntityId::from(id)).into(), BlockId::FoldedBuffer(id) => ("FoldedBuffer", EntityId::from(id)).into(),
} }
} }
@ -280,12 +279,10 @@ pub enum Block {
Custom(Arc<CustomBlock>), Custom(Arc<CustomBlock>),
FoldedBuffer { FoldedBuffer {
first_excerpt: ExcerptInfo, first_excerpt: ExcerptInfo,
prev_excerpt: Option<ExcerptInfo>,
height: u32, height: u32,
}, },
ExcerptBoundary { ExcerptBoundary {
prev_excerpt: Option<ExcerptInfo>, excerpt: ExcerptInfo,
next_excerpt: Option<ExcerptInfo>,
height: u32, height: u32,
starts_new_buffer: bool, starts_new_buffer: bool,
}, },
@ -295,9 +292,10 @@ impl Block {
pub fn id(&self) -> BlockId { pub fn id(&self) -> BlockId {
match self { match self {
Block::Custom(block) => BlockId::Custom(block.id), Block::Custom(block) => BlockId::Custom(block.id),
Block::ExcerptBoundary { next_excerpt, .. } => { Block::ExcerptBoundary {
BlockId::ExcerptBoundary(next_excerpt.as_ref().map(|info| info.id)) excerpt: next_excerpt,
} ..
} => BlockId::ExcerptBoundary(next_excerpt.id),
Block::FoldedBuffer { first_excerpt, .. } => BlockId::FoldedBuffer(first_excerpt.id), Block::FoldedBuffer { first_excerpt, .. } => BlockId::FoldedBuffer(first_excerpt.id),
} }
} }
@ -320,7 +318,7 @@ impl Block {
match self { match self {
Block::Custom(block) => matches!(block.placement, BlockPlacement::Above(_)), Block::Custom(block) => matches!(block.placement, BlockPlacement::Above(_)),
Block::FoldedBuffer { .. } => false, Block::FoldedBuffer { .. } => false,
Block::ExcerptBoundary { next_excerpt, .. } => next_excerpt.is_some(), Block::ExcerptBoundary { .. } => true,
} }
} }
@ -328,7 +326,7 @@ impl Block {
match self { match self {
Block::Custom(block) => matches!(block.placement, BlockPlacement::Below(_)), Block::Custom(block) => matches!(block.placement, BlockPlacement::Below(_)),
Block::FoldedBuffer { .. } => false, Block::FoldedBuffer { .. } => false,
Block::ExcerptBoundary { next_excerpt, .. } => next_excerpt.is_none(), Block::ExcerptBoundary { .. } => false,
} }
} }
@ -347,6 +345,16 @@ impl Block {
Block::ExcerptBoundary { .. } => true, Block::ExcerptBoundary { .. } => true,
} }
} }
pub fn is_buffer_header(&self) -> bool {
match self {
Block::Custom(_) => false,
Block::FoldedBuffer { .. } => true,
Block::ExcerptBoundary {
starts_new_buffer, ..
} => *starts_new_buffer,
}
}
} }
impl Debug for Block { impl Debug for Block {
@ -355,24 +363,21 @@ impl Debug for Block {
Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(), Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(),
Self::FoldedBuffer { Self::FoldedBuffer {
first_excerpt, first_excerpt,
prev_excerpt,
height, height,
} => f } => f
.debug_struct("FoldedBuffer") .debug_struct("FoldedBuffer")
.field("first_excerpt", &first_excerpt) .field("first_excerpt", &first_excerpt)
.field("prev_excerpt", prev_excerpt)
.field("height", height) .field("height", height)
.finish(), .finish(),
Self::ExcerptBoundary { Self::ExcerptBoundary {
starts_new_buffer, starts_new_buffer,
next_excerpt, excerpt,
prev_excerpt, height,
..
} => f } => f
.debug_struct("ExcerptBoundary") .debug_struct("ExcerptBoundary")
.field("prev_excerpt", prev_excerpt) .field("excerpt", excerpt)
.field("next_excerpt", next_excerpt)
.field("starts_new_buffer", starts_new_buffer) .field("starts_new_buffer", starts_new_buffer)
.field("height", height)
.finish(), .finish(),
} }
} }
@ -724,23 +729,13 @@ impl BlockMap {
std::iter::from_fn(move || { std::iter::from_fn(move || {
let excerpt_boundary = boundaries.next()?; let excerpt_boundary = boundaries.next()?;
let wrap_row = if excerpt_boundary.next.is_some() { let wrap_row = wrap_snapshot
wrap_snapshot.make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left) .make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
} else { .row();
wrap_snapshot.make_wrap_point(
Point::new(
excerpt_boundary.row.0,
buffer.line_len(excerpt_boundary.row),
),
Bias::Left,
)
}
.row();
let new_buffer_id = match (&excerpt_boundary.prev, &excerpt_boundary.next) { let new_buffer_id = match (&excerpt_boundary.prev, &excerpt_boundary.next) {
(_, None) => None, (None, next) => Some(next.buffer_id),
(None, Some(next)) => Some(next.buffer_id), (Some(prev), next) => {
(Some(prev), Some(next)) => {
if prev.buffer_id != next.buffer_id { if prev.buffer_id != next.buffer_id {
Some(next.buffer_id) Some(next.buffer_id)
} else { } else {
@ -749,24 +744,18 @@ impl BlockMap {
} }
}; };
let prev_excerpt = excerpt_boundary
.prev
.filter(|prev| !folded_buffers.contains(&prev.buffer_id));
let mut height = 0; let mut height = 0;
if let Some(new_buffer_id) = new_buffer_id { if let Some(new_buffer_id) = new_buffer_id {
let first_excerpt = excerpt_boundary.next.clone().unwrap(); let first_excerpt = excerpt_boundary.next.clone();
if folded_buffers.contains(&new_buffer_id) { if folded_buffers.contains(&new_buffer_id) {
let mut last_excerpt_end_row = first_excerpt.end_row; let mut last_excerpt_end_row = first_excerpt.end_row;
while let Some(next_boundary) = boundaries.peek() { while let Some(next_boundary) = boundaries.peek() {
if let Some(next_excerpt_boundary) = &next_boundary.next { if next_boundary.next.buffer_id == new_buffer_id {
if next_excerpt_boundary.buffer_id == new_buffer_id { last_excerpt_end_row = next_boundary.next.end_row;
last_excerpt_end_row = next_excerpt_boundary.end_row; } else {
} else { break;
break;
}
} }
boundaries.next(); boundaries.next();
@ -785,7 +774,6 @@ impl BlockMap {
return Some(( return Some((
BlockPlacement::Replace(WrapRow(wrap_row)..=WrapRow(wrap_end_row)), BlockPlacement::Replace(WrapRow(wrap_row)..=WrapRow(wrap_end_row)),
Block::FoldedBuffer { Block::FoldedBuffer {
prev_excerpt,
height: height + buffer_header_height, height: height + buffer_header_height,
first_excerpt, first_excerpt,
}, },
@ -793,27 +781,16 @@ impl BlockMap {
} }
} }
if excerpt_boundary.next.is_some() { if new_buffer_id.is_some() {
if new_buffer_id.is_some() { height += buffer_header_height;
height += buffer_header_height; } else {
} else { height += excerpt_header_height;
height += excerpt_header_height;
}
}
if height == 0 {
return None;
} }
Some(( Some((
if excerpt_boundary.next.is_some() { BlockPlacement::Above(WrapRow(wrap_row)),
BlockPlacement::Above(WrapRow(wrap_row))
} else {
BlockPlacement::Below(WrapRow(wrap_row))
},
Block::ExcerptBoundary { Block::ExcerptBoundary {
prev_excerpt, excerpt: excerpt_boundary.next,
next_excerpt: excerpt_boundary.next,
height, height,
starts_new_buffer: new_buffer_id.is_some(), starts_new_buffer: new_buffer_id.is_some(),
}, },
@ -861,31 +838,14 @@ impl BlockMap {
placement_comparison.then_with(|| match (block_a, block_b) { placement_comparison.then_with(|| match (block_a, block_b) {
( (
Block::ExcerptBoundary { Block::ExcerptBoundary {
next_excerpt: next_excerpt_a, excerpt: excerpt_a, ..
..
}, },
Block::ExcerptBoundary { Block::ExcerptBoundary {
next_excerpt: next_excerpt_b, excerpt: excerpt_b, ..
..
}, },
) => next_excerpt_a ) => Some(excerpt_a.id).cmp(&Some(excerpt_b.id)),
.as_ref() (Block::ExcerptBoundary { .. }, Block::Custom(_)) => Ordering::Less,
.map(|excerpt| excerpt.id) (Block::Custom(_), Block::ExcerptBoundary { .. }) => Ordering::Greater,
.cmp(&next_excerpt_b.as_ref().map(|excerpt| excerpt.id)),
(Block::ExcerptBoundary { next_excerpt, .. }, Block::Custom(_)) => {
if next_excerpt.is_some() {
Ordering::Less
} else {
Ordering::Greater
}
}
(Block::Custom(_), Block::ExcerptBoundary { next_excerpt, .. }) => {
if next_excerpt.is_some() {
Ordering::Greater
} else {
Ordering::Less
}
}
(Block::Custom(block_a), Block::Custom(block_b)) => block_a (Block::Custom(block_a), Block::Custom(block_b)) => block_a
.priority .priority
.cmp(&block_b.priority) .cmp(&block_b.priority)
@ -1405,51 +1365,19 @@ impl BlockSnapshot {
pub fn sticky_header_excerpt(&self, position: f32) -> Option<StickyHeaderExcerpt<'_>> { pub fn sticky_header_excerpt(&self, position: f32) -> Option<StickyHeaderExcerpt<'_>> {
let top_row = position as u32; let top_row = position as u32;
let mut cursor = self.transforms.cursor::<BlockRow>(&()); let mut cursor = self.transforms.cursor::<BlockRow>(&());
cursor.seek(&BlockRow(top_row), Bias::Left, &()); cursor.seek(&BlockRow(top_row), Bias::Right, &());
while let Some(transform) = cursor.item() { while let Some(transform) = cursor.item() {
let start = cursor.start().0;
let end = cursor.end(&()).0;
match &transform.block { match &transform.block {
Some(Block::ExcerptBoundary { Some(Block::ExcerptBoundary { excerpt, .. }) => {
prev_excerpt, return Some(StickyHeaderExcerpt { excerpt })
next_excerpt,
starts_new_buffer,
..
}) => {
let matches_start = (start as f32) < position;
if matches_start && top_row <= end {
return next_excerpt.as_ref().map(|excerpt| StickyHeaderExcerpt {
next_buffer_row: None,
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,
});
} }
Some(Block::FoldedBuffer { Some(block) if block.is_buffer_header() => return None,
prev_excerpt: Some(excerpt), _ => {
.. cursor.prev(&());
}) if top_row <= start => { continue;
return Some(StickyHeaderExcerpt {
next_buffer_row: Some(end),
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 None
@ -1463,14 +1391,9 @@ impl BlockSnapshot {
return Some(Block::Custom(custom_block.clone())); return Some(Block::Custom(custom_block.clone()));
} }
BlockId::ExcerptBoundary(next_excerpt_id) => { BlockId::ExcerptBoundary(next_excerpt_id) => {
if let Some(next_excerpt_id) = next_excerpt_id { let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?; self.wrap_snapshot
self.wrap_snapshot .make_wrap_point(excerpt_range.start, Bias::Left)
.make_wrap_point(excerpt_range.start, Bias::Left)
} else {
self.wrap_snapshot
.make_wrap_point(buffer.max_point(), Bias::Left)
}
} }
BlockId::FoldedBuffer(excerpt_id) => self BlockId::FoldedBuffer(excerpt_id) => self
.wrap_snapshot .wrap_snapshot
@ -1748,7 +1671,6 @@ impl BlockChunks<'_> {
pub struct StickyHeaderExcerpt<'a> { pub struct StickyHeaderExcerpt<'a> {
pub excerpt: &'a ExcerptInfo, pub excerpt: &'a ExcerptInfo,
pub next_buffer_row: Option<u32>,
} }
impl<'a> Iterator for BlockChunks<'a> { impl<'a> Iterator for BlockChunks<'a> {
@ -2254,9 +2176,9 @@ mod tests {
assert_eq!( assert_eq!(
blocks, blocks,
vec![ vec![
(0..1, BlockId::ExcerptBoundary(Some(excerpt_ids[0]))), // path, header (0..1, BlockId::ExcerptBoundary(excerpt_ids[0])), // path, header
(3..4, BlockId::ExcerptBoundary(Some(excerpt_ids[1]))), // path, header (3..4, BlockId::ExcerptBoundary(excerpt_ids[1])), // path, header
(6..7, BlockId::ExcerptBoundary(Some(excerpt_ids[2]))), // path, header (6..7, BlockId::ExcerptBoundary(excerpt_ids[2])), // path, header
] ]
); );
} }
@ -2953,10 +2875,7 @@ mod tests {
.iter() .iter()
.filter(|(_, block)| { .filter(|(_, block)| {
match block { match block {
Block::FoldedBuffer { prev_excerpt, .. } => { Block::FoldedBuffer { .. } => true,
assert!(prev_excerpt.is_none());
true
}
_ => false, _ => false,
} }
}) })

View file

@ -2640,7 +2640,7 @@ impl EditorElement {
} }
Block::ExcerptBoundary { Block::ExcerptBoundary {
next_excerpt, excerpt,
height, height,
starts_new_buffer, starts_new_buffer,
.. ..
@ -2648,40 +2648,31 @@ impl EditorElement {
let color = cx.theme().colors().clone(); let color = cx.theme().colors().clone();
let mut result = v_flex().id(block_id).w_full(); let mut result = v_flex().id(block_id).w_full();
if let Some(next_excerpt) = next_excerpt { let jump_data = header_jump_data(snapshot, block_row_start, *height, excerpt);
let jump_data =
header_jump_data(snapshot, block_row_start, *height, next_excerpt);
if *starts_new_buffer { if *starts_new_buffer {
if sticky_header_excerpt_id != Some(next_excerpt.id) { if sticky_header_excerpt_id != Some(excerpt.id) {
let selected = selected_buffer_ids.contains(&next_excerpt.buffer_id); let selected = selected_buffer_ids.contains(&excerpt.buffer_id);
result = result.child(self.render_buffer_header( result = result.child(self.render_buffer_header(
next_excerpt, excerpt, false, selected, false, jump_data, window, cx,
false, ));
selected,
false,
jump_data,
window,
cx,
));
} else {
result = result
.child(div().h(FILE_HEADER_HEIGHT as f32 * window.line_height()));
}
} else { } else {
result = result.child( result =
h_flex().relative().child( result.child(div().h(FILE_HEADER_HEIGHT as f32 * window.line_height()));
div() }
.top(line_height / 2.) } else {
.absolute() result = result.child(
.w_full() h_flex().relative().child(
.h_px() div()
.bg(color.border_variant), .top(line_height / 2.)
), .absolute()
); .w_full()
}; .h_px()
} .bg(color.border_variant),
),
);
};
result.into_any() result.into_any()
} }
@ -2972,6 +2963,7 @@ impl EditorElement {
element, element,
available_space: size(AvailableSpace::MinContent, element_size.height.into()), available_space: size(AvailableSpace::MinContent, element_size.height.into()),
style: BlockStyle::Fixed, style: BlockStyle::Fixed,
is_buffer_header: block.is_buffer_header(),
}); });
} }
@ -3022,6 +3014,7 @@ impl EditorElement {
element, element,
available_space: size(width.into(), element_size.height.into()), available_space: size(width.into(), element_size.height.into()),
style, style,
is_buffer_header: block.is_buffer_header(),
}); });
} }
@ -3072,6 +3065,7 @@ impl EditorElement {
element, element,
available_space: size(width, element_size.height.into()), available_space: size(width, element_size.height.into()),
style, style,
is_buffer_header: block.is_buffer_header(),
}); });
} }
} }
@ -3133,15 +3127,13 @@ impl EditorElement {
fn layout_sticky_buffer_header( fn layout_sticky_buffer_header(
&self, &self,
StickyHeaderExcerpt { StickyHeaderExcerpt { excerpt }: StickyHeaderExcerpt<'_>,
excerpt,
next_buffer_row,
}: StickyHeaderExcerpt<'_>,
scroll_position: f32, scroll_position: f32,
line_height: Pixels, line_height: Pixels,
snapshot: &EditorSnapshot, snapshot: &EditorSnapshot,
hitbox: &Hitbox, hitbox: &Hitbox,
selected_buffer_ids: &Vec<BufferId>, selected_buffer_ids: &Vec<BufferId>,
blocks: &[BlockLayout],
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) -> AnyElement { ) -> AnyElement {
@ -3177,17 +3169,23 @@ impl EditorElement {
.into_any_element(); .into_any_element();
let mut origin = hitbox.origin; let mut origin = hitbox.origin;
// Move floating header up to avoid colliding with the next buffer header.
for block in blocks.iter() {
if !block.is_buffer_header {
continue;
}
if let Some(next_buffer_row) = next_buffer_row { let Some(display_row) = block.row.filter(|row| row.0 > scroll_position as u32) else {
// Push up the sticky header when the excerpt is getting close to the top of the viewport continue;
};
let max_row = next_buffer_row - FILE_HEADER_HEIGHT * 2;
let max_row = display_row.0.saturating_sub(FILE_HEADER_HEIGHT);
let offset = scroll_position - max_row as f32; let offset = scroll_position - max_row as f32;
if offset > 0.0 { if offset > 0.0 {
origin.y -= Pixels(offset) * line_height; origin.y -= Pixels(offset) * line_height;
} }
break;
} }
let size = size( let size = size(
@ -7043,6 +7041,7 @@ impl Element for EditorElement {
&snapshot, &snapshot,
&hitbox, &hitbox,
&selected_buffer_ids, &selected_buffer_ids,
&blocks,
window, window,
cx, cx,
) )
@ -7926,6 +7925,7 @@ struct BlockLayout {
element: AnyElement, element: AnyElement,
available_space: Size<AvailableSpace>, available_space: Size<AvailableSpace>,
style: BlockStyle, style: BlockStyle,
is_buffer_header: bool,
} }
pub fn layout_line( pub fn layout_line(

View file

@ -1109,14 +1109,14 @@ mod tests {
px(14.0), px(14.0),
None, None,
0, 0,
2, 1,
FoldPlaceholder::test(), FoldPlaceholder::test(),
cx, cx,
) )
}); });
let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx)); let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
assert_eq!(snapshot.text(), "abc\ndefg\nhijkl\nmn"); assert_eq!(snapshot.text(), "abc\ndefg\n\nhijkl\nmn");
let col_2_x = snapshot let col_2_x = snapshot
.x_for_display_point(DisplayPoint::new(DisplayRow(0), 2), &text_layout_details); .x_for_display_point(DisplayPoint::new(DisplayRow(0), 2), &text_layout_details);
@ -1181,13 +1181,13 @@ mod tests {
); );
let col_5_x = snapshot let col_5_x = snapshot
.x_for_display_point(DisplayPoint::new(DisplayRow(2), 5), &text_layout_details); .x_for_display_point(DisplayPoint::new(DisplayRow(3), 5), &text_layout_details);
// Move up and down across second excerpt's header // Move up and down across second excerpt's header
assert_eq!( assert_eq!(
up( up(
&snapshot, &snapshot,
DisplayPoint::new(DisplayRow(2), 5), DisplayPoint::new(DisplayRow(3), 5),
SelectionGoal::HorizontalPosition(col_5_x.0), SelectionGoal::HorizontalPosition(col_5_x.0),
false, false,
&text_layout_details &text_layout_details
@ -1206,38 +1206,38 @@ mod tests {
&text_layout_details &text_layout_details
), ),
( (
DisplayPoint::new(DisplayRow(2), 5), DisplayPoint::new(DisplayRow(3), 5),
SelectionGoal::HorizontalPosition(col_5_x.0) SelectionGoal::HorizontalPosition(col_5_x.0)
), ),
); );
let max_point_x = snapshot let max_point_x = snapshot
.x_for_display_point(DisplayPoint::new(DisplayRow(3), 2), &text_layout_details); .x_for_display_point(DisplayPoint::new(DisplayRow(4), 2), &text_layout_details);
// Can't move down off the end, and attempting to do so leaves the selection goal unchanged // Can't move down off the end, and attempting to do so leaves the selection goal unchanged
assert_eq!( assert_eq!(
down( down(
&snapshot, &snapshot,
DisplayPoint::new(DisplayRow(3), 0), DisplayPoint::new(DisplayRow(4), 0),
SelectionGoal::HorizontalPosition(0.0), SelectionGoal::HorizontalPosition(0.0),
false, false,
&text_layout_details &text_layout_details
), ),
( (
DisplayPoint::new(DisplayRow(3), 2), DisplayPoint::new(DisplayRow(4), 2),
SelectionGoal::HorizontalPosition(0.0) SelectionGoal::HorizontalPosition(0.0)
), ),
); );
assert_eq!( assert_eq!(
down( down(
&snapshot, &snapshot,
DisplayPoint::new(DisplayRow(3), 2), DisplayPoint::new(DisplayRow(4), 2),
SelectionGoal::HorizontalPosition(max_point_x.0), SelectionGoal::HorizontalPosition(max_point_x.0),
false, false,
&text_layout_details &text_layout_details
), ),
( (
DisplayPoint::new(DisplayRow(3), 2), DisplayPoint::new(DisplayRow(4), 2),
SelectionGoal::HorizontalPosition(max_point_x.0) SelectionGoal::HorizontalPosition(max_point_x.0)
), ),
); );

View file

@ -346,17 +346,16 @@ impl std::fmt::Debug for ExcerptInfo {
#[derive(Debug)] #[derive(Debug)]
pub struct ExcerptBoundary { pub struct ExcerptBoundary {
pub prev: Option<ExcerptInfo>, pub prev: Option<ExcerptInfo>,
pub next: Option<ExcerptInfo>, pub next: ExcerptInfo,
/// The row in the `MultiBuffer` where the boundary is located /// The row in the `MultiBuffer` where the boundary is located
pub row: MultiBufferRow, pub row: MultiBufferRow,
} }
impl ExcerptBoundary { impl ExcerptBoundary {
pub fn starts_new_buffer(&self) -> bool { pub fn starts_new_buffer(&self) -> bool {
match (self.prev.as_ref(), self.next.as_ref()) { match (self.prev.as_ref(), &self.next) {
(None, _) => true, (None, _) => true,
(Some(_), None) => false, (Some(prev), next) => prev.buffer_id != next.buffer_id,
(Some(prev), Some(next)) => prev.buffer_id != next.buffer_id,
} }
} }
} }
@ -5200,27 +5199,19 @@ impl MultiBufferSnapshot {
cursor.next_excerpt(); cursor.next_excerpt();
let mut visited_end = false;
iter::from_fn(move || loop { iter::from_fn(move || loop {
if self.singleton { if self.singleton {
return None; return None;
} }
let next_region = cursor.region(); let next_region = cursor.region()?;
cursor.next_excerpt(); cursor.next_excerpt();
if !bounds.contains(&next_region.range.start.key) {
prev_region = Some(next_region);
continue;
}
let next_region_start = if let Some(region) = &next_region { let next_region_start = next_region.range.start.value.unwrap();
if !bounds.contains(&region.range.start.key) {
prev_region = next_region;
continue;
}
region.range.start.value.unwrap()
} else {
if !bounds.contains(&self.len()) {
return None;
}
self.max_point()
};
let next_region_end = if let Some(region) = cursor.region() { let next_region_end = if let Some(region) = cursor.region() {
region.range.start.value.unwrap() region.range.start.value.unwrap()
} else { } else {
@ -5235,29 +5226,21 @@ impl MultiBufferSnapshot {
end_row: MultiBufferRow(next_region_start.row), end_row: MultiBufferRow(next_region_start.row),
}); });
let next = next_region.as_ref().map(|region| ExcerptInfo { let next = ExcerptInfo {
id: region.excerpt.id, id: next_region.excerpt.id,
buffer: region.excerpt.buffer.clone(), buffer: next_region.excerpt.buffer.clone(),
buffer_id: region.excerpt.buffer_id, buffer_id: next_region.excerpt.buffer_id,
range: region.excerpt.range.clone(), range: next_region.excerpt.range.clone(),
end_row: if region.excerpt.has_trailing_newline { end_row: if next_region.excerpt.has_trailing_newline {
MultiBufferRow(next_region_end.row - 1) MultiBufferRow(next_region_end.row - 1)
} else { } else {
MultiBufferRow(next_region_end.row) MultiBufferRow(next_region_end.row)
}, },
}); };
if next.is_none() {
if visited_end {
return None;
} else {
visited_end = true;
}
}
let row = MultiBufferRow(next_region_start.row); let row = MultiBufferRow(next_region_start.row);
prev_region = next_region; prev_region = Some(next_region);
return Some(ExcerptBoundary { row, prev, next }); return Some(ExcerptBoundary { row, prev, next });
}) })

View file

@ -341,17 +341,17 @@ fn test_excerpt_boundaries_and_clipping(cx: &mut App) {
) -> Vec<(MultiBufferRow, String, bool)> { ) -> Vec<(MultiBufferRow, String, bool)> {
snapshot snapshot
.excerpt_boundaries_in_range(range) .excerpt_boundaries_in_range(range)
.filter_map(|boundary| { .map(|boundary| {
let starts_new_buffer = boundary.starts_new_buffer(); let starts_new_buffer = boundary.starts_new_buffer();
boundary.next.map(|next| { (
( boundary.row,
boundary.row, boundary
next.buffer .next
.text_for_range(next.range.context) .buffer
.collect::<String>(), .text_for_range(boundary.next.range.context)
starts_new_buffer, .collect::<String>(),
) starts_new_buffer,
}) )
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
@ -2695,7 +2695,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) {
let actual_text = snapshot.text(); let actual_text = snapshot.text();
let actual_boundary_rows = snapshot let actual_boundary_rows = snapshot
.excerpt_boundaries_in_range(0..) .excerpt_boundaries_in_range(0..)
.filter_map(|b| if b.next.is_some() { Some(b.row) } else { None }) .map(|b| b.row)
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(); let actual_row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();