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