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