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:
parent
94b63808e0
commit
25772b8777
5 changed files with 138 additions and 236 deletions
|
@ -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,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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(®ion.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 });
|
||||||
})
|
})
|
||||||
|
|
|
@ -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<_>>();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue