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