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