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