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