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