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