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