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