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