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