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