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