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, MultiBufferOffset, MultiBufferRow,
15 MultiBufferSnapshot, RowInfo, ToOffset, ToPoint as _,
16};
17use parking_lot::Mutex;
18use std::{
19 cell::RefCell,
20 cmp::{self, Ordering},
21 fmt::Debug,
22 ops::{Deref, DerefMut, Not, 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<MultiBufferOffset>>,
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<MultiBufferOffset>,
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 transform.block.as_ref().is_none_or(|block| {
1883 block.is_replacement()
1884 && self.transforms.start().0 == self.output_row
1885 && matches!(block, Block::FoldedBuffer { .. }).not()
1886 }) {
1887 self.input_rows.next()
1888 } else {
1889 Some(RowInfo::default())
1890 }
1891 }
1892}
1893
1894impl sum_tree::Item for Transform {
1895 type Summary = TransformSummary;
1896
1897 fn summary(&self, _cx: ()) -> Self::Summary {
1898 self.summary.clone()
1899 }
1900}
1901
1902impl sum_tree::ContextLessSummary for TransformSummary {
1903 fn zero() -> Self {
1904 Default::default()
1905 }
1906
1907 fn add_summary(&mut self, summary: &Self) {
1908 if summary.longest_row_chars > self.longest_row_chars {
1909 self.longest_row = self.output_rows + summary.longest_row;
1910 self.longest_row_chars = summary.longest_row_chars;
1911 }
1912 self.input_rows += summary.input_rows;
1913 self.output_rows += summary.output_rows;
1914 }
1915}
1916
1917impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
1918 fn zero(_cx: ()) -> Self {
1919 Default::default()
1920 }
1921
1922 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
1923 *self += summary.input_rows;
1924 }
1925}
1926
1927impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
1928 fn zero(_cx: ()) -> Self {
1929 Default::default()
1930 }
1931
1932 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
1933 *self += summary.output_rows;
1934 }
1935}
1936
1937impl Deref for BlockContext<'_, '_> {
1938 type Target = App;
1939
1940 fn deref(&self) -> &Self::Target {
1941 self.app
1942 }
1943}
1944
1945impl DerefMut for BlockContext<'_, '_> {
1946 fn deref_mut(&mut self) -> &mut Self::Target {
1947 self.app
1948 }
1949}
1950
1951impl CustomBlock {
1952 pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
1953 self.render.lock()(cx)
1954 }
1955
1956 pub fn start(&self) -> Anchor {
1957 *self.placement.start()
1958 }
1959
1960 pub fn end(&self) -> Anchor {
1961 *self.placement.end()
1962 }
1963
1964 pub fn style(&self) -> BlockStyle {
1965 self.style
1966 }
1967}
1968
1969impl Debug for CustomBlock {
1970 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1971 f.debug_struct("Block")
1972 .field("id", &self.id)
1973 .field("placement", &self.placement)
1974 .field("height", &self.height)
1975 .field("style", &self.style)
1976 .field("priority", &self.priority)
1977 .finish_non_exhaustive()
1978 }
1979}
1980
1981// Count the number of bytes prior to a target point. If the string doesn't contain the target
1982// point, return its total extent. Otherwise return the target point itself.
1983fn offset_for_row(s: &str, target: RowDelta) -> (RowDelta, usize) {
1984 let mut row = 0;
1985 let mut offset = 0;
1986 for (ix, line) in s.split('\n').enumerate() {
1987 if ix > 0 {
1988 row += 1;
1989 offset += 1;
1990 }
1991 if row >= target.0 {
1992 break;
1993 }
1994 offset += line.len();
1995 }
1996 (RowDelta(row), offset)
1997}
1998
1999#[cfg(test)]
2000mod tests {
2001 use super::*;
2002 use crate::{
2003 display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap},
2004 test::test_font,
2005 };
2006 use gpui::{App, AppContext as _, Element, div, font, px};
2007 use itertools::Itertools;
2008 use language::{Buffer, Capability};
2009 use multi_buffer::{ExcerptRange, MultiBuffer};
2010 use rand::prelude::*;
2011 use settings::SettingsStore;
2012 use std::env;
2013 use util::RandomCharIter;
2014
2015 #[gpui::test]
2016 fn test_offset_for_row() {
2017 assert_eq!(offset_for_row("", RowDelta(0)), (RowDelta(0), 0));
2018 assert_eq!(offset_for_row("", RowDelta(1)), (RowDelta(0), 0));
2019 assert_eq!(offset_for_row("abcd", RowDelta(0)), (RowDelta(0), 0));
2020 assert_eq!(offset_for_row("abcd", RowDelta(1)), (RowDelta(0), 4));
2021 assert_eq!(offset_for_row("\n", RowDelta(0)), (RowDelta(0), 0));
2022 assert_eq!(offset_for_row("\n", RowDelta(1)), (RowDelta(1), 1));
2023 assert_eq!(
2024 offset_for_row("abc\ndef\nghi", RowDelta(0)),
2025 (RowDelta(0), 0)
2026 );
2027 assert_eq!(
2028 offset_for_row("abc\ndef\nghi", RowDelta(1)),
2029 (RowDelta(1), 4)
2030 );
2031 assert_eq!(
2032 offset_for_row("abc\ndef\nghi", RowDelta(2)),
2033 (RowDelta(2), 8)
2034 );
2035 assert_eq!(
2036 offset_for_row("abc\ndef\nghi", RowDelta(3)),
2037 (RowDelta(2), 11)
2038 );
2039 }
2040
2041 #[gpui::test]
2042 fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
2043 cx.update(init_test);
2044
2045 let text = "aaa\nbbb\nccc\nddd";
2046
2047 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2048 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2049 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2050 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2051 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2052 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2053 let (wrap_map, wraps_snapshot) =
2054 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2055 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2056
2057 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2058 let block_ids = writer.insert(vec![
2059 BlockProperties {
2060 style: BlockStyle::Fixed,
2061 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2062 height: Some(1),
2063 render: Arc::new(|_| div().into_any()),
2064 priority: 0,
2065 },
2066 BlockProperties {
2067 style: BlockStyle::Fixed,
2068 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2069 height: Some(2),
2070 render: Arc::new(|_| div().into_any()),
2071 priority: 0,
2072 },
2073 BlockProperties {
2074 style: BlockStyle::Fixed,
2075 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2076 height: Some(3),
2077 render: Arc::new(|_| div().into_any()),
2078 priority: 0,
2079 },
2080 ]);
2081
2082 let snapshot = block_map.read(wraps_snapshot, Default::default());
2083 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2084
2085 let blocks = snapshot
2086 .blocks_in_range(BlockRow(0)..BlockRow(8))
2087 .map(|(start_row, block)| {
2088 let block = block.as_custom().unwrap();
2089 (start_row.0..start_row.0 + block.height.unwrap(), block.id)
2090 })
2091 .collect::<Vec<_>>();
2092
2093 // When multiple blocks are on the same line, the newer blocks appear first.
2094 assert_eq!(
2095 blocks,
2096 &[
2097 (1..2, block_ids[0]),
2098 (2..4, block_ids[1]),
2099 (7..10, block_ids[2]),
2100 ]
2101 );
2102
2103 assert_eq!(
2104 snapshot.to_block_point(WrapPoint::new(WrapRow(0), 3)),
2105 BlockPoint::new(BlockRow(0), 3)
2106 );
2107 assert_eq!(
2108 snapshot.to_block_point(WrapPoint::new(WrapRow(1), 0)),
2109 BlockPoint::new(BlockRow(4), 0)
2110 );
2111 assert_eq!(
2112 snapshot.to_block_point(WrapPoint::new(WrapRow(3), 3)),
2113 BlockPoint::new(BlockRow(6), 3)
2114 );
2115
2116 assert_eq!(
2117 snapshot.to_wrap_point(BlockPoint::new(BlockRow(0), 3), Bias::Left),
2118 WrapPoint::new(WrapRow(0), 3)
2119 );
2120 assert_eq!(
2121 snapshot.to_wrap_point(BlockPoint::new(BlockRow(1), 0), Bias::Left),
2122 WrapPoint::new(WrapRow(1), 0)
2123 );
2124 assert_eq!(
2125 snapshot.to_wrap_point(BlockPoint::new(BlockRow(3), 0), Bias::Left),
2126 WrapPoint::new(WrapRow(1), 0)
2127 );
2128 assert_eq!(
2129 snapshot.to_wrap_point(BlockPoint::new(BlockRow(7), 0), Bias::Left),
2130 WrapPoint::new(WrapRow(3), 3)
2131 );
2132
2133 assert_eq!(
2134 snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Left),
2135 BlockPoint::new(BlockRow(0), 3)
2136 );
2137 assert_eq!(
2138 snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Right),
2139 BlockPoint::new(BlockRow(4), 0)
2140 );
2141 assert_eq!(
2142 snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Left),
2143 BlockPoint::new(BlockRow(0), 3)
2144 );
2145 assert_eq!(
2146 snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Right),
2147 BlockPoint::new(BlockRow(4), 0)
2148 );
2149 assert_eq!(
2150 snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Left),
2151 BlockPoint::new(BlockRow(4), 0)
2152 );
2153 assert_eq!(
2154 snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Right),
2155 BlockPoint::new(BlockRow(4), 0)
2156 );
2157 assert_eq!(
2158 snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Left),
2159 BlockPoint::new(BlockRow(6), 3)
2160 );
2161 assert_eq!(
2162 snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Right),
2163 BlockPoint::new(BlockRow(6), 3)
2164 );
2165 assert_eq!(
2166 snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Left),
2167 BlockPoint::new(BlockRow(6), 3)
2168 );
2169 assert_eq!(
2170 snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Right),
2171 BlockPoint::new(BlockRow(6), 3)
2172 );
2173
2174 assert_eq!(
2175 snapshot
2176 .row_infos(BlockRow(0))
2177 .map(|row_info| row_info.buffer_row)
2178 .collect::<Vec<_>>(),
2179 &[
2180 Some(0),
2181 None,
2182 None,
2183 None,
2184 Some(1),
2185 Some(2),
2186 Some(3),
2187 None,
2188 None,
2189 None
2190 ]
2191 );
2192
2193 // Insert a line break, separating two block decorations into separate lines.
2194 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2195 buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
2196 buffer.snapshot(cx)
2197 });
2198
2199 let (inlay_snapshot, inlay_edits) =
2200 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
2201 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2202 let (tab_snapshot, tab_edits) =
2203 tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
2204 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2205 wrap_map.sync(tab_snapshot, tab_edits, cx)
2206 });
2207 let snapshot = block_map.read(wraps_snapshot, wrap_edits);
2208 assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
2209 }
2210
2211 #[gpui::test]
2212 fn test_multibuffer_headers_and_footers(cx: &mut App) {
2213 init_test(cx);
2214
2215 let buffer1 = cx.new(|cx| Buffer::local("Buffer 1", cx));
2216 let buffer2 = cx.new(|cx| Buffer::local("Buffer 2", cx));
2217 let buffer3 = cx.new(|cx| Buffer::local("Buffer 3", cx));
2218
2219 let mut excerpt_ids = Vec::new();
2220 let multi_buffer = cx.new(|cx| {
2221 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
2222 excerpt_ids.extend(multi_buffer.push_excerpts(
2223 buffer1.clone(),
2224 [ExcerptRange::new(0..buffer1.read(cx).len())],
2225 cx,
2226 ));
2227 excerpt_ids.extend(multi_buffer.push_excerpts(
2228 buffer2.clone(),
2229 [ExcerptRange::new(0..buffer2.read(cx).len())],
2230 cx,
2231 ));
2232 excerpt_ids.extend(multi_buffer.push_excerpts(
2233 buffer3.clone(),
2234 [ExcerptRange::new(0..buffer3.read(cx).len())],
2235 cx,
2236 ));
2237
2238 multi_buffer
2239 });
2240
2241 let font = test_font();
2242 let font_size = px(14.);
2243 let font_id = cx.text_system().resolve_font(&font);
2244 let mut wrap_width = px(0.);
2245 for c in "Buff".chars() {
2246 wrap_width += cx
2247 .text_system()
2248 .advance(font_id, font_size, c)
2249 .unwrap()
2250 .width;
2251 }
2252
2253 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2254 let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot);
2255 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2256 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2257 let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
2258
2259 let block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2260 let snapshot = block_map.read(wraps_snapshot, Default::default());
2261
2262 // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
2263 assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3");
2264
2265 let blocks: Vec<_> = snapshot
2266 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
2267 .map(|(row, block)| (row.0..row.0 + block.height(), block.id()))
2268 .collect();
2269 assert_eq!(
2270 blocks,
2271 vec![
2272 (0..1, BlockId::ExcerptBoundary(excerpt_ids[0])), // path, header
2273 (3..4, BlockId::ExcerptBoundary(excerpt_ids[1])), // path, header
2274 (6..7, BlockId::ExcerptBoundary(excerpt_ids[2])), // path, header
2275 ]
2276 );
2277 }
2278
2279 #[gpui::test]
2280 fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
2281 cx.update(init_test);
2282
2283 let text = "aaa\nbbb\nccc\nddd";
2284
2285 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2286 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2287 let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2288 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2289 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2290 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2291 let (_wrap_map, wraps_snapshot) =
2292 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2293 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2294
2295 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2296 let block_ids = writer.insert(vec![
2297 BlockProperties {
2298 style: BlockStyle::Fixed,
2299 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2300 height: Some(1),
2301 render: Arc::new(|_| div().into_any()),
2302 priority: 0,
2303 },
2304 BlockProperties {
2305 style: BlockStyle::Fixed,
2306 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2307 height: Some(2),
2308 render: Arc::new(|_| div().into_any()),
2309 priority: 0,
2310 },
2311 BlockProperties {
2312 style: BlockStyle::Fixed,
2313 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2314 height: Some(3),
2315 render: Arc::new(|_| div().into_any()),
2316 priority: 0,
2317 },
2318 ]);
2319
2320 {
2321 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2322 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2323
2324 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2325
2326 let mut new_heights = HashMap::default();
2327 new_heights.insert(block_ids[0], 2);
2328 block_map_writer.resize(new_heights);
2329 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2330 assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2331 }
2332
2333 {
2334 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2335
2336 let mut new_heights = HashMap::default();
2337 new_heights.insert(block_ids[0], 1);
2338 block_map_writer.resize(new_heights);
2339
2340 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2341 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2342 }
2343
2344 {
2345 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2346
2347 let mut new_heights = HashMap::default();
2348 new_heights.insert(block_ids[0], 0);
2349 block_map_writer.resize(new_heights);
2350
2351 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2352 assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
2353 }
2354
2355 {
2356 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2357
2358 let mut new_heights = HashMap::default();
2359 new_heights.insert(block_ids[0], 3);
2360 block_map_writer.resize(new_heights);
2361
2362 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2363 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2364 }
2365
2366 {
2367 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2368
2369 let mut new_heights = HashMap::default();
2370 new_heights.insert(block_ids[0], 3);
2371 block_map_writer.resize(new_heights);
2372
2373 let snapshot = block_map.read(wraps_snapshot, Default::default());
2374 // Same height as before, should remain the same
2375 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2376 }
2377 }
2378
2379 #[gpui::test]
2380 fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
2381 cx.update(init_test);
2382
2383 let text = "one two three\nfour five six\nseven eight";
2384
2385 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2386 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2387 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2388 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2389 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2390 let (_, wraps_snapshot) = cx.update(|cx| {
2391 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(90.)), cx)
2392 });
2393 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2394
2395 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2396 writer.insert(vec![
2397 BlockProperties {
2398 style: BlockStyle::Fixed,
2399 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
2400 render: Arc::new(|_| div().into_any()),
2401 height: Some(1),
2402 priority: 0,
2403 },
2404 BlockProperties {
2405 style: BlockStyle::Fixed,
2406 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
2407 render: Arc::new(|_| div().into_any()),
2408 height: Some(1),
2409 priority: 0,
2410 },
2411 ]);
2412
2413 // Blocks with an 'above' disposition go above their corresponding buffer line.
2414 // Blocks with a 'below' disposition go below their corresponding buffer line.
2415 let snapshot = block_map.read(wraps_snapshot, Default::default());
2416 assert_eq!(
2417 snapshot.text(),
2418 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
2419 );
2420 }
2421
2422 #[gpui::test]
2423 fn test_replace_lines(cx: &mut gpui::TestAppContext) {
2424 cx.update(init_test);
2425
2426 let text = "line1\nline2\nline3\nline4\nline5";
2427
2428 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2429 let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
2430 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2431 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2432 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2433 let tab_size = 1.try_into().unwrap();
2434 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
2435 let (wrap_map, wraps_snapshot) =
2436 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2437 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2438
2439 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2440 let replace_block_id = writer.insert(vec![BlockProperties {
2441 style: BlockStyle::Fixed,
2442 placement: BlockPlacement::Replace(
2443 buffer_snapshot.anchor_after(Point::new(1, 3))
2444 ..=buffer_snapshot.anchor_before(Point::new(3, 1)),
2445 ),
2446 height: Some(4),
2447 render: Arc::new(|_| div().into_any()),
2448 priority: 0,
2449 }])[0];
2450
2451 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
2452 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2453
2454 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2455 buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
2456 buffer.snapshot(cx)
2457 });
2458 let (inlay_snapshot, inlay_edits) =
2459 inlay_map.sync(buffer_snapshot, buffer_subscription.consume().into_inner());
2460 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2461 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2462 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2463 wrap_map.sync(tab_snapshot, tab_edits, cx)
2464 });
2465 let blocks_snapshot = block_map.read(wraps_snapshot, wrap_edits);
2466 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2467
2468 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2469 buffer.edit(
2470 [(
2471 Point::new(1, 5)..Point::new(1, 5),
2472 "\nline 2.1\nline2.2\nline 2.3\nline 2.4",
2473 )],
2474 None,
2475 cx,
2476 );
2477 buffer.snapshot(cx)
2478 });
2479 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
2480 buffer_snapshot.clone(),
2481 buffer_subscription.consume().into_inner(),
2482 );
2483 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2484 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2485 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2486 wrap_map.sync(tab_snapshot, tab_edits, cx)
2487 });
2488 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
2489 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2490
2491 // Blocks inserted right above the start or right below the end of the replaced region are hidden.
2492 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2493 writer.insert(vec![
2494 BlockProperties {
2495 style: BlockStyle::Fixed,
2496 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))),
2497 height: Some(1),
2498 render: Arc::new(|_| div().into_any()),
2499 priority: 0,
2500 },
2501 BlockProperties {
2502 style: BlockStyle::Fixed,
2503 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
2504 height: Some(1),
2505 render: Arc::new(|_| div().into_any()),
2506 priority: 0,
2507 },
2508 BlockProperties {
2509 style: BlockStyle::Fixed,
2510 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
2511 height: Some(1),
2512 render: Arc::new(|_| div().into_any()),
2513 priority: 0,
2514 },
2515 ]);
2516 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2517 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2518
2519 // Ensure blocks inserted *inside* replaced region are hidden.
2520 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2521 writer.insert(vec![
2522 BlockProperties {
2523 style: BlockStyle::Fixed,
2524 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
2525 height: Some(1),
2526 render: Arc::new(|_| div().into_any()),
2527 priority: 0,
2528 },
2529 BlockProperties {
2530 style: BlockStyle::Fixed,
2531 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
2532 height: Some(1),
2533 render: Arc::new(|_| div().into_any()),
2534 priority: 0,
2535 },
2536 BlockProperties {
2537 style: BlockStyle::Fixed,
2538 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
2539 height: Some(1),
2540 render: Arc::new(|_| div().into_any()),
2541 priority: 0,
2542 },
2543 ]);
2544 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2545 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2546
2547 // Removing the replace block shows all the hidden blocks again.
2548 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2549 writer.remove(HashSet::from_iter([replace_block_id]));
2550 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
2551 assert_eq!(
2552 blocks_snapshot.text(),
2553 "\nline1\n\nline2\n\n\nline 2.1\nline2.2\nline 2.3\nline 2.4\n\nline4\n\nline5"
2554 );
2555 }
2556
2557 #[gpui::test]
2558 fn test_custom_blocks_inside_buffer_folds(cx: &mut gpui::TestAppContext) {
2559 cx.update(init_test);
2560
2561 let text = "111\n222\n333\n444\n555\n666";
2562
2563 let buffer = cx.update(|cx| {
2564 MultiBuffer::build_multi(
2565 [
2566 (text, vec![Point::new(0, 0)..Point::new(0, 3)]),
2567 (
2568 text,
2569 vec![
2570 Point::new(1, 0)..Point::new(1, 3),
2571 Point::new(2, 0)..Point::new(2, 3),
2572 Point::new(3, 0)..Point::new(3, 3),
2573 ],
2574 ),
2575 (
2576 text,
2577 vec![
2578 Point::new(4, 0)..Point::new(4, 3),
2579 Point::new(5, 0)..Point::new(5, 3),
2580 ],
2581 ),
2582 ],
2583 cx,
2584 )
2585 });
2586 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2587 let buffer_ids = buffer_snapshot
2588 .excerpts()
2589 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2590 .dedup()
2591 .collect::<Vec<_>>();
2592 assert_eq!(buffer_ids.len(), 3);
2593 let buffer_id_1 = buffer_ids[0];
2594 let buffer_id_2 = buffer_ids[1];
2595 let buffer_id_3 = buffer_ids[2];
2596
2597 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2598 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2599 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2600 let (_, wrap_snapshot) =
2601 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2602 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
2603 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2604
2605 assert_eq!(
2606 blocks_snapshot.text(),
2607 "\n\n111\n\n\n222\n\n333\n\n444\n\n\n555\n\n666"
2608 );
2609 assert_eq!(
2610 blocks_snapshot
2611 .row_infos(BlockRow(0))
2612 .map(|i| i.buffer_row)
2613 .collect::<Vec<_>>(),
2614 vec![
2615 None,
2616 None,
2617 Some(0),
2618 None,
2619 None,
2620 Some(1),
2621 None,
2622 Some(2),
2623 None,
2624 Some(3),
2625 None,
2626 None,
2627 Some(4),
2628 None,
2629 Some(5),
2630 ]
2631 );
2632
2633 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2634 let excerpt_blocks_2 = writer.insert(vec![
2635 BlockProperties {
2636 style: BlockStyle::Fixed,
2637 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2638 height: Some(1),
2639 render: Arc::new(|_| div().into_any()),
2640 priority: 0,
2641 },
2642 BlockProperties {
2643 style: BlockStyle::Fixed,
2644 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 0))),
2645 height: Some(1),
2646 render: Arc::new(|_| div().into_any()),
2647 priority: 0,
2648 },
2649 BlockProperties {
2650 style: BlockStyle::Fixed,
2651 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 0))),
2652 height: Some(1),
2653 render: Arc::new(|_| div().into_any()),
2654 priority: 0,
2655 },
2656 ]);
2657 let excerpt_blocks_3 = writer.insert(vec![
2658 BlockProperties {
2659 style: BlockStyle::Fixed,
2660 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))),
2661 height: Some(1),
2662 render: Arc::new(|_| div().into_any()),
2663 priority: 0,
2664 },
2665 BlockProperties {
2666 style: BlockStyle::Fixed,
2667 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))),
2668 height: Some(1),
2669 render: Arc::new(|_| div().into_any()),
2670 priority: 0,
2671 },
2672 ]);
2673
2674 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2675 assert_eq!(
2676 blocks_snapshot.text(),
2677 "\n\n111\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
2678 );
2679 assert_eq!(
2680 blocks_snapshot
2681 .row_infos(BlockRow(0))
2682 .map(|i| i.buffer_row)
2683 .collect::<Vec<_>>(),
2684 vec![
2685 None,
2686 None,
2687 Some(0),
2688 None,
2689 None,
2690 None,
2691 Some(1),
2692 None,
2693 None,
2694 Some(2),
2695 None,
2696 Some(3),
2697 None,
2698 None,
2699 None,
2700 None,
2701 Some(4),
2702 None,
2703 Some(5),
2704 None,
2705 ]
2706 );
2707
2708 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2709 buffer.read_with(cx, |buffer, cx| {
2710 writer.fold_buffers([buffer_id_1], buffer, cx);
2711 });
2712 let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
2713 style: BlockStyle::Fixed,
2714 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))),
2715 height: Some(1),
2716 render: Arc::new(|_| div().into_any()),
2717 priority: 0,
2718 }]);
2719 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2720 let blocks = blocks_snapshot
2721 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
2722 .collect::<Vec<_>>();
2723 for (_, block) in &blocks {
2724 if let BlockId::Custom(custom_block_id) = block.id() {
2725 assert!(
2726 !excerpt_blocks_1.contains(&custom_block_id),
2727 "Should have no blocks from the folded buffer"
2728 );
2729 assert!(
2730 excerpt_blocks_2.contains(&custom_block_id)
2731 || excerpt_blocks_3.contains(&custom_block_id),
2732 "Should have only blocks from unfolded buffers"
2733 );
2734 }
2735 }
2736 assert_eq!(
2737 1,
2738 blocks
2739 .iter()
2740 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2741 .count(),
2742 "Should have one folded block, producing a header of the second buffer"
2743 );
2744 assert_eq!(
2745 blocks_snapshot.text(),
2746 "\n\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
2747 );
2748 assert_eq!(
2749 blocks_snapshot
2750 .row_infos(BlockRow(0))
2751 .map(|i| i.buffer_row)
2752 .collect::<Vec<_>>(),
2753 vec![
2754 None,
2755 None,
2756 None,
2757 None,
2758 None,
2759 Some(1),
2760 None,
2761 None,
2762 Some(2),
2763 None,
2764 Some(3),
2765 None,
2766 None,
2767 None,
2768 None,
2769 Some(4),
2770 None,
2771 Some(5),
2772 None,
2773 ]
2774 );
2775
2776 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2777 buffer.read_with(cx, |buffer, cx| {
2778 writer.fold_buffers([buffer_id_2], buffer, cx);
2779 });
2780 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2781 let blocks = blocks_snapshot
2782 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
2783 .collect::<Vec<_>>();
2784 for (_, block) in &blocks {
2785 if let BlockId::Custom(custom_block_id) = block.id() {
2786 assert!(
2787 !excerpt_blocks_1.contains(&custom_block_id),
2788 "Should have no blocks from the folded buffer_1"
2789 );
2790 assert!(
2791 !excerpt_blocks_2.contains(&custom_block_id),
2792 "Should have no blocks from the folded buffer_2"
2793 );
2794 assert!(
2795 excerpt_blocks_3.contains(&custom_block_id),
2796 "Should have only blocks from unfolded buffers"
2797 );
2798 }
2799 }
2800 assert_eq!(
2801 2,
2802 blocks
2803 .iter()
2804 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2805 .count(),
2806 "Should have two folded blocks, producing headers"
2807 );
2808 assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n555\n\n666\n");
2809 assert_eq!(
2810 blocks_snapshot
2811 .row_infos(BlockRow(0))
2812 .map(|i| i.buffer_row)
2813 .collect::<Vec<_>>(),
2814 vec![
2815 None,
2816 None,
2817 None,
2818 None,
2819 None,
2820 None,
2821 None,
2822 Some(4),
2823 None,
2824 Some(5),
2825 None,
2826 ]
2827 );
2828
2829 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2830 buffer.read_with(cx, |buffer, cx| {
2831 writer.unfold_buffers([buffer_id_1], buffer, cx);
2832 });
2833 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2834 let blocks = blocks_snapshot
2835 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
2836 .collect::<Vec<_>>();
2837 for (_, block) in &blocks {
2838 if let BlockId::Custom(custom_block_id) = block.id() {
2839 assert!(
2840 !excerpt_blocks_2.contains(&custom_block_id),
2841 "Should have no blocks from the folded buffer_2"
2842 );
2843 assert!(
2844 excerpt_blocks_1.contains(&custom_block_id)
2845 || excerpt_blocks_3.contains(&custom_block_id),
2846 "Should have only blocks from unfolded buffers"
2847 );
2848 }
2849 }
2850 assert_eq!(
2851 1,
2852 blocks
2853 .iter()
2854 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2855 .count(),
2856 "Should be back to a single folded buffer, producing a header for buffer_2"
2857 );
2858 assert_eq!(
2859 blocks_snapshot.text(),
2860 "\n\n\n111\n\n\n\n\n\n555\n\n666\n",
2861 "Should have extra newline for 111 buffer, due to a new block added when it was folded"
2862 );
2863 assert_eq!(
2864 blocks_snapshot
2865 .row_infos(BlockRow(0))
2866 .map(|i| i.buffer_row)
2867 .collect::<Vec<_>>(),
2868 vec![
2869 None,
2870 None,
2871 None,
2872 Some(0),
2873 None,
2874 None,
2875 None,
2876 None,
2877 None,
2878 Some(4),
2879 None,
2880 Some(5),
2881 None,
2882 ]
2883 );
2884
2885 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2886 buffer.read_with(cx, |buffer, cx| {
2887 writer.fold_buffers([buffer_id_3], buffer, cx);
2888 });
2889 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
2890 let blocks = blocks_snapshot
2891 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
2892 .collect::<Vec<_>>();
2893 for (_, block) in &blocks {
2894 if let BlockId::Custom(custom_block_id) = block.id() {
2895 assert!(
2896 excerpt_blocks_1.contains(&custom_block_id),
2897 "Should have no blocks from the folded buffer_1"
2898 );
2899 assert!(
2900 !excerpt_blocks_2.contains(&custom_block_id),
2901 "Should have only blocks from unfolded buffers"
2902 );
2903 assert!(
2904 !excerpt_blocks_3.contains(&custom_block_id),
2905 "Should have only blocks from unfolded buffers"
2906 );
2907 }
2908 }
2909
2910 assert_eq!(
2911 blocks_snapshot.text(),
2912 "\n\n\n111\n\n\n\n",
2913 "Should have a single, first buffer left after folding"
2914 );
2915 assert_eq!(
2916 blocks_snapshot
2917 .row_infos(BlockRow(0))
2918 .map(|i| i.buffer_row)
2919 .collect::<Vec<_>>(),
2920 vec![None, None, None, Some(0), None, None, None, None,]
2921 );
2922 }
2923
2924 #[gpui::test]
2925 fn test_basic_buffer_fold(cx: &mut gpui::TestAppContext) {
2926 cx.update(init_test);
2927
2928 let text = "111";
2929
2930 let buffer = cx.update(|cx| {
2931 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(0, 3)])], cx)
2932 });
2933 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2934 let buffer_ids = buffer_snapshot
2935 .excerpts()
2936 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2937 .dedup()
2938 .collect::<Vec<_>>();
2939 assert_eq!(buffer_ids.len(), 1);
2940 let buffer_id = buffer_ids[0];
2941
2942 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
2943 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2944 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2945 let (_, wrap_snapshot) =
2946 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2947 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
2948 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2949
2950 assert_eq!(blocks_snapshot.text(), "\n\n111");
2951
2952 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2953 buffer.read_with(cx, |buffer, cx| {
2954 writer.fold_buffers([buffer_id], buffer, cx);
2955 });
2956 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
2957 let blocks = blocks_snapshot
2958 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
2959 .collect::<Vec<_>>();
2960 assert_eq!(
2961 1,
2962 blocks
2963 .iter()
2964 .filter(|(_, block)| { matches!(block, Block::FoldedBuffer { .. }) })
2965 .count(),
2966 "Should have one folded block, producing a header of the second buffer"
2967 );
2968 assert_eq!(blocks_snapshot.text(), "\n");
2969 assert_eq!(
2970 blocks_snapshot
2971 .row_infos(BlockRow(0))
2972 .map(|i| i.buffer_row)
2973 .collect::<Vec<_>>(),
2974 vec![None, None],
2975 "When fully folded, should be no buffer rows"
2976 );
2977 }
2978
2979 #[gpui::test(iterations = 60)]
2980 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
2981 cx.update(init_test);
2982
2983 let operations = env::var("OPERATIONS")
2984 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2985 .unwrap_or(10);
2986
2987 let wrap_width = if rng.random_bool(0.2) {
2988 None
2989 } else {
2990 Some(px(rng.random_range(0.0..=100.0)))
2991 };
2992 let tab_size = 1.try_into().unwrap();
2993 let font_size = px(14.0);
2994 let buffer_start_header_height = rng.random_range(1..=5);
2995 let excerpt_header_height = rng.random_range(1..=5);
2996
2997 log::info!("Wrap width: {:?}", wrap_width);
2998 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
2999 let is_singleton = rng.random();
3000 let buffer = if is_singleton {
3001 let len = rng.random_range(0..10);
3002 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
3003 log::info!("initial singleton buffer text: {:?}", text);
3004 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
3005 } else {
3006 cx.update(|cx| {
3007 let multibuffer = MultiBuffer::build_random(&mut rng, cx);
3008 log::info!(
3009 "initial multi-buffer text: {:?}",
3010 multibuffer.read(cx).read(cx).text()
3011 );
3012 multibuffer
3013 })
3014 };
3015
3016 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3017 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3018 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3019 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3020 let font = test_font();
3021 let (wrap_map, wraps_snapshot) =
3022 cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
3023 let mut block_map = BlockMap::new(
3024 wraps_snapshot,
3025 buffer_start_header_height,
3026 excerpt_header_height,
3027 );
3028
3029 for _ in 0..operations {
3030 let mut buffer_edits = Vec::new();
3031 match rng.random_range(0..=100) {
3032 0..=19 => {
3033 let wrap_width = if rng.random_bool(0.2) {
3034 None
3035 } else {
3036 Some(px(rng.random_range(0.0..=100.0)))
3037 };
3038 log::info!("Setting wrap width to {:?}", wrap_width);
3039 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
3040 }
3041 20..=39 => {
3042 let block_count = rng.random_range(1..=5);
3043 let block_properties = (0..block_count)
3044 .map(|_| {
3045 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
3046 let offset = buffer.clip_offset(
3047 rng.random_range(MultiBufferOffset(0)..=buffer.len()),
3048 Bias::Left,
3049 );
3050 let mut min_height = 0;
3051 let placement = match rng.random_range(0..3) {
3052 0 => {
3053 min_height = 1;
3054 let start = buffer.anchor_after(offset);
3055 let end = buffer.anchor_after(buffer.clip_offset(
3056 rng.random_range(offset..=buffer.len()),
3057 Bias::Left,
3058 ));
3059 BlockPlacement::Replace(start..=end)
3060 }
3061 1 => BlockPlacement::Above(buffer.anchor_after(offset)),
3062 _ => BlockPlacement::Below(buffer.anchor_after(offset)),
3063 };
3064
3065 let height = rng.random_range(min_height..512);
3066 BlockProperties {
3067 style: BlockStyle::Fixed,
3068 placement,
3069 height: Some(height),
3070 render: Arc::new(|_| div().into_any()),
3071 priority: 0,
3072 }
3073 })
3074 .collect::<Vec<_>>();
3075
3076 let (inlay_snapshot, inlay_edits) =
3077 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3078 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3079 let (tab_snapshot, tab_edits) =
3080 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3081 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3082 wrap_map.sync(tab_snapshot, tab_edits, cx)
3083 });
3084 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3085 let block_ids =
3086 block_map.insert(block_properties.iter().map(|props| BlockProperties {
3087 placement: props.placement.clone(),
3088 height: props.height,
3089 style: props.style,
3090 render: Arc::new(|_| div().into_any()),
3091 priority: 0,
3092 }));
3093
3094 for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3095 log::info!(
3096 "inserted block {:?} with height {:?} and id {:?}",
3097 block_properties
3098 .placement
3099 .as_ref()
3100 .map(|p| p.to_point(&buffer_snapshot)),
3101 block_properties.height,
3102 block_id
3103 );
3104 }
3105 }
3106 40..=59 if !block_map.custom_blocks.is_empty() => {
3107 let block_count = rng.random_range(1..=4.min(block_map.custom_blocks.len()));
3108 let block_ids_to_remove = block_map
3109 .custom_blocks
3110 .choose_multiple(&mut rng, block_count)
3111 .map(|block| block.id)
3112 .collect::<HashSet<_>>();
3113
3114 let (inlay_snapshot, inlay_edits) =
3115 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3116 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3117 let (tab_snapshot, tab_edits) =
3118 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3119 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3120 wrap_map.sync(tab_snapshot, tab_edits, cx)
3121 });
3122 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3123 log::info!(
3124 "removing {} blocks: {:?}",
3125 block_ids_to_remove.len(),
3126 block_ids_to_remove
3127 );
3128 block_map.remove(block_ids_to_remove);
3129 }
3130 60..=79 => {
3131 if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
3132 log::info!("Noop fold/unfold operation on a singleton buffer");
3133 continue;
3134 }
3135 let (inlay_snapshot, inlay_edits) =
3136 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3137 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3138 let (tab_snapshot, tab_edits) =
3139 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3140 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3141 wrap_map.sync(tab_snapshot, tab_edits, cx)
3142 });
3143 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3144 let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
3145 let folded_buffers = block_map
3146 .0
3147 .folded_buffers
3148 .iter()
3149 .cloned()
3150 .collect::<Vec<_>>();
3151 let mut unfolded_buffers = buffer.excerpt_buffer_ids();
3152 unfolded_buffers.dedup();
3153 log::debug!("All buffers {unfolded_buffers:?}");
3154 log::debug!("Folded buffers {folded_buffers:?}");
3155 unfolded_buffers
3156 .retain(|buffer_id| !block_map.0.folded_buffers.contains(buffer_id));
3157 (unfolded_buffers, folded_buffers)
3158 });
3159 let mut folded_count = folded_buffers.len();
3160 let mut unfolded_count = unfolded_buffers.len();
3161
3162 let fold = !unfolded_buffers.is_empty() && rng.random_bool(0.5);
3163 let unfold = !folded_buffers.is_empty() && rng.random_bool(0.5);
3164 if !fold && !unfold {
3165 log::info!(
3166 "Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3167 );
3168 continue;
3169 }
3170
3171 buffer.update(cx, |buffer, cx| {
3172 if fold {
3173 let buffer_to_fold =
3174 unfolded_buffers[rng.random_range(0..unfolded_buffers.len())];
3175 log::info!("Folding {buffer_to_fold:?}");
3176 let related_excerpts = buffer_snapshot
3177 .excerpts()
3178 .filter_map(|(excerpt_id, buffer, range)| {
3179 if buffer.remote_id() == buffer_to_fold {
3180 Some((
3181 excerpt_id,
3182 buffer
3183 .text_for_range(range.context)
3184 .collect::<String>(),
3185 ))
3186 } else {
3187 None
3188 }
3189 })
3190 .collect::<Vec<_>>();
3191 log::info!(
3192 "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
3193 );
3194 folded_count += 1;
3195 unfolded_count -= 1;
3196 block_map.fold_buffers([buffer_to_fold], buffer, cx);
3197 }
3198 if unfold {
3199 let buffer_to_unfold =
3200 folded_buffers[rng.random_range(0..folded_buffers.len())];
3201 log::info!("Unfolding {buffer_to_unfold:?}");
3202 unfolded_count += 1;
3203 folded_count -= 1;
3204 block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
3205 }
3206 log::info!(
3207 "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3208 );
3209 });
3210 }
3211 _ => {
3212 buffer.update(cx, |buffer, cx| {
3213 let mutation_count = rng.random_range(1..=5);
3214 let subscription = buffer.subscribe();
3215 buffer.randomly_mutate(&mut rng, mutation_count, cx);
3216 buffer_snapshot = buffer.snapshot(cx);
3217 buffer_edits.extend(subscription.consume());
3218 log::info!("buffer text: {:?}", buffer_snapshot.text());
3219 });
3220 }
3221 }
3222
3223 let (inlay_snapshot, inlay_edits) =
3224 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
3225 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3226 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3227 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3228 wrap_map.sync(tab_snapshot, tab_edits, cx)
3229 });
3230 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
3231 assert_eq!(
3232 blocks_snapshot.transforms.summary().input_rows,
3233 wraps_snapshot.max_point().row() + RowDelta(1)
3234 );
3235 log::info!("wrapped text: {:?}", wraps_snapshot.text());
3236 log::info!("blocks text: {:?}", blocks_snapshot.text());
3237
3238 let mut expected_blocks = Vec::new();
3239 expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
3240 Some((
3241 block.placement.to_wrap_row(&wraps_snapshot)?,
3242 Block::Custom(block.clone()),
3243 ))
3244 }));
3245
3246 // Note that this needs to be synced with the related section in BlockMap::sync
3247 expected_blocks.extend(block_map.header_and_footer_blocks(
3248 &buffer_snapshot,
3249 MultiBufferOffset(0)..,
3250 &wraps_snapshot,
3251 ));
3252
3253 BlockMap::sort_blocks(&mut expected_blocks);
3254
3255 for (placement, block) in &expected_blocks {
3256 log::info!(
3257 "Block {:?} placement: {:?} Height: {:?}",
3258 block.id(),
3259 placement,
3260 block.height()
3261 );
3262 }
3263
3264 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
3265
3266 let input_buffer_rows = buffer_snapshot
3267 .row_infos(MultiBufferRow(0))
3268 .map(|row| row.buffer_row)
3269 .collect::<Vec<_>>();
3270 let mut expected_buffer_rows = Vec::new();
3271 let mut expected_text = String::new();
3272 let mut expected_block_positions = Vec::new();
3273 let mut expected_replaced_buffer_rows = HashSet::default();
3274 let input_text = wraps_snapshot.text();
3275
3276 // Loop over the input lines, creating (N - 1) empty lines for
3277 // blocks of height N.
3278 //
3279 // It's important to note that output *starts* as one empty line,
3280 // so we special case row 0 to assume a leading '\n'.
3281 //
3282 // Linehood is the birthright of strings.
3283 let input_text_lines = input_text.split('\n').enumerate().peekable();
3284 let mut block_row = 0;
3285 for (wrap_row, input_line) in input_text_lines {
3286 let wrap_row = WrapRow(wrap_row as u32);
3287 let multibuffer_row = wraps_snapshot
3288 .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
3289 .row;
3290
3291 // Create empty lines for the above block
3292 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3293 if *placement.start() == wrap_row && block.place_above() {
3294 let (_, block) = sorted_blocks_iter.next().unwrap();
3295 expected_block_positions.push((block_row, block.id()));
3296 if block.height() > 0 {
3297 let text = "\n".repeat((block.height() - 1) as usize);
3298 if block_row > 0 {
3299 expected_text.push('\n')
3300 }
3301 expected_text.push_str(&text);
3302 for _ in 0..block.height() {
3303 expected_buffer_rows.push(None);
3304 }
3305 block_row += block.height();
3306 }
3307 } else {
3308 break;
3309 }
3310 }
3311
3312 // Skip lines within replace blocks, then create empty lines for the replace block's height
3313 let mut is_in_replace_block = false;
3314 if let Some((BlockPlacement::Replace(replace_range), block)) =
3315 sorted_blocks_iter.peek()
3316 && wrap_row >= *replace_range.start()
3317 {
3318 is_in_replace_block = true;
3319
3320 if wrap_row == *replace_range.start() {
3321 if matches!(block, Block::FoldedBuffer { .. }) {
3322 expected_buffer_rows.push(None);
3323 } else {
3324 expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]);
3325 }
3326 }
3327
3328 if wrap_row == *replace_range.end() {
3329 expected_block_positions.push((block_row, block.id()));
3330 let text = "\n".repeat((block.height() - 1) as usize);
3331 if block_row > 0 {
3332 expected_text.push('\n');
3333 }
3334 expected_text.push_str(&text);
3335
3336 for _ in 1..block.height() {
3337 expected_buffer_rows.push(None);
3338 }
3339 block_row += block.height();
3340
3341 sorted_blocks_iter.next();
3342 }
3343 }
3344
3345 if is_in_replace_block {
3346 expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
3347 } else {
3348 let buffer_row = input_buffer_rows[multibuffer_row as usize];
3349 let soft_wrapped = wraps_snapshot
3350 .to_tab_point(WrapPoint::new(wrap_row, 0))
3351 .column()
3352 > 0;
3353 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
3354 if block_row > 0 {
3355 expected_text.push('\n');
3356 }
3357 expected_text.push_str(input_line);
3358 block_row += 1;
3359 }
3360
3361 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3362 if *placement.end() == wrap_row && block.place_below() {
3363 let (_, block) = sorted_blocks_iter.next().unwrap();
3364 expected_block_positions.push((block_row, block.id()));
3365 if block.height() > 0 {
3366 let text = "\n".repeat((block.height() - 1) as usize);
3367 if block_row > 0 {
3368 expected_text.push('\n')
3369 }
3370 expected_text.push_str(&text);
3371 for _ in 0..block.height() {
3372 expected_buffer_rows.push(None);
3373 }
3374 block_row += block.height();
3375 }
3376 } else {
3377 break;
3378 }
3379 }
3380 }
3381
3382 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
3383 let expected_row_count = expected_lines.len();
3384 log::info!("expected text: {expected_text:?}");
3385
3386 assert_eq!(
3387 blocks_snapshot.max_point().row + 1,
3388 expected_row_count as u32,
3389 "actual row count != expected row count",
3390 );
3391 assert_eq!(
3392 blocks_snapshot.text(),
3393 expected_text,
3394 "actual text != expected text",
3395 );
3396
3397 for start_row in 0..expected_row_count {
3398 let end_row = rng.random_range(start_row + 1..=expected_row_count);
3399 let mut expected_text = expected_lines[start_row..end_row].join("\n");
3400 if end_row < expected_row_count {
3401 expected_text.push('\n');
3402 }
3403
3404 let actual_text = blocks_snapshot
3405 .chunks(
3406 BlockRow(start_row as u32)..BlockRow(end_row as u32),
3407 false,
3408 false,
3409 Highlights::default(),
3410 )
3411 .map(|chunk| chunk.text)
3412 .collect::<String>();
3413 assert_eq!(
3414 actual_text,
3415 expected_text,
3416 "incorrect text starting row row range {:?}",
3417 start_row..end_row
3418 );
3419 assert_eq!(
3420 blocks_snapshot
3421 .row_infos(BlockRow(start_row as u32))
3422 .map(|row_info| row_info.buffer_row)
3423 .collect::<Vec<_>>(),
3424 &expected_buffer_rows[start_row..],
3425 "incorrect buffer_rows starting at row {:?}",
3426 start_row
3427 );
3428 }
3429
3430 assert_eq!(
3431 blocks_snapshot
3432 .blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
3433 .map(|(row, block)| (row.0, block.id()))
3434 .collect::<Vec<_>>(),
3435 expected_block_positions,
3436 "invalid blocks_in_range({:?})",
3437 0..expected_row_count
3438 );
3439
3440 for (_, expected_block) in
3441 blocks_snapshot.blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
3442 {
3443 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
3444 assert_eq!(
3445 actual_block.map(|block| block.id()),
3446 Some(expected_block.id())
3447 );
3448 }
3449
3450 for (block_row, block_id) in expected_block_positions {
3451 if let BlockId::Custom(block_id) = block_id {
3452 assert_eq!(
3453 blocks_snapshot.row_for_block(block_id),
3454 Some(BlockRow(block_row))
3455 );
3456 }
3457 }
3458
3459 let mut expected_longest_rows = Vec::new();
3460 let mut longest_line_len = -1_isize;
3461 for (row, line) in expected_lines.iter().enumerate() {
3462 let row = row as u32;
3463
3464 assert_eq!(
3465 blocks_snapshot.line_len(BlockRow(row)),
3466 line.len() as u32,
3467 "invalid line len for row {}",
3468 row
3469 );
3470
3471 let line_char_count = line.chars().count() as isize;
3472 match line_char_count.cmp(&longest_line_len) {
3473 Ordering::Less => {}
3474 Ordering::Equal => expected_longest_rows.push(row),
3475 Ordering::Greater => {
3476 longest_line_len = line_char_count;
3477 expected_longest_rows.clear();
3478 expected_longest_rows.push(row);
3479 }
3480 }
3481 }
3482
3483 let longest_row = blocks_snapshot.longest_row();
3484 assert!(
3485 expected_longest_rows.contains(&longest_row.0),
3486 "incorrect longest row {}. expected {:?} with length {}",
3487 longest_row.0,
3488 expected_longest_rows,
3489 longest_line_len,
3490 );
3491
3492 for _ in 0..10 {
3493 let end_row = rng.random_range(1..=expected_lines.len());
3494 let start_row = rng.random_range(0..end_row);
3495
3496 let mut expected_longest_rows_in_range = vec![];
3497 let mut longest_line_len_in_range = 0;
3498
3499 let mut row = start_row as u32;
3500 for line in &expected_lines[start_row..end_row] {
3501 let line_char_count = line.chars().count() as isize;
3502 match line_char_count.cmp(&longest_line_len_in_range) {
3503 Ordering::Less => {}
3504 Ordering::Equal => expected_longest_rows_in_range.push(row),
3505 Ordering::Greater => {
3506 longest_line_len_in_range = line_char_count;
3507 expected_longest_rows_in_range.clear();
3508 expected_longest_rows_in_range.push(row);
3509 }
3510 }
3511 row += 1;
3512 }
3513
3514 let longest_row_in_range = blocks_snapshot
3515 .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
3516 assert!(
3517 expected_longest_rows_in_range.contains(&longest_row_in_range.0),
3518 "incorrect longest row {} in range {:?}. expected {:?} with length {}",
3519 longest_row.0,
3520 start_row..end_row,
3521 expected_longest_rows_in_range,
3522 longest_line_len_in_range,
3523 );
3524 }
3525
3526 // Ensure that conversion between block points and wrap points is stable.
3527 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row().0 {
3528 let wrap_point = WrapPoint::new(WrapRow(row), 0);
3529 let block_point = blocks_snapshot.to_block_point(wrap_point);
3530 let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
3531 let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
3532 assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
3533 assert_eq!(
3534 blocks_snapshot.to_block_point(right_wrap_point),
3535 block_point
3536 );
3537 }
3538
3539 let mut block_point = BlockPoint::new(BlockRow(0), 0);
3540 for c in expected_text.chars() {
3541 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
3542 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
3543 assert_eq!(
3544 blocks_snapshot
3545 .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
3546 left_point,
3547 "block point: {:?}, wrap point: {:?}",
3548 block_point,
3549 blocks_snapshot.to_wrap_point(left_point, Bias::Left)
3550 );
3551 assert_eq!(
3552 left_buffer_point,
3553 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
3554 "{:?} is not valid in buffer coordinates",
3555 left_point
3556 );
3557
3558 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
3559 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
3560 assert_eq!(
3561 blocks_snapshot
3562 .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
3563 right_point,
3564 "block point: {:?}, wrap point: {:?}",
3565 block_point,
3566 blocks_snapshot.to_wrap_point(right_point, Bias::Right)
3567 );
3568 assert_eq!(
3569 right_buffer_point,
3570 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
3571 "{:?} is not valid in buffer coordinates",
3572 right_point
3573 );
3574
3575 if c == '\n' {
3576 block_point.0 += Point::new(1, 0);
3577 } else {
3578 block_point.column += c.len_utf8() as u32;
3579 }
3580 }
3581
3582 for buffer_row in 0..=buffer_snapshot.max_point().row {
3583 let buffer_row = MultiBufferRow(buffer_row);
3584 assert_eq!(
3585 blocks_snapshot.is_line_replaced(buffer_row),
3586 expected_replaced_buffer_rows.contains(&buffer_row),
3587 "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
3588 );
3589 }
3590 }
3591 }
3592
3593 #[gpui::test]
3594 fn test_remove_intersecting_replace_blocks_edge_case(cx: &mut gpui::TestAppContext) {
3595 cx.update(init_test);
3596
3597 let text = "abc\ndef\nghi\njkl\nmno";
3598 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3599 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3600 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3601 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3602 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3603 let (_wrap_map, wraps_snapshot) =
3604 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3605 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3606
3607 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
3608 let _block_id = writer.insert(vec![BlockProperties {
3609 style: BlockStyle::Fixed,
3610 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3611 height: Some(1),
3612 render: Arc::new(|_| div().into_any()),
3613 priority: 0,
3614 }])[0];
3615
3616 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
3617 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
3618
3619 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
3620 writer.remove_intersecting_replace_blocks(
3621 [buffer_snapshot
3622 .anchor_after(Point::new(1, 0))
3623 .to_offset(&buffer_snapshot)
3624 ..buffer_snapshot
3625 .anchor_after(Point::new(1, 0))
3626 .to_offset(&buffer_snapshot)],
3627 false,
3628 );
3629 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
3630 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
3631 }
3632
3633 #[gpui::test]
3634 fn test_folded_buffer_with_near_blocks(cx: &mut gpui::TestAppContext) {
3635 cx.update(init_test);
3636
3637 let text = "line 1\nline 2\nline 3";
3638 let buffer = cx.update(|cx| {
3639 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(2, 6)])], cx)
3640 });
3641 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3642 let buffer_ids = buffer_snapshot
3643 .excerpts()
3644 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3645 .dedup()
3646 .collect::<Vec<_>>();
3647 assert_eq!(buffer_ids.len(), 1);
3648 let buffer_id = buffer_ids[0];
3649
3650 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3651 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3652 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3653 let (_, wrap_snapshot) =
3654 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3655 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
3656
3657 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
3658 writer.insert(vec![BlockProperties {
3659 style: BlockStyle::Fixed,
3660 placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(0, 0))),
3661 height: Some(1),
3662 render: Arc::new(|_| div().into_any()),
3663 priority: 0,
3664 }]);
3665
3666 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
3667 assert_eq!(blocks_snapshot.text(), "\nline 1\n\nline 2\nline 3");
3668
3669 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
3670 buffer.read_with(cx, |buffer, cx| {
3671 writer.fold_buffers([buffer_id], buffer, cx);
3672 });
3673
3674 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
3675 assert_eq!(blocks_snapshot.text(), "");
3676 }
3677
3678 #[gpui::test]
3679 fn test_folded_buffer_with_near_blocks_on_last_line(cx: &mut gpui::TestAppContext) {
3680 cx.update(init_test);
3681
3682 let text = "line 1\nline 2\nline 3\nline 4";
3683 let buffer = cx.update(|cx| {
3684 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(3, 6)])], cx)
3685 });
3686 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3687 let buffer_ids = buffer_snapshot
3688 .excerpts()
3689 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3690 .dedup()
3691 .collect::<Vec<_>>();
3692 assert_eq!(buffer_ids.len(), 1);
3693 let buffer_id = buffer_ids[0];
3694
3695 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3696 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3697 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3698 let (_, wrap_snapshot) =
3699 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3700 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
3701
3702 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
3703 writer.insert(vec![BlockProperties {
3704 style: BlockStyle::Fixed,
3705 placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(3, 6))),
3706 height: Some(1),
3707 render: Arc::new(|_| div().into_any()),
3708 priority: 0,
3709 }]);
3710
3711 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
3712 assert_eq!(blocks_snapshot.text(), "\nline 1\nline 2\nline 3\nline 4\n");
3713
3714 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
3715 buffer.read_with(cx, |buffer, cx| {
3716 writer.fold_buffers([buffer_id], buffer, cx);
3717 });
3718
3719 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
3720 assert_eq!(blocks_snapshot.text(), "");
3721 }
3722
3723 fn init_test(cx: &mut gpui::App) {
3724 let settings = SettingsStore::test(cx);
3725 cx.set_global(settings);
3726 theme::init(theme::LoadThemes::JustBase, cx);
3727 assets::Assets.load_test_fonts(cx);
3728 }
3729
3730 impl Block {
3731 fn as_custom(&self) -> Option<&CustomBlock> {
3732 match self {
3733 Block::Custom(block) => Some(block),
3734 _ => None,
3735 }
3736 }
3737 }
3738
3739 impl BlockSnapshot {
3740 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
3741 self.wrap_snapshot
3742 .to_point(self.to_wrap_point(point, bias), bias)
3743 }
3744 }
3745}