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