1use super::{
2 Highlights,
3 fold_map::Chunk,
4 wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
5};
6use crate::{EditorStyle, GutterDimensions};
7use collections::{Bound, HashMap, HashSet};
8use gpui::{AnyElement, App, EntityId, Pixels, Window};
9use language::{Patch, Point};
10use multi_buffer::{
11 Anchor, ExcerptId, ExcerptInfo, MultiBuffer, MultiBufferRow, MultiBufferSnapshot, RowInfo,
12 ToOffset, ToPoint as _,
13};
14use parking_lot::Mutex;
15use std::{
16 cell::RefCell,
17 cmp::{self, Ordering},
18 fmt::Debug,
19 ops::{Deref, DerefMut, Range, RangeBounds, RangeInclusive},
20 sync::{
21 Arc,
22 atomic::{AtomicUsize, Ordering::SeqCst},
23 },
24};
25use sum_tree::{Bias, ContextLessSummary, Dimensions, SumTree, TreeMap};
26use text::{BufferId, Edit};
27use ui::ElementId;
28
29const NEWLINES: &[u8; rope::Chunk::MASK_BITS] = &[b'\n'; _];
30const BULLETS: &[u8; rope::Chunk::MASK_BITS] = &[b'*'; _];
31
32/// Tracks custom blocks such as diagnostics that should be displayed within buffer.
33///
34/// See the [`display_map` module documentation](crate::display_map) for more information.
35pub struct BlockMap {
36 pub(super) wrap_snapshot: RefCell<WrapSnapshot>,
37 next_block_id: AtomicUsize,
38 custom_blocks: Vec<Arc<CustomBlock>>,
39 custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
40 transforms: RefCell<SumTree<Transform>>,
41 buffer_header_height: u32,
42 excerpt_header_height: u32,
43 pub(super) folded_buffers: HashSet<BufferId>,
44 buffers_with_disabled_headers: HashSet<BufferId>,
45}
46
47pub struct BlockMapReader<'a> {
48 blocks: &'a Vec<Arc<CustomBlock>>,
49 pub snapshot: BlockSnapshot,
50}
51
52pub struct BlockMapWriter<'a>(&'a mut BlockMap);
53
54#[derive(Clone)]
55pub struct BlockSnapshot {
56 pub(super) wrap_snapshot: WrapSnapshot,
57 transforms: SumTree<Transform>,
58 custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
59 pub(super) buffer_header_height: u32,
60 pub(super) excerpt_header_height: u32,
61}
62
63#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
64pub struct CustomBlockId(pub usize);
65
66impl From<CustomBlockId> for ElementId {
67 fn from(val: CustomBlockId) -> Self {
68 val.0.into()
69 }
70}
71
72#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
73pub struct BlockPoint(pub Point);
74
75#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
76pub struct BlockRow(pub(super) u32);
77
78#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
79struct WrapRow(u32);
80
81pub type RenderBlock = Arc<dyn Send + Sync + Fn(&mut BlockContext) -> AnyElement>;
82
83#[derive(Clone, Debug, Eq, PartialEq)]
84pub enum BlockPlacement<T> {
85 Above(T),
86 Below(T),
87 Near(T),
88 Replace(RangeInclusive<T>),
89}
90
91impl<T> BlockPlacement<T> {
92 pub fn start(&self) -> &T {
93 match self {
94 BlockPlacement::Above(position) => position,
95 BlockPlacement::Below(position) => position,
96 BlockPlacement::Near(position) => position,
97 BlockPlacement::Replace(range) => range.start(),
98 }
99 }
100
101 fn end(&self) -> &T {
102 match self {
103 BlockPlacement::Above(position) => position,
104 BlockPlacement::Below(position) => position,
105 BlockPlacement::Near(position) => position,
106 BlockPlacement::Replace(range) => range.end(),
107 }
108 }
109
110 pub fn as_ref(&self) -> BlockPlacement<&T> {
111 match self {
112 BlockPlacement::Above(position) => BlockPlacement::Above(position),
113 BlockPlacement::Below(position) => BlockPlacement::Below(position),
114 BlockPlacement::Near(position) => BlockPlacement::Near(position),
115 BlockPlacement::Replace(range) => BlockPlacement::Replace(range.start()..=range.end()),
116 }
117 }
118
119 pub fn map<R>(self, mut f: impl FnMut(T) -> R) -> BlockPlacement<R> {
120 match self {
121 BlockPlacement::Above(position) => BlockPlacement::Above(f(position)),
122 BlockPlacement::Below(position) => BlockPlacement::Below(f(position)),
123 BlockPlacement::Near(position) => BlockPlacement::Near(f(position)),
124 BlockPlacement::Replace(range) => {
125 let (start, end) = range.into_inner();
126 BlockPlacement::Replace(f(start)..=f(end))
127 }
128 }
129 }
130
131 fn tie_break(&self) -> u8 {
132 match self {
133 BlockPlacement::Replace(_) => 0,
134 BlockPlacement::Above(_) => 1,
135 BlockPlacement::Near(_) => 2,
136 BlockPlacement::Below(_) => 3,
137 }
138 }
139}
140
141impl BlockPlacement<Anchor> {
142 fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
143 self.start()
144 .cmp(other.start(), buffer)
145 .then_with(|| other.end().cmp(self.end(), buffer))
146 .then_with(|| self.tie_break().cmp(&other.tie_break()))
147 }
148
149 fn to_wrap_row(&self, wrap_snapshot: &WrapSnapshot) -> Option<BlockPlacement<WrapRow>> {
150 let buffer_snapshot = wrap_snapshot.buffer_snapshot();
151 match self {
152 BlockPlacement::Above(position) => {
153 let mut position = position.to_point(buffer_snapshot);
154 position.column = 0;
155 let wrap_row = WrapRow(wrap_snapshot.make_wrap_point(position, Bias::Left).row());
156 Some(BlockPlacement::Above(wrap_row))
157 }
158 BlockPlacement::Near(position) => {
159 let mut position = position.to_point(buffer_snapshot);
160 position.column = buffer_snapshot.line_len(MultiBufferRow(position.row));
161 let wrap_row = WrapRow(wrap_snapshot.make_wrap_point(position, Bias::Left).row());
162 Some(BlockPlacement::Near(wrap_row))
163 }
164 BlockPlacement::Below(position) => {
165 let mut position = position.to_point(buffer_snapshot);
166 position.column = buffer_snapshot.line_len(MultiBufferRow(position.row));
167 let wrap_row = WrapRow(wrap_snapshot.make_wrap_point(position, Bias::Left).row());
168 Some(BlockPlacement::Below(wrap_row))
169 }
170 BlockPlacement::Replace(range) => {
171 let mut start = range.start().to_point(buffer_snapshot);
172 let mut end = range.end().to_point(buffer_snapshot);
173 if start == end {
174 None
175 } else {
176 start.column = 0;
177 let start_wrap_row =
178 WrapRow(wrap_snapshot.make_wrap_point(start, Bias::Left).row());
179 end.column = buffer_snapshot.line_len(MultiBufferRow(end.row));
180 let end_wrap_row =
181 WrapRow(wrap_snapshot.make_wrap_point(end, Bias::Left).row());
182 Some(BlockPlacement::Replace(start_wrap_row..=end_wrap_row))
183 }
184 }
185 }
186 }
187}
188
189pub struct CustomBlock {
190 pub id: CustomBlockId,
191 pub placement: BlockPlacement<Anchor>,
192 pub height: Option<u32>,
193 style: BlockStyle,
194 render: Arc<Mutex<RenderBlock>>,
195 priority: usize,
196}
197
198#[derive(Clone)]
199pub struct BlockProperties<P> {
200 pub placement: BlockPlacement<P>,
201 // None if the block takes up no space
202 // (e.g. a horizontal line)
203 pub height: Option<u32>,
204 pub style: BlockStyle,
205 pub render: RenderBlock,
206 pub priority: usize,
207}
208
209impl<P: Debug> Debug for BlockProperties<P> {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 f.debug_struct("BlockProperties")
212 .field("placement", &self.placement)
213 .field("height", &self.height)
214 .field("style", &self.style)
215 .finish()
216 }
217}
218
219#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
220pub enum BlockStyle {
221 Fixed,
222 Flex,
223 Sticky,
224}
225
226#[derive(Debug, Default, Copy, Clone)]
227pub struct EditorMargins {
228 pub gutter: GutterDimensions,
229 pub right: Pixels,
230}
231
232#[derive(gpui::AppContext, gpui::VisualContext)]
233pub struct BlockContext<'a, 'b> {
234 #[window]
235 pub window: &'a mut Window,
236 #[app]
237 pub app: &'b mut App,
238 pub anchor_x: Pixels,
239 pub max_width: Pixels,
240 pub margins: &'b EditorMargins,
241 pub em_width: Pixels,
242 pub line_height: Pixels,
243 pub block_id: BlockId,
244 pub selected: bool,
245 pub editor_style: &'b EditorStyle,
246}
247
248#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
249pub enum BlockId {
250 ExcerptBoundary(ExcerptId),
251 FoldedBuffer(ExcerptId),
252 Custom(CustomBlockId),
253}
254
255impl From<BlockId> for ElementId {
256 fn from(value: BlockId) -> Self {
257 match value {
258 BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(),
259 BlockId::ExcerptBoundary(excerpt_id) => {
260 ("ExcerptBoundary", EntityId::from(excerpt_id)).into()
261 }
262 BlockId::FoldedBuffer(id) => ("FoldedBuffer", EntityId::from(id)).into(),
263 }
264 }
265}
266
267impl std::fmt::Display for BlockId {
268 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269 match self {
270 Self::Custom(id) => write!(f, "Block({id:?})"),
271 Self::ExcerptBoundary(id) => write!(f, "ExcerptHeader({id:?})"),
272 Self::FoldedBuffer(id) => write!(f, "FoldedBuffer({id:?})"),
273 }
274 }
275}
276
277#[derive(Clone, Debug)]
278struct Transform {
279 summary: TransformSummary,
280 block: Option<Block>,
281}
282
283#[derive(Clone)]
284pub enum Block {
285 Custom(Arc<CustomBlock>),
286 FoldedBuffer {
287 first_excerpt: ExcerptInfo,
288 height: u32,
289 },
290 ExcerptBoundary {
291 excerpt: ExcerptInfo,
292 height: u32,
293 },
294 BufferHeader {
295 excerpt: ExcerptInfo,
296 height: u32,
297 },
298}
299
300impl Block {
301 pub fn id(&self) -> BlockId {
302 match self {
303 Block::Custom(block) => BlockId::Custom(block.id),
304 Block::ExcerptBoundary {
305 excerpt: next_excerpt,
306 ..
307 } => BlockId::ExcerptBoundary(next_excerpt.id),
308 Block::FoldedBuffer { first_excerpt, .. } => BlockId::FoldedBuffer(first_excerpt.id),
309 Block::BufferHeader {
310 excerpt: next_excerpt,
311 ..
312 } => BlockId::ExcerptBoundary(next_excerpt.id),
313 }
314 }
315
316 pub fn has_height(&self) -> bool {
317 match self {
318 Block::Custom(block) => block.height.is_some(),
319 Block::ExcerptBoundary { .. }
320 | Block::FoldedBuffer { .. }
321 | Block::BufferHeader { .. } => true,
322 }
323 }
324
325 pub fn height(&self) -> u32 {
326 match self {
327 Block::Custom(block) => block.height.unwrap_or(0),
328 Block::ExcerptBoundary { height, .. }
329 | Block::FoldedBuffer { height, .. }
330 | Block::BufferHeader { height, .. } => *height,
331 }
332 }
333
334 pub fn style(&self) -> BlockStyle {
335 match self {
336 Block::Custom(block) => block.style,
337 Block::ExcerptBoundary { .. }
338 | Block::FoldedBuffer { .. }
339 | Block::BufferHeader { .. } => BlockStyle::Sticky,
340 }
341 }
342
343 fn place_above(&self) -> bool {
344 match self {
345 Block::Custom(block) => matches!(block.placement, BlockPlacement::Above(_)),
346 Block::FoldedBuffer { .. } => false,
347 Block::ExcerptBoundary { .. } => true,
348 Block::BufferHeader { .. } => true,
349 }
350 }
351
352 pub fn place_near(&self) -> bool {
353 match self {
354 Block::Custom(block) => matches!(block.placement, BlockPlacement::Near(_)),
355 Block::FoldedBuffer { .. } => false,
356 Block::ExcerptBoundary { .. } => false,
357 Block::BufferHeader { .. } => false,
358 }
359 }
360
361 fn place_below(&self) -> bool {
362 match self {
363 Block::Custom(block) => matches!(
364 block.placement,
365 BlockPlacement::Below(_) | BlockPlacement::Near(_)
366 ),
367 Block::FoldedBuffer { .. } => false,
368 Block::ExcerptBoundary { .. } => false,
369 Block::BufferHeader { .. } => false,
370 }
371 }
372
373 fn is_replacement(&self) -> bool {
374 match self {
375 Block::Custom(block) => matches!(block.placement, BlockPlacement::Replace(_)),
376 Block::FoldedBuffer { .. } => true,
377 Block::ExcerptBoundary { .. } => false,
378 Block::BufferHeader { .. } => false,
379 }
380 }
381
382 fn is_header(&self) -> bool {
383 match self {
384 Block::Custom(_) => false,
385 Block::FoldedBuffer { .. } => true,
386 Block::ExcerptBoundary { .. } => true,
387 Block::BufferHeader { .. } => true,
388 }
389 }
390
391 pub fn is_buffer_header(&self) -> bool {
392 match self {
393 Block::Custom(_) => false,
394 Block::FoldedBuffer { .. } => true,
395 Block::ExcerptBoundary { .. } => false,
396 Block::BufferHeader { .. } => true,
397 }
398 }
399}
400
401impl Debug for Block {
402 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
403 match self {
404 Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(),
405 Self::FoldedBuffer {
406 first_excerpt,
407 height,
408 } => f
409 .debug_struct("FoldedBuffer")
410 .field("first_excerpt", &first_excerpt)
411 .field("height", height)
412 .finish(),
413 Self::ExcerptBoundary { excerpt, height } => f
414 .debug_struct("ExcerptBoundary")
415 .field("excerpt", excerpt)
416 .field("height", height)
417 .finish(),
418 Self::BufferHeader { excerpt, height } => f
419 .debug_struct("BufferHeader")
420 .field("excerpt", excerpt)
421 .field("height", height)
422 .finish(),
423 }
424 }
425}
426
427#[derive(Clone, Debug, Default)]
428struct TransformSummary {
429 input_rows: u32,
430 output_rows: u32,
431 longest_row: u32,
432 longest_row_chars: u32,
433}
434
435pub struct BlockChunks<'a> {
436 transforms: sum_tree::Cursor<'a, 'static, Transform, Dimensions<BlockRow, WrapRow>>,
437 input_chunks: wrap_map::WrapChunks<'a>,
438 input_chunk: Chunk<'a>,
439 output_row: u32,
440 max_output_row: u32,
441 masked: bool,
442}
443
444#[derive(Clone)]
445pub struct BlockRows<'a> {
446 transforms: sum_tree::Cursor<'a, 'static, Transform, Dimensions<BlockRow, WrapRow>>,
447 input_rows: wrap_map::WrapRows<'a>,
448 output_row: BlockRow,
449 started: bool,
450}
451
452impl BlockMap {
453 pub fn new(
454 wrap_snapshot: WrapSnapshot,
455 buffer_header_height: u32,
456 excerpt_header_height: u32,
457 ) -> Self {
458 let row_count = wrap_snapshot.max_point().row() + 1;
459 let mut transforms = SumTree::default();
460 push_isomorphic(&mut transforms, row_count, &wrap_snapshot);
461 let map = Self {
462 next_block_id: AtomicUsize::new(0),
463 custom_blocks: Vec::new(),
464 custom_blocks_by_id: TreeMap::default(),
465 folded_buffers: HashSet::default(),
466 buffers_with_disabled_headers: HashSet::default(),
467 transforms: RefCell::new(transforms),
468 wrap_snapshot: RefCell::new(wrap_snapshot.clone()),
469 buffer_header_height,
470 excerpt_header_height,
471 };
472 map.sync(
473 &wrap_snapshot,
474 Patch::new(vec![Edit {
475 old: 0..row_count,
476 new: 0..row_count,
477 }]),
478 );
479 map
480 }
481
482 pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapReader<'_> {
483 self.sync(&wrap_snapshot, edits);
484 *self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone();
485 BlockMapReader {
486 blocks: &self.custom_blocks,
487 snapshot: BlockSnapshot {
488 wrap_snapshot,
489 transforms: self.transforms.borrow().clone(),
490 custom_blocks_by_id: self.custom_blocks_by_id.clone(),
491 buffer_header_height: self.buffer_header_height,
492 excerpt_header_height: self.excerpt_header_height,
493 },
494 }
495 }
496
497 pub fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapWriter<'_> {
498 self.sync(&wrap_snapshot, edits);
499 *self.wrap_snapshot.borrow_mut() = wrap_snapshot;
500 BlockMapWriter(self)
501 }
502
503 fn sync(&self, wrap_snapshot: &WrapSnapshot, mut edits: Patch<u32>) {
504 let buffer = wrap_snapshot.buffer_snapshot();
505
506 // Handle changing the last excerpt if it is empty.
507 if buffer.trailing_excerpt_update_count()
508 != self
509 .wrap_snapshot
510 .borrow()
511 .buffer_snapshot()
512 .trailing_excerpt_update_count()
513 {
514 let max_point = wrap_snapshot.max_point();
515 let edit_start = wrap_snapshot.prev_row_boundary(max_point);
516 let edit_end = max_point.row() + 1;
517 edits = edits.compose([WrapEdit {
518 old: edit_start..edit_end,
519 new: edit_start..edit_end,
520 }]);
521 }
522
523 let edits = edits.into_inner();
524 if edits.is_empty() {
525 return;
526 }
527
528 let mut transforms = self.transforms.borrow_mut();
529 let mut new_transforms = SumTree::default();
530 let mut cursor = transforms.cursor::<WrapRow>(());
531 let mut last_block_ix = 0;
532 let mut blocks_in_edit = Vec::new();
533 let mut edits = edits.into_iter().peekable();
534
535 while let Some(edit) = edits.next() {
536 let mut old_start = WrapRow(edit.old.start);
537 let mut new_start = WrapRow(edit.new.start);
538
539 // Only preserve transforms that:
540 // * Strictly precedes this edit
541 // * Isomorphic transforms that end *at* the start of the edit
542 // * Below blocks that end at the start of the edit
543 // However, if we hit a replace block that ends at the start of the edit we want to reconstruct it.
544 new_transforms.append(cursor.slice(&old_start, Bias::Left), ());
545 if let Some(transform) = cursor.item()
546 && transform.summary.input_rows > 0
547 && cursor.end() == old_start
548 && transform.block.as_ref().is_none_or(|b| !b.is_replacement())
549 {
550 // Preserve the transform (push and next)
551 new_transforms.push(transform.clone(), ());
552 cursor.next();
553
554 // Preserve below blocks at end of edit
555 while let Some(transform) = cursor.item() {
556 if transform.block.as_ref().is_some_and(|b| b.place_below()) {
557 new_transforms.push(transform.clone(), ());
558 cursor.next();
559 } else {
560 break;
561 }
562 }
563 }
564
565 // Ensure the edit starts at a transform boundary.
566 // If the edit starts within an isomorphic transform, preserve its prefix
567 // If the edit lands within a replacement block, expand the edit to include the start of the replaced input range
568 let transform = cursor.item().unwrap();
569 let transform_rows_before_edit = old_start.0 - cursor.start().0;
570 if transform_rows_before_edit > 0 {
571 if transform.block.is_none() {
572 // Preserve any portion of the old isomorphic transform that precedes this edit.
573 push_isomorphic(
574 &mut new_transforms,
575 transform_rows_before_edit,
576 wrap_snapshot,
577 );
578 } else {
579 // We landed within a block that replaces some lines, so we
580 // extend the edit to start at the beginning of the
581 // replacement.
582 debug_assert!(transform.summary.input_rows > 0);
583 old_start.0 -= transform_rows_before_edit;
584 new_start.0 -= transform_rows_before_edit;
585 }
586 }
587
588 // Decide where the edit ends
589 // * It should end at a transform boundary
590 // * Coalesce edits that intersect the same transform
591 let mut old_end = WrapRow(edit.old.end);
592 let mut new_end = WrapRow(edit.new.end);
593 loop {
594 // Seek to the transform starting at or after the end of the edit
595 cursor.seek(&old_end, Bias::Left);
596 cursor.next();
597
598 // Extend edit to the end of the discarded transform so it is reconstructed in full
599 let transform_rows_after_edit = cursor.start().0 - old_end.0;
600 old_end.0 += transform_rows_after_edit;
601 new_end.0 += transform_rows_after_edit;
602
603 // Combine this edit with any subsequent edits that intersect the same transform.
604 while let Some(next_edit) = edits.peek() {
605 if next_edit.old.start <= cursor.start().0 {
606 old_end = WrapRow(next_edit.old.end);
607 new_end = WrapRow(next_edit.new.end);
608 cursor.seek(&old_end, Bias::Left);
609 cursor.next();
610 edits.next();
611 } else {
612 break;
613 }
614 }
615
616 if *cursor.start() == old_end {
617 break;
618 }
619 }
620
621 // Discard below blocks at the end of the edit. They'll be reconstructed.
622 while let Some(transform) = cursor.item() {
623 if transform.block.as_ref().is_some_and(|b| b.place_below()) {
624 cursor.next();
625 } else {
626 break;
627 }
628 }
629
630 // Find the blocks within this edited region.
631 let new_buffer_start =
632 wrap_snapshot.to_point(WrapPoint::new(new_start.0, 0), Bias::Left);
633 let start_bound = Bound::Included(new_buffer_start);
634 let start_block_ix =
635 match self.custom_blocks[last_block_ix..].binary_search_by(|probe| {
636 probe
637 .start()
638 .to_point(buffer)
639 .cmp(&new_buffer_start)
640 // Move left until we find the index of the first block starting within this edit
641 .then(Ordering::Greater)
642 }) {
643 Ok(ix) | Err(ix) => last_block_ix + ix,
644 };
645
646 let end_bound;
647 let end_block_ix = if new_end.0 > wrap_snapshot.max_point().row() {
648 end_bound = Bound::Unbounded;
649 self.custom_blocks.len()
650 } else {
651 let new_buffer_end =
652 wrap_snapshot.to_point(WrapPoint::new(new_end.0, 0), Bias::Left);
653 end_bound = Bound::Excluded(new_buffer_end);
654 match self.custom_blocks[start_block_ix..].binary_search_by(|probe| {
655 probe
656 .start()
657 .to_point(buffer)
658 .cmp(&new_buffer_end)
659 .then(Ordering::Greater)
660 }) {
661 Ok(ix) | Err(ix) => start_block_ix + ix,
662 }
663 };
664 last_block_ix = end_block_ix;
665
666 debug_assert!(blocks_in_edit.is_empty());
667
668 blocks_in_edit.extend(
669 self.custom_blocks[start_block_ix..end_block_ix]
670 .iter()
671 .filter_map(|block| {
672 let placement = block.placement.to_wrap_row(wrap_snapshot)?;
673 if let BlockPlacement::Above(row) = placement
674 && row < new_start
675 {
676 return None;
677 }
678 Some((placement, Block::Custom(block.clone())))
679 }),
680 );
681
682 blocks_in_edit.extend(self.header_and_footer_blocks(
683 buffer,
684 (start_bound, end_bound),
685 wrap_snapshot,
686 ));
687
688 BlockMap::sort_blocks(&mut blocks_in_edit);
689
690 // For each of these blocks, insert a new isomorphic transform preceding the block,
691 // and then insert the block itself.
692 let mut just_processed_folded_buffer = false;
693 for (block_placement, block) in blocks_in_edit.drain(..) {
694 let mut summary = TransformSummary {
695 input_rows: 0,
696 output_rows: block.height(),
697 longest_row: 0,
698 longest_row_chars: 0,
699 };
700
701 let rows_before_block;
702 match block_placement {
703 BlockPlacement::Above(position) => {
704 rows_before_block = position.0 - new_transforms.summary().input_rows;
705 just_processed_folded_buffer = false;
706 }
707 BlockPlacement::Near(position) | BlockPlacement::Below(position) => {
708 if just_processed_folded_buffer {
709 continue;
710 }
711 if position.0 + 1 < new_transforms.summary().input_rows {
712 continue;
713 }
714 rows_before_block = (position.0 + 1) - new_transforms.summary().input_rows;
715 }
716 BlockPlacement::Replace(range) => {
717 rows_before_block = range.start().0 - new_transforms.summary().input_rows;
718 summary.input_rows = range.end().0 - range.start().0 + 1;
719 just_processed_folded_buffer = matches!(block, Block::FoldedBuffer { .. });
720 }
721 }
722
723 push_isomorphic(&mut new_transforms, rows_before_block, wrap_snapshot);
724 new_transforms.push(
725 Transform {
726 summary,
727 block: Some(block),
728 },
729 (),
730 );
731 }
732
733 // Insert an isomorphic transform after the final block.
734 let rows_after_last_block = new_end
735 .0
736 .saturating_sub(new_transforms.summary().input_rows);
737 push_isomorphic(&mut new_transforms, rows_after_last_block, wrap_snapshot);
738 }
739
740 new_transforms.append(cursor.suffix(), ());
741 debug_assert_eq!(
742 new_transforms.summary().input_rows,
743 wrap_snapshot.max_point().row() + 1
744 );
745
746 drop(cursor);
747 *transforms = new_transforms;
748 }
749
750 pub fn replace_blocks(&mut self, mut renderers: HashMap<CustomBlockId, RenderBlock>) {
751 for block in &mut self.custom_blocks {
752 if let Some(render) = renderers.remove(&block.id) {
753 *block.render.lock() = render;
754 }
755 }
756 }
757
758 fn header_and_footer_blocks<'a, R, T>(
759 &'a self,
760 buffer: &'a multi_buffer::MultiBufferSnapshot,
761 range: R,
762 wrap_snapshot: &'a WrapSnapshot,
763 ) -> impl Iterator<Item = (BlockPlacement<WrapRow>, Block)> + 'a
764 where
765 R: RangeBounds<T>,
766 T: multi_buffer::ToOffset,
767 {
768 let mut boundaries = buffer.excerpt_boundaries_in_range(range).peekable();
769
770 std::iter::from_fn(move || {
771 loop {
772 let excerpt_boundary = boundaries.next()?;
773 let wrap_row = wrap_snapshot
774 .make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
775 .row();
776
777 let new_buffer_id = match (&excerpt_boundary.prev, &excerpt_boundary.next) {
778 (None, next) => Some(next.buffer_id),
779 (Some(prev), next) => {
780 if prev.buffer_id != next.buffer_id {
781 Some(next.buffer_id)
782 } else {
783 None
784 }
785 }
786 };
787
788 let mut height = 0;
789
790 if let Some(new_buffer_id) = new_buffer_id {
791 let first_excerpt = excerpt_boundary.next.clone();
792 if self.buffers_with_disabled_headers.contains(&new_buffer_id) {
793 continue;
794 }
795 if self.folded_buffers.contains(&new_buffer_id) && buffer.show_headers() {
796 let mut last_excerpt_end_row = first_excerpt.end_row;
797
798 while let Some(next_boundary) = boundaries.peek() {
799 if next_boundary.next.buffer_id == new_buffer_id {
800 last_excerpt_end_row = next_boundary.next.end_row;
801 } else {
802 break;
803 }
804
805 boundaries.next();
806 }
807
808 let wrap_end_row = wrap_snapshot
809 .make_wrap_point(
810 Point::new(
811 last_excerpt_end_row.0,
812 buffer.line_len(last_excerpt_end_row),
813 ),
814 Bias::Right,
815 )
816 .row();
817
818 return Some((
819 BlockPlacement::Replace(WrapRow(wrap_row)..=WrapRow(wrap_end_row)),
820 Block::FoldedBuffer {
821 height: height + self.buffer_header_height,
822 first_excerpt,
823 },
824 ));
825 }
826 }
827
828 let starts_new_buffer = new_buffer_id.is_some();
829 let block = if starts_new_buffer && buffer.show_headers() {
830 height += self.buffer_header_height;
831 Block::BufferHeader {
832 excerpt: excerpt_boundary.next,
833 height,
834 }
835 } else if excerpt_boundary.prev.is_some() {
836 height += self.excerpt_header_height;
837 Block::ExcerptBoundary {
838 excerpt: excerpt_boundary.next,
839 height,
840 }
841 } else {
842 continue;
843 };
844
845 return Some((BlockPlacement::Above(WrapRow(wrap_row)), block));
846 }
847 })
848 }
849
850 fn sort_blocks(blocks: &mut Vec<(BlockPlacement<WrapRow>, Block)>) {
851 blocks.sort_unstable_by(|(placement_a, block_a), (placement_b, block_b)| {
852 placement_a
853 .start()
854 .cmp(placement_b.start())
855 .then_with(|| placement_b.end().cmp(placement_a.end()))
856 .then_with(|| placement_a.tie_break().cmp(&placement_b.tie_break()))
857 .then_with(|| {
858 if block_a.is_header() {
859 Ordering::Less
860 } else if block_b.is_header() {
861 Ordering::Greater
862 } else {
863 Ordering::Equal
864 }
865 })
866 .then_with(|| match (block_a, block_b) {
867 (
868 Block::ExcerptBoundary {
869 excerpt: excerpt_a, ..
870 }
871 | Block::BufferHeader {
872 excerpt: excerpt_a, ..
873 },
874 Block::ExcerptBoundary {
875 excerpt: excerpt_b, ..
876 }
877 | Block::BufferHeader {
878 excerpt: excerpt_b, ..
879 },
880 ) => Some(excerpt_a.id).cmp(&Some(excerpt_b.id)),
881 (
882 Block::ExcerptBoundary { .. } | Block::BufferHeader { .. },
883 Block::Custom(_),
884 ) => Ordering::Less,
885 (
886 Block::Custom(_),
887 Block::ExcerptBoundary { .. } | Block::BufferHeader { .. },
888 ) => Ordering::Greater,
889 (Block::Custom(block_a), Block::Custom(block_b)) => block_a
890 .priority
891 .cmp(&block_b.priority)
892 .then_with(|| block_a.id.cmp(&block_b.id)),
893 _ => {
894 unreachable!()
895 }
896 })
897 });
898 blocks.dedup_by(|right, left| match (left.0.clone(), right.0.clone()) {
899 (BlockPlacement::Replace(range), BlockPlacement::Above(row))
900 | (BlockPlacement::Replace(range), BlockPlacement::Below(row)) => range.contains(&row),
901 (BlockPlacement::Replace(range_a), BlockPlacement::Replace(range_b)) => {
902 if range_a.end() >= range_b.start() && range_a.start() <= range_b.end() {
903 left.0 = BlockPlacement::Replace(
904 *range_a.start()..=*range_a.end().max(range_b.end()),
905 );
906 true
907 } else {
908 false
909 }
910 }
911 _ => false,
912 });
913 }
914}
915
916fn push_isomorphic(tree: &mut SumTree<Transform>, rows: u32, wrap_snapshot: &WrapSnapshot) {
917 if rows == 0 {
918 return;
919 }
920
921 let wrap_row_start = tree.summary().input_rows;
922 let wrap_row_end = wrap_row_start + rows;
923 let wrap_summary = wrap_snapshot.text_summary_for_range(wrap_row_start..wrap_row_end);
924 let summary = TransformSummary {
925 input_rows: rows,
926 output_rows: rows,
927 longest_row: wrap_summary.longest_row,
928 longest_row_chars: wrap_summary.longest_row_chars,
929 };
930 let mut merged = false;
931 tree.update_last(
932 |last_transform| {
933 if last_transform.block.is_none() {
934 last_transform.summary.add_summary(&summary);
935 merged = true;
936 }
937 },
938 (),
939 );
940 if !merged {
941 tree.push(
942 Transform {
943 summary,
944 block: None,
945 },
946 (),
947 );
948 }
949}
950
951impl BlockPoint {
952 pub fn new(row: u32, column: u32) -> Self {
953 Self(Point::new(row, column))
954 }
955}
956
957impl Deref for BlockPoint {
958 type Target = Point;
959
960 fn deref(&self) -> &Self::Target {
961 &self.0
962 }
963}
964
965impl std::ops::DerefMut for BlockPoint {
966 fn deref_mut(&mut self) -> &mut Self::Target {
967 &mut self.0
968 }
969}
970
971impl Deref for BlockMapReader<'_> {
972 type Target = BlockSnapshot;
973
974 fn deref(&self) -> &Self::Target {
975 &self.snapshot
976 }
977}
978
979impl DerefMut for BlockMapReader<'_> {
980 fn deref_mut(&mut self) -> &mut Self::Target {
981 &mut self.snapshot
982 }
983}
984
985impl BlockMapReader<'_> {
986 pub fn row_for_block(&self, block_id: CustomBlockId) -> Option<BlockRow> {
987 let block = self.blocks.iter().find(|block| block.id == block_id)?;
988 let buffer_row = block
989 .start()
990 .to_point(self.wrap_snapshot.buffer_snapshot())
991 .row;
992 let wrap_row = self
993 .wrap_snapshot
994 .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
995 .row();
996 let start_wrap_row = WrapRow(
997 self.wrap_snapshot
998 .prev_row_boundary(WrapPoint::new(wrap_row, 0)),
999 );
1000 let end_wrap_row = WrapRow(
1001 self.wrap_snapshot
1002 .next_row_boundary(WrapPoint::new(wrap_row, 0))
1003 .unwrap_or(self.wrap_snapshot.max_point().row() + 1),
1004 );
1005
1006 let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(());
1007 cursor.seek(&start_wrap_row, Bias::Left);
1008 while let Some(transform) = cursor.item() {
1009 if cursor.start().0 > end_wrap_row {
1010 break;
1011 }
1012
1013 if let Some(BlockId::Custom(id)) = transform.block.as_ref().map(|block| block.id())
1014 && id == block_id
1015 {
1016 return Some(cursor.start().1);
1017 }
1018 cursor.next();
1019 }
1020
1021 None
1022 }
1023}
1024
1025impl BlockMapWriter<'_> {
1026 pub fn insert(
1027 &mut self,
1028 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
1029 ) -> Vec<CustomBlockId> {
1030 let blocks = blocks.into_iter();
1031 let mut ids = Vec::with_capacity(blocks.size_hint().1.unwrap_or(0));
1032 let mut edits = Patch::default();
1033 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
1034 let buffer = wrap_snapshot.buffer_snapshot();
1035
1036 let mut previous_wrap_row_range: Option<Range<u32>> = None;
1037 for block in blocks {
1038 if let BlockPlacement::Replace(_) = &block.placement {
1039 debug_assert!(block.height.unwrap() > 0);
1040 }
1041
1042 let id = CustomBlockId(self.0.next_block_id.fetch_add(1, SeqCst));
1043 ids.push(id);
1044
1045 let start = block.placement.start().to_point(buffer);
1046 let end = block.placement.end().to_point(buffer);
1047 let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
1048 let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
1049
1050 let (start_row, end_row) = {
1051 previous_wrap_row_range.take_if(|range| {
1052 !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
1053 });
1054 let range = previous_wrap_row_range.get_or_insert_with(|| {
1055 let start_row =
1056 wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1057 let end_row = wrap_snapshot
1058 .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1059 .unwrap_or(wrap_snapshot.max_point().row() + 1);
1060 start_row..end_row
1061 });
1062 (range.start, range.end)
1063 };
1064 let block_ix = match self
1065 .0
1066 .custom_blocks
1067 .binary_search_by(|probe| probe.placement.cmp(&block.placement, buffer))
1068 {
1069 Ok(ix) | Err(ix) => ix,
1070 };
1071 let new_block = Arc::new(CustomBlock {
1072 id,
1073 placement: block.placement,
1074 height: block.height,
1075 render: Arc::new(Mutex::new(block.render)),
1076 style: block.style,
1077 priority: block.priority,
1078 });
1079 self.0.custom_blocks.insert(block_ix, new_block.clone());
1080 self.0.custom_blocks_by_id.insert(id, new_block);
1081
1082 edits = edits.compose([Edit {
1083 old: start_row..end_row,
1084 new: start_row..end_row,
1085 }]);
1086 }
1087
1088 self.0.sync(wrap_snapshot, edits);
1089 ids
1090 }
1091
1092 pub fn resize(&mut self, mut heights: HashMap<CustomBlockId, u32>) {
1093 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
1094 let buffer = wrap_snapshot.buffer_snapshot();
1095 let mut edits = Patch::default();
1096 let mut last_block_buffer_row = None;
1097
1098 for block in &mut self.0.custom_blocks {
1099 if let Some(new_height) = heights.remove(&block.id) {
1100 if let BlockPlacement::Replace(_) = &block.placement {
1101 debug_assert!(new_height > 0);
1102 }
1103
1104 if block.height != Some(new_height) {
1105 let new_block = CustomBlock {
1106 id: block.id,
1107 placement: block.placement.clone(),
1108 height: Some(new_height),
1109 style: block.style,
1110 render: block.render.clone(),
1111 priority: block.priority,
1112 };
1113 let new_block = Arc::new(new_block);
1114 *block = new_block.clone();
1115 self.0.custom_blocks_by_id.insert(block.id, new_block);
1116
1117 let start_row = block.placement.start().to_point(buffer).row;
1118 let end_row = block.placement.end().to_point(buffer).row;
1119 if last_block_buffer_row != Some(end_row) {
1120 last_block_buffer_row = Some(end_row);
1121 let start_wrap_row = wrap_snapshot
1122 .make_wrap_point(Point::new(start_row, 0), Bias::Left)
1123 .row();
1124 let end_wrap_row = wrap_snapshot
1125 .make_wrap_point(Point::new(end_row, 0), Bias::Left)
1126 .row();
1127 let start =
1128 wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1129 let end = wrap_snapshot
1130 .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1131 .unwrap_or(wrap_snapshot.max_point().row() + 1);
1132 edits.push(Edit {
1133 old: start..end,
1134 new: start..end,
1135 })
1136 }
1137 }
1138 }
1139 }
1140
1141 self.0.sync(wrap_snapshot, edits);
1142 }
1143
1144 pub fn remove(&mut self, block_ids: HashSet<CustomBlockId>) {
1145 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
1146 let buffer = wrap_snapshot.buffer_snapshot();
1147 let mut edits = Patch::default();
1148 let mut last_block_buffer_row = None;
1149 let mut previous_wrap_row_range: Option<Range<u32>> = None;
1150 self.0.custom_blocks.retain(|block| {
1151 if block_ids.contains(&block.id) {
1152 let start = block.placement.start().to_point(buffer);
1153 let end = block.placement.end().to_point(buffer);
1154 if last_block_buffer_row != Some(end.row) {
1155 last_block_buffer_row = Some(end.row);
1156 let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
1157 let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
1158 let (start_row, end_row) = {
1159 previous_wrap_row_range.take_if(|range| {
1160 !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
1161 });
1162 let range = previous_wrap_row_range.get_or_insert_with(|| {
1163 let start_row =
1164 wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1165 let end_row = wrap_snapshot
1166 .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1167 .unwrap_or(wrap_snapshot.max_point().row() + 1);
1168 start_row..end_row
1169 });
1170 (range.start, range.end)
1171 };
1172
1173 edits.push(Edit {
1174 old: start_row..end_row,
1175 new: start_row..end_row,
1176 })
1177 }
1178 false
1179 } else {
1180 true
1181 }
1182 });
1183 self.0
1184 .custom_blocks_by_id
1185 .retain(|id, _| !block_ids.contains(id));
1186 self.0.sync(wrap_snapshot, edits);
1187 }
1188
1189 pub fn remove_intersecting_replace_blocks(
1190 &mut self,
1191 ranges: impl IntoIterator<Item = Range<usize>>,
1192 inclusive: bool,
1193 ) {
1194 let wrap_snapshot = self.0.wrap_snapshot.borrow();
1195 let mut blocks_to_remove = HashSet::default();
1196 for range in ranges {
1197 for block in self.blocks_intersecting_buffer_range(range, inclusive) {
1198 if matches!(block.placement, BlockPlacement::Replace(_)) {
1199 blocks_to_remove.insert(block.id);
1200 }
1201 }
1202 }
1203 drop(wrap_snapshot);
1204 self.remove(blocks_to_remove);
1205 }
1206
1207 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId) {
1208 self.0.buffers_with_disabled_headers.insert(buffer_id);
1209 }
1210
1211 pub fn fold_buffers(
1212 &mut self,
1213 buffer_ids: impl IntoIterator<Item = BufferId>,
1214 multi_buffer: &MultiBuffer,
1215 cx: &App,
1216 ) {
1217 self.fold_or_unfold_buffers(true, buffer_ids, multi_buffer, cx);
1218 }
1219
1220 pub fn unfold_buffers(
1221 &mut self,
1222 buffer_ids: impl IntoIterator<Item = BufferId>,
1223 multi_buffer: &MultiBuffer,
1224 cx: &App,
1225 ) {
1226 self.fold_or_unfold_buffers(false, buffer_ids, multi_buffer, cx);
1227 }
1228
1229 fn fold_or_unfold_buffers(
1230 &mut self,
1231 fold: bool,
1232 buffer_ids: impl IntoIterator<Item = BufferId>,
1233 multi_buffer: &MultiBuffer,
1234 cx: &App,
1235 ) {
1236 let mut ranges = Vec::new();
1237 for buffer_id in buffer_ids {
1238 if fold {
1239 self.0.folded_buffers.insert(buffer_id);
1240 } else {
1241 self.0.folded_buffers.remove(&buffer_id);
1242 }
1243 ranges.extend(multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx));
1244 }
1245 ranges.sort_unstable_by_key(|range| range.start);
1246
1247 let mut edits = Patch::default();
1248 let wrap_snapshot = self.0.wrap_snapshot.borrow().clone();
1249 for range in ranges {
1250 let last_edit_row = cmp::min(
1251 wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + 1,
1252 wrap_snapshot.max_point().row(),
1253 ) + 1;
1254 let range = wrap_snapshot.make_wrap_point(range.start, Bias::Left).row()..last_edit_row;
1255 edits.push(Edit {
1256 old: range.clone(),
1257 new: range,
1258 });
1259 }
1260
1261 self.0.sync(&wrap_snapshot, edits);
1262 }
1263
1264 fn blocks_intersecting_buffer_range(
1265 &self,
1266 range: Range<usize>,
1267 inclusive: bool,
1268 ) -> &[Arc<CustomBlock>] {
1269 if range.is_empty() && !inclusive {
1270 return &[];
1271 }
1272 let wrap_snapshot = self.0.wrap_snapshot.borrow();
1273 let buffer = wrap_snapshot.buffer_snapshot();
1274
1275 let start_block_ix = match self.0.custom_blocks.binary_search_by(|block| {
1276 let block_end = block.end().to_offset(buffer);
1277 block_end.cmp(&range.start).then(Ordering::Greater)
1278 }) {
1279 Ok(ix) | Err(ix) => ix,
1280 };
1281 let end_block_ix = match self.0.custom_blocks[start_block_ix..].binary_search_by(|block| {
1282 let block_start = block.start().to_offset(buffer);
1283 block_start.cmp(&range.end).then(if inclusive {
1284 Ordering::Less
1285 } else {
1286 Ordering::Greater
1287 })
1288 }) {
1289 Ok(ix) | Err(ix) => ix,
1290 };
1291
1292 &self.0.custom_blocks[start_block_ix..][..end_block_ix]
1293 }
1294}
1295
1296impl BlockSnapshot {
1297 #[cfg(test)]
1298 pub fn text(&self) -> String {
1299 self.chunks(
1300 0..self.transforms.summary().output_rows,
1301 false,
1302 false,
1303 Highlights::default(),
1304 )
1305 .map(|chunk| chunk.text)
1306 .collect()
1307 }
1308
1309 pub(crate) fn chunks<'a>(
1310 &'a self,
1311 rows: Range<u32>,
1312 language_aware: bool,
1313 masked: bool,
1314 highlights: Highlights<'a>,
1315 ) -> BlockChunks<'a> {
1316 let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
1317
1318 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1319 cursor.seek(&BlockRow(rows.start), Bias::Right);
1320 let transform_output_start = cursor.start().0.0;
1321 let transform_input_start = cursor.start().1.0;
1322
1323 let mut input_start = transform_input_start;
1324 let mut input_end = transform_input_start;
1325 if let Some(transform) = cursor.item()
1326 && transform.block.is_none()
1327 {
1328 input_start += rows.start - transform_output_start;
1329 input_end += cmp::min(
1330 rows.end - transform_output_start,
1331 transform.summary.input_rows,
1332 );
1333 }
1334
1335 BlockChunks {
1336 input_chunks: self.wrap_snapshot.chunks(
1337 input_start..input_end,
1338 language_aware,
1339 highlights,
1340 ),
1341 input_chunk: Default::default(),
1342 transforms: cursor,
1343 output_row: rows.start,
1344 max_output_row,
1345 masked,
1346 }
1347 }
1348
1349 pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows<'_> {
1350 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1351 cursor.seek(&start_row, Bias::Right);
1352 let Dimensions(output_start, input_start, _) = cursor.start();
1353 let overshoot = if cursor
1354 .item()
1355 .is_some_and(|transform| transform.block.is_none())
1356 {
1357 start_row.0 - output_start.0
1358 } else {
1359 0
1360 };
1361 let input_start_row = input_start.0 + overshoot;
1362 BlockRows {
1363 transforms: cursor,
1364 input_rows: self.wrap_snapshot.row_infos(input_start_row),
1365 output_row: start_row,
1366 started: false,
1367 }
1368 }
1369
1370 pub fn blocks_in_range(&self, rows: Range<u32>) -> impl Iterator<Item = (u32, &Block)> {
1371 let mut cursor = self.transforms.cursor::<BlockRow>(());
1372 cursor.seek(&BlockRow(rows.start), Bias::Left);
1373 while cursor.start().0 < rows.start && cursor.end().0 <= rows.start {
1374 cursor.next();
1375 }
1376
1377 std::iter::from_fn(move || {
1378 while let Some(transform) = cursor.item() {
1379 let start_row = cursor.start().0;
1380 if start_row > rows.end
1381 || (start_row == rows.end
1382 && transform
1383 .block
1384 .as_ref()
1385 .is_some_and(|block| block.height() > 0))
1386 {
1387 break;
1388 }
1389 if let Some(block) = &transform.block {
1390 cursor.next();
1391 return Some((start_row, block));
1392 } else {
1393 cursor.next();
1394 }
1395 }
1396 None
1397 })
1398 }
1399
1400 pub(crate) fn sticky_header_excerpt(&self, position: f64) -> Option<StickyHeaderExcerpt<'_>> {
1401 let top_row = position as u32;
1402 let mut cursor = self.transforms.cursor::<BlockRow>(());
1403 cursor.seek(&BlockRow(top_row), Bias::Right);
1404
1405 while let Some(transform) = cursor.item() {
1406 match &transform.block {
1407 Some(
1408 Block::ExcerptBoundary { excerpt, .. } | Block::BufferHeader { excerpt, .. },
1409 ) => {
1410 return Some(StickyHeaderExcerpt { excerpt });
1411 }
1412 Some(block) if block.is_buffer_header() => return None,
1413 _ => {
1414 cursor.prev();
1415 continue;
1416 }
1417 }
1418 }
1419
1420 None
1421 }
1422
1423 pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
1424 let buffer = self.wrap_snapshot.buffer_snapshot();
1425 let wrap_point = match block_id {
1426 BlockId::Custom(custom_block_id) => {
1427 let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
1428 return Some(Block::Custom(custom_block.clone()));
1429 }
1430 BlockId::ExcerptBoundary(next_excerpt_id) => {
1431 let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
1432 self.wrap_snapshot
1433 .make_wrap_point(excerpt_range.start, Bias::Left)
1434 }
1435 BlockId::FoldedBuffer(excerpt_id) => self
1436 .wrap_snapshot
1437 .make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left),
1438 };
1439 let wrap_row = WrapRow(wrap_point.row());
1440
1441 let mut cursor = self.transforms.cursor::<WrapRow>(());
1442 cursor.seek(&wrap_row, Bias::Left);
1443
1444 while let Some(transform) = cursor.item() {
1445 if let Some(block) = transform.block.as_ref() {
1446 if block.id() == block_id {
1447 return Some(block.clone());
1448 }
1449 } else if *cursor.start() > wrap_row {
1450 break;
1451 }
1452
1453 cursor.next();
1454 }
1455
1456 None
1457 }
1458
1459 pub fn max_point(&self) -> BlockPoint {
1460 let row = self.transforms.summary().output_rows.saturating_sub(1);
1461 BlockPoint::new(row, self.line_len(BlockRow(row)))
1462 }
1463
1464 pub fn longest_row(&self) -> u32 {
1465 self.transforms.summary().longest_row
1466 }
1467
1468 pub fn longest_row_in_range(&self, range: Range<BlockRow>) -> BlockRow {
1469 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1470 cursor.seek(&range.start, Bias::Right);
1471
1472 let mut longest_row = range.start;
1473 let mut longest_row_chars = 0;
1474 if let Some(transform) = cursor.item() {
1475 if transform.block.is_none() {
1476 let Dimensions(output_start, input_start, _) = cursor.start();
1477 let overshoot = range.start.0 - output_start.0;
1478 let wrap_start_row = input_start.0 + overshoot;
1479 let wrap_end_row = cmp::min(
1480 input_start.0 + (range.end.0 - output_start.0),
1481 cursor.end().1.0,
1482 );
1483 let summary = self
1484 .wrap_snapshot
1485 .text_summary_for_range(wrap_start_row..wrap_end_row);
1486 longest_row = BlockRow(range.start.0 + summary.longest_row);
1487 longest_row_chars = summary.longest_row_chars;
1488 }
1489 cursor.next();
1490 }
1491
1492 let cursor_start_row = cursor.start().0;
1493 if range.end > cursor_start_row {
1494 let summary = cursor.summary::<_, TransformSummary>(&range.end, Bias::Right);
1495 if summary.longest_row_chars > longest_row_chars {
1496 longest_row = BlockRow(cursor_start_row.0 + summary.longest_row);
1497 longest_row_chars = summary.longest_row_chars;
1498 }
1499
1500 if let Some(transform) = cursor.item()
1501 && transform.block.is_none()
1502 {
1503 let Dimensions(output_start, input_start, _) = cursor.start();
1504 let overshoot = range.end.0 - output_start.0;
1505 let wrap_start_row = input_start.0;
1506 let wrap_end_row = input_start.0 + overshoot;
1507 let summary = self
1508 .wrap_snapshot
1509 .text_summary_for_range(wrap_start_row..wrap_end_row);
1510 if summary.longest_row_chars > longest_row_chars {
1511 longest_row = BlockRow(output_start.0 + summary.longest_row);
1512 }
1513 }
1514 }
1515
1516 longest_row
1517 }
1518
1519 pub(super) fn line_len(&self, row: BlockRow) -> u32 {
1520 let (start, _, item) =
1521 self.transforms
1522 .find::<Dimensions<BlockRow, WrapRow>, _>((), &row, Bias::Right);
1523 if let Some(transform) = item {
1524 let Dimensions(output_start, input_start, _) = start;
1525 let overshoot = row.0 - output_start.0;
1526 if transform.block.is_some() {
1527 0
1528 } else {
1529 self.wrap_snapshot.line_len(input_start.0 + overshoot)
1530 }
1531 } else if row.0 == 0 {
1532 0
1533 } else {
1534 panic!("row out of range");
1535 }
1536 }
1537
1538 pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
1539 let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
1540 item.is_some_and(|t| t.block.is_some())
1541 }
1542
1543 pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool {
1544 let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
1545 let Some(transform) = item else {
1546 return false;
1547 };
1548 matches!(transform.block, Some(Block::FoldedBuffer { .. }))
1549 }
1550
1551 pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool {
1552 let wrap_point = self
1553 .wrap_snapshot
1554 .make_wrap_point(Point::new(row.0, 0), Bias::Left);
1555 let (_, _, item) =
1556 self.transforms
1557 .find::<WrapRow, _>((), &WrapRow(wrap_point.row()), Bias::Right);
1558 item.is_some_and(|transform| {
1559 transform
1560 .block
1561 .as_ref()
1562 .is_some_and(|block| block.is_replacement())
1563 })
1564 }
1565
1566 pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
1567 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1568 cursor.seek(&BlockRow(point.row), Bias::Right);
1569
1570 let max_input_row = WrapRow(self.transforms.summary().input_rows);
1571 let mut search_left =
1572 (bias == Bias::Left && cursor.start().1.0 > 0) || cursor.end().1 == max_input_row;
1573 let mut reversed = false;
1574
1575 loop {
1576 if let Some(transform) = cursor.item() {
1577 let Dimensions(output_start_row, input_start_row, _) = cursor.start();
1578 let Dimensions(output_end_row, input_end_row, _) = cursor.end();
1579 let output_start = Point::new(output_start_row.0, 0);
1580 let input_start = Point::new(input_start_row.0, 0);
1581 let input_end = Point::new(input_end_row.0, 0);
1582
1583 match transform.block.as_ref() {
1584 Some(block) => {
1585 if block.is_replacement()
1586 && (((bias == Bias::Left || search_left) && output_start <= point.0)
1587 || (!search_left && output_start >= point.0))
1588 {
1589 return BlockPoint(output_start);
1590 }
1591 }
1592 None => {
1593 let input_point = if point.row >= output_end_row.0 {
1594 let line_len = self.wrap_snapshot.line_len(input_end_row.0 - 1);
1595 self.wrap_snapshot
1596 .clip_point(WrapPoint::new(input_end_row.0 - 1, line_len), bias)
1597 } else {
1598 let output_overshoot = point.0.saturating_sub(output_start);
1599 self.wrap_snapshot
1600 .clip_point(WrapPoint(input_start + output_overshoot), bias)
1601 };
1602
1603 if (input_start..input_end).contains(&input_point.0) {
1604 let input_overshoot = input_point.0.saturating_sub(input_start);
1605 return BlockPoint(output_start + input_overshoot);
1606 }
1607 }
1608 }
1609
1610 if search_left {
1611 cursor.prev();
1612 } else {
1613 cursor.next();
1614 }
1615 } else if reversed {
1616 return self.max_point();
1617 } else {
1618 reversed = true;
1619 search_left = !search_left;
1620 cursor.seek(&BlockRow(point.row), Bias::Right);
1621 }
1622 }
1623 }
1624
1625 pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
1626 let (start, _, item) = self.transforms.find::<Dimensions<WrapRow, BlockRow>, _>(
1627 (),
1628 &WrapRow(wrap_point.row()),
1629 Bias::Right,
1630 );
1631 if let Some(transform) = item {
1632 if transform.block.is_some() {
1633 BlockPoint::new(start.1.0, 0)
1634 } else {
1635 let Dimensions(input_start_row, output_start_row, _) = start;
1636 let input_start = Point::new(input_start_row.0, 0);
1637 let output_start = Point::new(output_start_row.0, 0);
1638 let input_overshoot = wrap_point.0 - input_start;
1639 BlockPoint(output_start + input_overshoot)
1640 }
1641 } else {
1642 self.max_point()
1643 }
1644 }
1645
1646 pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
1647 let (start, end, item) = self.transforms.find::<Dimensions<BlockRow, WrapRow>, _>(
1648 (),
1649 &BlockRow(block_point.row),
1650 Bias::Right,
1651 );
1652 if let Some(transform) = item {
1653 match transform.block.as_ref() {
1654 Some(block) => {
1655 if block.place_below() {
1656 let wrap_row = start.1.0 - 1;
1657 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1658 } else if block.place_above() {
1659 WrapPoint::new(start.1.0, 0)
1660 } else if bias == Bias::Left {
1661 WrapPoint::new(start.1.0, 0)
1662 } else {
1663 let wrap_row = end.1.0 - 1;
1664 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1665 }
1666 }
1667 None => {
1668 let overshoot = block_point.row - start.0.0;
1669 let wrap_row = start.1.0 + overshoot;
1670 WrapPoint::new(wrap_row, block_point.column)
1671 }
1672 }
1673 } else {
1674 self.wrap_snapshot.max_point()
1675 }
1676 }
1677}
1678
1679impl BlockChunks<'_> {
1680 /// Go to the next transform
1681 fn advance(&mut self) {
1682 self.input_chunk = Chunk::default();
1683 self.transforms.next();
1684 while let Some(transform) = self.transforms.item() {
1685 if transform
1686 .block
1687 .as_ref()
1688 .is_some_and(|block| block.height() == 0)
1689 {
1690 self.transforms.next();
1691 } else {
1692 break;
1693 }
1694 }
1695
1696 if self
1697 .transforms
1698 .item()
1699 .is_some_and(|transform| transform.block.is_none())
1700 {
1701 let start_input_row = self.transforms.start().1.0;
1702 let start_output_row = self.transforms.start().0.0;
1703 if start_output_row < self.max_output_row {
1704 let end_input_row = cmp::min(
1705 self.transforms.end().1.0,
1706 start_input_row + (self.max_output_row - start_output_row),
1707 );
1708 self.input_chunks.seek(start_input_row..end_input_row);
1709 }
1710 }
1711 }
1712}
1713
1714pub struct StickyHeaderExcerpt<'a> {
1715 pub excerpt: &'a ExcerptInfo,
1716}
1717
1718impl<'a> Iterator for BlockChunks<'a> {
1719 type Item = Chunk<'a>;
1720
1721 fn next(&mut self) -> Option<Self::Item> {
1722 if self.output_row >= self.max_output_row {
1723 return None;
1724 }
1725
1726 let transform = self.transforms.item()?;
1727 if transform.block.is_some() {
1728 let block_start = self.transforms.start().0.0;
1729 let mut block_end = self.transforms.end().0.0;
1730 self.advance();
1731 if self.transforms.item().is_none() {
1732 block_end -= 1;
1733 }
1734
1735 let start_in_block = self.output_row - block_start;
1736 let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
1737 // todo: We need to split the chunk here?
1738 let line_count = cmp::min(end_in_block - start_in_block, u128::BITS);
1739 self.output_row += line_count;
1740
1741 return Some(Chunk {
1742 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) },
1743 chars: 1u128.unbounded_shl(line_count) - 1,
1744 ..Default::default()
1745 });
1746 }
1747
1748 if self.input_chunk.text.is_empty() {
1749 if let Some(input_chunk) = self.input_chunks.next() {
1750 self.input_chunk = input_chunk;
1751 } else {
1752 if self.output_row < self.max_output_row {
1753 self.output_row += 1;
1754 self.advance();
1755 if self.transforms.item().is_some() {
1756 return Some(Chunk {
1757 text: "\n",
1758 chars: 1,
1759 ..Default::default()
1760 });
1761 }
1762 }
1763 return None;
1764 }
1765 }
1766
1767 let transform_end = self.transforms.end().0.0;
1768 let (prefix_rows, prefix_bytes) =
1769 offset_for_row(self.input_chunk.text, transform_end - self.output_row);
1770 self.output_row += prefix_rows;
1771
1772 let (mut prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
1773 self.input_chunk.text = suffix;
1774 self.input_chunk.tabs >>= prefix_bytes.saturating_sub(1);
1775 self.input_chunk.chars >>= prefix_bytes.saturating_sub(1);
1776
1777 let mut tabs = self.input_chunk.tabs;
1778 let mut chars = self.input_chunk.chars;
1779
1780 if self.masked {
1781 // Not great for multibyte text because to keep cursor math correct we
1782 // need to have the same number of chars in the input as output.
1783 let chars_count = prefix.chars().count();
1784 let bullet_len = chars_count;
1785 prefix = unsafe { std::str::from_utf8_unchecked(&BULLETS[..bullet_len]) };
1786 chars = 1u128.unbounded_shl(bullet_len as u32).wrapping_sub(1);
1787 tabs = 0;
1788 }
1789
1790 let chunk = Chunk {
1791 text: prefix,
1792 tabs,
1793 chars,
1794 ..self.input_chunk.clone()
1795 };
1796
1797 if self.output_row == transform_end {
1798 self.advance();
1799 }
1800
1801 Some(chunk)
1802 }
1803}
1804
1805impl Iterator for BlockRows<'_> {
1806 type Item = RowInfo;
1807
1808 fn next(&mut self) -> Option<Self::Item> {
1809 if self.started {
1810 self.output_row.0 += 1;
1811 } else {
1812 self.started = true;
1813 }
1814
1815 if self.output_row.0 >= self.transforms.end().0.0 {
1816 self.transforms.next();
1817 while let Some(transform) = self.transforms.item() {
1818 if transform
1819 .block
1820 .as_ref()
1821 .is_some_and(|block| block.height() == 0)
1822 {
1823 self.transforms.next();
1824 } else {
1825 break;
1826 }
1827 }
1828
1829 let transform = self.transforms.item()?;
1830 if transform
1831 .block
1832 .as_ref()
1833 .is_none_or(|block| block.is_replacement())
1834 {
1835 self.input_rows.seek(self.transforms.start().1.0);
1836 }
1837 }
1838
1839 let transform = self.transforms.item()?;
1840 if let Some(block) = transform.block.as_ref() {
1841 if block.is_replacement() && self.transforms.start().0 == self.output_row {
1842 if matches!(block, Block::FoldedBuffer { .. }) {
1843 Some(RowInfo::default())
1844 } else {
1845 Some(self.input_rows.next().unwrap())
1846 }
1847 } else {
1848 Some(RowInfo::default())
1849 }
1850 } else {
1851 Some(self.input_rows.next().unwrap())
1852 }
1853 }
1854}
1855
1856impl sum_tree::Item for Transform {
1857 type Summary = TransformSummary;
1858
1859 fn summary(&self, _cx: ()) -> Self::Summary {
1860 self.summary.clone()
1861 }
1862}
1863
1864impl sum_tree::ContextLessSummary for TransformSummary {
1865 fn zero() -> Self {
1866 Default::default()
1867 }
1868
1869 fn add_summary(&mut self, summary: &Self) {
1870 if summary.longest_row_chars > self.longest_row_chars {
1871 self.longest_row = self.output_rows + summary.longest_row;
1872 self.longest_row_chars = summary.longest_row_chars;
1873 }
1874 self.input_rows += summary.input_rows;
1875 self.output_rows += summary.output_rows;
1876 }
1877}
1878
1879impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
1880 fn zero(_cx: ()) -> Self {
1881 Default::default()
1882 }
1883
1884 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
1885 self.0 += summary.input_rows;
1886 }
1887}
1888
1889impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
1890 fn zero(_cx: ()) -> Self {
1891 Default::default()
1892 }
1893
1894 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
1895 self.0 += summary.output_rows;
1896 }
1897}
1898
1899impl Deref for BlockContext<'_, '_> {
1900 type Target = App;
1901
1902 fn deref(&self) -> &Self::Target {
1903 self.app
1904 }
1905}
1906
1907impl DerefMut for BlockContext<'_, '_> {
1908 fn deref_mut(&mut self) -> &mut Self::Target {
1909 self.app
1910 }
1911}
1912
1913impl CustomBlock {
1914 pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
1915 self.render.lock()(cx)
1916 }
1917
1918 pub fn start(&self) -> Anchor {
1919 *self.placement.start()
1920 }
1921
1922 pub fn end(&self) -> Anchor {
1923 *self.placement.end()
1924 }
1925
1926 pub fn style(&self) -> BlockStyle {
1927 self.style
1928 }
1929}
1930
1931impl Debug for CustomBlock {
1932 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1933 f.debug_struct("Block")
1934 .field("id", &self.id)
1935 .field("placement", &self.placement)
1936 .field("height", &self.height)
1937 .field("style", &self.style)
1938 .field("priority", &self.priority)
1939 .finish_non_exhaustive()
1940 }
1941}
1942
1943// Count the number of bytes prior to a target point. If the string doesn't contain the target
1944// point, return its total extent. Otherwise return the target point itself.
1945fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
1946 let mut row = 0;
1947 let mut offset = 0;
1948 for (ix, line) in s.split('\n').enumerate() {
1949 if ix > 0 {
1950 row += 1;
1951 offset += 1;
1952 }
1953 if row >= target {
1954 break;
1955 }
1956 offset += line.len();
1957 }
1958 (row, offset)
1959}
1960
1961#[cfg(test)]
1962mod tests {
1963 use super::*;
1964 use crate::{
1965 display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap},
1966 test::test_font,
1967 };
1968 use gpui::{App, AppContext as _, Element, div, font, px};
1969 use itertools::Itertools;
1970 use language::{Buffer, Capability};
1971 use multi_buffer::{ExcerptRange, MultiBuffer};
1972 use rand::prelude::*;
1973 use settings::SettingsStore;
1974 use std::env;
1975 use util::RandomCharIter;
1976
1977 #[gpui::test]
1978 fn test_offset_for_row() {
1979 assert_eq!(offset_for_row("", 0), (0, 0));
1980 assert_eq!(offset_for_row("", 1), (0, 0));
1981 assert_eq!(offset_for_row("abcd", 0), (0, 0));
1982 assert_eq!(offset_for_row("abcd", 1), (0, 4));
1983 assert_eq!(offset_for_row("\n", 0), (0, 0));
1984 assert_eq!(offset_for_row("\n", 1), (1, 1));
1985 assert_eq!(offset_for_row("abc\ndef\nghi", 0), (0, 0));
1986 assert_eq!(offset_for_row("abc\ndef\nghi", 1), (1, 4));
1987 assert_eq!(offset_for_row("abc\ndef\nghi", 2), (2, 8));
1988 assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11));
1989 }
1990
1991 #[gpui::test]
1992 fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
1993 cx.update(init_test);
1994
1995 let text = "aaa\nbbb\nccc\nddd";
1996
1997 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
1998 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1999 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2000 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2001 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2002 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2003 let (wrap_map, wraps_snapshot) =
2004 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2005 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2006
2007 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2008 let block_ids = writer.insert(vec![
2009 BlockProperties {
2010 style: BlockStyle::Fixed,
2011 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2012 height: Some(1),
2013 render: Arc::new(|_| div().into_any()),
2014 priority: 0,
2015 },
2016 BlockProperties {
2017 style: BlockStyle::Fixed,
2018 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2019 height: Some(2),
2020 render: Arc::new(|_| div().into_any()),
2021 priority: 0,
2022 },
2023 BlockProperties {
2024 style: BlockStyle::Fixed,
2025 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2026 height: Some(3),
2027 render: Arc::new(|_| div().into_any()),
2028 priority: 0,
2029 },
2030 ]);
2031
2032 let snapshot = block_map.read(wraps_snapshot, Default::default());
2033 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2034
2035 let blocks = snapshot
2036 .blocks_in_range(0..8)
2037 .map(|(start_row, block)| {
2038 let block = block.as_custom().unwrap();
2039 (start_row..start_row + block.height.unwrap(), block.id)
2040 })
2041 .collect::<Vec<_>>();
2042
2043 // When multiple blocks are on the same line, the newer blocks appear first.
2044 assert_eq!(
2045 blocks,
2046 &[
2047 (1..2, block_ids[0]),
2048 (2..4, block_ids[1]),
2049 (7..10, block_ids[2]),
2050 ]
2051 );
2052
2053 assert_eq!(
2054 snapshot.to_block_point(WrapPoint::new(0, 3)),
2055 BlockPoint::new(0, 3)
2056 );
2057 assert_eq!(
2058 snapshot.to_block_point(WrapPoint::new(1, 0)),
2059 BlockPoint::new(4, 0)
2060 );
2061 assert_eq!(
2062 snapshot.to_block_point(WrapPoint::new(3, 3)),
2063 BlockPoint::new(6, 3)
2064 );
2065
2066 assert_eq!(
2067 snapshot.to_wrap_point(BlockPoint::new(0, 3), Bias::Left),
2068 WrapPoint::new(0, 3)
2069 );
2070 assert_eq!(
2071 snapshot.to_wrap_point(BlockPoint::new(1, 0), Bias::Left),
2072 WrapPoint::new(1, 0)
2073 );
2074 assert_eq!(
2075 snapshot.to_wrap_point(BlockPoint::new(3, 0), Bias::Left),
2076 WrapPoint::new(1, 0)
2077 );
2078 assert_eq!(
2079 snapshot.to_wrap_point(BlockPoint::new(7, 0), Bias::Left),
2080 WrapPoint::new(3, 3)
2081 );
2082
2083 assert_eq!(
2084 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left),
2085 BlockPoint::new(0, 3)
2086 );
2087 assert_eq!(
2088 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right),
2089 BlockPoint::new(4, 0)
2090 );
2091 assert_eq!(
2092 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left),
2093 BlockPoint::new(0, 3)
2094 );
2095 assert_eq!(
2096 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right),
2097 BlockPoint::new(4, 0)
2098 );
2099 assert_eq!(
2100 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Left),
2101 BlockPoint::new(4, 0)
2102 );
2103 assert_eq!(
2104 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Right),
2105 BlockPoint::new(4, 0)
2106 );
2107 assert_eq!(
2108 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Left),
2109 BlockPoint::new(6, 3)
2110 );
2111 assert_eq!(
2112 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Right),
2113 BlockPoint::new(6, 3)
2114 );
2115 assert_eq!(
2116 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Left),
2117 BlockPoint::new(6, 3)
2118 );
2119 assert_eq!(
2120 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Right),
2121 BlockPoint::new(6, 3)
2122 );
2123
2124 assert_eq!(
2125 snapshot
2126 .row_infos(BlockRow(0))
2127 .map(|row_info| row_info.buffer_row)
2128 .collect::<Vec<_>>(),
2129 &[
2130 Some(0),
2131 None,
2132 None,
2133 None,
2134 Some(1),
2135 Some(2),
2136 Some(3),
2137 None,
2138 None,
2139 None
2140 ]
2141 );
2142
2143 // Insert a line break, separating two block decorations into separate lines.
2144 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2145 buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
2146 buffer.snapshot(cx)
2147 });
2148
2149 let (inlay_snapshot, inlay_edits) =
2150 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
2151 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2152 let (tab_snapshot, tab_edits) =
2153 tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
2154 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2155 wrap_map.sync(tab_snapshot, tab_edits, cx)
2156 });
2157 let snapshot = block_map.read(wraps_snapshot, wrap_edits);
2158 assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
2159 }
2160
2161 #[gpui::test]
2162 fn test_multibuffer_headers_and_footers(cx: &mut App) {
2163 init_test(cx);
2164
2165 let buffer1 = cx.new(|cx| Buffer::local("Buffer 1", cx));
2166 let buffer2 = cx.new(|cx| Buffer::local("Buffer 2", cx));
2167 let buffer3 = cx.new(|cx| Buffer::local("Buffer 3", cx));
2168
2169 let mut excerpt_ids = Vec::new();
2170 let multi_buffer = cx.new(|cx| {
2171 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
2172 excerpt_ids.extend(multi_buffer.push_excerpts(
2173 buffer1.clone(),
2174 [ExcerptRange::new(0..buffer1.read(cx).len())],
2175 cx,
2176 ));
2177 excerpt_ids.extend(multi_buffer.push_excerpts(
2178 buffer2.clone(),
2179 [ExcerptRange::new(0..buffer2.read(cx).len())],
2180 cx,
2181 ));
2182 excerpt_ids.extend(multi_buffer.push_excerpts(
2183 buffer3.clone(),
2184 [ExcerptRange::new(0..buffer3.read(cx).len())],
2185 cx,
2186 ));
2187
2188 multi_buffer
2189 });
2190
2191 let font = test_font();
2192 let font_size = px(14.);
2193 let font_id = cx.text_system().resolve_font(&font);
2194 let mut wrap_width = px(0.);
2195 for c in "Buff".chars() {
2196 wrap_width += cx
2197 .text_system()
2198 .advance(font_id, font_size, c)
2199 .unwrap()
2200 .width;
2201 }
2202
2203 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2204 let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot);
2205 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2206 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2207 let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
2208
2209 let block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2210 let snapshot = block_map.read(wraps_snapshot, Default::default());
2211
2212 // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
2213 assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3");
2214
2215 let blocks: Vec<_> = snapshot
2216 .blocks_in_range(0..u32::MAX)
2217 .map(|(row, block)| (row..row + block.height(), block.id()))
2218 .collect();
2219 assert_eq!(
2220 blocks,
2221 vec![
2222 (0..1, BlockId::ExcerptBoundary(excerpt_ids[0])), // path, header
2223 (3..4, BlockId::ExcerptBoundary(excerpt_ids[1])), // path, header
2224 (6..7, BlockId::ExcerptBoundary(excerpt_ids[2])), // path, header
2225 ]
2226 );
2227 }
2228
2229 #[gpui::test]
2230 fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
2231 cx.update(init_test);
2232
2233 let text = "aaa\nbbb\nccc\nddd";
2234
2235 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2236 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2237 let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2238 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2239 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2240 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2241 let (_wrap_map, wraps_snapshot) =
2242 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2243 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2244
2245 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2246 let block_ids = writer.insert(vec![
2247 BlockProperties {
2248 style: BlockStyle::Fixed,
2249 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2250 height: Some(1),
2251 render: Arc::new(|_| div().into_any()),
2252 priority: 0,
2253 },
2254 BlockProperties {
2255 style: BlockStyle::Fixed,
2256 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2257 height: Some(2),
2258 render: Arc::new(|_| div().into_any()),
2259 priority: 0,
2260 },
2261 BlockProperties {
2262 style: BlockStyle::Fixed,
2263 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2264 height: Some(3),
2265 render: Arc::new(|_| div().into_any()),
2266 priority: 0,
2267 },
2268 ]);
2269
2270 {
2271 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2272 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2273
2274 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2275
2276 let mut new_heights = HashMap::default();
2277 new_heights.insert(block_ids[0], 2);
2278 block_map_writer.resize(new_heights);
2279 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2280 assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2281 }
2282
2283 {
2284 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2285
2286 let mut new_heights = HashMap::default();
2287 new_heights.insert(block_ids[0], 1);
2288 block_map_writer.resize(new_heights);
2289
2290 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2291 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2292 }
2293
2294 {
2295 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2296
2297 let mut new_heights = HashMap::default();
2298 new_heights.insert(block_ids[0], 0);
2299 block_map_writer.resize(new_heights);
2300
2301 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2302 assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
2303 }
2304
2305 {
2306 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2307
2308 let mut new_heights = HashMap::default();
2309 new_heights.insert(block_ids[0], 3);
2310 block_map_writer.resize(new_heights);
2311
2312 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2313 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2314 }
2315
2316 {
2317 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2318
2319 let mut new_heights = HashMap::default();
2320 new_heights.insert(block_ids[0], 3);
2321 block_map_writer.resize(new_heights);
2322
2323 let snapshot = block_map.read(wraps_snapshot, Default::default());
2324 // Same height as before, should remain the same
2325 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2326 }
2327 }
2328
2329 #[gpui::test]
2330 fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
2331 cx.update(init_test);
2332
2333 let text = "one two three\nfour five six\nseven eight";
2334
2335 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2336 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2337 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2338 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2339 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2340 let (_, wraps_snapshot) = cx.update(|cx| {
2341 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(90.)), cx)
2342 });
2343 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2344
2345 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2346 writer.insert(vec![
2347 BlockProperties {
2348 style: BlockStyle::Fixed,
2349 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
2350 render: Arc::new(|_| div().into_any()),
2351 height: Some(1),
2352 priority: 0,
2353 },
2354 BlockProperties {
2355 style: BlockStyle::Fixed,
2356 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
2357 render: Arc::new(|_| div().into_any()),
2358 height: Some(1),
2359 priority: 0,
2360 },
2361 ]);
2362
2363 // Blocks with an 'above' disposition go above their corresponding buffer line.
2364 // Blocks with a 'below' disposition go below their corresponding buffer line.
2365 let snapshot = block_map.read(wraps_snapshot, Default::default());
2366 assert_eq!(
2367 snapshot.text(),
2368 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
2369 );
2370 }
2371
2372 #[gpui::test]
2373 fn test_replace_lines(cx: &mut gpui::TestAppContext) {
2374 cx.update(init_test);
2375
2376 let text = "line1\nline2\nline3\nline4\nline5";
2377
2378 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2379 let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
2380 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2381 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2382 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2383 let tab_size = 1.try_into().unwrap();
2384 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
2385 let (wrap_map, wraps_snapshot) =
2386 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2387 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2388
2389 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2390 let replace_block_id = writer.insert(vec![BlockProperties {
2391 style: BlockStyle::Fixed,
2392 placement: BlockPlacement::Replace(
2393 buffer_snapshot.anchor_after(Point::new(1, 3))
2394 ..=buffer_snapshot.anchor_before(Point::new(3, 1)),
2395 ),
2396 height: Some(4),
2397 render: Arc::new(|_| div().into_any()),
2398 priority: 0,
2399 }])[0];
2400
2401 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
2402 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2403
2404 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2405 buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
2406 buffer.snapshot(cx)
2407 });
2408 let (inlay_snapshot, inlay_edits) =
2409 inlay_map.sync(buffer_snapshot, buffer_subscription.consume().into_inner());
2410 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2411 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2412 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2413 wrap_map.sync(tab_snapshot, tab_edits, cx)
2414 });
2415 let blocks_snapshot = block_map.read(wraps_snapshot, wrap_edits);
2416 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2417
2418 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2419 buffer.edit(
2420 [(
2421 Point::new(1, 5)..Point::new(1, 5),
2422 "\nline 2.1\nline2.2\nline 2.3\nline 2.4",
2423 )],
2424 None,
2425 cx,
2426 );
2427 buffer.snapshot(cx)
2428 });
2429 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
2430 buffer_snapshot.clone(),
2431 buffer_subscription.consume().into_inner(),
2432 );
2433 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2434 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2435 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2436 wrap_map.sync(tab_snapshot, tab_edits, cx)
2437 });
2438 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
2439 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2440
2441 // Blocks inserted right above the start or right below the end of the replaced region are hidden.
2442 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2443 writer.insert(vec![
2444 BlockProperties {
2445 style: BlockStyle::Fixed,
2446 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))),
2447 height: Some(1),
2448 render: Arc::new(|_| div().into_any()),
2449 priority: 0,
2450 },
2451 BlockProperties {
2452 style: BlockStyle::Fixed,
2453 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
2454 height: Some(1),
2455 render: Arc::new(|_| div().into_any()),
2456 priority: 0,
2457 },
2458 BlockProperties {
2459 style: BlockStyle::Fixed,
2460 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
2461 height: Some(1),
2462 render: Arc::new(|_| div().into_any()),
2463 priority: 0,
2464 },
2465 ]);
2466 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2467 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2468
2469 // Ensure blocks inserted *inside* replaced region are hidden.
2470 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2471 writer.insert(vec![
2472 BlockProperties {
2473 style: BlockStyle::Fixed,
2474 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
2475 height: Some(1),
2476 render: Arc::new(|_| div().into_any()),
2477 priority: 0,
2478 },
2479 BlockProperties {
2480 style: BlockStyle::Fixed,
2481 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
2482 height: Some(1),
2483 render: Arc::new(|_| div().into_any()),
2484 priority: 0,
2485 },
2486 BlockProperties {
2487 style: BlockStyle::Fixed,
2488 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
2489 height: Some(1),
2490 render: Arc::new(|_| div().into_any()),
2491 priority: 0,
2492 },
2493 ]);
2494 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2495 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2496
2497 // Removing the replace block shows all the hidden blocks again.
2498 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2499 writer.remove(HashSet::from_iter([replace_block_id]));
2500 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
2501 assert_eq!(
2502 blocks_snapshot.text(),
2503 "\nline1\n\nline2\n\n\nline 2.1\nline2.2\nline 2.3\nline 2.4\n\nline4\n\nline5"
2504 );
2505 }
2506
2507 #[gpui::test]
2508 fn test_custom_blocks_inside_buffer_folds(cx: &mut gpui::TestAppContext) {
2509 cx.update(init_test);
2510
2511 let text = "111\n222\n333\n444\n555\n666";
2512
2513 let buffer = cx.update(|cx| {
2514 MultiBuffer::build_multi(
2515 [
2516 (text, vec![Point::new(0, 0)..Point::new(0, 3)]),
2517 (
2518 text,
2519 vec![
2520 Point::new(1, 0)..Point::new(1, 3),
2521 Point::new(2, 0)..Point::new(2, 3),
2522 Point::new(3, 0)..Point::new(3, 3),
2523 ],
2524 ),
2525 (
2526 text,
2527 vec![
2528 Point::new(4, 0)..Point::new(4, 3),
2529 Point::new(5, 0)..Point::new(5, 3),
2530 ],
2531 ),
2532 ],
2533 cx,
2534 )
2535 });
2536 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2537 let buffer_ids = buffer_snapshot
2538 .excerpts()
2539 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2540 .dedup()
2541 .collect::<Vec<_>>();
2542 assert_eq!(buffer_ids.len(), 3);
2543 let buffer_id_1 = buffer_ids[0];
2544 let buffer_id_2 = buffer_ids[1];
2545 let buffer_id_3 = buffer_ids[2];
2546
2547 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2548 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2549 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2550 let (_, wrap_snapshot) =
2551 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2552 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
2553 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2554
2555 assert_eq!(
2556 blocks_snapshot.text(),
2557 "\n\n111\n\n\n222\n\n333\n\n444\n\n\n555\n\n666"
2558 );
2559 assert_eq!(
2560 blocks_snapshot
2561 .row_infos(BlockRow(0))
2562 .map(|i| i.buffer_row)
2563 .collect::<Vec<_>>(),
2564 vec![
2565 None,
2566 None,
2567 Some(0),
2568 None,
2569 None,
2570 Some(1),
2571 None,
2572 Some(2),
2573 None,
2574 Some(3),
2575 None,
2576 None,
2577 Some(4),
2578 None,
2579 Some(5),
2580 ]
2581 );
2582
2583 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2584 let excerpt_blocks_2 = writer.insert(vec![
2585 BlockProperties {
2586 style: BlockStyle::Fixed,
2587 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2588 height: Some(1),
2589 render: Arc::new(|_| div().into_any()),
2590 priority: 0,
2591 },
2592 BlockProperties {
2593 style: BlockStyle::Fixed,
2594 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 0))),
2595 height: Some(1),
2596 render: Arc::new(|_| div().into_any()),
2597 priority: 0,
2598 },
2599 BlockProperties {
2600 style: BlockStyle::Fixed,
2601 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 0))),
2602 height: Some(1),
2603 render: Arc::new(|_| div().into_any()),
2604 priority: 0,
2605 },
2606 ]);
2607 let excerpt_blocks_3 = writer.insert(vec![
2608 BlockProperties {
2609 style: BlockStyle::Fixed,
2610 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))),
2611 height: Some(1),
2612 render: Arc::new(|_| div().into_any()),
2613 priority: 0,
2614 },
2615 BlockProperties {
2616 style: BlockStyle::Fixed,
2617 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))),
2618 height: Some(1),
2619 render: Arc::new(|_| div().into_any()),
2620 priority: 0,
2621 },
2622 ]);
2623
2624 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2625 assert_eq!(
2626 blocks_snapshot.text(),
2627 "\n\n111\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
2628 );
2629 assert_eq!(
2630 blocks_snapshot
2631 .row_infos(BlockRow(0))
2632 .map(|i| i.buffer_row)
2633 .collect::<Vec<_>>(),
2634 vec![
2635 None,
2636 None,
2637 Some(0),
2638 None,
2639 None,
2640 None,
2641 Some(1),
2642 None,
2643 None,
2644 Some(2),
2645 None,
2646 Some(3),
2647 None,
2648 None,
2649 None,
2650 None,
2651 Some(4),
2652 None,
2653 Some(5),
2654 None,
2655 ]
2656 );
2657
2658 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2659 buffer.read_with(cx, |buffer, cx| {
2660 writer.fold_buffers([buffer_id_1], buffer, cx);
2661 });
2662 let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
2663 style: BlockStyle::Fixed,
2664 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))),
2665 height: Some(1),
2666 render: Arc::new(|_| div().into_any()),
2667 priority: 0,
2668 }]);
2669 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2670 let blocks = blocks_snapshot
2671 .blocks_in_range(0..u32::MAX)
2672 .collect::<Vec<_>>();
2673 for (_, block) in &blocks {
2674 if let BlockId::Custom(custom_block_id) = block.id() {
2675 assert!(
2676 !excerpt_blocks_1.contains(&custom_block_id),
2677 "Should have no blocks from the folded buffer"
2678 );
2679 assert!(
2680 excerpt_blocks_2.contains(&custom_block_id)
2681 || excerpt_blocks_3.contains(&custom_block_id),
2682 "Should have only blocks from unfolded buffers"
2683 );
2684 }
2685 }
2686 assert_eq!(
2687 1,
2688 blocks
2689 .iter()
2690 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2691 .count(),
2692 "Should have one folded block, producing a header of the second buffer"
2693 );
2694 assert_eq!(
2695 blocks_snapshot.text(),
2696 "\n\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
2697 );
2698 assert_eq!(
2699 blocks_snapshot
2700 .row_infos(BlockRow(0))
2701 .map(|i| i.buffer_row)
2702 .collect::<Vec<_>>(),
2703 vec![
2704 None,
2705 None,
2706 None,
2707 None,
2708 None,
2709 Some(1),
2710 None,
2711 None,
2712 Some(2),
2713 None,
2714 Some(3),
2715 None,
2716 None,
2717 None,
2718 None,
2719 Some(4),
2720 None,
2721 Some(5),
2722 None,
2723 ]
2724 );
2725
2726 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2727 buffer.read_with(cx, |buffer, cx| {
2728 writer.fold_buffers([buffer_id_2], buffer, cx);
2729 });
2730 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2731 let blocks = blocks_snapshot
2732 .blocks_in_range(0..u32::MAX)
2733 .collect::<Vec<_>>();
2734 for (_, block) in &blocks {
2735 if let BlockId::Custom(custom_block_id) = block.id() {
2736 assert!(
2737 !excerpt_blocks_1.contains(&custom_block_id),
2738 "Should have no blocks from the folded buffer_1"
2739 );
2740 assert!(
2741 !excerpt_blocks_2.contains(&custom_block_id),
2742 "Should have no blocks from the folded buffer_2"
2743 );
2744 assert!(
2745 excerpt_blocks_3.contains(&custom_block_id),
2746 "Should have only blocks from unfolded buffers"
2747 );
2748 }
2749 }
2750 assert_eq!(
2751 2,
2752 blocks
2753 .iter()
2754 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2755 .count(),
2756 "Should have two folded blocks, producing headers"
2757 );
2758 assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n555\n\n666\n");
2759 assert_eq!(
2760 blocks_snapshot
2761 .row_infos(BlockRow(0))
2762 .map(|i| i.buffer_row)
2763 .collect::<Vec<_>>(),
2764 vec![
2765 None,
2766 None,
2767 None,
2768 None,
2769 None,
2770 None,
2771 None,
2772 Some(4),
2773 None,
2774 Some(5),
2775 None,
2776 ]
2777 );
2778
2779 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2780 buffer.read_with(cx, |buffer, cx| {
2781 writer.unfold_buffers([buffer_id_1], buffer, cx);
2782 });
2783 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2784 let blocks = blocks_snapshot
2785 .blocks_in_range(0..u32::MAX)
2786 .collect::<Vec<_>>();
2787 for (_, block) in &blocks {
2788 if let BlockId::Custom(custom_block_id) = block.id() {
2789 assert!(
2790 !excerpt_blocks_2.contains(&custom_block_id),
2791 "Should have no blocks from the folded buffer_2"
2792 );
2793 assert!(
2794 excerpt_blocks_1.contains(&custom_block_id)
2795 || excerpt_blocks_3.contains(&custom_block_id),
2796 "Should have only blocks from unfolded buffers"
2797 );
2798 }
2799 }
2800 assert_eq!(
2801 1,
2802 blocks
2803 .iter()
2804 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2805 .count(),
2806 "Should be back to a single folded buffer, producing a header for buffer_2"
2807 );
2808 assert_eq!(
2809 blocks_snapshot.text(),
2810 "\n\n\n111\n\n\n\n\n\n555\n\n666\n",
2811 "Should have extra newline for 111 buffer, due to a new block added when it was folded"
2812 );
2813 assert_eq!(
2814 blocks_snapshot
2815 .row_infos(BlockRow(0))
2816 .map(|i| i.buffer_row)
2817 .collect::<Vec<_>>(),
2818 vec![
2819 None,
2820 None,
2821 None,
2822 Some(0),
2823 None,
2824 None,
2825 None,
2826 None,
2827 None,
2828 Some(4),
2829 None,
2830 Some(5),
2831 None,
2832 ]
2833 );
2834
2835 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2836 buffer.read_with(cx, |buffer, cx| {
2837 writer.fold_buffers([buffer_id_3], buffer, cx);
2838 });
2839 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
2840 let blocks = blocks_snapshot
2841 .blocks_in_range(0..u32::MAX)
2842 .collect::<Vec<_>>();
2843 for (_, block) in &blocks {
2844 if let BlockId::Custom(custom_block_id) = block.id() {
2845 assert!(
2846 excerpt_blocks_1.contains(&custom_block_id),
2847 "Should have no blocks from the folded buffer_1"
2848 );
2849 assert!(
2850 !excerpt_blocks_2.contains(&custom_block_id),
2851 "Should have only blocks from unfolded buffers"
2852 );
2853 assert!(
2854 !excerpt_blocks_3.contains(&custom_block_id),
2855 "Should have only blocks from unfolded buffers"
2856 );
2857 }
2858 }
2859
2860 assert_eq!(
2861 blocks_snapshot.text(),
2862 "\n\n\n111\n\n\n\n",
2863 "Should have a single, first buffer left after folding"
2864 );
2865 assert_eq!(
2866 blocks_snapshot
2867 .row_infos(BlockRow(0))
2868 .map(|i| i.buffer_row)
2869 .collect::<Vec<_>>(),
2870 vec![None, None, None, Some(0), None, None, None, None,]
2871 );
2872 }
2873
2874 #[gpui::test]
2875 fn test_basic_buffer_fold(cx: &mut gpui::TestAppContext) {
2876 cx.update(init_test);
2877
2878 let text = "111";
2879
2880 let buffer = cx.update(|cx| {
2881 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(0, 3)])], cx)
2882 });
2883 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2884 let buffer_ids = buffer_snapshot
2885 .excerpts()
2886 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2887 .dedup()
2888 .collect::<Vec<_>>();
2889 assert_eq!(buffer_ids.len(), 1);
2890 let buffer_id = buffer_ids[0];
2891
2892 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
2893 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2894 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2895 let (_, wrap_snapshot) =
2896 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2897 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
2898 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2899
2900 assert_eq!(blocks_snapshot.text(), "\n\n111");
2901
2902 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2903 buffer.read_with(cx, |buffer, cx| {
2904 writer.fold_buffers([buffer_id], buffer, cx);
2905 });
2906 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
2907 let blocks = blocks_snapshot
2908 .blocks_in_range(0..u32::MAX)
2909 .collect::<Vec<_>>();
2910 assert_eq!(
2911 1,
2912 blocks
2913 .iter()
2914 .filter(|(_, block)| { matches!(block, Block::FoldedBuffer { .. }) })
2915 .count(),
2916 "Should have one folded block, producing a header of the second buffer"
2917 );
2918 assert_eq!(blocks_snapshot.text(), "\n");
2919 assert_eq!(
2920 blocks_snapshot
2921 .row_infos(BlockRow(0))
2922 .map(|i| i.buffer_row)
2923 .collect::<Vec<_>>(),
2924 vec![None, None],
2925 "When fully folded, should be no buffer rows"
2926 );
2927 }
2928
2929 #[gpui::test(iterations = 100)]
2930 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
2931 cx.update(init_test);
2932
2933 let operations = env::var("OPERATIONS")
2934 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2935 .unwrap_or(10);
2936
2937 let wrap_width = if rng.random_bool(0.2) {
2938 None
2939 } else {
2940 Some(px(rng.random_range(0.0..=100.0)))
2941 };
2942 let tab_size = 1.try_into().unwrap();
2943 let font_size = px(14.0);
2944 let buffer_start_header_height = rng.random_range(1..=5);
2945 let excerpt_header_height = rng.random_range(1..=5);
2946
2947 log::info!("Wrap width: {:?}", wrap_width);
2948 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
2949 let is_singleton = rng.random();
2950 let buffer = if is_singleton {
2951 let len = rng.random_range(0..10);
2952 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
2953 log::info!("initial singleton buffer text: {:?}", text);
2954 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
2955 } else {
2956 cx.update(|cx| {
2957 let multibuffer = MultiBuffer::build_random(&mut rng, cx);
2958 log::info!(
2959 "initial multi-buffer text: {:?}",
2960 multibuffer.read(cx).read(cx).text()
2961 );
2962 multibuffer
2963 })
2964 };
2965
2966 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2967 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2968 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2969 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2970 let font = test_font();
2971 let (wrap_map, wraps_snapshot) =
2972 cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
2973 let mut block_map = BlockMap::new(
2974 wraps_snapshot,
2975 buffer_start_header_height,
2976 excerpt_header_height,
2977 );
2978
2979 for _ in 0..operations {
2980 let mut buffer_edits = Vec::new();
2981 match rng.random_range(0..=100) {
2982 0..=19 => {
2983 let wrap_width = if rng.random_bool(0.2) {
2984 None
2985 } else {
2986 Some(px(rng.random_range(0.0..=100.0)))
2987 };
2988 log::info!("Setting wrap width to {:?}", wrap_width);
2989 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
2990 }
2991 20..=39 => {
2992 let block_count = rng.random_range(1..=5);
2993 let block_properties = (0..block_count)
2994 .map(|_| {
2995 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
2996 let offset =
2997 buffer.clip_offset(rng.random_range(0..=buffer.len()), Bias::Left);
2998 let mut min_height = 0;
2999 let placement = match rng.random_range(0..3) {
3000 0 => {
3001 min_height = 1;
3002 let start = buffer.anchor_after(offset);
3003 let end = buffer.anchor_after(buffer.clip_offset(
3004 rng.random_range(offset..=buffer.len()),
3005 Bias::Left,
3006 ));
3007 BlockPlacement::Replace(start..=end)
3008 }
3009 1 => BlockPlacement::Above(buffer.anchor_after(offset)),
3010 _ => BlockPlacement::Below(buffer.anchor_after(offset)),
3011 };
3012
3013 let height = rng.random_range(min_height..5);
3014 BlockProperties {
3015 style: BlockStyle::Fixed,
3016 placement,
3017 height: Some(height),
3018 render: Arc::new(|_| div().into_any()),
3019 priority: 0,
3020 }
3021 })
3022 .collect::<Vec<_>>();
3023
3024 let (inlay_snapshot, inlay_edits) =
3025 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3026 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3027 let (tab_snapshot, tab_edits) =
3028 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3029 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3030 wrap_map.sync(tab_snapshot, tab_edits, cx)
3031 });
3032 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3033 let block_ids =
3034 block_map.insert(block_properties.iter().map(|props| BlockProperties {
3035 placement: props.placement.clone(),
3036 height: props.height,
3037 style: props.style,
3038 render: Arc::new(|_| div().into_any()),
3039 priority: 0,
3040 }));
3041
3042 for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3043 log::info!(
3044 "inserted block {:?} with height {:?} and id {:?}",
3045 block_properties
3046 .placement
3047 .as_ref()
3048 .map(|p| p.to_point(&buffer_snapshot)),
3049 block_properties.height,
3050 block_id
3051 );
3052 }
3053 }
3054 40..=59 if !block_map.custom_blocks.is_empty() => {
3055 let block_count = rng.random_range(1..=4.min(block_map.custom_blocks.len()));
3056 let block_ids_to_remove = block_map
3057 .custom_blocks
3058 .choose_multiple(&mut rng, block_count)
3059 .map(|block| block.id)
3060 .collect::<HashSet<_>>();
3061
3062 let (inlay_snapshot, inlay_edits) =
3063 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3064 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3065 let (tab_snapshot, tab_edits) =
3066 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3067 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3068 wrap_map.sync(tab_snapshot, tab_edits, cx)
3069 });
3070 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3071 log::info!(
3072 "removing {} blocks: {:?}",
3073 block_ids_to_remove.len(),
3074 block_ids_to_remove
3075 );
3076 block_map.remove(block_ids_to_remove);
3077 }
3078 60..=79 => {
3079 if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
3080 log::info!("Noop fold/unfold operation on a singleton buffer");
3081 continue;
3082 }
3083 let (inlay_snapshot, inlay_edits) =
3084 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3085 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3086 let (tab_snapshot, tab_edits) =
3087 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3088 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3089 wrap_map.sync(tab_snapshot, tab_edits, cx)
3090 });
3091 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3092 let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
3093 let folded_buffers = block_map
3094 .0
3095 .folded_buffers
3096 .iter()
3097 .cloned()
3098 .collect::<Vec<_>>();
3099 let mut unfolded_buffers = buffer.excerpt_buffer_ids();
3100 unfolded_buffers.dedup();
3101 log::debug!("All buffers {unfolded_buffers:?}");
3102 log::debug!("Folded buffers {folded_buffers:?}");
3103 unfolded_buffers
3104 .retain(|buffer_id| !block_map.0.folded_buffers.contains(buffer_id));
3105 (unfolded_buffers, folded_buffers)
3106 });
3107 let mut folded_count = folded_buffers.len();
3108 let mut unfolded_count = unfolded_buffers.len();
3109
3110 let fold = !unfolded_buffers.is_empty() && rng.random_bool(0.5);
3111 let unfold = !folded_buffers.is_empty() && rng.random_bool(0.5);
3112 if !fold && !unfold {
3113 log::info!(
3114 "Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3115 );
3116 continue;
3117 }
3118
3119 buffer.update(cx, |buffer, cx| {
3120 if fold {
3121 let buffer_to_fold =
3122 unfolded_buffers[rng.random_range(0..unfolded_buffers.len())];
3123 log::info!("Folding {buffer_to_fold:?}");
3124 let related_excerpts = buffer_snapshot
3125 .excerpts()
3126 .filter_map(|(excerpt_id, buffer, range)| {
3127 if buffer.remote_id() == buffer_to_fold {
3128 Some((
3129 excerpt_id,
3130 buffer
3131 .text_for_range(range.context)
3132 .collect::<String>(),
3133 ))
3134 } else {
3135 None
3136 }
3137 })
3138 .collect::<Vec<_>>();
3139 log::info!(
3140 "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
3141 );
3142 folded_count += 1;
3143 unfolded_count -= 1;
3144 block_map.fold_buffers([buffer_to_fold], buffer, cx);
3145 }
3146 if unfold {
3147 let buffer_to_unfold =
3148 folded_buffers[rng.random_range(0..folded_buffers.len())];
3149 log::info!("Unfolding {buffer_to_unfold:?}");
3150 unfolded_count += 1;
3151 folded_count -= 1;
3152 block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
3153 }
3154 log::info!(
3155 "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3156 );
3157 });
3158 }
3159 _ => {
3160 buffer.update(cx, |buffer, cx| {
3161 let mutation_count = rng.random_range(1..=5);
3162 let subscription = buffer.subscribe();
3163 buffer.randomly_mutate(&mut rng, mutation_count, cx);
3164 buffer_snapshot = buffer.snapshot(cx);
3165 buffer_edits.extend(subscription.consume());
3166 log::info!("buffer text: {:?}", buffer_snapshot.text());
3167 });
3168 }
3169 }
3170
3171 let (inlay_snapshot, inlay_edits) =
3172 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
3173 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3174 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3175 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3176 wrap_map.sync(tab_snapshot, tab_edits, cx)
3177 });
3178 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
3179 assert_eq!(
3180 blocks_snapshot.transforms.summary().input_rows,
3181 wraps_snapshot.max_point().row() + 1
3182 );
3183 log::info!("wrapped text: {:?}", wraps_snapshot.text());
3184 log::info!("blocks text: {:?}", blocks_snapshot.text());
3185
3186 let mut expected_blocks = Vec::new();
3187 expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
3188 Some((
3189 block.placement.to_wrap_row(&wraps_snapshot)?,
3190 Block::Custom(block.clone()),
3191 ))
3192 }));
3193
3194 // Note that this needs to be synced with the related section in BlockMap::sync
3195 expected_blocks.extend(block_map.header_and_footer_blocks(
3196 &buffer_snapshot,
3197 0..,
3198 &wraps_snapshot,
3199 ));
3200
3201 BlockMap::sort_blocks(&mut expected_blocks);
3202
3203 for (placement, block) in &expected_blocks {
3204 log::info!(
3205 "Block {:?} placement: {:?} Height: {:?}",
3206 block.id(),
3207 placement,
3208 block.height()
3209 );
3210 }
3211
3212 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
3213
3214 let input_buffer_rows = buffer_snapshot
3215 .row_infos(MultiBufferRow(0))
3216 .map(|row| row.buffer_row)
3217 .collect::<Vec<_>>();
3218 let mut expected_buffer_rows = Vec::new();
3219 let mut expected_text = String::new();
3220 let mut expected_block_positions = Vec::new();
3221 let mut expected_replaced_buffer_rows = HashSet::default();
3222 let input_text = wraps_snapshot.text();
3223
3224 // Loop over the input lines, creating (N - 1) empty lines for
3225 // blocks of height N.
3226 //
3227 // It's important to note that output *starts* as one empty line,
3228 // so we special case row 0 to assume a leading '\n'.
3229 //
3230 // Linehood is the birthright of strings.
3231 let input_text_lines = input_text.split('\n').enumerate().peekable();
3232 let mut block_row = 0;
3233 for (wrap_row, input_line) in input_text_lines {
3234 let wrap_row = wrap_row as u32;
3235 let multibuffer_row = wraps_snapshot
3236 .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
3237 .row;
3238
3239 // Create empty lines for the above block
3240 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3241 if placement.start().0 == wrap_row && block.place_above() {
3242 let (_, block) = sorted_blocks_iter.next().unwrap();
3243 expected_block_positions.push((block_row, block.id()));
3244 if block.height() > 0 {
3245 let text = "\n".repeat((block.height() - 1) as usize);
3246 if block_row > 0 {
3247 expected_text.push('\n')
3248 }
3249 expected_text.push_str(&text);
3250 for _ in 0..block.height() {
3251 expected_buffer_rows.push(None);
3252 }
3253 block_row += block.height();
3254 }
3255 } else {
3256 break;
3257 }
3258 }
3259
3260 // Skip lines within replace blocks, then create empty lines for the replace block's height
3261 let mut is_in_replace_block = false;
3262 if let Some((BlockPlacement::Replace(replace_range), block)) =
3263 sorted_blocks_iter.peek()
3264 && wrap_row >= replace_range.start().0
3265 {
3266 is_in_replace_block = true;
3267
3268 if wrap_row == replace_range.start().0 {
3269 if matches!(block, Block::FoldedBuffer { .. }) {
3270 expected_buffer_rows.push(None);
3271 } else {
3272 expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]);
3273 }
3274 }
3275
3276 if wrap_row == replace_range.end().0 {
3277 expected_block_positions.push((block_row, block.id()));
3278 let text = "\n".repeat((block.height() - 1) as usize);
3279 if block_row > 0 {
3280 expected_text.push('\n');
3281 }
3282 expected_text.push_str(&text);
3283
3284 for _ in 1..block.height() {
3285 expected_buffer_rows.push(None);
3286 }
3287 block_row += block.height();
3288
3289 sorted_blocks_iter.next();
3290 }
3291 }
3292
3293 if is_in_replace_block {
3294 expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
3295 } else {
3296 let buffer_row = input_buffer_rows[multibuffer_row as usize];
3297 let soft_wrapped = wraps_snapshot
3298 .to_tab_point(WrapPoint::new(wrap_row, 0))
3299 .column()
3300 > 0;
3301 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
3302 if block_row > 0 {
3303 expected_text.push('\n');
3304 }
3305 expected_text.push_str(input_line);
3306 block_row += 1;
3307 }
3308
3309 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3310 if placement.end().0 == wrap_row && block.place_below() {
3311 let (_, block) = sorted_blocks_iter.next().unwrap();
3312 expected_block_positions.push((block_row, block.id()));
3313 if block.height() > 0 {
3314 let text = "\n".repeat((block.height() - 1) as usize);
3315 if block_row > 0 {
3316 expected_text.push('\n')
3317 }
3318 expected_text.push_str(&text);
3319 for _ in 0..block.height() {
3320 expected_buffer_rows.push(None);
3321 }
3322 block_row += block.height();
3323 }
3324 } else {
3325 break;
3326 }
3327 }
3328 }
3329
3330 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
3331 let expected_row_count = expected_lines.len();
3332 log::info!("expected text: {expected_text:?}");
3333
3334 assert_eq!(
3335 blocks_snapshot.max_point().row + 1,
3336 expected_row_count as u32,
3337 "actual row count != expected row count",
3338 );
3339 assert_eq!(
3340 blocks_snapshot.text(),
3341 expected_text,
3342 "actual text != expected text",
3343 );
3344
3345 for start_row in 0..expected_row_count {
3346 let end_row = rng.random_range(start_row + 1..=expected_row_count);
3347 let mut expected_text = expected_lines[start_row..end_row].join("\n");
3348 if end_row < expected_row_count {
3349 expected_text.push('\n');
3350 }
3351
3352 let actual_text = blocks_snapshot
3353 .chunks(
3354 start_row as u32..end_row as u32,
3355 false,
3356 false,
3357 Highlights::default(),
3358 )
3359 .map(|chunk| chunk.text)
3360 .collect::<String>();
3361 assert_eq!(
3362 actual_text,
3363 expected_text,
3364 "incorrect text starting row row range {:?}",
3365 start_row..end_row
3366 );
3367 assert_eq!(
3368 blocks_snapshot
3369 .row_infos(BlockRow(start_row as u32))
3370 .map(|row_info| row_info.buffer_row)
3371 .collect::<Vec<_>>(),
3372 &expected_buffer_rows[start_row..],
3373 "incorrect buffer_rows starting at row {:?}",
3374 start_row
3375 );
3376 }
3377
3378 assert_eq!(
3379 blocks_snapshot
3380 .blocks_in_range(0..(expected_row_count as u32))
3381 .map(|(row, block)| (row, block.id()))
3382 .collect::<Vec<_>>(),
3383 expected_block_positions,
3384 "invalid blocks_in_range({:?})",
3385 0..expected_row_count
3386 );
3387
3388 for (_, expected_block) in
3389 blocks_snapshot.blocks_in_range(0..(expected_row_count as u32))
3390 {
3391 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
3392 assert_eq!(
3393 actual_block.map(|block| block.id()),
3394 Some(expected_block.id())
3395 );
3396 }
3397
3398 for (block_row, block_id) in expected_block_positions {
3399 if let BlockId::Custom(block_id) = block_id {
3400 assert_eq!(
3401 blocks_snapshot.row_for_block(block_id),
3402 Some(BlockRow(block_row))
3403 );
3404 }
3405 }
3406
3407 let mut expected_longest_rows = Vec::new();
3408 let mut longest_line_len = -1_isize;
3409 for (row, line) in expected_lines.iter().enumerate() {
3410 let row = row as u32;
3411
3412 assert_eq!(
3413 blocks_snapshot.line_len(BlockRow(row)),
3414 line.len() as u32,
3415 "invalid line len for row {}",
3416 row
3417 );
3418
3419 let line_char_count = line.chars().count() as isize;
3420 match line_char_count.cmp(&longest_line_len) {
3421 Ordering::Less => {}
3422 Ordering::Equal => expected_longest_rows.push(row),
3423 Ordering::Greater => {
3424 longest_line_len = line_char_count;
3425 expected_longest_rows.clear();
3426 expected_longest_rows.push(row);
3427 }
3428 }
3429 }
3430
3431 let longest_row = blocks_snapshot.longest_row();
3432 assert!(
3433 expected_longest_rows.contains(&longest_row),
3434 "incorrect longest row {}. expected {:?} with length {}",
3435 longest_row,
3436 expected_longest_rows,
3437 longest_line_len,
3438 );
3439
3440 for _ in 0..10 {
3441 let end_row = rng.random_range(1..=expected_lines.len());
3442 let start_row = rng.random_range(0..end_row);
3443
3444 let mut expected_longest_rows_in_range = vec![];
3445 let mut longest_line_len_in_range = 0;
3446
3447 let mut row = start_row as u32;
3448 for line in &expected_lines[start_row..end_row] {
3449 let line_char_count = line.chars().count() as isize;
3450 match line_char_count.cmp(&longest_line_len_in_range) {
3451 Ordering::Less => {}
3452 Ordering::Equal => expected_longest_rows_in_range.push(row),
3453 Ordering::Greater => {
3454 longest_line_len_in_range = line_char_count;
3455 expected_longest_rows_in_range.clear();
3456 expected_longest_rows_in_range.push(row);
3457 }
3458 }
3459 row += 1;
3460 }
3461
3462 let longest_row_in_range = blocks_snapshot
3463 .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
3464 assert!(
3465 expected_longest_rows_in_range.contains(&longest_row_in_range.0),
3466 "incorrect longest row {} in range {:?}. expected {:?} with length {}",
3467 longest_row,
3468 start_row..end_row,
3469 expected_longest_rows_in_range,
3470 longest_line_len_in_range,
3471 );
3472 }
3473
3474 // Ensure that conversion between block points and wrap points is stable.
3475 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
3476 let wrap_point = WrapPoint::new(row, 0);
3477 let block_point = blocks_snapshot.to_block_point(wrap_point);
3478 let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
3479 let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
3480 assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
3481 assert_eq!(
3482 blocks_snapshot.to_block_point(right_wrap_point),
3483 block_point
3484 );
3485 }
3486
3487 let mut block_point = BlockPoint::new(0, 0);
3488 for c in expected_text.chars() {
3489 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
3490 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
3491 assert_eq!(
3492 blocks_snapshot
3493 .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
3494 left_point,
3495 "block point: {:?}, wrap point: {:?}",
3496 block_point,
3497 blocks_snapshot.to_wrap_point(left_point, Bias::Left)
3498 );
3499 assert_eq!(
3500 left_buffer_point,
3501 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
3502 "{:?} is not valid in buffer coordinates",
3503 left_point
3504 );
3505
3506 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
3507 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
3508 assert_eq!(
3509 blocks_snapshot
3510 .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
3511 right_point,
3512 "block point: {:?}, wrap point: {:?}",
3513 block_point,
3514 blocks_snapshot.to_wrap_point(right_point, Bias::Right)
3515 );
3516 assert_eq!(
3517 right_buffer_point,
3518 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
3519 "{:?} is not valid in buffer coordinates",
3520 right_point
3521 );
3522
3523 if c == '\n' {
3524 block_point.0 += Point::new(1, 0);
3525 } else {
3526 block_point.column += c.len_utf8() as u32;
3527 }
3528 }
3529
3530 for buffer_row in 0..=buffer_snapshot.max_point().row {
3531 let buffer_row = MultiBufferRow(buffer_row);
3532 assert_eq!(
3533 blocks_snapshot.is_line_replaced(buffer_row),
3534 expected_replaced_buffer_rows.contains(&buffer_row),
3535 "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
3536 );
3537 }
3538 }
3539 }
3540
3541 #[gpui::test]
3542 fn test_remove_intersecting_replace_blocks_edge_case(cx: &mut gpui::TestAppContext) {
3543 cx.update(init_test);
3544
3545 let text = "abc\ndef\nghi\njkl\nmno";
3546 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3547 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3548 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3549 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3550 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3551 let (_wrap_map, wraps_snapshot) =
3552 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3553 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3554
3555 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
3556 let _block_id = writer.insert(vec![BlockProperties {
3557 style: BlockStyle::Fixed,
3558 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3559 height: Some(1),
3560 render: Arc::new(|_| div().into_any()),
3561 priority: 0,
3562 }])[0];
3563
3564 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
3565 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
3566
3567 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
3568 writer.remove_intersecting_replace_blocks(
3569 [buffer_snapshot
3570 .anchor_after(Point::new(1, 0))
3571 .to_offset(&buffer_snapshot)
3572 ..buffer_snapshot
3573 .anchor_after(Point::new(1, 0))
3574 .to_offset(&buffer_snapshot)],
3575 false,
3576 );
3577 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
3578 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
3579 }
3580
3581 #[gpui::test]
3582 fn test_folded_buffer_with_near_blocks(cx: &mut gpui::TestAppContext) {
3583 cx.update(init_test);
3584
3585 let text = "line 1\nline 2\nline 3";
3586 let buffer = cx.update(|cx| {
3587 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(2, 6)])], cx)
3588 });
3589 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3590 let buffer_ids = buffer_snapshot
3591 .excerpts()
3592 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3593 .dedup()
3594 .collect::<Vec<_>>();
3595 assert_eq!(buffer_ids.len(), 1);
3596 let buffer_id = buffer_ids[0];
3597
3598 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3599 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3600 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3601 let (_, wrap_snapshot) =
3602 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3603 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
3604
3605 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
3606 writer.insert(vec![BlockProperties {
3607 style: BlockStyle::Fixed,
3608 placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(0, 0))),
3609 height: Some(1),
3610 render: Arc::new(|_| div().into_any()),
3611 priority: 0,
3612 }]);
3613
3614 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
3615 assert_eq!(blocks_snapshot.text(), "\nline 1\n\nline 2\nline 3");
3616
3617 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
3618 buffer.read_with(cx, |buffer, cx| {
3619 writer.fold_buffers([buffer_id], buffer, cx);
3620 });
3621
3622 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
3623 assert_eq!(blocks_snapshot.text(), "");
3624 }
3625
3626 #[gpui::test]
3627 fn test_folded_buffer_with_near_blocks_on_last_line(cx: &mut gpui::TestAppContext) {
3628 cx.update(init_test);
3629
3630 let text = "line 1\nline 2\nline 3\nline 4";
3631 let buffer = cx.update(|cx| {
3632 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(3, 6)])], cx)
3633 });
3634 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3635 let buffer_ids = buffer_snapshot
3636 .excerpts()
3637 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3638 .dedup()
3639 .collect::<Vec<_>>();
3640 assert_eq!(buffer_ids.len(), 1);
3641 let buffer_id = buffer_ids[0];
3642
3643 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3644 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3645 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3646 let (_, wrap_snapshot) =
3647 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3648 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
3649
3650 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
3651 writer.insert(vec![BlockProperties {
3652 style: BlockStyle::Fixed,
3653 placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(3, 6))),
3654 height: Some(1),
3655 render: Arc::new(|_| div().into_any()),
3656 priority: 0,
3657 }]);
3658
3659 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
3660 assert_eq!(blocks_snapshot.text(), "\nline 1\nline 2\nline 3\nline 4\n");
3661
3662 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
3663 buffer.read_with(cx, |buffer, cx| {
3664 writer.fold_buffers([buffer_id], buffer, cx);
3665 });
3666
3667 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
3668 assert_eq!(blocks_snapshot.text(), "");
3669 }
3670
3671 fn init_test(cx: &mut gpui::App) {
3672 let settings = SettingsStore::test(cx);
3673 cx.set_global(settings);
3674 theme::init(theme::LoadThemes::JustBase, cx);
3675 assets::Assets.load_test_fonts(cx);
3676 }
3677
3678 impl Block {
3679 fn as_custom(&self) -> Option<&CustomBlock> {
3680 match self {
3681 Block::Custom(block) => Some(block),
3682 _ => None,
3683 }
3684 }
3685 }
3686
3687 impl BlockSnapshot {
3688 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
3689 self.wrap_snapshot
3690 .to_point(self.to_wrap_point(point, bias), bias)
3691 }
3692 }
3693}