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