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