1use super::{
2 Highlights,
3 fold_map::Chunk,
4 wrap_map::{self, WrapEdit, WrapPatch, WrapPoint, WrapSnapshot},
5};
6use crate::{
7 EditorStyle, GutterDimensions,
8 display_map::{dimensions::RowDelta, wrap_map::WrapRow},
9};
10use collections::{Bound, HashMap, HashSet};
11use gpui::{AnyElement, App, EntityId, Pixels, Window};
12use language::{Patch, Point};
13use multi_buffer::{
14 Anchor, ExcerptId, ExcerptInfo, MultiBuffer, MultiBufferRow, MultiBufferSnapshot, RowInfo,
15 ToOffset, ToPoint as _,
16};
17use parking_lot::Mutex;
18use std::{
19 cell::RefCell,
20 cmp::{self, Ordering},
21 fmt::Debug,
22 ops::{Deref, DerefMut, 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<usize>>,
1212 inclusive: bool,
1213 ) {
1214 let wrap_snapshot = self.0.wrap_snapshot.borrow();
1215 let mut blocks_to_remove = HashSet::default();
1216 for range in ranges {
1217 for block in self.blocks_intersecting_buffer_range(range, inclusive) {
1218 if matches!(block.placement, BlockPlacement::Replace(_)) {
1219 blocks_to_remove.insert(block.id);
1220 }
1221 }
1222 }
1223 drop(wrap_snapshot);
1224 self.remove(blocks_to_remove);
1225 }
1226
1227 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId) {
1228 self.0.buffers_with_disabled_headers.insert(buffer_id);
1229 }
1230
1231 pub fn fold_buffers(
1232 &mut self,
1233 buffer_ids: impl IntoIterator<Item = BufferId>,
1234 multi_buffer: &MultiBuffer,
1235 cx: &App,
1236 ) {
1237 self.fold_or_unfold_buffers(true, buffer_ids, multi_buffer, cx);
1238 }
1239
1240 pub fn unfold_buffers(
1241 &mut self,
1242 buffer_ids: impl IntoIterator<Item = BufferId>,
1243 multi_buffer: &MultiBuffer,
1244 cx: &App,
1245 ) {
1246 self.fold_or_unfold_buffers(false, buffer_ids, multi_buffer, cx);
1247 }
1248
1249 fn fold_or_unfold_buffers(
1250 &mut self,
1251 fold: bool,
1252 buffer_ids: impl IntoIterator<Item = BufferId>,
1253 multi_buffer: &MultiBuffer,
1254 cx: &App,
1255 ) {
1256 let mut ranges = Vec::new();
1257 for buffer_id in buffer_ids {
1258 if fold {
1259 self.0.folded_buffers.insert(buffer_id);
1260 } else {
1261 self.0.folded_buffers.remove(&buffer_id);
1262 }
1263 ranges.extend(multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx));
1264 }
1265 ranges.sort_unstable_by_key(|range| range.start);
1266
1267 let mut edits = Patch::default();
1268 let wrap_snapshot = self.0.wrap_snapshot.borrow().clone();
1269 for range in ranges {
1270 let last_edit_row = cmp::min(
1271 wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + WrapRow(1),
1272 wrap_snapshot.max_point().row(),
1273 ) + WrapRow(1);
1274 let range = wrap_snapshot.make_wrap_point(range.start, Bias::Left).row()..last_edit_row;
1275 edits.push(Edit {
1276 old: range.clone(),
1277 new: range,
1278 });
1279 }
1280
1281 self.0.sync(&wrap_snapshot, edits);
1282 }
1283
1284 fn blocks_intersecting_buffer_range(
1285 &self,
1286 range: Range<usize>,
1287 inclusive: bool,
1288 ) -> &[Arc<CustomBlock>] {
1289 if range.is_empty() && !inclusive {
1290 return &[];
1291 }
1292 let wrap_snapshot = self.0.wrap_snapshot.borrow();
1293 let buffer = wrap_snapshot.buffer_snapshot();
1294
1295 let start_block_ix = match self.0.custom_blocks.binary_search_by(|block| {
1296 let block_end = block.end().to_offset(buffer);
1297 block_end.cmp(&range.start).then(Ordering::Greater)
1298 }) {
1299 Ok(ix) | Err(ix) => ix,
1300 };
1301 let end_block_ix = match self.0.custom_blocks[start_block_ix..].binary_search_by(|block| {
1302 let block_start = block.start().to_offset(buffer);
1303 block_start.cmp(&range.end).then(if inclusive {
1304 Ordering::Less
1305 } else {
1306 Ordering::Greater
1307 })
1308 }) {
1309 Ok(ix) | Err(ix) => ix,
1310 };
1311
1312 &self.0.custom_blocks[start_block_ix..][..end_block_ix]
1313 }
1314}
1315
1316impl BlockSnapshot {
1317 #[cfg(test)]
1318 pub fn text(&self) -> String {
1319 self.chunks(
1320 BlockRow(0)..self.transforms.summary().output_rows,
1321 false,
1322 false,
1323 Highlights::default(),
1324 )
1325 .map(|chunk| chunk.text)
1326 .collect()
1327 }
1328
1329 pub(crate) fn chunks<'a>(
1330 &'a self,
1331 rows: Range<BlockRow>,
1332 language_aware: bool,
1333 masked: bool,
1334 highlights: Highlights<'a>,
1335 ) -> BlockChunks<'a> {
1336 let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
1337
1338 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1339 cursor.seek(&rows.start, Bias::Right);
1340 let transform_output_start = cursor.start().0;
1341 let transform_input_start = cursor.start().1;
1342
1343 let mut input_start = transform_input_start;
1344 let mut input_end = transform_input_start;
1345 if let Some(transform) = cursor.item()
1346 && transform.block.is_none()
1347 {
1348 input_start += rows.start - transform_output_start;
1349 input_end += cmp::min(
1350 rows.end - transform_output_start,
1351 RowDelta(transform.summary.input_rows.0),
1352 );
1353 }
1354
1355 BlockChunks {
1356 input_chunks: self.wrap_snapshot.chunks(
1357 input_start..input_end,
1358 language_aware,
1359 highlights,
1360 ),
1361 input_chunk: Default::default(),
1362 transforms: cursor,
1363 output_row: rows.start,
1364 line_count_overflow: RowDelta(0),
1365 max_output_row,
1366 masked,
1367 }
1368 }
1369
1370 pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows<'_> {
1371 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1372 cursor.seek(&start_row, Bias::Right);
1373 let Dimensions(output_start, input_start, _) = cursor.start();
1374 let overshoot = if cursor
1375 .item()
1376 .is_some_and(|transform| transform.block.is_none())
1377 {
1378 start_row - *output_start
1379 } else {
1380 RowDelta(0)
1381 };
1382 let input_start_row = *input_start + overshoot;
1383 BlockRows {
1384 transforms: cursor,
1385 input_rows: self.wrap_snapshot.row_infos(input_start_row),
1386 output_row: start_row,
1387 started: false,
1388 }
1389 }
1390
1391 pub fn blocks_in_range(
1392 &self,
1393 rows: Range<BlockRow>,
1394 ) -> impl Iterator<Item = (BlockRow, &Block)> {
1395 let mut cursor = self.transforms.cursor::<BlockRow>(());
1396 cursor.seek(&rows.start, Bias::Left);
1397 while *cursor.start() < rows.start && cursor.end() <= rows.start {
1398 cursor.next();
1399 }
1400
1401 std::iter::from_fn(move || {
1402 while let Some(transform) = cursor.item() {
1403 let start_row = *cursor.start();
1404 if start_row > rows.end
1405 || (start_row == rows.end
1406 && transform
1407 .block
1408 .as_ref()
1409 .is_some_and(|block| block.height() > 0))
1410 {
1411 break;
1412 }
1413 if let Some(block) = &transform.block {
1414 cursor.next();
1415 return Some((start_row, block));
1416 } else {
1417 cursor.next();
1418 }
1419 }
1420 None
1421 })
1422 }
1423
1424 pub(crate) fn sticky_header_excerpt(&self, position: f64) -> Option<StickyHeaderExcerpt<'_>> {
1425 let top_row = position as u32;
1426 let mut cursor = self.transforms.cursor::<BlockRow>(());
1427 cursor.seek(&BlockRow(top_row), Bias::Right);
1428
1429 while let Some(transform) = cursor.item() {
1430 match &transform.block {
1431 Some(
1432 Block::ExcerptBoundary { excerpt, .. } | Block::BufferHeader { excerpt, .. },
1433 ) => {
1434 return Some(StickyHeaderExcerpt { excerpt });
1435 }
1436 Some(block) if block.is_buffer_header() => return None,
1437 _ => {
1438 cursor.prev();
1439 continue;
1440 }
1441 }
1442 }
1443
1444 None
1445 }
1446
1447 pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
1448 let buffer = self.wrap_snapshot.buffer_snapshot();
1449 let wrap_point = match block_id {
1450 BlockId::Custom(custom_block_id) => {
1451 let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
1452 return Some(Block::Custom(custom_block.clone()));
1453 }
1454 BlockId::ExcerptBoundary(next_excerpt_id) => {
1455 let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
1456 self.wrap_snapshot
1457 .make_wrap_point(excerpt_range.start, Bias::Left)
1458 }
1459 BlockId::FoldedBuffer(excerpt_id) => self
1460 .wrap_snapshot
1461 .make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left),
1462 };
1463 let wrap_row = wrap_point.row();
1464
1465 let mut cursor = self.transforms.cursor::<WrapRow>(());
1466 cursor.seek(&wrap_row, Bias::Left);
1467
1468 while let Some(transform) = cursor.item() {
1469 if let Some(block) = transform.block.as_ref() {
1470 if block.id() == block_id {
1471 return Some(block.clone());
1472 }
1473 } else if *cursor.start() > wrap_row {
1474 break;
1475 }
1476
1477 cursor.next();
1478 }
1479
1480 None
1481 }
1482
1483 pub fn max_point(&self) -> BlockPoint {
1484 let row = self
1485 .transforms
1486 .summary()
1487 .output_rows
1488 .saturating_sub(RowDelta(1));
1489 BlockPoint::new(row, self.line_len(row))
1490 }
1491
1492 pub fn longest_row(&self) -> BlockRow {
1493 self.transforms.summary().longest_row
1494 }
1495
1496 pub fn longest_row_in_range(&self, range: Range<BlockRow>) -> BlockRow {
1497 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1498 cursor.seek(&range.start, Bias::Right);
1499
1500 let mut longest_row = range.start;
1501 let mut longest_row_chars = 0;
1502 if let Some(transform) = cursor.item() {
1503 if transform.block.is_none() {
1504 let &Dimensions(output_start, input_start, _) = cursor.start();
1505 let overshoot = range.start - output_start;
1506 let wrap_start_row = input_start + WrapRow(overshoot.0);
1507 let wrap_end_row = cmp::min(
1508 input_start + WrapRow((range.end - output_start).0),
1509 cursor.end().1,
1510 );
1511 let summary = self
1512 .wrap_snapshot
1513 .text_summary_for_range(wrap_start_row..wrap_end_row);
1514 longest_row = BlockRow(range.start.0 + summary.longest_row);
1515 longest_row_chars = summary.longest_row_chars;
1516 }
1517 cursor.next();
1518 }
1519
1520 let cursor_start_row = cursor.start().0;
1521 if range.end > cursor_start_row {
1522 let summary = cursor.summary::<_, TransformSummary>(&range.end, Bias::Right);
1523 if summary.longest_row_chars > longest_row_chars {
1524 longest_row = cursor_start_row + summary.longest_row;
1525 longest_row_chars = summary.longest_row_chars;
1526 }
1527
1528 if let Some(transform) = cursor.item()
1529 && transform.block.is_none()
1530 {
1531 let &Dimensions(output_start, input_start, _) = cursor.start();
1532 let overshoot = range.end - output_start;
1533 let wrap_start_row = input_start;
1534 let wrap_end_row = input_start + overshoot;
1535 let summary = self
1536 .wrap_snapshot
1537 .text_summary_for_range(wrap_start_row..wrap_end_row);
1538 if summary.longest_row_chars > longest_row_chars {
1539 longest_row = output_start + RowDelta(summary.longest_row);
1540 }
1541 }
1542 }
1543
1544 longest_row
1545 }
1546
1547 pub(super) fn line_len(&self, row: BlockRow) -> u32 {
1548 let (start, _, item) =
1549 self.transforms
1550 .find::<Dimensions<BlockRow, WrapRow>, _>((), &row, Bias::Right);
1551 if let Some(transform) = item {
1552 let Dimensions(output_start, input_start, _) = start;
1553 let overshoot = row - output_start;
1554 if transform.block.is_some() {
1555 0
1556 } else {
1557 self.wrap_snapshot.line_len(input_start + overshoot)
1558 }
1559 } else if row == BlockRow(0) {
1560 0
1561 } else {
1562 panic!("row out of range");
1563 }
1564 }
1565
1566 pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
1567 let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
1568 item.is_some_and(|t| t.block.is_some())
1569 }
1570
1571 pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool {
1572 let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
1573 let Some(transform) = item else {
1574 return false;
1575 };
1576 matches!(transform.block, Some(Block::FoldedBuffer { .. }))
1577 }
1578
1579 pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool {
1580 let wrap_point = self
1581 .wrap_snapshot
1582 .make_wrap_point(Point::new(row.0, 0), Bias::Left);
1583 let (_, _, item) = self
1584 .transforms
1585 .find::<WrapRow, _>((), &wrap_point.row(), Bias::Right);
1586 item.is_some_and(|transform| {
1587 transform
1588 .block
1589 .as_ref()
1590 .is_some_and(|block| block.is_replacement())
1591 })
1592 }
1593
1594 pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
1595 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
1596 cursor.seek(&BlockRow(point.row), Bias::Right);
1597
1598 let max_input_row = self.transforms.summary().input_rows;
1599 let mut search_left = (bias == Bias::Left && cursor.start().1 > WrapRow(0))
1600 || cursor.end().1 == max_input_row;
1601 let mut reversed = false;
1602
1603 loop {
1604 if let Some(transform) = cursor.item() {
1605 let Dimensions(output_start_row, input_start_row, _) = cursor.start();
1606 let Dimensions(output_end_row, input_end_row, _) = cursor.end();
1607 let output_start = Point::new(output_start_row.0, 0);
1608 let input_start = Point::new(input_start_row.0, 0);
1609 let input_end = Point::new(input_end_row.0, 0);
1610
1611 match transform.block.as_ref() {
1612 Some(block) => {
1613 if block.is_replacement()
1614 && (((bias == Bias::Left || search_left) && output_start <= point.0)
1615 || (!search_left && output_start >= point.0))
1616 {
1617 return BlockPoint(output_start);
1618 }
1619 }
1620 None => {
1621 let input_point = if point.row >= output_end_row.0 {
1622 let line_len = self.wrap_snapshot.line_len(input_end_row - RowDelta(1));
1623 self.wrap_snapshot.clip_point(
1624 WrapPoint::new(input_end_row - RowDelta(1), line_len),
1625 bias,
1626 )
1627 } else {
1628 let output_overshoot = point.0.saturating_sub(output_start);
1629 self.wrap_snapshot
1630 .clip_point(WrapPoint(input_start + output_overshoot), bias)
1631 };
1632
1633 if (input_start..input_end).contains(&input_point.0) {
1634 let input_overshoot = input_point.0.saturating_sub(input_start);
1635 return BlockPoint(output_start + input_overshoot);
1636 }
1637 }
1638 }
1639
1640 if search_left {
1641 cursor.prev();
1642 } else {
1643 cursor.next();
1644 }
1645 } else if reversed {
1646 return self.max_point();
1647 } else {
1648 reversed = true;
1649 search_left = !search_left;
1650 cursor.seek(&BlockRow(point.row), Bias::Right);
1651 }
1652 }
1653 }
1654
1655 pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
1656 let (start, _, item) = self.transforms.find::<Dimensions<WrapRow, BlockRow>, _>(
1657 (),
1658 &wrap_point.row(),
1659 Bias::Right,
1660 );
1661 if let Some(transform) = item {
1662 if transform.block.is_some() {
1663 BlockPoint::new(start.1, 0)
1664 } else {
1665 let Dimensions(input_start_row, output_start_row, _) = start;
1666 let input_start = Point::new(input_start_row.0, 0);
1667 let output_start = Point::new(output_start_row.0, 0);
1668 let input_overshoot = wrap_point.0 - input_start;
1669 BlockPoint(output_start + input_overshoot)
1670 }
1671 } else {
1672 self.max_point()
1673 }
1674 }
1675
1676 pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
1677 let (start, end, item) = self.transforms.find::<Dimensions<BlockRow, WrapRow>, _>(
1678 (),
1679 &BlockRow(block_point.row),
1680 Bias::Right,
1681 );
1682 if let Some(transform) = item {
1683 match transform.block.as_ref() {
1684 Some(block) => {
1685 if block.place_below() {
1686 let wrap_row = start.1 - RowDelta(1);
1687 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1688 } else if block.place_above() {
1689 WrapPoint::new(start.1, 0)
1690 } else if bias == Bias::Left {
1691 WrapPoint::new(start.1, 0)
1692 } else {
1693 let wrap_row = end.1 - RowDelta(1);
1694 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1695 }
1696 }
1697 None => {
1698 let overshoot = block_point.row() - start.0;
1699 let wrap_row = start.1 + RowDelta(overshoot.0);
1700 WrapPoint::new(wrap_row, block_point.column)
1701 }
1702 }
1703 } else {
1704 self.wrap_snapshot.max_point()
1705 }
1706 }
1707}
1708
1709impl BlockChunks<'_> {
1710 /// Go to the next transform
1711 fn advance(&mut self) {
1712 self.input_chunk = Chunk::default();
1713 self.transforms.next();
1714 while let Some(transform) = self.transforms.item() {
1715 if transform
1716 .block
1717 .as_ref()
1718 .is_some_and(|block| block.height() == 0)
1719 {
1720 self.transforms.next();
1721 } else {
1722 break;
1723 }
1724 }
1725
1726 if self
1727 .transforms
1728 .item()
1729 .is_some_and(|transform| transform.block.is_none())
1730 {
1731 let start_input_row = self.transforms.start().1;
1732 let start_output_row = self.transforms.start().0;
1733 if start_output_row < self.max_output_row {
1734 let end_input_row = cmp::min(
1735 self.transforms.end().1,
1736 start_input_row + (self.max_output_row - start_output_row),
1737 );
1738 self.input_chunks.seek(start_input_row..end_input_row);
1739 }
1740 }
1741 }
1742}
1743
1744pub struct StickyHeaderExcerpt<'a> {
1745 pub excerpt: &'a ExcerptInfo,
1746}
1747
1748impl<'a> Iterator for BlockChunks<'a> {
1749 type Item = Chunk<'a>;
1750
1751 fn next(&mut self) -> Option<Self::Item> {
1752 if self.output_row >= self.max_output_row {
1753 return None;
1754 }
1755
1756 if self.line_count_overflow > RowDelta(0) {
1757 let lines = self.line_count_overflow.0.min(u128::BITS);
1758 self.line_count_overflow.0 -= lines;
1759 self.output_row += RowDelta(lines);
1760 return Some(Chunk {
1761 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..lines as usize]) },
1762 chars: 1u128.unbounded_shl(lines).wrapping_sub(1),
1763 ..Default::default()
1764 });
1765 }
1766
1767 let transform = self.transforms.item()?;
1768 if transform.block.is_some() {
1769 let block_start = self.transforms.start().0;
1770 let mut block_end = self.transforms.end().0;
1771 self.advance();
1772 if self.transforms.item().is_none() {
1773 block_end -= RowDelta(1);
1774 }
1775
1776 let start_in_block = self.output_row - block_start;
1777 let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
1778 let line_count = end_in_block - start_in_block;
1779 let lines = RowDelta(line_count.0.min(u128::BITS));
1780 self.line_count_overflow = line_count - lines;
1781 self.output_row += lines;
1782
1783 return Some(Chunk {
1784 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..lines.0 as usize]) },
1785 chars: 1u128.unbounded_shl(lines.0).wrapping_sub(1),
1786 ..Default::default()
1787 });
1788 }
1789
1790 if self.input_chunk.text.is_empty() {
1791 if let Some(input_chunk) = self.input_chunks.next() {
1792 self.input_chunk = input_chunk;
1793 } else {
1794 if self.output_row < self.max_output_row {
1795 self.output_row.0 += 1;
1796 self.advance();
1797 if self.transforms.item().is_some() {
1798 return Some(Chunk {
1799 text: "\n",
1800 chars: 1,
1801 ..Default::default()
1802 });
1803 }
1804 }
1805 return None;
1806 }
1807 }
1808
1809 let transform_end = self.transforms.end().0;
1810 let (prefix_rows, prefix_bytes) =
1811 offset_for_row(self.input_chunk.text, transform_end - self.output_row);
1812 self.output_row += prefix_rows;
1813
1814 let (mut prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
1815 self.input_chunk.text = suffix;
1816 self.input_chunk.tabs >>= prefix_bytes.saturating_sub(1);
1817 self.input_chunk.chars >>= prefix_bytes.saturating_sub(1);
1818
1819 let mut tabs = self.input_chunk.tabs;
1820 let mut chars = self.input_chunk.chars;
1821
1822 if self.masked {
1823 // Not great for multibyte text because to keep cursor math correct we
1824 // need to have the same number of chars in the input as output.
1825 let chars_count = prefix.chars().count();
1826 let bullet_len = chars_count;
1827 prefix = unsafe { std::str::from_utf8_unchecked(&BULLETS[..bullet_len]) };
1828 chars = 1u128.unbounded_shl(bullet_len as u32).wrapping_sub(1);
1829 tabs = 0;
1830 }
1831
1832 let chunk = Chunk {
1833 text: prefix,
1834 tabs,
1835 chars,
1836 ..self.input_chunk.clone()
1837 };
1838
1839 if self.output_row == transform_end {
1840 self.advance();
1841 }
1842
1843 Some(chunk)
1844 }
1845}
1846
1847impl Iterator for BlockRows<'_> {
1848 type Item = RowInfo;
1849
1850 fn next(&mut self) -> Option<Self::Item> {
1851 if self.started {
1852 self.output_row.0 += 1;
1853 } else {
1854 self.started = true;
1855 }
1856
1857 if self.output_row >= self.transforms.end().0 {
1858 self.transforms.next();
1859 while let Some(transform) = self.transforms.item() {
1860 if transform
1861 .block
1862 .as_ref()
1863 .is_some_and(|block| block.height() == 0)
1864 {
1865 self.transforms.next();
1866 } else {
1867 break;
1868 }
1869 }
1870
1871 let transform = self.transforms.item()?;
1872 if transform
1873 .block
1874 .as_ref()
1875 .is_none_or(|block| block.is_replacement())
1876 {
1877 self.input_rows.seek(self.transforms.start().1);
1878 }
1879 }
1880
1881 let transform = self.transforms.item()?;
1882 if 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 = 100)]
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 =
3047 buffer.clip_offset(rng.random_range(0..=buffer.len()), Bias::Left);
3048 let mut min_height = 0;
3049 let placement = match rng.random_range(0..3) {
3050 0 => {
3051 min_height = 1;
3052 let start = buffer.anchor_after(offset);
3053 let end = buffer.anchor_after(buffer.clip_offset(
3054 rng.random_range(offset..=buffer.len()),
3055 Bias::Left,
3056 ));
3057 BlockPlacement::Replace(start..=end)
3058 }
3059 1 => BlockPlacement::Above(buffer.anchor_after(offset)),
3060 _ => BlockPlacement::Below(buffer.anchor_after(offset)),
3061 };
3062
3063 let height = rng.random_range(min_height..512);
3064 BlockProperties {
3065 style: BlockStyle::Fixed,
3066 placement,
3067 height: Some(height),
3068 render: Arc::new(|_| div().into_any()),
3069 priority: 0,
3070 }
3071 })
3072 .collect::<Vec<_>>();
3073
3074 let (inlay_snapshot, inlay_edits) =
3075 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3076 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3077 let (tab_snapshot, tab_edits) =
3078 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3079 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3080 wrap_map.sync(tab_snapshot, tab_edits, cx)
3081 });
3082 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3083 let block_ids =
3084 block_map.insert(block_properties.iter().map(|props| BlockProperties {
3085 placement: props.placement.clone(),
3086 height: props.height,
3087 style: props.style,
3088 render: Arc::new(|_| div().into_any()),
3089 priority: 0,
3090 }));
3091
3092 for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3093 log::info!(
3094 "inserted block {:?} with height {:?} and id {:?}",
3095 block_properties
3096 .placement
3097 .as_ref()
3098 .map(|p| p.to_point(&buffer_snapshot)),
3099 block_properties.height,
3100 block_id
3101 );
3102 }
3103 }
3104 40..=59 if !block_map.custom_blocks.is_empty() => {
3105 let block_count = rng.random_range(1..=4.min(block_map.custom_blocks.len()));
3106 let block_ids_to_remove = block_map
3107 .custom_blocks
3108 .choose_multiple(&mut rng, block_count)
3109 .map(|block| block.id)
3110 .collect::<HashSet<_>>();
3111
3112 let (inlay_snapshot, inlay_edits) =
3113 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3114 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3115 let (tab_snapshot, tab_edits) =
3116 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3117 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3118 wrap_map.sync(tab_snapshot, tab_edits, cx)
3119 });
3120 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3121 log::info!(
3122 "removing {} blocks: {:?}",
3123 block_ids_to_remove.len(),
3124 block_ids_to_remove
3125 );
3126 block_map.remove(block_ids_to_remove);
3127 }
3128 60..=79 => {
3129 if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
3130 log::info!("Noop fold/unfold operation on a singleton buffer");
3131 continue;
3132 }
3133 let (inlay_snapshot, inlay_edits) =
3134 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3135 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3136 let (tab_snapshot, tab_edits) =
3137 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3138 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3139 wrap_map.sync(tab_snapshot, tab_edits, cx)
3140 });
3141 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3142 let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
3143 let folded_buffers = block_map
3144 .0
3145 .folded_buffers
3146 .iter()
3147 .cloned()
3148 .collect::<Vec<_>>();
3149 let mut unfolded_buffers = buffer.excerpt_buffer_ids();
3150 unfolded_buffers.dedup();
3151 log::debug!("All buffers {unfolded_buffers:?}");
3152 log::debug!("Folded buffers {folded_buffers:?}");
3153 unfolded_buffers
3154 .retain(|buffer_id| !block_map.0.folded_buffers.contains(buffer_id));
3155 (unfolded_buffers, folded_buffers)
3156 });
3157 let mut folded_count = folded_buffers.len();
3158 let mut unfolded_count = unfolded_buffers.len();
3159
3160 let fold = !unfolded_buffers.is_empty() && rng.random_bool(0.5);
3161 let unfold = !folded_buffers.is_empty() && rng.random_bool(0.5);
3162 if !fold && !unfold {
3163 log::info!(
3164 "Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3165 );
3166 continue;
3167 }
3168
3169 buffer.update(cx, |buffer, cx| {
3170 if fold {
3171 let buffer_to_fold =
3172 unfolded_buffers[rng.random_range(0..unfolded_buffers.len())];
3173 log::info!("Folding {buffer_to_fold:?}");
3174 let related_excerpts = buffer_snapshot
3175 .excerpts()
3176 .filter_map(|(excerpt_id, buffer, range)| {
3177 if buffer.remote_id() == buffer_to_fold {
3178 Some((
3179 excerpt_id,
3180 buffer
3181 .text_for_range(range.context)
3182 .collect::<String>(),
3183 ))
3184 } else {
3185 None
3186 }
3187 })
3188 .collect::<Vec<_>>();
3189 log::info!(
3190 "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
3191 );
3192 folded_count += 1;
3193 unfolded_count -= 1;
3194 block_map.fold_buffers([buffer_to_fold], buffer, cx);
3195 }
3196 if unfold {
3197 let buffer_to_unfold =
3198 folded_buffers[rng.random_range(0..folded_buffers.len())];
3199 log::info!("Unfolding {buffer_to_unfold:?}");
3200 unfolded_count += 1;
3201 folded_count -= 1;
3202 block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
3203 }
3204 log::info!(
3205 "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3206 );
3207 });
3208 }
3209 _ => {
3210 buffer.update(cx, |buffer, cx| {
3211 let mutation_count = rng.random_range(1..=5);
3212 let subscription = buffer.subscribe();
3213 buffer.randomly_mutate(&mut rng, mutation_count, cx);
3214 buffer_snapshot = buffer.snapshot(cx);
3215 buffer_edits.extend(subscription.consume());
3216 log::info!("buffer text: {:?}", buffer_snapshot.text());
3217 });
3218 }
3219 }
3220
3221 let (inlay_snapshot, inlay_edits) =
3222 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
3223 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3224 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3225 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3226 wrap_map.sync(tab_snapshot, tab_edits, cx)
3227 });
3228 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
3229 assert_eq!(
3230 blocks_snapshot.transforms.summary().input_rows,
3231 wraps_snapshot.max_point().row() + RowDelta(1)
3232 );
3233 log::info!("wrapped text: {:?}", wraps_snapshot.text());
3234 log::info!("blocks text: {:?}", blocks_snapshot.text());
3235
3236 let mut expected_blocks = Vec::new();
3237 expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
3238 Some((
3239 block.placement.to_wrap_row(&wraps_snapshot)?,
3240 Block::Custom(block.clone()),
3241 ))
3242 }));
3243
3244 // Note that this needs to be synced with the related section in BlockMap::sync
3245 expected_blocks.extend(block_map.header_and_footer_blocks(
3246 &buffer_snapshot,
3247 0..,
3248 &wraps_snapshot,
3249 ));
3250
3251 BlockMap::sort_blocks(&mut expected_blocks);
3252
3253 for (placement, block) in &expected_blocks {
3254 log::info!(
3255 "Block {:?} placement: {:?} Height: {:?}",
3256 block.id(),
3257 placement,
3258 block.height()
3259 );
3260 }
3261
3262 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
3263
3264 let input_buffer_rows = buffer_snapshot
3265 .row_infos(MultiBufferRow(0))
3266 .map(|row| row.buffer_row)
3267 .collect::<Vec<_>>();
3268 let mut expected_buffer_rows = Vec::new();
3269 let mut expected_text = String::new();
3270 let mut expected_block_positions = Vec::new();
3271 let mut expected_replaced_buffer_rows = HashSet::default();
3272 let input_text = wraps_snapshot.text();
3273
3274 // Loop over the input lines, creating (N - 1) empty lines for
3275 // blocks of height N.
3276 //
3277 // It's important to note that output *starts* as one empty line,
3278 // so we special case row 0 to assume a leading '\n'.
3279 //
3280 // Linehood is the birthright of strings.
3281 let input_text_lines = input_text.split('\n').enumerate().peekable();
3282 let mut block_row = 0;
3283 for (wrap_row, input_line) in input_text_lines {
3284 let wrap_row = WrapRow(wrap_row as u32);
3285 let multibuffer_row = wraps_snapshot
3286 .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
3287 .row;
3288
3289 // Create empty lines for the above block
3290 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3291 if *placement.start() == wrap_row && block.place_above() {
3292 let (_, block) = sorted_blocks_iter.next().unwrap();
3293 expected_block_positions.push((block_row, block.id()));
3294 if block.height() > 0 {
3295 let text = "\n".repeat((block.height() - 1) as usize);
3296 if block_row > 0 {
3297 expected_text.push('\n')
3298 }
3299 expected_text.push_str(&text);
3300 for _ in 0..block.height() {
3301 expected_buffer_rows.push(None);
3302 }
3303 block_row += block.height();
3304 }
3305 } else {
3306 break;
3307 }
3308 }
3309
3310 // Skip lines within replace blocks, then create empty lines for the replace block's height
3311 let mut is_in_replace_block = false;
3312 if let Some((BlockPlacement::Replace(replace_range), block)) =
3313 sorted_blocks_iter.peek()
3314 && wrap_row >= *replace_range.start()
3315 {
3316 is_in_replace_block = true;
3317
3318 if wrap_row == *replace_range.start() {
3319 if matches!(block, Block::FoldedBuffer { .. }) {
3320 expected_buffer_rows.push(None);
3321 } else {
3322 expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]);
3323 }
3324 }
3325
3326 if wrap_row == *replace_range.end() {
3327 expected_block_positions.push((block_row, block.id()));
3328 let text = "\n".repeat((block.height() - 1) as usize);
3329 if block_row > 0 {
3330 expected_text.push('\n');
3331 }
3332 expected_text.push_str(&text);
3333
3334 for _ in 1..block.height() {
3335 expected_buffer_rows.push(None);
3336 }
3337 block_row += block.height();
3338
3339 sorted_blocks_iter.next();
3340 }
3341 }
3342
3343 if is_in_replace_block {
3344 expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
3345 } else {
3346 let buffer_row = input_buffer_rows[multibuffer_row as usize];
3347 let soft_wrapped = wraps_snapshot
3348 .to_tab_point(WrapPoint::new(wrap_row, 0))
3349 .column()
3350 > 0;
3351 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
3352 if block_row > 0 {
3353 expected_text.push('\n');
3354 }
3355 expected_text.push_str(input_line);
3356 block_row += 1;
3357 }
3358
3359 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3360 if *placement.end() == wrap_row && block.place_below() {
3361 let (_, block) = sorted_blocks_iter.next().unwrap();
3362 expected_block_positions.push((block_row, block.id()));
3363 if block.height() > 0 {
3364 let text = "\n".repeat((block.height() - 1) as usize);
3365 if block_row > 0 {
3366 expected_text.push('\n')
3367 }
3368 expected_text.push_str(&text);
3369 for _ in 0..block.height() {
3370 expected_buffer_rows.push(None);
3371 }
3372 block_row += block.height();
3373 }
3374 } else {
3375 break;
3376 }
3377 }
3378 }
3379
3380 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
3381 let expected_row_count = expected_lines.len();
3382 log::info!("expected text: {expected_text:?}");
3383
3384 assert_eq!(
3385 blocks_snapshot.max_point().row + 1,
3386 expected_row_count as u32,
3387 "actual row count != expected row count",
3388 );
3389 assert_eq!(
3390 blocks_snapshot.text(),
3391 expected_text,
3392 "actual text != expected text",
3393 );
3394
3395 for start_row in 0..expected_row_count {
3396 let end_row = rng.random_range(start_row + 1..=expected_row_count);
3397 let mut expected_text = expected_lines[start_row..end_row].join("\n");
3398 if end_row < expected_row_count {
3399 expected_text.push('\n');
3400 }
3401
3402 let actual_text = blocks_snapshot
3403 .chunks(
3404 BlockRow(start_row as u32)..BlockRow(end_row as u32),
3405 false,
3406 false,
3407 Highlights::default(),
3408 )
3409 .map(|chunk| chunk.text)
3410 .collect::<String>();
3411 assert_eq!(
3412 actual_text,
3413 expected_text,
3414 "incorrect text starting row row range {:?}",
3415 start_row..end_row
3416 );
3417 assert_eq!(
3418 blocks_snapshot
3419 .row_infos(BlockRow(start_row as u32))
3420 .map(|row_info| row_info.buffer_row)
3421 .collect::<Vec<_>>(),
3422 &expected_buffer_rows[start_row..],
3423 "incorrect buffer_rows starting at row {:?}",
3424 start_row
3425 );
3426 }
3427
3428 assert_eq!(
3429 blocks_snapshot
3430 .blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
3431 .map(|(row, block)| (row.0, block.id()))
3432 .collect::<Vec<_>>(),
3433 expected_block_positions,
3434 "invalid blocks_in_range({:?})",
3435 0..expected_row_count
3436 );
3437
3438 for (_, expected_block) in
3439 blocks_snapshot.blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
3440 {
3441 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
3442 assert_eq!(
3443 actual_block.map(|block| block.id()),
3444 Some(expected_block.id())
3445 );
3446 }
3447
3448 for (block_row, block_id) in expected_block_positions {
3449 if let BlockId::Custom(block_id) = block_id {
3450 assert_eq!(
3451 blocks_snapshot.row_for_block(block_id),
3452 Some(BlockRow(block_row))
3453 );
3454 }
3455 }
3456
3457 let mut expected_longest_rows = Vec::new();
3458 let mut longest_line_len = -1_isize;
3459 for (row, line) in expected_lines.iter().enumerate() {
3460 let row = row as u32;
3461
3462 assert_eq!(
3463 blocks_snapshot.line_len(BlockRow(row)),
3464 line.len() as u32,
3465 "invalid line len for row {}",
3466 row
3467 );
3468
3469 let line_char_count = line.chars().count() as isize;
3470 match line_char_count.cmp(&longest_line_len) {
3471 Ordering::Less => {}
3472 Ordering::Equal => expected_longest_rows.push(row),
3473 Ordering::Greater => {
3474 longest_line_len = line_char_count;
3475 expected_longest_rows.clear();
3476 expected_longest_rows.push(row);
3477 }
3478 }
3479 }
3480
3481 let longest_row = blocks_snapshot.longest_row();
3482 assert!(
3483 expected_longest_rows.contains(&longest_row.0),
3484 "incorrect longest row {}. expected {:?} with length {}",
3485 longest_row.0,
3486 expected_longest_rows,
3487 longest_line_len,
3488 );
3489
3490 for _ in 0..10 {
3491 let end_row = rng.random_range(1..=expected_lines.len());
3492 let start_row = rng.random_range(0..end_row);
3493
3494 let mut expected_longest_rows_in_range = vec![];
3495 let mut longest_line_len_in_range = 0;
3496
3497 let mut row = start_row as u32;
3498 for line in &expected_lines[start_row..end_row] {
3499 let line_char_count = line.chars().count() as isize;
3500 match line_char_count.cmp(&longest_line_len_in_range) {
3501 Ordering::Less => {}
3502 Ordering::Equal => expected_longest_rows_in_range.push(row),
3503 Ordering::Greater => {
3504 longest_line_len_in_range = line_char_count;
3505 expected_longest_rows_in_range.clear();
3506 expected_longest_rows_in_range.push(row);
3507 }
3508 }
3509 row += 1;
3510 }
3511
3512 let longest_row_in_range = blocks_snapshot
3513 .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
3514 assert!(
3515 expected_longest_rows_in_range.contains(&longest_row_in_range.0),
3516 "incorrect longest row {} in range {:?}. expected {:?} with length {}",
3517 longest_row.0,
3518 start_row..end_row,
3519 expected_longest_rows_in_range,
3520 longest_line_len_in_range,
3521 );
3522 }
3523
3524 // Ensure that conversion between block points and wrap points is stable.
3525 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row().0 {
3526 let wrap_point = WrapPoint::new(WrapRow(row), 0);
3527 let block_point = blocks_snapshot.to_block_point(wrap_point);
3528 let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
3529 let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
3530 assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
3531 assert_eq!(
3532 blocks_snapshot.to_block_point(right_wrap_point),
3533 block_point
3534 );
3535 }
3536
3537 let mut block_point = BlockPoint::new(BlockRow(0), 0);
3538 for c in expected_text.chars() {
3539 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
3540 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
3541 assert_eq!(
3542 blocks_snapshot
3543 .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
3544 left_point,
3545 "block point: {:?}, wrap point: {:?}",
3546 block_point,
3547 blocks_snapshot.to_wrap_point(left_point, Bias::Left)
3548 );
3549 assert_eq!(
3550 left_buffer_point,
3551 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
3552 "{:?} is not valid in buffer coordinates",
3553 left_point
3554 );
3555
3556 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
3557 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
3558 assert_eq!(
3559 blocks_snapshot
3560 .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
3561 right_point,
3562 "block point: {:?}, wrap point: {:?}",
3563 block_point,
3564 blocks_snapshot.to_wrap_point(right_point, Bias::Right)
3565 );
3566 assert_eq!(
3567 right_buffer_point,
3568 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
3569 "{:?} is not valid in buffer coordinates",
3570 right_point
3571 );
3572
3573 if c == '\n' {
3574 block_point.0 += Point::new(1, 0);
3575 } else {
3576 block_point.column += c.len_utf8() as u32;
3577 }
3578 }
3579
3580 for buffer_row in 0..=buffer_snapshot.max_point().row {
3581 let buffer_row = MultiBufferRow(buffer_row);
3582 assert_eq!(
3583 blocks_snapshot.is_line_replaced(buffer_row),
3584 expected_replaced_buffer_rows.contains(&buffer_row),
3585 "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
3586 );
3587 }
3588 }
3589 }
3590
3591 #[gpui::test]
3592 fn test_remove_intersecting_replace_blocks_edge_case(cx: &mut gpui::TestAppContext) {
3593 cx.update(init_test);
3594
3595 let text = "abc\ndef\nghi\njkl\nmno";
3596 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3597 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3598 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3599 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3600 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3601 let (_wrap_map, wraps_snapshot) =
3602 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3603 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3604
3605 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
3606 let _block_id = writer.insert(vec![BlockProperties {
3607 style: BlockStyle::Fixed,
3608 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3609 height: Some(1),
3610 render: Arc::new(|_| div().into_any()),
3611 priority: 0,
3612 }])[0];
3613
3614 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
3615 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
3616
3617 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
3618 writer.remove_intersecting_replace_blocks(
3619 [buffer_snapshot
3620 .anchor_after(Point::new(1, 0))
3621 .to_offset(&buffer_snapshot)
3622 ..buffer_snapshot
3623 .anchor_after(Point::new(1, 0))
3624 .to_offset(&buffer_snapshot)],
3625 false,
3626 );
3627 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
3628 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
3629 }
3630
3631 #[gpui::test]
3632 fn test_folded_buffer_with_near_blocks(cx: &mut gpui::TestAppContext) {
3633 cx.update(init_test);
3634
3635 let text = "line 1\nline 2\nline 3";
3636 let buffer = cx.update(|cx| {
3637 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(2, 6)])], cx)
3638 });
3639 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3640 let buffer_ids = buffer_snapshot
3641 .excerpts()
3642 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3643 .dedup()
3644 .collect::<Vec<_>>();
3645 assert_eq!(buffer_ids.len(), 1);
3646 let buffer_id = buffer_ids[0];
3647
3648 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3649 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3650 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3651 let (_, wrap_snapshot) =
3652 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3653 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
3654
3655 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
3656 writer.insert(vec![BlockProperties {
3657 style: BlockStyle::Fixed,
3658 placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(0, 0))),
3659 height: Some(1),
3660 render: Arc::new(|_| div().into_any()),
3661 priority: 0,
3662 }]);
3663
3664 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
3665 assert_eq!(blocks_snapshot.text(), "\nline 1\n\nline 2\nline 3");
3666
3667 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
3668 buffer.read_with(cx, |buffer, cx| {
3669 writer.fold_buffers([buffer_id], buffer, cx);
3670 });
3671
3672 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
3673 assert_eq!(blocks_snapshot.text(), "");
3674 }
3675
3676 #[gpui::test]
3677 fn test_folded_buffer_with_near_blocks_on_last_line(cx: &mut gpui::TestAppContext) {
3678 cx.update(init_test);
3679
3680 let text = "line 1\nline 2\nline 3\nline 4";
3681 let buffer = cx.update(|cx| {
3682 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(3, 6)])], cx)
3683 });
3684 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3685 let buffer_ids = buffer_snapshot
3686 .excerpts()
3687 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3688 .dedup()
3689 .collect::<Vec<_>>();
3690 assert_eq!(buffer_ids.len(), 1);
3691 let buffer_id = buffer_ids[0];
3692
3693 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3694 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3695 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3696 let (_, wrap_snapshot) =
3697 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3698 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
3699
3700 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
3701 writer.insert(vec![BlockProperties {
3702 style: BlockStyle::Fixed,
3703 placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(3, 6))),
3704 height: Some(1),
3705 render: Arc::new(|_| div().into_any()),
3706 priority: 0,
3707 }]);
3708
3709 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
3710 assert_eq!(blocks_snapshot.text(), "\nline 1\nline 2\nline 3\nline 4\n");
3711
3712 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
3713 buffer.read_with(cx, |buffer, cx| {
3714 writer.fold_buffers([buffer_id], buffer, cx);
3715 });
3716
3717 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
3718 assert_eq!(blocks_snapshot.text(), "");
3719 }
3720
3721 fn init_test(cx: &mut gpui::App) {
3722 let settings = SettingsStore::test(cx);
3723 cx.set_global(settings);
3724 theme::init(theme::LoadThemes::JustBase, cx);
3725 assets::Assets.load_test_fonts(cx);
3726 }
3727
3728 impl Block {
3729 fn as_custom(&self) -> Option<&CustomBlock> {
3730 match self {
3731 Block::Custom(block) => Some(block),
3732 _ => None,
3733 }
3734 }
3735 }
3736
3737 impl BlockSnapshot {
3738 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
3739 self.wrap_snapshot
3740 .to_point(self.to_wrap_point(point, bias), bias)
3741 }
3742 }
3743}