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