Avoid losing focus when block decorations go offscreen (#14815)
Release Notes: - Fixed a bug that caused focus to be lost when renames and inline assists were scrolled offscreen. --------- Co-authored-by: Nathan <nathan@zed.dev>
This commit is contained in:
parent
f5d50f2b1e
commit
d61eaea4b9
18 changed files with 941 additions and 584 deletions
|
@ -18,7 +18,7 @@ use std::{
|
|||
Arc,
|
||||
},
|
||||
};
|
||||
use sum_tree::{Bias, SumTree};
|
||||
use sum_tree::{Bias, SumTree, TreeMap};
|
||||
use text::Edit;
|
||||
use ui::ElementId;
|
||||
|
||||
|
@ -30,7 +30,8 @@ const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
|
|||
pub struct BlockMap {
|
||||
next_block_id: AtomicUsize,
|
||||
wrap_snapshot: RefCell<WrapSnapshot>,
|
||||
blocks: Vec<Arc<Block>>,
|
||||
custom_blocks: Vec<Arc<CustomBlock>>,
|
||||
custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
|
||||
transforms: RefCell<SumTree<Transform>>,
|
||||
show_excerpt_controls: bool,
|
||||
buffer_header_height: u8,
|
||||
|
@ -39,7 +40,7 @@ pub struct BlockMap {
|
|||
}
|
||||
|
||||
pub struct BlockMapReader<'a> {
|
||||
blocks: &'a Vec<Arc<Block>>,
|
||||
blocks: &'a Vec<Arc<CustomBlock>>,
|
||||
pub snapshot: BlockSnapshot,
|
||||
}
|
||||
|
||||
|
@ -49,12 +50,13 @@ pub struct BlockMapWriter<'a>(&'a mut BlockMap);
|
|||
pub struct BlockSnapshot {
|
||||
wrap_snapshot: WrapSnapshot,
|
||||
transforms: SumTree<Transform>,
|
||||
custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct BlockId(usize);
|
||||
pub struct CustomBlockId(usize);
|
||||
|
||||
impl Into<ElementId> for BlockId {
|
||||
impl Into<ElementId> for CustomBlockId {
|
||||
fn into(self) -> ElementId {
|
||||
ElementId::Integer(self.0)
|
||||
}
|
||||
|
@ -71,8 +73,8 @@ struct WrapRow(u32);
|
|||
|
||||
pub type RenderBlock = Box<dyn Send + FnMut(&mut BlockContext) -> AnyElement>;
|
||||
|
||||
pub struct Block {
|
||||
id: BlockId,
|
||||
pub struct CustomBlock {
|
||||
id: CustomBlockId,
|
||||
position: Anchor,
|
||||
height: u8,
|
||||
style: BlockStyle,
|
||||
|
@ -113,41 +115,41 @@ pub struct BlockContext<'a, 'b> {
|
|||
pub gutter_dimensions: &'b GutterDimensions,
|
||||
pub em_width: Pixels,
|
||||
pub line_height: Pixels,
|
||||
pub transform_block_id: TransformBlockId,
|
||||
pub block_id: BlockId,
|
||||
pub editor_style: &'b EditorStyle,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum TransformBlockId {
|
||||
Block(BlockId),
|
||||
pub enum BlockId {
|
||||
Custom(CustomBlockId),
|
||||
ExcerptHeader(ExcerptId),
|
||||
ExcerptFooter(ExcerptId),
|
||||
}
|
||||
|
||||
impl From<TransformBlockId> for EntityId {
|
||||
fn from(value: TransformBlockId) -> Self {
|
||||
impl From<BlockId> for EntityId {
|
||||
fn from(value: BlockId) -> Self {
|
||||
match value {
|
||||
TransformBlockId::Block(BlockId(id)) => EntityId::from(id as u64),
|
||||
TransformBlockId::ExcerptHeader(id) => id.into(),
|
||||
TransformBlockId::ExcerptFooter(id) => id.into(),
|
||||
BlockId::Custom(CustomBlockId(id)) => EntityId::from(id as u64),
|
||||
BlockId::ExcerptHeader(id) => id.into(),
|
||||
BlockId::ExcerptFooter(id) => id.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<ElementId> for TransformBlockId {
|
||||
impl Into<ElementId> for BlockId {
|
||||
fn into(self) -> ElementId {
|
||||
match self {
|
||||
Self::Block(BlockId(id)) => ("Block", id).into(),
|
||||
Self::Custom(CustomBlockId(id)) => ("Block", id).into(),
|
||||
Self::ExcerptHeader(id) => ("ExcerptHeader", EntityId::from(id)).into(),
|
||||
Self::ExcerptFooter(id) => ("ExcerptFooter", EntityId::from(id)).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TransformBlockId {
|
||||
impl std::fmt::Display for BlockId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Block(id) => write!(f, "Block({id:?})"),
|
||||
Self::Custom(id) => write!(f, "Block({id:?})"),
|
||||
Self::ExcerptHeader(id) => write!(f, "ExcerptHeader({id:?})"),
|
||||
Self::ExcerptFooter(id) => write!(f, "ExcerptFooter({id:?})"),
|
||||
}
|
||||
|
@ -164,11 +166,11 @@ pub enum BlockDisposition {
|
|||
#[derive(Clone, Debug)]
|
||||
struct Transform {
|
||||
summary: TransformSummary,
|
||||
block: Option<TransformBlock>,
|
||||
block: Option<Block>,
|
||||
}
|
||||
|
||||
pub(crate) enum BlockType {
|
||||
Custom(BlockId),
|
||||
Custom(CustomBlockId),
|
||||
Header,
|
||||
Footer,
|
||||
}
|
||||
|
@ -180,8 +182,8 @@ pub(crate) trait BlockLike {
|
|||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone)]
|
||||
pub enum TransformBlock {
|
||||
Custom(Arc<Block>),
|
||||
pub enum Block {
|
||||
Custom(Arc<CustomBlock>),
|
||||
ExcerptHeader {
|
||||
id: ExcerptId,
|
||||
buffer: BufferSnapshot,
|
||||
|
@ -197,12 +199,12 @@ pub enum TransformBlock {
|
|||
},
|
||||
}
|
||||
|
||||
impl BlockLike for TransformBlock {
|
||||
impl BlockLike for Block {
|
||||
fn block_type(&self) -> BlockType {
|
||||
match self {
|
||||
TransformBlock::Custom(block) => BlockType::Custom(block.id),
|
||||
TransformBlock::ExcerptHeader { .. } => BlockType::Header,
|
||||
TransformBlock::ExcerptFooter { .. } => BlockType::Footer,
|
||||
Block::Custom(block) => BlockType::Custom(block.id),
|
||||
Block::ExcerptHeader { .. } => BlockType::Header,
|
||||
Block::ExcerptFooter { .. } => BlockType::Footer,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,33 +213,41 @@ impl BlockLike for TransformBlock {
|
|||
}
|
||||
}
|
||||
|
||||
impl TransformBlock {
|
||||
pub fn id(&self) -> TransformBlockId {
|
||||
impl Block {
|
||||
pub fn id(&self) -> BlockId {
|
||||
match self {
|
||||
TransformBlock::Custom(block) => TransformBlockId::Block(block.id),
|
||||
TransformBlock::ExcerptHeader { id, .. } => TransformBlockId::ExcerptHeader(*id),
|
||||
TransformBlock::ExcerptFooter { id, .. } => TransformBlockId::ExcerptFooter(*id),
|
||||
Block::Custom(block) => BlockId::Custom(block.id),
|
||||
Block::ExcerptHeader { id, .. } => BlockId::ExcerptHeader(*id),
|
||||
Block::ExcerptFooter { id, .. } => BlockId::ExcerptFooter(*id),
|
||||
}
|
||||
}
|
||||
|
||||
fn disposition(&self) -> BlockDisposition {
|
||||
match self {
|
||||
TransformBlock::Custom(block) => block.disposition,
|
||||
TransformBlock::ExcerptHeader { .. } => BlockDisposition::Above,
|
||||
TransformBlock::ExcerptFooter { disposition, .. } => *disposition,
|
||||
Block::Custom(block) => block.disposition,
|
||||
Block::ExcerptHeader { .. } => BlockDisposition::Above,
|
||||
Block::ExcerptFooter { disposition, .. } => *disposition,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn height(&self) -> u8 {
|
||||
match self {
|
||||
TransformBlock::Custom(block) => block.height,
|
||||
TransformBlock::ExcerptHeader { height, .. } => *height,
|
||||
TransformBlock::ExcerptFooter { height, .. } => *height,
|
||||
Block::Custom(block) => block.height,
|
||||
Block::ExcerptHeader { height, .. } => *height,
|
||||
Block::ExcerptFooter { height, .. } => *height,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn style(&self) -> BlockStyle {
|
||||
match self {
|
||||
Block::Custom(block) => block.style,
|
||||
Block::ExcerptHeader { .. } => BlockStyle::Sticky,
|
||||
Block::ExcerptFooter { .. } => BlockStyle::Sticky,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for TransformBlock {
|
||||
impl Debug for Block {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(),
|
||||
|
@ -252,7 +262,7 @@ impl Debug for TransformBlock {
|
|||
.field("path", &buffer.file().map(|f| f.path()))
|
||||
.field("starts_new_buffer", &starts_new_buffer)
|
||||
.finish(),
|
||||
TransformBlock::ExcerptFooter {
|
||||
Block::ExcerptFooter {
|
||||
id, disposition, ..
|
||||
} => f
|
||||
.debug_struct("ExcerptFooter")
|
||||
|
@ -296,7 +306,8 @@ impl BlockMap {
|
|||
let row_count = wrap_snapshot.max_point().row() + 1;
|
||||
let map = Self {
|
||||
next_block_id: AtomicUsize::new(0),
|
||||
blocks: Vec::new(),
|
||||
custom_blocks: Vec::new(),
|
||||
custom_blocks_by_id: TreeMap::default(),
|
||||
transforms: RefCell::new(SumTree::from_item(Transform::isomorphic(row_count), &())),
|
||||
wrap_snapshot: RefCell::new(wrap_snapshot.clone()),
|
||||
show_excerpt_controls,
|
||||
|
@ -318,10 +329,11 @@ impl BlockMap {
|
|||
self.sync(&wrap_snapshot, edits);
|
||||
*self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone();
|
||||
BlockMapReader {
|
||||
blocks: &self.blocks,
|
||||
blocks: &self.custom_blocks,
|
||||
snapshot: BlockSnapshot {
|
||||
wrap_snapshot,
|
||||
transforms: self.transforms.borrow().clone(),
|
||||
custom_blocks_by_id: self.custom_blocks_by_id.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -443,25 +455,26 @@ impl BlockMap {
|
|||
let new_buffer_start =
|
||||
wrap_snapshot.to_point(WrapPoint::new(new_start.0, 0), Bias::Left);
|
||||
let start_bound = Bound::Included(new_buffer_start);
|
||||
let start_block_ix = match self.blocks[last_block_ix..].binary_search_by(|probe| {
|
||||
probe
|
||||
.position
|
||||
.to_point(buffer)
|
||||
.cmp(&new_buffer_start)
|
||||
.then(Ordering::Greater)
|
||||
}) {
|
||||
Ok(ix) | Err(ix) => last_block_ix + ix,
|
||||
};
|
||||
let start_block_ix =
|
||||
match self.custom_blocks[last_block_ix..].binary_search_by(|probe| {
|
||||
probe
|
||||
.position
|
||||
.to_point(buffer)
|
||||
.cmp(&new_buffer_start)
|
||||
.then(Ordering::Greater)
|
||||
}) {
|
||||
Ok(ix) | Err(ix) => last_block_ix + ix,
|
||||
};
|
||||
|
||||
let end_bound;
|
||||
let end_block_ix = if new_end.0 > wrap_snapshot.max_point().row() {
|
||||
end_bound = Bound::Unbounded;
|
||||
self.blocks.len()
|
||||
self.custom_blocks.len()
|
||||
} else {
|
||||
let new_buffer_end =
|
||||
wrap_snapshot.to_point(WrapPoint::new(new_end.0, 0), Bias::Left);
|
||||
end_bound = Bound::Excluded(new_buffer_end);
|
||||
match self.blocks[start_block_ix..].binary_search_by(|probe| {
|
||||
match self.custom_blocks[start_block_ix..].binary_search_by(|probe| {
|
||||
probe
|
||||
.position
|
||||
.to_point(buffer)
|
||||
|
@ -474,24 +487,22 @@ impl BlockMap {
|
|||
last_block_ix = end_block_ix;
|
||||
|
||||
debug_assert!(blocks_in_edit.is_empty());
|
||||
blocks_in_edit.extend(
|
||||
self.blocks[start_block_ix..end_block_ix]
|
||||
.iter()
|
||||
.map(|block| {
|
||||
let mut position = block.position.to_point(buffer);
|
||||
match block.disposition {
|
||||
BlockDisposition::Above => position.column = 0,
|
||||
BlockDisposition::Below => {
|
||||
position.column = buffer.line_len(MultiBufferRow(position.row))
|
||||
}
|
||||
blocks_in_edit.extend(self.custom_blocks[start_block_ix..end_block_ix].iter().map(
|
||||
|block| {
|
||||
let mut position = block.position.to_point(buffer);
|
||||
match block.disposition {
|
||||
BlockDisposition::Above => position.column = 0,
|
||||
BlockDisposition::Below => {
|
||||
position.column = buffer.line_len(MultiBufferRow(position.row))
|
||||
}
|
||||
let position = wrap_snapshot.make_wrap_point(position, Bias::Left);
|
||||
(position.row(), TransformBlock::Custom(block.clone()))
|
||||
}),
|
||||
);
|
||||
}
|
||||
let position = wrap_snapshot.make_wrap_point(position, Bias::Left);
|
||||
(position.row(), Block::Custom(block.clone()))
|
||||
},
|
||||
));
|
||||
|
||||
if buffer.show_headers() {
|
||||
blocks_in_edit.extend(BlockMap::header_blocks(
|
||||
blocks_in_edit.extend(BlockMap::header_and_footer_blocks(
|
||||
self.show_excerpt_controls,
|
||||
self.excerpt_footer_height,
|
||||
self.buffer_header_height,
|
||||
|
@ -538,8 +549,8 @@ impl BlockMap {
|
|||
*transforms = new_transforms;
|
||||
}
|
||||
|
||||
pub fn replace_renderers(&mut self, mut renderers: HashMap<BlockId, RenderBlock>) {
|
||||
for block in &mut self.blocks {
|
||||
pub fn replace_renderers(&mut self, mut renderers: HashMap<CustomBlockId, RenderBlock>) {
|
||||
for block in &mut self.custom_blocks {
|
||||
if let Some(render) = renderers.remove(&block.id) {
|
||||
*block.render.lock() = render;
|
||||
}
|
||||
|
@ -550,7 +561,7 @@ impl BlockMap {
|
|||
self.show_excerpt_controls
|
||||
}
|
||||
|
||||
pub fn header_blocks<'a, 'b: 'a, 'c: 'a + 'b, R, T>(
|
||||
pub fn header_and_footer_blocks<'a, 'b: 'a, 'c: 'a + 'b, R, T>(
|
||||
show_excerpt_controls: bool,
|
||||
excerpt_footer_height: u8,
|
||||
buffer_header_height: u8,
|
||||
|
@ -558,7 +569,7 @@ impl BlockMap {
|
|||
buffer: &'b multi_buffer::MultiBufferSnapshot,
|
||||
range: R,
|
||||
wrap_snapshot: &'c WrapSnapshot,
|
||||
) -> impl Iterator<Item = (u32, TransformBlock)> + 'b
|
||||
) -> impl Iterator<Item = (u32, Block)> + 'b
|
||||
where
|
||||
R: RangeBounds<T>,
|
||||
T: multi_buffer::ToOffset,
|
||||
|
@ -566,24 +577,36 @@ impl BlockMap {
|
|||
buffer
|
||||
.excerpt_boundaries_in_range(range)
|
||||
.flat_map(move |excerpt_boundary| {
|
||||
let wrap_row = wrap_snapshot
|
||||
let mut wrap_row = wrap_snapshot
|
||||
.make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
|
||||
.row();
|
||||
|
||||
[
|
||||
show_excerpt_controls
|
||||
.then(|| {
|
||||
let disposition;
|
||||
if excerpt_boundary.next.is_some() {
|
||||
disposition = BlockDisposition::Above;
|
||||
} else {
|
||||
wrap_row = wrap_snapshot
|
||||
.make_wrap_point(
|
||||
Point::new(
|
||||
excerpt_boundary.row.0,
|
||||
buffer.line_len(excerpt_boundary.row),
|
||||
),
|
||||
Bias::Left,
|
||||
)
|
||||
.row();
|
||||
disposition = BlockDisposition::Below;
|
||||
}
|
||||
|
||||
excerpt_boundary.prev.as_ref().map(|prev| {
|
||||
(
|
||||
wrap_row,
|
||||
TransformBlock::ExcerptFooter {
|
||||
Block::ExcerptFooter {
|
||||
id: prev.id,
|
||||
height: excerpt_footer_height,
|
||||
disposition: if excerpt_boundary.next.is_some() {
|
||||
BlockDisposition::Above
|
||||
} else {
|
||||
BlockDisposition::Below
|
||||
},
|
||||
disposition,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
@ -596,7 +619,7 @@ impl BlockMap {
|
|||
|
||||
(
|
||||
wrap_row,
|
||||
TransformBlock::ExcerptHeader {
|
||||
Block::ExcerptHeader {
|
||||
id: next.id,
|
||||
buffer: next.buffer,
|
||||
range: next.range,
|
||||
|
@ -692,7 +715,7 @@ impl<'a> DerefMut for BlockMapReader<'a> {
|
|||
}
|
||||
|
||||
impl<'a> BlockMapReader<'a> {
|
||||
pub fn row_for_block(&self, block_id: BlockId) -> Option<BlockRow> {
|
||||
pub fn row_for_block(&self, block_id: CustomBlockId) -> Option<BlockRow> {
|
||||
let block = self.blocks.iter().find(|block| block.id == block_id)?;
|
||||
let buffer_row = block
|
||||
.position
|
||||
|
@ -737,14 +760,14 @@ impl<'a> BlockMapWriter<'a> {
|
|||
pub fn insert(
|
||||
&mut self,
|
||||
blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
|
||||
) -> Vec<BlockId> {
|
||||
) -> Vec<CustomBlockId> {
|
||||
let mut ids = Vec::new();
|
||||
let mut edits = Patch::default();
|
||||
let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
|
||||
let buffer = wrap_snapshot.buffer_snapshot();
|
||||
|
||||
for block in blocks {
|
||||
let id = BlockId(self.0.next_block_id.fetch_add(1, SeqCst));
|
||||
let id = CustomBlockId(self.0.next_block_id.fetch_add(1, SeqCst));
|
||||
ids.push(id);
|
||||
|
||||
let position = block.position;
|
||||
|
@ -759,22 +782,21 @@ impl<'a> BlockMapWriter<'a> {
|
|||
|
||||
let block_ix = match self
|
||||
.0
|
||||
.blocks
|
||||
.custom_blocks
|
||||
.binary_search_by(|probe| probe.position.cmp(&position, buffer))
|
||||
{
|
||||
Ok(ix) | Err(ix) => ix,
|
||||
};
|
||||
self.0.blocks.insert(
|
||||
block_ix,
|
||||
Arc::new(Block {
|
||||
id,
|
||||
position,
|
||||
height: block.height,
|
||||
render: Mutex::new(block.render),
|
||||
disposition: block.disposition,
|
||||
style: block.style,
|
||||
}),
|
||||
);
|
||||
let new_block = Arc::new(CustomBlock {
|
||||
id,
|
||||
position,
|
||||
height: block.height,
|
||||
render: Mutex::new(block.render),
|
||||
disposition: block.disposition,
|
||||
style: block.style,
|
||||
});
|
||||
self.0.custom_blocks.insert(block_ix, new_block.clone());
|
||||
self.0.custom_blocks_by_id.insert(id, new_block);
|
||||
|
||||
edits = edits.compose([Edit {
|
||||
old: start_row..end_row,
|
||||
|
@ -786,16 +808,19 @@ impl<'a> BlockMapWriter<'a> {
|
|||
ids
|
||||
}
|
||||
|
||||
pub fn replace(&mut self, mut heights_and_renderers: HashMap<BlockId, (u8, RenderBlock)>) {
|
||||
pub fn replace(
|
||||
&mut self,
|
||||
mut heights_and_renderers: HashMap<CustomBlockId, (u8, RenderBlock)>,
|
||||
) {
|
||||
let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
|
||||
let buffer = wrap_snapshot.buffer_snapshot();
|
||||
let mut edits = Patch::default();
|
||||
let mut last_block_buffer_row = None;
|
||||
|
||||
for block in &mut self.0.blocks {
|
||||
for block in &mut self.0.custom_blocks {
|
||||
if let Some((new_height, render)) = heights_and_renderers.remove(&block.id) {
|
||||
if block.height != new_height {
|
||||
let new_block = Block {
|
||||
let new_block = CustomBlock {
|
||||
id: block.id,
|
||||
position: block.position,
|
||||
height: new_height,
|
||||
|
@ -803,7 +828,9 @@ impl<'a> BlockMapWriter<'a> {
|
|||
render: Mutex::new(render),
|
||||
disposition: block.disposition,
|
||||
};
|
||||
*block = Arc::new(new_block);
|
||||
let new_block = Arc::new(new_block);
|
||||
*block = new_block.clone();
|
||||
self.0.custom_blocks_by_id.insert(block.id, new_block);
|
||||
|
||||
let buffer_row = block.position.to_point(buffer).row;
|
||||
if last_block_buffer_row != Some(buffer_row) {
|
||||
|
@ -828,12 +855,12 @@ impl<'a> BlockMapWriter<'a> {
|
|||
self.0.sync(wrap_snapshot, edits);
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, block_ids: HashSet<BlockId>) {
|
||||
pub fn remove(&mut self, block_ids: HashSet<CustomBlockId>) {
|
||||
let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
|
||||
let buffer = wrap_snapshot.buffer_snapshot();
|
||||
let mut edits = Patch::default();
|
||||
let mut last_block_buffer_row = None;
|
||||
self.0.blocks.retain(|block| {
|
||||
self.0.custom_blocks.retain(|block| {
|
||||
if block_ids.contains(&block.id) {
|
||||
let buffer_row = block.position.to_point(buffer).row;
|
||||
if last_block_buffer_row != Some(buffer_row) {
|
||||
|
@ -850,6 +877,7 @@ impl<'a> BlockMapWriter<'a> {
|
|||
new: start_row..end_row,
|
||||
})
|
||||
}
|
||||
self.0.custom_blocks_by_id.remove(&block.id);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
|
@ -934,10 +962,7 @@ impl BlockSnapshot {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn blocks_in_range(
|
||||
&self,
|
||||
rows: Range<u32>,
|
||||
) -> impl Iterator<Item = (u32, &TransformBlock)> {
|
||||
pub fn blocks_in_range(&self, rows: Range<u32>) -> impl Iterator<Item = (u32, &Block)> {
|
||||
let mut cursor = self.transforms.cursor::<BlockRow>();
|
||||
cursor.seek(&BlockRow(rows.start), Bias::Right, &());
|
||||
std::iter::from_fn(move || {
|
||||
|
@ -957,6 +982,60 @@ impl BlockSnapshot {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
|
||||
let buffer = self.wrap_snapshot.buffer_snapshot();
|
||||
|
||||
match block_id {
|
||||
BlockId::Custom(custom_block_id) => {
|
||||
let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
|
||||
Some(Block::Custom(custom_block.clone()))
|
||||
}
|
||||
BlockId::ExcerptHeader(excerpt_id) => {
|
||||
let excerpt_range = buffer.range_for_excerpt::<Point>(excerpt_id)?;
|
||||
let wrap_point = self
|
||||
.wrap_snapshot
|
||||
.make_wrap_point(excerpt_range.start, Bias::Left);
|
||||
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
|
||||
cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
|
||||
while let Some(transform) = cursor.item() {
|
||||
if let Some(block) = transform.block.as_ref() {
|
||||
if block.id() == block_id {
|
||||
return Some(block.clone());
|
||||
}
|
||||
} else if cursor.start().0 > WrapRow(wrap_point.row()) {
|
||||
break;
|
||||
}
|
||||
|
||||
cursor.next(&());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
BlockId::ExcerptFooter(excerpt_id) => {
|
||||
let excerpt_range = buffer.range_for_excerpt::<Point>(excerpt_id)?;
|
||||
let wrap_point = self
|
||||
.wrap_snapshot
|
||||
.make_wrap_point(excerpt_range.end, Bias::Left);
|
||||
|
||||
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
|
||||
cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
|
||||
while let Some(transform) = cursor.item() {
|
||||
if let Some(block) = transform.block.as_ref() {
|
||||
if block.id() == block_id {
|
||||
return Some(block.clone());
|
||||
}
|
||||
} else if cursor.start().0 > WrapRow(wrap_point.row()) {
|
||||
break;
|
||||
}
|
||||
|
||||
cursor.next(&());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_point(&self) -> BlockPoint {
|
||||
let row = self.transforms.summary().output_rows - 1;
|
||||
BlockPoint::new(row, self.line_len(BlockRow(row)))
|
||||
|
@ -1086,7 +1165,7 @@ impl Transform {
|
|||
}
|
||||
}
|
||||
|
||||
fn block(block: TransformBlock) -> Self {
|
||||
fn block(block: Block) -> Self {
|
||||
Self {
|
||||
summary: TransformSummary {
|
||||
input_rows: 0,
|
||||
|
@ -1235,7 +1314,7 @@ impl DerefMut for BlockContext<'_, '_> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Block {
|
||||
impl CustomBlock {
|
||||
pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
|
||||
self.render.lock()(cx)
|
||||
}
|
||||
|
@ -1249,7 +1328,7 @@ impl Block {
|
|||
}
|
||||
}
|
||||
|
||||
impl Debug for Block {
|
||||
impl Debug for CustomBlock {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Block")
|
||||
.field("id", &self.id)
|
||||
|
@ -1279,15 +1358,16 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::env;
|
||||
|
||||
use super::*;
|
||||
use crate::display_map::inlay_map::InlayMap;
|
||||
use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
|
||||
use gpui::{div, font, px, Element};
|
||||
use crate::display_map::{
|
||||
fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap,
|
||||
};
|
||||
use gpui::{div, font, px, AppContext, Context as _, Element};
|
||||
use language::{Buffer, Capability};
|
||||
use multi_buffer::MultiBuffer;
|
||||
use rand::prelude::*;
|
||||
use settings::SettingsStore;
|
||||
use std::env;
|
||||
use util::RandomCharIter;
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -1474,6 +1554,89 @@ mod tests {
|
|||
assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_multibuffer_headers_and_footers(cx: &mut AppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let buffer1 = cx.new_model(|cx| Buffer::local("Buffer 1", cx));
|
||||
let buffer2 = cx.new_model(|cx| Buffer::local("Buffer 2", cx));
|
||||
let buffer3 = cx.new_model(|cx| Buffer::local("Buffer 3", cx));
|
||||
|
||||
let mut excerpt_ids = Vec::new();
|
||||
let multi_buffer = cx.new_model(|cx| {
|
||||
let mut multi_buffer = MultiBuffer::new(0, Capability::ReadWrite);
|
||||
excerpt_ids.extend(multi_buffer.push_excerpts(
|
||||
buffer1.clone(),
|
||||
[ExcerptRange {
|
||||
context: 0..buffer1.read(cx).len(),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
));
|
||||
excerpt_ids.extend(multi_buffer.push_excerpts(
|
||||
buffer2.clone(),
|
||||
[ExcerptRange {
|
||||
context: 0..buffer2.read(cx).len(),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
));
|
||||
excerpt_ids.extend(multi_buffer.push_excerpts(
|
||||
buffer3.clone(),
|
||||
[ExcerptRange {
|
||||
context: 0..buffer3.read(cx).len(),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
));
|
||||
|
||||
multi_buffer
|
||||
});
|
||||
|
||||
let font = font("Helvetica");
|
||||
let font_size = px(14.);
|
||||
let font_id = cx.text_system().resolve_font(&font);
|
||||
let mut wrap_width = px(0.);
|
||||
for c in "Buff".chars() {
|
||||
wrap_width += cx
|
||||
.text_system()
|
||||
.advance(font_id, font_size, c)
|
||||
.unwrap()
|
||||
.width;
|
||||
}
|
||||
|
||||
let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
|
||||
let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot.clone());
|
||||
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
|
||||
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
|
||||
let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
|
||||
|
||||
let block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
|
||||
let snapshot = block_map.read(wraps_snapshot, Default::default());
|
||||
|
||||
// Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
|
||||
assert_eq!(
|
||||
snapshot.text(),
|
||||
"\nBuff\ner 1\n\n\nBuff\ner 2\n\n\nBuff\ner 3\n"
|
||||
);
|
||||
|
||||
let blocks: Vec<_> = snapshot
|
||||
.blocks_in_range(0..u32::MAX)
|
||||
.map(|(row, block)| (row, block.id()))
|
||||
.collect();
|
||||
assert_eq!(
|
||||
blocks,
|
||||
vec![
|
||||
(0, BlockId::ExcerptHeader(excerpt_ids[0])),
|
||||
(3, BlockId::ExcerptFooter(excerpt_ids[0])),
|
||||
(4, BlockId::ExcerptHeader(excerpt_ids[1])),
|
||||
(7, BlockId::ExcerptFooter(excerpt_ids[1])),
|
||||
(8, BlockId::ExcerptHeader(excerpt_ids[2])),
|
||||
(11, BlockId::ExcerptFooter(excerpt_ids[2]))
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
|
||||
let _update = cx.update(|cx| init_test(cx));
|
||||
|
@ -1807,7 +1970,7 @@ mod tests {
|
|||
|
||||
// Note that this needs to be synced with the related section in BlockMap::sync
|
||||
expected_blocks.extend(
|
||||
BlockMap::header_blocks(
|
||||
BlockMap::header_and_footer_blocks(
|
||||
true,
|
||||
excerpt_footer_height,
|
||||
buffer_start_header_height,
|
||||
|
@ -1911,6 +2074,16 @@ mod tests {
|
|||
expected_block_positions
|
||||
);
|
||||
|
||||
for (_, expected_block) in
|
||||
blocks_snapshot.blocks_in_range(0..(expected_row_count as u32))
|
||||
{
|
||||
let actual_block = blocks_snapshot.block_for_id(expected_block.id());
|
||||
assert_eq!(
|
||||
actual_block.map(|block| block.id()),
|
||||
Some(expected_block.id())
|
||||
);
|
||||
}
|
||||
|
||||
for (block_row, block) in expected_block_positions {
|
||||
if let BlockType::Custom(block_id) = block.block_type() {
|
||||
assert_eq!(
|
||||
|
@ -2007,7 +2180,7 @@ mod tests {
|
|||
},
|
||||
Custom {
|
||||
disposition: BlockDisposition,
|
||||
id: BlockId,
|
||||
id: CustomBlockId,
|
||||
height: u8,
|
||||
},
|
||||
}
|
||||
|
@ -2044,15 +2217,15 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<TransformBlock> for ExpectedBlock {
|
||||
fn from(block: TransformBlock) -> Self {
|
||||
impl From<Block> for ExpectedBlock {
|
||||
fn from(block: Block) -> Self {
|
||||
match block {
|
||||
TransformBlock::Custom(block) => ExpectedBlock::Custom {
|
||||
Block::Custom(block) => ExpectedBlock::Custom {
|
||||
id: block.id,
|
||||
disposition: block.disposition,
|
||||
height: block.height,
|
||||
},
|
||||
TransformBlock::ExcerptHeader {
|
||||
Block::ExcerptHeader {
|
||||
height,
|
||||
starts_new_buffer,
|
||||
..
|
||||
|
@ -2060,7 +2233,7 @@ mod tests {
|
|||
height,
|
||||
starts_new_buffer,
|
||||
},
|
||||
TransformBlock::ExcerptFooter {
|
||||
Block::ExcerptFooter {
|
||||
height,
|
||||
disposition,
|
||||
..
|
||||
|
@ -2080,12 +2253,12 @@ mod tests {
|
|||
assets::Assets.load_test_fonts(cx);
|
||||
}
|
||||
|
||||
impl TransformBlock {
|
||||
fn as_custom(&self) -> Option<&Block> {
|
||||
impl Block {
|
||||
fn as_custom(&self) -> Option<&CustomBlock> {
|
||||
match self {
|
||||
TransformBlock::Custom(block) => Some(block),
|
||||
TransformBlock::ExcerptHeader { .. } => None,
|
||||
TransformBlock::ExcerptFooter { .. } => None,
|
||||
Block::Custom(block) => Some(block),
|
||||
Block::ExcerptHeader { .. } => None,
|
||||
Block::ExcerptFooter { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue