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