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::{LanguageAwareStyling, 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 multi_buffer_snapshot = multi_buffer.snapshot(cx);
2047 let mut ranges = Vec::new();
2048 let mut companion_buffer_ids = HashSet::default();
2049 for buffer_id in buffer_ids {
2050 if fold {
2051 self.block_map.folded_buffers.insert(buffer_id);
2052 } else {
2053 self.block_map.folded_buffers.remove(&buffer_id);
2054 }
2055 ranges.extend(multi_buffer_snapshot.range_for_buffer(buffer_id));
2056 if let Some(companion) = &self.companion
2057 && companion.inverse.is_some()
2058 {
2059 companion_buffer_ids.extend(
2060 companion
2061 .companion
2062 .buffer_to_companion_buffer(companion.display_map_id)
2063 .get(&buffer_id)
2064 .copied(),
2065 )
2066 }
2067 }
2068 ranges.sort_unstable_by_key(|range| range.start);
2069
2070 let mut edits = Patch::default();
2071 let wrap_snapshot = self.block_map.wrap_snapshot.borrow().clone();
2072 for range in ranges {
2073 let last_edit_row = cmp::min(
2074 wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + WrapRow(1),
2075 wrap_snapshot.max_point().row(),
2076 ) + WrapRow(1);
2077 let range = wrap_snapshot.make_wrap_point(range.start, Bias::Left).row()..last_edit_row;
2078 edits.push(Edit {
2079 old: range.clone(),
2080 new: range,
2081 });
2082 }
2083
2084 self.block_map.sync(
2085 &wrap_snapshot,
2086 edits.clone(),
2087 self.companion
2088 .as_ref()
2089 .map(BlockMapWriterCompanion::companion_view),
2090 );
2091 if let Some(companion) = &mut self.companion
2092 && let Some(inverse) = &mut companion.inverse
2093 {
2094 inverse.companion_writer.fold_or_unfold_buffers(
2095 fold,
2096 companion_buffer_ids,
2097 inverse.companion_multibuffer,
2098 cx,
2099 );
2100 }
2101 }
2102
2103 #[ztracing::instrument(skip_all)]
2104 fn blocks_intersecting_buffer_range(
2105 &self,
2106 range: Range<MultiBufferOffset>,
2107 inclusive: bool,
2108 ) -> &[Arc<CustomBlock>] {
2109 if range.is_empty() && !inclusive {
2110 return &[];
2111 }
2112 let wrap_snapshot = self.block_map.wrap_snapshot.borrow();
2113 let buffer = wrap_snapshot.buffer_snapshot();
2114
2115 let start_block_ix = match self.block_map.custom_blocks.binary_search_by(|block| {
2116 let block_end = block.end().to_offset(buffer);
2117 block_end.cmp(&range.start).then(Ordering::Greater)
2118 }) {
2119 Ok(ix) | Err(ix) => ix,
2120 };
2121 let end_block_ix =
2122 match self.block_map.custom_blocks[start_block_ix..].binary_search_by(|block| {
2123 let block_start = block.start().to_offset(buffer);
2124 block_start.cmp(&range.end).then(if inclusive {
2125 Ordering::Less
2126 } else {
2127 Ordering::Greater
2128 })
2129 }) {
2130 Ok(ix) | Err(ix) => ix,
2131 };
2132
2133 &self.block_map.custom_blocks[start_block_ix..][..end_block_ix]
2134 }
2135}
2136
2137impl BlockSnapshot {
2138 #[cfg(test)]
2139 #[ztracing::instrument(skip_all)]
2140 pub fn text(&self) -> String {
2141 self.chunks(
2142 BlockRow(0)..self.transforms.summary().output_rows,
2143 LanguageAwareStyling {
2144 tree_sitter: false,
2145 diagnostics: false,
2146 },
2147 false,
2148 Highlights::default(),
2149 )
2150 .map(|chunk| chunk.text)
2151 .collect()
2152 }
2153
2154 #[ztracing::instrument(skip_all)]
2155 pub(crate) fn chunks<'a>(
2156 &'a self,
2157 rows: Range<BlockRow>,
2158 language_aware: LanguageAwareStyling,
2159 masked: bool,
2160 highlights: Highlights<'a>,
2161 ) -> BlockChunks<'a> {
2162 let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
2163
2164 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2165 cursor.seek(&rows.start, Bias::Right);
2166 let transform_output_start = cursor.start().0;
2167 let transform_input_start = cursor.start().1;
2168
2169 let mut input_start = transform_input_start;
2170 let mut input_end = transform_input_start;
2171 if let Some(transform) = cursor.item()
2172 && transform.block.is_none()
2173 {
2174 input_start += rows.start - transform_output_start;
2175 input_end += cmp::min(
2176 rows.end - transform_output_start,
2177 RowDelta(transform.summary.input_rows.0),
2178 );
2179 }
2180
2181 BlockChunks {
2182 input_chunks: self.wrap_snapshot.chunks(
2183 input_start..input_end,
2184 language_aware,
2185 highlights,
2186 ),
2187 input_chunk: Default::default(),
2188 transforms: cursor,
2189 output_row: rows.start,
2190 line_count_overflow: RowDelta(0),
2191 max_output_row,
2192 masked,
2193 }
2194 }
2195
2196 #[ztracing::instrument(skip_all)]
2197 pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows<'_> {
2198 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2199 cursor.seek(&start_row, Bias::Right);
2200 let Dimensions(output_start, input_start, _) = cursor.start();
2201 let overshoot = if cursor
2202 .item()
2203 .is_some_and(|transform| transform.block.is_none())
2204 {
2205 start_row - *output_start
2206 } else {
2207 RowDelta(0)
2208 };
2209 let input_start_row = *input_start + overshoot;
2210 BlockRows {
2211 transforms: cursor,
2212 input_rows: self.wrap_snapshot.row_infos(input_start_row),
2213 output_row: start_row,
2214 started: false,
2215 }
2216 }
2217
2218 #[ztracing::instrument(skip_all)]
2219 pub fn blocks_in_range(
2220 &self,
2221 rows: Range<BlockRow>,
2222 ) -> impl Iterator<Item = (BlockRow, &Block)> {
2223 let mut cursor = self.transforms.cursor::<BlockRow>(());
2224 cursor.seek(&rows.start, Bias::Left);
2225 while *cursor.start() < rows.start && cursor.end() <= rows.start {
2226 cursor.next();
2227 }
2228
2229 std::iter::from_fn(move || {
2230 while let Some(transform) = cursor.item() {
2231 let start_row = *cursor.start();
2232 if start_row > rows.end
2233 || (start_row == rows.end
2234 && transform
2235 .block
2236 .as_ref()
2237 .is_some_and(|block| block.height() > 0))
2238 {
2239 break;
2240 }
2241 if let Some(block) = &transform.block {
2242 cursor.next();
2243 return Some((start_row, block));
2244 } else {
2245 cursor.next();
2246 }
2247 }
2248 None
2249 })
2250 }
2251
2252 #[ztracing::instrument(skip_all)]
2253 pub(crate) fn sticky_header_excerpt(&self, position: f64) -> Option<StickyHeaderExcerpt<'_>> {
2254 let top_row = position as u32;
2255 let mut cursor = self.transforms.cursor::<BlockRow>(());
2256 cursor.seek(&BlockRow(top_row), Bias::Right);
2257
2258 while let Some(transform) = cursor.item() {
2259 match &transform.block {
2260 Some(
2261 Block::ExcerptBoundary { excerpt, .. } | Block::BufferHeader { excerpt, .. },
2262 ) => {
2263 return Some(StickyHeaderExcerpt { excerpt });
2264 }
2265 Some(block) if block.is_buffer_header() => return None,
2266 _ => {
2267 cursor.prev();
2268 continue;
2269 }
2270 }
2271 }
2272
2273 None
2274 }
2275
2276 #[ztracing::instrument(skip_all)]
2277 pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
2278 let buffer = self.wrap_snapshot.buffer_snapshot();
2279 let wrap_point = match block_id {
2280 BlockId::Custom(custom_block_id) => {
2281 let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
2282 return Some(Block::Custom(custom_block.clone()));
2283 }
2284 BlockId::ExcerptBoundary(start_anchor) => {
2285 let start_point = start_anchor.to_point(&buffer);
2286 self.wrap_snapshot.make_wrap_point(start_point, Bias::Left)
2287 }
2288 BlockId::FoldedBuffer(buffer_id) => self.wrap_snapshot.make_wrap_point(
2289 buffer
2290 .anchor_in_excerpt(buffer.excerpts_for_buffer(buffer_id).next()?.context.start)?
2291 .to_point(buffer),
2292 Bias::Left,
2293 ),
2294 BlockId::Spacer(_) => return None,
2295 };
2296 let wrap_row = wrap_point.row();
2297
2298 let mut cursor = self.transforms.cursor::<WrapRow>(());
2299 cursor.seek(&wrap_row, Bias::Left);
2300
2301 while let Some(transform) = cursor.item() {
2302 if let Some(block) = transform.block.as_ref() {
2303 if block.id() == block_id {
2304 return Some(block.clone());
2305 }
2306 } else if *cursor.start() > wrap_row {
2307 break;
2308 }
2309
2310 cursor.next();
2311 }
2312
2313 None
2314 }
2315
2316 #[ztracing::instrument(skip_all)]
2317 pub fn max_point(&self) -> BlockPoint {
2318 let row = self
2319 .transforms
2320 .summary()
2321 .output_rows
2322 .saturating_sub(RowDelta(1));
2323 BlockPoint::new(row, self.line_len(row))
2324 }
2325
2326 #[ztracing::instrument(skip_all)]
2327 pub fn longest_row(&self) -> BlockRow {
2328 self.transforms.summary().longest_row
2329 }
2330
2331 #[ztracing::instrument(skip_all)]
2332 pub fn longest_row_in_range(&self, range: Range<BlockRow>) -> BlockRow {
2333 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2334 cursor.seek(&range.start, Bias::Right);
2335
2336 let mut longest_row = range.start;
2337 let mut longest_row_chars = 0;
2338 if let Some(transform) = cursor.item() {
2339 if transform.block.is_none() {
2340 let &Dimensions(output_start, input_start, _) = cursor.start();
2341 let overshoot = range.start - output_start;
2342 let wrap_start_row = input_start + WrapRow(overshoot.0);
2343 let wrap_end_row = cmp::min(
2344 input_start + WrapRow((range.end - output_start).0),
2345 cursor.end().1,
2346 );
2347 let summary = self
2348 .wrap_snapshot
2349 .text_summary_for_range(wrap_start_row..wrap_end_row);
2350 longest_row = BlockRow(range.start.0 + summary.longest_row);
2351 longest_row_chars = summary.longest_row_chars;
2352 }
2353 cursor.next();
2354 }
2355
2356 let cursor_start_row = cursor.start().0;
2357 if range.end > cursor_start_row {
2358 let summary = cursor.summary::<_, TransformSummary>(&range.end, Bias::Right);
2359 if summary.longest_row_chars > longest_row_chars {
2360 longest_row = cursor_start_row + summary.longest_row;
2361 longest_row_chars = summary.longest_row_chars;
2362 }
2363
2364 if let Some(transform) = cursor.item()
2365 && transform.block.is_none()
2366 {
2367 let &Dimensions(output_start, input_start, _) = cursor.start();
2368 let overshoot = range.end - output_start;
2369 let wrap_start_row = input_start;
2370 let wrap_end_row = input_start + overshoot;
2371 let summary = self
2372 .wrap_snapshot
2373 .text_summary_for_range(wrap_start_row..wrap_end_row);
2374 if summary.longest_row_chars > longest_row_chars {
2375 longest_row = output_start + RowDelta(summary.longest_row);
2376 }
2377 }
2378 }
2379
2380 longest_row
2381 }
2382
2383 #[ztracing::instrument(skip_all)]
2384 pub(super) fn line_len(&self, row: BlockRow) -> u32 {
2385 let (start, _, item) =
2386 self.transforms
2387 .find::<Dimensions<BlockRow, WrapRow>, _>((), &row, Bias::Right);
2388 if let Some(transform) = item {
2389 let Dimensions(output_start, input_start, _) = start;
2390 let overshoot = row - output_start;
2391 if transform.block.is_some() {
2392 0
2393 } else {
2394 self.wrap_snapshot.line_len(input_start + overshoot)
2395 }
2396 } else if row == BlockRow(0) {
2397 0
2398 } else {
2399 panic!("row out of range");
2400 }
2401 }
2402
2403 #[ztracing::instrument(skip_all)]
2404 pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
2405 let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
2406 item.is_some_and(|t| t.block.is_some())
2407 }
2408
2409 #[ztracing::instrument(skip_all)]
2410 pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool {
2411 let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
2412 let Some(transform) = item else {
2413 return false;
2414 };
2415 matches!(transform.block, Some(Block::FoldedBuffer { .. }))
2416 }
2417
2418 #[ztracing::instrument(skip_all)]
2419 pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool {
2420 let wrap_point = self
2421 .wrap_snapshot
2422 .make_wrap_point(Point::new(row.0, 0), Bias::Left);
2423 let (_, _, item) = self
2424 .transforms
2425 .find::<WrapRow, _>((), &wrap_point.row(), Bias::Right);
2426 item.is_some_and(|transform| {
2427 transform
2428 .block
2429 .as_ref()
2430 .is_some_and(|block| block.is_replacement())
2431 })
2432 }
2433
2434 #[ztracing::instrument(skip_all)]
2435 pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
2436 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
2437 cursor.seek(&BlockRow(point.row), Bias::Right);
2438
2439 let max_input_row = self.transforms.summary().input_rows;
2440 let mut search_left = (bias == Bias::Left && cursor.start().1 > WrapRow(0))
2441 || cursor.end().1 == max_input_row;
2442 let mut reversed = false;
2443
2444 loop {
2445 if let Some(transform) = cursor.item() {
2446 let Dimensions(output_start_row, input_start_row, _) = cursor.start();
2447 let Dimensions(output_end_row, input_end_row, _) = cursor.end();
2448 let output_start = Point::new(output_start_row.0, 0);
2449 let input_start = Point::new(input_start_row.0, 0);
2450 let input_end = Point::new(input_end_row.0, 0);
2451
2452 match transform.block.as_ref() {
2453 Some(block) => {
2454 if block.is_replacement()
2455 && (((bias == Bias::Left || search_left) && output_start <= point.0)
2456 || (!search_left && output_start >= point.0))
2457 {
2458 return BlockPoint(output_start);
2459 }
2460 }
2461 None => {
2462 let input_point = if point.row >= output_end_row.0 {
2463 let line_len = self.wrap_snapshot.line_len(input_end_row - RowDelta(1));
2464 self.wrap_snapshot.clip_point(
2465 WrapPoint::new(input_end_row - RowDelta(1), line_len),
2466 bias,
2467 )
2468 } else {
2469 let output_overshoot = point.0.saturating_sub(output_start);
2470 self.wrap_snapshot
2471 .clip_point(WrapPoint(input_start + output_overshoot), bias)
2472 };
2473
2474 if (input_start..input_end).contains(&input_point.0) {
2475 let input_overshoot = input_point.0.saturating_sub(input_start);
2476 return BlockPoint(output_start + input_overshoot);
2477 }
2478 }
2479 }
2480
2481 if search_left {
2482 cursor.prev();
2483 } else {
2484 cursor.next();
2485 }
2486 } else if reversed {
2487 return self.max_point();
2488 } else {
2489 reversed = true;
2490 search_left = !search_left;
2491 cursor.seek(&BlockRow(point.row), Bias::Right);
2492 }
2493 }
2494 }
2495
2496 #[ztracing::instrument(skip_all)]
2497 pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
2498 let (start, _, item) = self.transforms.find::<Dimensions<WrapRow, BlockRow>, _>(
2499 (),
2500 &wrap_point.row(),
2501 Bias::Right,
2502 );
2503 if let Some(transform) = item {
2504 if transform.block.is_some() {
2505 BlockPoint::new(start.1, 0)
2506 } else {
2507 let Dimensions(input_start_row, output_start_row, _) = start;
2508 let input_start = Point::new(input_start_row.0, 0);
2509 let output_start = Point::new(output_start_row.0, 0);
2510 let input_overshoot = wrap_point.0 - input_start;
2511 BlockPoint(output_start + input_overshoot)
2512 }
2513 } else {
2514 self.max_point()
2515 }
2516 }
2517
2518 #[ztracing::instrument(skip_all)]
2519 pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
2520 let (start, end, item) = self.transforms.find::<Dimensions<BlockRow, WrapRow>, _>(
2521 (),
2522 &BlockRow(block_point.row),
2523 Bias::Right,
2524 );
2525 if let Some(transform) = item {
2526 match transform.block.as_ref() {
2527 Some(block) => {
2528 if block.place_below() {
2529 let wrap_row = start.1 - RowDelta(1);
2530 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
2531 } else if block.place_above() {
2532 WrapPoint::new(start.1, 0)
2533 } else if bias == Bias::Left {
2534 WrapPoint::new(start.1, 0)
2535 } else {
2536 let wrap_row = end.1 - RowDelta(1);
2537 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
2538 }
2539 }
2540 None => {
2541 let overshoot = block_point.row() - start.0;
2542 let wrap_row = start.1 + RowDelta(overshoot.0);
2543 WrapPoint::new(wrap_row, block_point.column)
2544 }
2545 }
2546 } else {
2547 self.wrap_snapshot.max_point()
2548 }
2549 }
2550}
2551
2552impl BlockChunks<'_> {
2553 /// Go to the next transform
2554 #[ztracing::instrument(skip_all)]
2555 fn advance(&mut self) {
2556 self.input_chunk = Chunk::default();
2557 self.transforms.next();
2558 while let Some(transform) = self.transforms.item() {
2559 if transform
2560 .block
2561 .as_ref()
2562 .is_some_and(|block| block.height() == 0)
2563 {
2564 self.transforms.next();
2565 } else {
2566 break;
2567 }
2568 }
2569
2570 if self
2571 .transforms
2572 .item()
2573 .is_some_and(|transform| transform.block.is_none())
2574 {
2575 let start_input_row = self.transforms.start().1;
2576 let start_output_row = self.transforms.start().0;
2577 if start_output_row < self.max_output_row {
2578 let end_input_row = cmp::min(
2579 self.transforms.end().1,
2580 start_input_row + (self.max_output_row - start_output_row),
2581 );
2582 self.input_chunks.seek(start_input_row..end_input_row);
2583 }
2584 }
2585 }
2586}
2587
2588pub struct StickyHeaderExcerpt<'a> {
2589 pub excerpt: &'a ExcerptBoundaryInfo,
2590}
2591
2592impl<'a> Iterator for BlockChunks<'a> {
2593 type Item = Chunk<'a>;
2594
2595 #[ztracing::instrument(skip_all)]
2596 fn next(&mut self) -> Option<Self::Item> {
2597 if self.output_row >= self.max_output_row {
2598 return None;
2599 }
2600
2601 if self.line_count_overflow > RowDelta(0) {
2602 let lines = self.line_count_overflow.0.min(u128::BITS);
2603 self.line_count_overflow.0 -= lines;
2604 self.output_row += RowDelta(lines);
2605 return Some(Chunk {
2606 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..lines as usize]) },
2607 chars: 1u128.unbounded_shl(lines).wrapping_sub(1),
2608 ..Default::default()
2609 });
2610 }
2611
2612 let transform = self.transforms.item()?;
2613 if transform.block.is_some() {
2614 let block_start = self.transforms.start().0;
2615 let mut block_end = self.transforms.end().0;
2616 self.advance();
2617 if self.transforms.item().is_none() {
2618 block_end -= RowDelta(1);
2619 }
2620
2621 let start_in_block = self.output_row - block_start;
2622 let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
2623 let line_count = end_in_block - start_in_block;
2624 let lines = RowDelta(line_count.0.min(u128::BITS));
2625 self.line_count_overflow = line_count - lines;
2626 self.output_row += lines;
2627
2628 return Some(Chunk {
2629 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..lines.0 as usize]) },
2630 chars: 1u128.unbounded_shl(lines.0).wrapping_sub(1),
2631 ..Default::default()
2632 });
2633 }
2634
2635 if self.input_chunk.text.is_empty() {
2636 if let Some(input_chunk) = self.input_chunks.next() {
2637 self.input_chunk = input_chunk;
2638 } else {
2639 if self.output_row < self.max_output_row {
2640 self.output_row.0 += 1;
2641 self.advance();
2642 if self.transforms.item().is_some() {
2643 return Some(Chunk {
2644 text: "\n",
2645 chars: 1,
2646 ..Default::default()
2647 });
2648 }
2649 }
2650 return None;
2651 }
2652 }
2653
2654 let transform_end = self.transforms.end().0;
2655 let (prefix_rows, prefix_bytes) =
2656 offset_for_row(self.input_chunk.text, transform_end - self.output_row);
2657 self.output_row += prefix_rows;
2658
2659 let (mut prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
2660 self.input_chunk.text = suffix;
2661 self.input_chunk.tabs >>= prefix_bytes.saturating_sub(1);
2662 self.input_chunk.chars >>= prefix_bytes.saturating_sub(1);
2663 self.input_chunk.newlines >>= prefix_bytes.saturating_sub(1);
2664
2665 let mut tabs = self.input_chunk.tabs;
2666 let mut chars = self.input_chunk.chars;
2667 let mut newlines = self.input_chunk.newlines;
2668
2669 if self.masked {
2670 // Not great for multibyte text because to keep cursor math correct we
2671 // need to have the same number of chars in the input as output.
2672 let chars_count = prefix.chars().count();
2673 let bullet_len = chars_count;
2674 prefix = unsafe { std::str::from_utf8_unchecked(&BULLETS[..bullet_len]) };
2675 chars = 1u128.unbounded_shl(bullet_len as u32).wrapping_sub(1);
2676 tabs = 0;
2677 newlines = 0;
2678 }
2679
2680 let chunk = Chunk {
2681 text: prefix,
2682 tabs,
2683 chars,
2684 newlines,
2685 ..self.input_chunk.clone()
2686 };
2687
2688 if self.output_row == transform_end {
2689 self.advance();
2690 }
2691
2692 Some(chunk)
2693 }
2694}
2695
2696impl Iterator for BlockRows<'_> {
2697 type Item = RowInfo;
2698
2699 #[ztracing::instrument(skip_all)]
2700 fn next(&mut self) -> Option<Self::Item> {
2701 if self.started {
2702 self.output_row.0 += 1;
2703 } else {
2704 self.started = true;
2705 }
2706
2707 if self.output_row >= self.transforms.end().0 {
2708 self.transforms.next();
2709 while let Some(transform) = self.transforms.item() {
2710 if transform
2711 .block
2712 .as_ref()
2713 .is_some_and(|block| block.height() == 0)
2714 {
2715 self.transforms.next();
2716 } else {
2717 break;
2718 }
2719 }
2720
2721 let transform = self.transforms.item()?;
2722 if transform
2723 .block
2724 .as_ref()
2725 .is_none_or(|block| block.is_replacement())
2726 {
2727 self.input_rows.seek(self.transforms.start().1);
2728 }
2729 }
2730
2731 let transform = self.transforms.item()?;
2732 if transform.block.as_ref().is_none_or(|block| {
2733 block.is_replacement()
2734 && self.transforms.start().0 == self.output_row
2735 && matches!(block, Block::FoldedBuffer { .. }).not()
2736 }) {
2737 self.input_rows.next()
2738 } else {
2739 Some(RowInfo::default())
2740 }
2741 }
2742}
2743
2744impl sum_tree::Item for Transform {
2745 type Summary = TransformSummary;
2746
2747 fn summary(&self, _cx: ()) -> Self::Summary {
2748 self.summary.clone()
2749 }
2750}
2751
2752impl sum_tree::ContextLessSummary for TransformSummary {
2753 fn zero() -> Self {
2754 Default::default()
2755 }
2756
2757 fn add_summary(&mut self, summary: &Self) {
2758 if summary.longest_row_chars > self.longest_row_chars {
2759 self.longest_row = self.output_rows + summary.longest_row;
2760 self.longest_row_chars = summary.longest_row_chars;
2761 }
2762 self.input_rows += summary.input_rows;
2763 self.output_rows += summary.output_rows;
2764 }
2765}
2766
2767impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
2768 fn zero(_cx: ()) -> Self {
2769 Default::default()
2770 }
2771
2772 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
2773 *self += summary.input_rows;
2774 }
2775}
2776
2777impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
2778 fn zero(_cx: ()) -> Self {
2779 Default::default()
2780 }
2781
2782 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
2783 *self += summary.output_rows;
2784 }
2785}
2786
2787impl Deref for BlockContext<'_, '_> {
2788 type Target = App;
2789
2790 fn deref(&self) -> &Self::Target {
2791 self.app
2792 }
2793}
2794
2795impl DerefMut for BlockContext<'_, '_> {
2796 fn deref_mut(&mut self) -> &mut Self::Target {
2797 self.app
2798 }
2799}
2800
2801impl CustomBlock {
2802 #[ztracing::instrument(skip_all)]
2803 pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
2804 self.render.lock()(cx)
2805 }
2806
2807 #[ztracing::instrument(skip_all)]
2808 pub fn start(&self) -> Anchor {
2809 *self.placement.start()
2810 }
2811
2812 #[ztracing::instrument(skip_all)]
2813 pub fn end(&self) -> Anchor {
2814 *self.placement.end()
2815 }
2816
2817 pub fn style(&self) -> BlockStyle {
2818 self.style
2819 }
2820
2821 pub fn properties(&self) -> BlockProperties<Anchor> {
2822 BlockProperties {
2823 placement: self.placement.clone(),
2824 height: self.height,
2825 style: self.style,
2826 render: Arc::new(|_| {
2827 // Not used
2828 gpui::Empty.into_any_element()
2829 }),
2830 priority: self.priority,
2831 }
2832 }
2833}
2834
2835impl Debug for CustomBlock {
2836 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2837 f.debug_struct("Block")
2838 .field("id", &self.id)
2839 .field("placement", &self.placement)
2840 .field("height", &self.height)
2841 .field("style", &self.style)
2842 .field("priority", &self.priority)
2843 .finish_non_exhaustive()
2844 }
2845}
2846
2847// Count the number of bytes prior to a target point. If the string doesn't contain the target
2848// point, return its total extent. Otherwise return the target point itself.
2849fn offset_for_row(s: &str, target: RowDelta) -> (RowDelta, usize) {
2850 let mut row = 0;
2851 let mut offset = 0;
2852 for (ix, line) in s.split('\n').enumerate() {
2853 if ix > 0 {
2854 row += 1;
2855 offset += 1;
2856 }
2857 if row >= target.0 {
2858 break;
2859 }
2860 offset += line.len();
2861 }
2862 (RowDelta(row), offset)
2863}
2864
2865#[cfg(test)]
2866mod tests {
2867 use super::*;
2868 use crate::{
2869 display_map::{
2870 Companion, fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap,
2871 },
2872 split::{convert_lhs_rows_to_rhs, convert_rhs_rows_to_lhs},
2873 test::test_font,
2874 };
2875 use buffer_diff::BufferDiff;
2876 use gpui::{App, AppContext as _, Element, div, font, px};
2877 use itertools::Itertools;
2878 use language::{Buffer, Capability, Point};
2879 use multi_buffer::{MultiBuffer, PathKey};
2880 use rand::prelude::*;
2881 use settings::SettingsStore;
2882 use std::env;
2883 use util::RandomCharIter;
2884
2885 #[gpui::test]
2886 fn test_offset_for_row() {
2887 assert_eq!(offset_for_row("", RowDelta(0)), (RowDelta(0), 0));
2888 assert_eq!(offset_for_row("", RowDelta(1)), (RowDelta(0), 0));
2889 assert_eq!(offset_for_row("abcd", RowDelta(0)), (RowDelta(0), 0));
2890 assert_eq!(offset_for_row("abcd", RowDelta(1)), (RowDelta(0), 4));
2891 assert_eq!(offset_for_row("\n", RowDelta(0)), (RowDelta(0), 0));
2892 assert_eq!(offset_for_row("\n", RowDelta(1)), (RowDelta(1), 1));
2893 assert_eq!(
2894 offset_for_row("abc\ndef\nghi", RowDelta(0)),
2895 (RowDelta(0), 0)
2896 );
2897 assert_eq!(
2898 offset_for_row("abc\ndef\nghi", RowDelta(1)),
2899 (RowDelta(1), 4)
2900 );
2901 assert_eq!(
2902 offset_for_row("abc\ndef\nghi", RowDelta(2)),
2903 (RowDelta(2), 8)
2904 );
2905 assert_eq!(
2906 offset_for_row("abc\ndef\nghi", RowDelta(3)),
2907 (RowDelta(2), 11)
2908 );
2909 }
2910
2911 #[gpui::test]
2912 fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
2913 cx.update(init_test);
2914
2915 let text = "aaa\nbbb\nccc\nddd";
2916
2917 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2918 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2919 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2920 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2921 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2922 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2923 let (wrap_map, wraps_snapshot) =
2924 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2925 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2926
2927 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
2928 let block_ids = writer.insert(vec![
2929 BlockProperties {
2930 style: BlockStyle::Fixed,
2931 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2932 height: Some(1),
2933 render: Arc::new(|_| div().into_any()),
2934 priority: 0,
2935 },
2936 BlockProperties {
2937 style: BlockStyle::Fixed,
2938 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2939 height: Some(2),
2940 render: Arc::new(|_| div().into_any()),
2941 priority: 0,
2942 },
2943 BlockProperties {
2944 style: BlockStyle::Fixed,
2945 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2946 height: Some(3),
2947 render: Arc::new(|_| div().into_any()),
2948 priority: 0,
2949 },
2950 ]);
2951
2952 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
2953 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2954
2955 let blocks = snapshot
2956 .blocks_in_range(BlockRow(0)..BlockRow(8))
2957 .map(|(start_row, block)| {
2958 let block = block.as_custom().unwrap();
2959 (start_row.0..start_row.0 + block.height.unwrap(), block.id)
2960 })
2961 .collect::<Vec<_>>();
2962
2963 // When multiple blocks are on the same line, the newer blocks appear first.
2964 assert_eq!(
2965 blocks,
2966 &[
2967 (1..2, block_ids[0]),
2968 (2..4, block_ids[1]),
2969 (7..10, block_ids[2]),
2970 ]
2971 );
2972
2973 assert_eq!(
2974 snapshot.to_block_point(WrapPoint::new(WrapRow(0), 3)),
2975 BlockPoint::new(BlockRow(0), 3)
2976 );
2977 assert_eq!(
2978 snapshot.to_block_point(WrapPoint::new(WrapRow(1), 0)),
2979 BlockPoint::new(BlockRow(4), 0)
2980 );
2981 assert_eq!(
2982 snapshot.to_block_point(WrapPoint::new(WrapRow(3), 3)),
2983 BlockPoint::new(BlockRow(6), 3)
2984 );
2985
2986 assert_eq!(
2987 snapshot.to_wrap_point(BlockPoint::new(BlockRow(0), 3), Bias::Left),
2988 WrapPoint::new(WrapRow(0), 3)
2989 );
2990 assert_eq!(
2991 snapshot.to_wrap_point(BlockPoint::new(BlockRow(1), 0), Bias::Left),
2992 WrapPoint::new(WrapRow(1), 0)
2993 );
2994 assert_eq!(
2995 snapshot.to_wrap_point(BlockPoint::new(BlockRow(3), 0), Bias::Left),
2996 WrapPoint::new(WrapRow(1), 0)
2997 );
2998 assert_eq!(
2999 snapshot.to_wrap_point(BlockPoint::new(BlockRow(7), 0), Bias::Left),
3000 WrapPoint::new(WrapRow(3), 3)
3001 );
3002
3003 assert_eq!(
3004 snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Left),
3005 BlockPoint::new(BlockRow(0), 3)
3006 );
3007 assert_eq!(
3008 snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Right),
3009 BlockPoint::new(BlockRow(4), 0)
3010 );
3011 assert_eq!(
3012 snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Left),
3013 BlockPoint::new(BlockRow(0), 3)
3014 );
3015 assert_eq!(
3016 snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Right),
3017 BlockPoint::new(BlockRow(4), 0)
3018 );
3019 assert_eq!(
3020 snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Left),
3021 BlockPoint::new(BlockRow(4), 0)
3022 );
3023 assert_eq!(
3024 snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Right),
3025 BlockPoint::new(BlockRow(4), 0)
3026 );
3027 assert_eq!(
3028 snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Left),
3029 BlockPoint::new(BlockRow(6), 3)
3030 );
3031 assert_eq!(
3032 snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Right),
3033 BlockPoint::new(BlockRow(6), 3)
3034 );
3035 assert_eq!(
3036 snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Left),
3037 BlockPoint::new(BlockRow(6), 3)
3038 );
3039 assert_eq!(
3040 snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Right),
3041 BlockPoint::new(BlockRow(6), 3)
3042 );
3043
3044 assert_eq!(
3045 snapshot
3046 .row_infos(BlockRow(0))
3047 .map(|row_info| row_info.buffer_row)
3048 .collect::<Vec<_>>(),
3049 &[
3050 Some(0),
3051 None,
3052 None,
3053 None,
3054 Some(1),
3055 Some(2),
3056 Some(3),
3057 None,
3058 None,
3059 None
3060 ]
3061 );
3062
3063 // Insert a line break, separating two block decorations into separate lines.
3064 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3065 buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
3066 buffer.snapshot(cx)
3067 });
3068
3069 let (inlay_snapshot, inlay_edits) =
3070 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
3071 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3072 let (tab_snapshot, tab_edits) =
3073 tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
3074 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3075 wrap_map.sync(tab_snapshot, tab_edits, cx)
3076 });
3077 let snapshot = block_map.read(wraps_snapshot, wrap_edits, None);
3078 assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
3079 }
3080
3081 #[gpui::test]
3082 fn test_multibuffer_headers_and_footers(cx: &mut App) {
3083 init_test(cx);
3084
3085 let buffer1 = cx.new(|cx| Buffer::local("Buffer 1", cx));
3086 let buffer2 = cx.new(|cx| Buffer::local("Buffer 2", cx));
3087 let buffer3 = cx.new(|cx| Buffer::local("Buffer 3", cx));
3088
3089 let multi_buffer = cx.new(|cx| {
3090 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
3091 multi_buffer.set_excerpts_for_path(
3092 PathKey::sorted(0),
3093 buffer1.clone(),
3094 [Point::zero()..buffer1.read(cx).max_point()],
3095 0,
3096 cx,
3097 );
3098 multi_buffer.set_excerpts_for_path(
3099 PathKey::sorted(1),
3100 buffer2.clone(),
3101 [Point::zero()..buffer2.read(cx).max_point()],
3102 0,
3103 cx,
3104 );
3105 multi_buffer.set_excerpts_for_path(
3106 PathKey::sorted(2),
3107 buffer3.clone(),
3108 [Point::zero()..buffer3.read(cx).max_point()],
3109 0,
3110 cx,
3111 );
3112 multi_buffer
3113 });
3114 let excerpt_start_anchors = multi_buffer.read_with(cx, |mb, _| {
3115 let snapshot = mb.snapshot(cx);
3116 snapshot
3117 .excerpts()
3118 .map(|e| snapshot.anchor_in_excerpt(e.context.start).unwrap())
3119 .collect::<Vec<_>>()
3120 });
3121
3122 let font = test_font();
3123 let font_size = px(14.);
3124 let font_id = cx.text_system().resolve_font(&font);
3125 let mut wrap_width = px(0.);
3126 for c in "Buff".chars() {
3127 wrap_width += cx
3128 .text_system()
3129 .advance(font_id, font_size, c)
3130 .unwrap()
3131 .width;
3132 }
3133
3134 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
3135 let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot);
3136 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3137 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3138 let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
3139
3140 let block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3141 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3142
3143 // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
3144 assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3");
3145
3146 let blocks: Vec<_> = snapshot
3147 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3148 .map(|(row, block)| (row.0..row.0 + block.height(), block.id()))
3149 .collect();
3150 assert_eq!(
3151 blocks,
3152 vec![
3153 (0..1, BlockId::ExcerptBoundary(excerpt_start_anchors[0])), // path, header
3154 (3..4, BlockId::ExcerptBoundary(excerpt_start_anchors[1])), // path, header
3155 (6..7, BlockId::ExcerptBoundary(excerpt_start_anchors[2])), // path, header
3156 ]
3157 );
3158 }
3159
3160 #[gpui::test]
3161 fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
3162 cx.update(init_test);
3163
3164 let text = "aaa\nbbb\nccc\nddd";
3165
3166 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3167 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3168 let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
3169 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3170 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3171 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
3172 let (_wrap_map, wraps_snapshot) =
3173 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3174 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3175
3176 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3177 let block_ids = writer.insert(vec![
3178 BlockProperties {
3179 style: BlockStyle::Fixed,
3180 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3181 height: Some(1),
3182 render: Arc::new(|_| div().into_any()),
3183 priority: 0,
3184 },
3185 BlockProperties {
3186 style: BlockStyle::Fixed,
3187 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
3188 height: Some(2),
3189 render: Arc::new(|_| div().into_any()),
3190 priority: 0,
3191 },
3192 BlockProperties {
3193 style: BlockStyle::Fixed,
3194 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
3195 height: Some(3),
3196 render: Arc::new(|_| div().into_any()),
3197 priority: 0,
3198 },
3199 ]);
3200
3201 {
3202 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3203 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
3204
3205 let mut block_map_writer =
3206 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3207
3208 let mut new_heights = HashMap::default();
3209 new_heights.insert(block_ids[0], 2);
3210 block_map_writer.resize(new_heights);
3211 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3212 assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3213 }
3214
3215 {
3216 let mut block_map_writer =
3217 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3218
3219 let mut new_heights = HashMap::default();
3220 new_heights.insert(block_ids[0], 1);
3221 block_map_writer.resize(new_heights);
3222
3223 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3224 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
3225 }
3226
3227 {
3228 let mut block_map_writer =
3229 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3230
3231 let mut new_heights = HashMap::default();
3232 new_heights.insert(block_ids[0], 0);
3233 block_map_writer.resize(new_heights);
3234
3235 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3236 assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
3237 }
3238
3239 {
3240 let mut block_map_writer =
3241 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3242
3243 let mut new_heights = HashMap::default();
3244 new_heights.insert(block_ids[0], 3);
3245 block_map_writer.resize(new_heights);
3246
3247 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3248 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3249 }
3250
3251 {
3252 let mut block_map_writer =
3253 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3254
3255 let mut new_heights = HashMap::default();
3256 new_heights.insert(block_ids[0], 3);
3257 block_map_writer.resize(new_heights);
3258
3259 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3260 // Same height as before, should remain the same
3261 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3262 }
3263 }
3264
3265 #[gpui::test]
3266 fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
3267 cx.update(init_test);
3268
3269 let text = "one two three\nfour five six\nseven eight";
3270
3271 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3272 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3273 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3274 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3275 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3276 let (_, wraps_snapshot) = cx.update(|cx| {
3277 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(90.)), cx)
3278 });
3279 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3280
3281 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3282 writer.insert(vec![
3283 BlockProperties {
3284 style: BlockStyle::Fixed,
3285 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
3286 render: Arc::new(|_| div().into_any()),
3287 height: Some(1),
3288 priority: 0,
3289 },
3290 BlockProperties {
3291 style: BlockStyle::Fixed,
3292 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
3293 render: Arc::new(|_| div().into_any()),
3294 height: Some(1),
3295 priority: 0,
3296 },
3297 ]);
3298
3299 // Blocks with an 'above' disposition go above their corresponding buffer line.
3300 // Blocks with a 'below' disposition go below their corresponding buffer line.
3301 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3302 assert_eq!(
3303 snapshot.text(),
3304 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
3305 );
3306 }
3307
3308 #[gpui::test]
3309 fn test_replace_lines(cx: &mut gpui::TestAppContext) {
3310 cx.update(init_test);
3311
3312 let text = "line1\nline2\nline3\nline4\nline5";
3313
3314 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3315 let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
3316 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3317 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3318 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3319 let tab_size = 1.try_into().unwrap();
3320 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
3321 let (wrap_map, wraps_snapshot) =
3322 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3323 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3324
3325 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3326 let replace_block_id = writer.insert(vec![BlockProperties {
3327 style: BlockStyle::Fixed,
3328 placement: BlockPlacement::Replace(
3329 buffer_snapshot.anchor_after(Point::new(1, 3))
3330 ..=buffer_snapshot.anchor_before(Point::new(3, 1)),
3331 ),
3332 height: Some(4),
3333 render: Arc::new(|_| div().into_any()),
3334 priority: 0,
3335 }])[0];
3336
3337 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3338 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3339
3340 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3341 buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
3342 buffer.snapshot(cx)
3343 });
3344 let (inlay_snapshot, inlay_edits) =
3345 inlay_map.sync(buffer_snapshot, buffer_subscription.consume().into_inner());
3346 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3347 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3348 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3349 wrap_map.sync(tab_snapshot, tab_edits, cx)
3350 });
3351 let blocks_snapshot = block_map.read(wraps_snapshot, wrap_edits, None);
3352 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3353
3354 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3355 buffer.edit(
3356 [(
3357 Point::new(1, 5)..Point::new(1, 5),
3358 "\nline 2.1\nline2.2\nline 2.3\nline 2.4",
3359 )],
3360 None,
3361 cx,
3362 );
3363 buffer.snapshot(cx)
3364 });
3365 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
3366 buffer_snapshot.clone(),
3367 buffer_subscription.consume().into_inner(),
3368 );
3369 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3370 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3371 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3372 wrap_map.sync(tab_snapshot, tab_edits, cx)
3373 });
3374 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, None);
3375 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3376
3377 // Blocks inserted right above the start or right below the end of the replaced region are hidden.
3378 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3379 writer.insert(vec![
3380 BlockProperties {
3381 style: BlockStyle::Fixed,
3382 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))),
3383 height: Some(1),
3384 render: Arc::new(|_| div().into_any()),
3385 priority: 0,
3386 },
3387 BlockProperties {
3388 style: BlockStyle::Fixed,
3389 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
3390 height: Some(1),
3391 render: Arc::new(|_| div().into_any()),
3392 priority: 0,
3393 },
3394 BlockProperties {
3395 style: BlockStyle::Fixed,
3396 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
3397 height: Some(1),
3398 render: Arc::new(|_| div().into_any()),
3399 priority: 0,
3400 },
3401 ]);
3402 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3403 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
3404
3405 // Ensure blocks inserted *inside* replaced region are hidden.
3406 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3407 writer.insert(vec![
3408 BlockProperties {
3409 style: BlockStyle::Fixed,
3410 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
3411 height: Some(1),
3412 render: Arc::new(|_| div().into_any()),
3413 priority: 0,
3414 },
3415 BlockProperties {
3416 style: BlockStyle::Fixed,
3417 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
3418 height: Some(1),
3419 render: Arc::new(|_| div().into_any()),
3420 priority: 0,
3421 },
3422 BlockProperties {
3423 style: BlockStyle::Fixed,
3424 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
3425 height: Some(1),
3426 render: Arc::new(|_| div().into_any()),
3427 priority: 0,
3428 },
3429 ]);
3430 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3431 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
3432
3433 // Removing the replace block shows all the hidden blocks again.
3434 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3435 writer.remove(HashSet::from_iter([replace_block_id]));
3436 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3437 assert_eq!(
3438 blocks_snapshot.text(),
3439 "\nline1\n\nline2\n\n\nline 2.1\nline2.2\nline 2.3\nline 2.4\n\nline4\n\nline5"
3440 );
3441 }
3442
3443 #[gpui::test]
3444 fn test_custom_blocks_inside_buffer_folds(cx: &mut gpui::TestAppContext) {
3445 cx.update(init_test);
3446
3447 let text = "111\n\n222\n\n333\n\n444\n\n555\n\n666";
3448
3449 let buffer = cx.update(|cx| {
3450 let multibuffer = MultiBuffer::build_multi(
3451 [
3452 (text, vec![Point::new(0, 0)..Point::new(0, 3)]),
3453 (
3454 text,
3455 vec![
3456 Point::new(2, 0)..Point::new(2, 3),
3457 Point::new(4, 0)..Point::new(4, 3),
3458 Point::new(6, 0)..Point::new(6, 3),
3459 ],
3460 ),
3461 (
3462 text,
3463 vec![
3464 Point::new(8, 0)..Point::new(8, 3),
3465 Point::new(10, 0)..Point::new(10, 3),
3466 ],
3467 ),
3468 ],
3469 cx,
3470 );
3471 assert_eq!(multibuffer.read(cx).snapshot(cx).excerpts().count(), 6);
3472 multibuffer
3473 });
3474 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3475 let buffer_ids = buffer_snapshot
3476 .excerpts()
3477 .map(|excerpt| excerpt.context.start.buffer_id)
3478 .dedup()
3479 .collect::<Vec<_>>();
3480 assert_eq!(buffer_ids.len(), 3);
3481 let buffer_id_1 = buffer_ids[0];
3482 let buffer_id_2 = buffer_ids[1];
3483 let buffer_id_3 = buffer_ids[2];
3484
3485 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3486 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3487 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3488 let (_, wrap_snapshot) =
3489 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3490 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
3491 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3492
3493 assert_eq!(
3494 blocks_snapshot.text(),
3495 "\n\n111\n\n\n222\n\n333\n\n444\n\n\n555\n\n666"
3496 );
3497 assert_eq!(
3498 blocks_snapshot
3499 .row_infos(BlockRow(0))
3500 .map(|i| i.buffer_row)
3501 .collect::<Vec<_>>(),
3502 vec![
3503 None,
3504 None,
3505 Some(0),
3506 None,
3507 None,
3508 Some(2),
3509 None,
3510 Some(4),
3511 None,
3512 Some(6),
3513 None,
3514 None,
3515 Some(8),
3516 None,
3517 Some(10),
3518 ]
3519 );
3520
3521 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3522 let excerpt_blocks_2 = writer.insert(vec![
3523 BlockProperties {
3524 style: BlockStyle::Fixed,
3525 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3526 height: Some(1),
3527 render: Arc::new(|_| div().into_any()),
3528 priority: 0,
3529 },
3530 BlockProperties {
3531 style: BlockStyle::Fixed,
3532 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 0))),
3533 height: Some(1),
3534 render: Arc::new(|_| div().into_any()),
3535 priority: 0,
3536 },
3537 BlockProperties {
3538 style: BlockStyle::Fixed,
3539 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 0))),
3540 height: Some(1),
3541 render: Arc::new(|_| div().into_any()),
3542 priority: 0,
3543 },
3544 ]);
3545 let excerpt_blocks_3 = writer.insert(vec![
3546 BlockProperties {
3547 style: BlockStyle::Fixed,
3548 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))),
3549 height: Some(1),
3550 render: Arc::new(|_| div().into_any()),
3551 priority: 0,
3552 },
3553 BlockProperties {
3554 style: BlockStyle::Fixed,
3555 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))),
3556 height: Some(1),
3557 render: Arc::new(|_| div().into_any()),
3558 priority: 0,
3559 },
3560 ]);
3561
3562 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3563 assert_eq!(
3564 blocks_snapshot.text(),
3565 "\n\n111\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
3566 );
3567 assert_eq!(
3568 blocks_snapshot
3569 .row_infos(BlockRow(0))
3570 .map(|i| i.buffer_row)
3571 .collect::<Vec<_>>(),
3572 vec![
3573 None,
3574 None,
3575 Some(0),
3576 None,
3577 None,
3578 None,
3579 Some(2),
3580 None,
3581 None,
3582 Some(4),
3583 None,
3584 Some(6),
3585 None,
3586 None,
3587 None,
3588 None,
3589 Some(8),
3590 None,
3591 Some(10),
3592 None,
3593 ]
3594 );
3595
3596 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3597 buffer.read_with(cx, |buffer, cx| {
3598 writer.fold_buffers([buffer_id_1], buffer, cx);
3599 });
3600 let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
3601 style: BlockStyle::Fixed,
3602 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))),
3603 height: Some(1),
3604 render: Arc::new(|_| div().into_any()),
3605 priority: 0,
3606 }]);
3607 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3608 let blocks = blocks_snapshot
3609 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3610 .collect::<Vec<_>>();
3611 for (_, block) in &blocks {
3612 if let BlockId::Custom(custom_block_id) = block.id() {
3613 assert!(
3614 !excerpt_blocks_1.contains(&custom_block_id),
3615 "Should have no blocks from the folded buffer"
3616 );
3617 assert!(
3618 excerpt_blocks_2.contains(&custom_block_id)
3619 || excerpt_blocks_3.contains(&custom_block_id),
3620 "Should have only blocks from unfolded buffers"
3621 );
3622 }
3623 }
3624 assert_eq!(
3625 1,
3626 blocks
3627 .iter()
3628 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3629 .count(),
3630 "Should have one folded block, producing a header of the second buffer"
3631 );
3632 assert_eq!(
3633 blocks_snapshot.text(),
3634 "\n\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
3635 );
3636 assert_eq!(
3637 blocks_snapshot
3638 .row_infos(BlockRow(0))
3639 .map(|i| i.buffer_row)
3640 .collect::<Vec<_>>(),
3641 vec![
3642 None,
3643 None,
3644 None,
3645 None,
3646 None,
3647 Some(2),
3648 None,
3649 None,
3650 Some(4),
3651 None,
3652 Some(6),
3653 None,
3654 None,
3655 None,
3656 None,
3657 Some(8),
3658 None,
3659 Some(10),
3660 None,
3661 ]
3662 );
3663
3664 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3665 buffer.read_with(cx, |buffer, cx| {
3666 writer.fold_buffers([buffer_id_2], buffer, cx);
3667 });
3668 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3669 let blocks = blocks_snapshot
3670 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3671 .collect::<Vec<_>>();
3672 for (_, block) in &blocks {
3673 if let BlockId::Custom(custom_block_id) = block.id() {
3674 assert!(
3675 !excerpt_blocks_1.contains(&custom_block_id),
3676 "Should have no blocks from the folded buffer_1"
3677 );
3678 assert!(
3679 !excerpt_blocks_2.contains(&custom_block_id),
3680 "Should have no blocks from the folded buffer_2"
3681 );
3682 assert!(
3683 excerpt_blocks_3.contains(&custom_block_id),
3684 "Should have only blocks from unfolded buffers"
3685 );
3686 }
3687 }
3688 assert_eq!(
3689 2,
3690 blocks
3691 .iter()
3692 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3693 .count(),
3694 "Should have two folded blocks, producing headers"
3695 );
3696 assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n555\n\n666\n");
3697 assert_eq!(
3698 blocks_snapshot
3699 .row_infos(BlockRow(0))
3700 .map(|i| i.buffer_row)
3701 .collect::<Vec<_>>(),
3702 vec![
3703 None,
3704 None,
3705 None,
3706 None,
3707 None,
3708 None,
3709 None,
3710 Some(8),
3711 None,
3712 Some(10),
3713 None,
3714 ]
3715 );
3716
3717 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3718 buffer.read_with(cx, |buffer, cx| {
3719 writer.unfold_buffers([buffer_id_1], buffer, cx);
3720 });
3721 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3722 let blocks = blocks_snapshot
3723 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3724 .collect::<Vec<_>>();
3725 for (_, block) in &blocks {
3726 if let BlockId::Custom(custom_block_id) = block.id() {
3727 assert!(
3728 !excerpt_blocks_2.contains(&custom_block_id),
3729 "Should have no blocks from the folded buffer_2"
3730 );
3731 assert!(
3732 excerpt_blocks_1.contains(&custom_block_id)
3733 || excerpt_blocks_3.contains(&custom_block_id),
3734 "Should have only blocks from unfolded buffers"
3735 );
3736 }
3737 }
3738 assert_eq!(
3739 1,
3740 blocks
3741 .iter()
3742 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3743 .count(),
3744 "Should be back to a single folded buffer, producing a header for buffer_2"
3745 );
3746 assert_eq!(
3747 blocks_snapshot.text(),
3748 "\n\n\n111\n\n\n\n\n\n555\n\n666\n",
3749 "Should have extra newline for 111 buffer, due to a new block added when it was folded"
3750 );
3751 assert_eq!(
3752 blocks_snapshot
3753 .row_infos(BlockRow(0))
3754 .map(|i| i.buffer_row)
3755 .collect::<Vec<_>>(),
3756 vec![
3757 None,
3758 None,
3759 None,
3760 Some(0),
3761 None,
3762 None,
3763 None,
3764 None,
3765 None,
3766 Some(8),
3767 None,
3768 Some(10),
3769 None,
3770 ]
3771 );
3772
3773 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3774 buffer.read_with(cx, |buffer, cx| {
3775 writer.fold_buffers([buffer_id_3], buffer, cx);
3776 });
3777 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
3778 let blocks = blocks_snapshot
3779 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3780 .collect::<Vec<_>>();
3781 for (_, block) in &blocks {
3782 if let BlockId::Custom(custom_block_id) = block.id() {
3783 assert!(
3784 excerpt_blocks_1.contains(&custom_block_id),
3785 "Should have no blocks from the folded buffer_1"
3786 );
3787 assert!(
3788 !excerpt_blocks_2.contains(&custom_block_id),
3789 "Should have only blocks from unfolded buffers"
3790 );
3791 assert!(
3792 !excerpt_blocks_3.contains(&custom_block_id),
3793 "Should have only blocks from unfolded buffers"
3794 );
3795 }
3796 }
3797
3798 assert_eq!(
3799 blocks_snapshot.text(),
3800 "\n\n\n111\n\n\n\n",
3801 "Should have a single, first buffer left after folding"
3802 );
3803 assert_eq!(
3804 blocks_snapshot
3805 .row_infos(BlockRow(0))
3806 .map(|i| i.buffer_row)
3807 .collect::<Vec<_>>(),
3808 vec![None, None, None, Some(0), None, None, None, None,]
3809 );
3810 }
3811
3812 #[gpui::test]
3813 fn test_basic_buffer_fold(cx: &mut gpui::TestAppContext) {
3814 cx.update(init_test);
3815
3816 let text = "111";
3817
3818 let buffer = cx.update(|cx| {
3819 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(0, 3)])], cx)
3820 });
3821 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3822 let buffer_ids = buffer_snapshot
3823 .excerpts()
3824 .map(|excerpt| excerpt.context.start.buffer_id)
3825 .dedup()
3826 .collect::<Vec<_>>();
3827 assert_eq!(buffer_ids.len(), 1);
3828 let buffer_id = buffer_ids[0];
3829
3830 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
3831 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3832 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3833 let (_, wrap_snapshot) =
3834 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3835 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
3836 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3837
3838 assert_eq!(blocks_snapshot.text(), "\n\n111");
3839
3840 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3841 buffer.read_with(cx, |buffer, cx| {
3842 writer.fold_buffers([buffer_id], buffer, cx);
3843 });
3844 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
3845 let blocks = blocks_snapshot
3846 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3847 .collect::<Vec<_>>();
3848 assert_eq!(
3849 1,
3850 blocks
3851 .iter()
3852 .filter(|(_, block)| { matches!(block, Block::FoldedBuffer { .. }) })
3853 .count(),
3854 "Should have one folded block, producing a header of the second buffer"
3855 );
3856 assert_eq!(blocks_snapshot.text(), "\n");
3857 assert_eq!(
3858 blocks_snapshot
3859 .row_infos(BlockRow(0))
3860 .map(|i| i.buffer_row)
3861 .collect::<Vec<_>>(),
3862 vec![None, None],
3863 "When fully folded, should be no buffer rows"
3864 );
3865 }
3866
3867 #[gpui::test(iterations = 60)]
3868 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
3869 cx.update(init_test);
3870
3871 let operations = env::var("OPERATIONS")
3872 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3873 .unwrap_or(10);
3874
3875 let wrap_width = if rng.random_bool(0.2) {
3876 None
3877 } else {
3878 Some(px(rng.random_range(0.0..=100.0)))
3879 };
3880 let tab_size = 1.try_into().unwrap();
3881 let font_size = px(14.0);
3882 let buffer_start_header_height = rng.random_range(1..=5);
3883 let excerpt_header_height = rng.random_range(1..=5);
3884
3885 log::info!("Wrap width: {:?}", wrap_width);
3886 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
3887 let is_singleton = rng.random();
3888 let buffer = if is_singleton {
3889 let len = rng.random_range(0..10);
3890 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
3891 log::info!("initial singleton buffer text: {:?}", text);
3892 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
3893 } else {
3894 cx.update(|cx| {
3895 let multibuffer = MultiBuffer::build_random(&mut rng, cx);
3896 log::info!(
3897 "initial multi-buffer text: {:?}",
3898 multibuffer.read(cx).read(cx).text()
3899 );
3900 multibuffer
3901 })
3902 };
3903
3904 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3905 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3906 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3907 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3908 let font = test_font();
3909 let (wrap_map, wraps_snapshot) =
3910 cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
3911 let mut block_map = BlockMap::new(
3912 wraps_snapshot,
3913 buffer_start_header_height,
3914 excerpt_header_height,
3915 );
3916
3917 for _ in 0..operations {
3918 let mut buffer_edits = Vec::new();
3919 match rng.random_range(0..=100) {
3920 0..=19 => {
3921 let wrap_width = if rng.random_bool(0.2) {
3922 None
3923 } else {
3924 Some(px(rng.random_range(0.0..=100.0)))
3925 };
3926 log::info!("Setting wrap width to {:?}", wrap_width);
3927 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
3928 }
3929 20..=39 => {
3930 let block_count = rng.random_range(1..=5);
3931 let block_properties = (0..block_count)
3932 .map(|_| {
3933 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
3934 let offset = buffer.clip_offset(
3935 rng.random_range(MultiBufferOffset(0)..=buffer.len()),
3936 Bias::Left,
3937 );
3938 let mut min_height = 0;
3939 let placement = match rng.random_range(0..3) {
3940 0 => {
3941 min_height = 1;
3942 let start = buffer.anchor_after(offset);
3943 let end = buffer.anchor_after(buffer.clip_offset(
3944 rng.random_range(offset..=buffer.len()),
3945 Bias::Left,
3946 ));
3947 BlockPlacement::Replace(start..=end)
3948 }
3949 1 => BlockPlacement::Above(buffer.anchor_after(offset)),
3950 _ => BlockPlacement::Below(buffer.anchor_after(offset)),
3951 };
3952
3953 let height = rng.random_range(min_height..512);
3954 BlockProperties {
3955 style: BlockStyle::Fixed,
3956 placement,
3957 height: Some(height),
3958 render: Arc::new(|_| div().into_any()),
3959 priority: 0,
3960 }
3961 })
3962 .collect::<Vec<_>>();
3963
3964 let (inlay_snapshot, inlay_edits) =
3965 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3966 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3967 let (tab_snapshot, tab_edits) =
3968 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3969 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3970 wrap_map.sync(tab_snapshot, tab_edits, cx)
3971 });
3972 let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3973 let block_ids =
3974 block_map.insert(block_properties.iter().map(|props| BlockProperties {
3975 placement: props.placement.clone(),
3976 height: props.height,
3977 style: props.style,
3978 render: Arc::new(|_| div().into_any()),
3979 priority: 0,
3980 }));
3981
3982 for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3983 log::info!(
3984 "inserted block {:?} with height {:?} and id {:?}",
3985 block_properties
3986 .placement
3987 .as_ref()
3988 .map(|p| p.to_point(&buffer_snapshot)),
3989 block_properties.height,
3990 block_id
3991 );
3992 }
3993 }
3994 40..=59 if !block_map.custom_blocks.is_empty() => {
3995 let block_count = rng.random_range(1..=4.min(block_map.custom_blocks.len()));
3996 let block_ids_to_remove = block_map
3997 .custom_blocks
3998 .choose_multiple(&mut rng, block_count)
3999 .map(|block| block.id)
4000 .collect::<HashSet<_>>();
4001
4002 let (inlay_snapshot, inlay_edits) =
4003 inlay_map.sync(buffer_snapshot.clone(), vec![]);
4004 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
4005 let (tab_snapshot, tab_edits) =
4006 tab_map.sync(fold_snapshot, fold_edits, tab_size);
4007 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
4008 wrap_map.sync(tab_snapshot, tab_edits, cx)
4009 });
4010 let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
4011 log::info!(
4012 "removing {} blocks: {:?}",
4013 block_ids_to_remove.len(),
4014 block_ids_to_remove
4015 );
4016 block_map.remove(block_ids_to_remove);
4017 }
4018 60..=79 => {
4019 if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
4020 log::info!("Noop fold/unfold operation on a singleton buffer");
4021 continue;
4022 }
4023 let (inlay_snapshot, inlay_edits) =
4024 inlay_map.sync(buffer_snapshot.clone(), vec![]);
4025 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
4026 let (tab_snapshot, tab_edits) =
4027 tab_map.sync(fold_snapshot, fold_edits, tab_size);
4028 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
4029 wrap_map.sync(tab_snapshot, tab_edits, cx)
4030 });
4031 let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
4032 let folded_buffers: Vec<_> =
4033 block_map.block_map.folded_buffers.iter().cloned().collect();
4034 let mut unfolded_buffers = buffer_snapshot
4035 .buffer_ids_for_range(Anchor::Min..Anchor::Max)
4036 .collect::<Vec<_>>();
4037 unfolded_buffers.dedup();
4038 log::debug!("All buffers {unfolded_buffers:?}");
4039 log::debug!("Folded buffers {folded_buffers:?}");
4040 unfolded_buffers.retain(|buffer_id| {
4041 !block_map.block_map.folded_buffers.contains(buffer_id)
4042 });
4043 let mut folded_count = folded_buffers.len();
4044 let mut unfolded_count = unfolded_buffers.len();
4045
4046 let fold = !unfolded_buffers.is_empty() && rng.random_bool(0.5);
4047 let unfold = !folded_buffers.is_empty() && rng.random_bool(0.5);
4048 if !fold && !unfold {
4049 log::info!(
4050 "Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
4051 );
4052 continue;
4053 }
4054
4055 buffer.update(cx, |buffer, cx| {
4056 if fold {
4057 let buffer_to_fold =
4058 unfolded_buffers[rng.random_range(0..unfolded_buffers.len())];
4059 log::info!("Folding {buffer_to_fold:?}");
4060 let related_excerpts = buffer_snapshot
4061 .excerpts()
4062 .filter_map(|excerpt| {
4063 if excerpt.context.start.buffer_id == buffer_to_fold {
4064 Some((
4065 excerpt.context.start,
4066 buffer_snapshot
4067 .buffer_for_id(buffer_to_fold)
4068 .unwrap()
4069 .text_for_range(excerpt.context)
4070 .collect::<String>(),
4071 ))
4072 } else {
4073 None
4074 }
4075 })
4076 .collect::<Vec<_>>();
4077 log::info!(
4078 "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
4079 );
4080 folded_count += 1;
4081 unfolded_count -= 1;
4082 block_map.fold_buffers([buffer_to_fold], buffer, cx);
4083 }
4084 if unfold {
4085 let buffer_to_unfold =
4086 folded_buffers[rng.random_range(0..folded_buffers.len())];
4087 log::info!("Unfolding {buffer_to_unfold:?}");
4088 unfolded_count += 1;
4089 folded_count -= 1;
4090 block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
4091 }
4092 log::info!(
4093 "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
4094 );
4095 });
4096 }
4097 _ => {
4098 buffer.update(cx, |buffer, cx| {
4099 let mutation_count = rng.random_range(1..=5);
4100 let subscription = buffer.subscribe();
4101 buffer.randomly_mutate(&mut rng, mutation_count, cx);
4102 buffer_snapshot = buffer.snapshot(cx);
4103 buffer_edits.extend(subscription.consume());
4104 log::info!("buffer text: {:?}", buffer_snapshot.text());
4105 });
4106 }
4107 }
4108
4109 let (inlay_snapshot, inlay_edits) =
4110 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
4111 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
4112 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
4113 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
4114 wrap_map.sync(tab_snapshot, tab_edits, cx)
4115 });
4116 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, None);
4117 assert_eq!(
4118 blocks_snapshot.transforms.summary().input_rows,
4119 wraps_snapshot.max_point().row() + RowDelta(1)
4120 );
4121 log::info!("wrapped text: {:?}", wraps_snapshot.text());
4122 log::info!("blocks text: {:?}", blocks_snapshot.text());
4123
4124 let mut expected_blocks = Vec::new();
4125 expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
4126 Some((
4127 block.placement.to_wrap_row(&wraps_snapshot)?,
4128 Block::Custom(block.clone()),
4129 ))
4130 }));
4131
4132 let mut inlay_point_cursor = wraps_snapshot.inlay_point_cursor();
4133 let mut tab_point_cursor = wraps_snapshot.tab_point_cursor();
4134 let mut fold_point_cursor = wraps_snapshot.fold_point_cursor();
4135 let mut wrap_point_cursor = wraps_snapshot.wrap_point_cursor();
4136
4137 // Note that this needs to be synced with the related section in BlockMap::sync
4138 expected_blocks.extend(block_map.header_and_footer_blocks(
4139 &buffer_snapshot,
4140 MultiBufferOffset(0)..,
4141 |point, bias| {
4142 wrap_point_cursor
4143 .map(
4144 tab_point_cursor.map(
4145 fold_point_cursor.map(inlay_point_cursor.map(point, bias), bias),
4146 ),
4147 )
4148 .row()
4149 },
4150 ));
4151
4152 BlockMap::sort_blocks(&mut expected_blocks);
4153
4154 for (placement, block) in &expected_blocks {
4155 log::info!(
4156 "Block {:?} placement: {:?} Height: {:?}",
4157 block.id(),
4158 placement,
4159 block.height()
4160 );
4161 }
4162
4163 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
4164
4165 let input_buffer_rows = buffer_snapshot
4166 .row_infos(MultiBufferRow(0))
4167 .map(|row| row.buffer_row)
4168 .collect::<Vec<_>>();
4169 let mut expected_buffer_rows = Vec::new();
4170 let mut expected_text = String::new();
4171 let mut expected_block_positions = Vec::new();
4172 let mut expected_replaced_buffer_rows = HashSet::default();
4173 let input_text = wraps_snapshot.text();
4174
4175 // Loop over the input lines, creating (N - 1) empty lines for
4176 // blocks of height N.
4177 //
4178 // It's important to note that output *starts* as one empty line,
4179 // so we special case row 0 to assume a leading '\n'.
4180 //
4181 // Linehood is the birthright of strings.
4182 let input_text_lines = input_text.split('\n').enumerate().peekable();
4183 let mut block_row = 0;
4184 for (wrap_row, input_line) in input_text_lines {
4185 let wrap_row = WrapRow(wrap_row as u32);
4186 let multibuffer_row = wraps_snapshot
4187 .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
4188 .row;
4189
4190 // Create empty lines for the above block
4191 while let Some((placement, block)) = sorted_blocks_iter.peek() {
4192 if *placement.start() == wrap_row && block.place_above() {
4193 let (_, block) = sorted_blocks_iter.next().unwrap();
4194 expected_block_positions.push((block_row, block.id()));
4195 if block.height() > 0 {
4196 let text = "\n".repeat((block.height() - 1) as usize);
4197 if block_row > 0 {
4198 expected_text.push('\n')
4199 }
4200 expected_text.push_str(&text);
4201 for _ in 0..block.height() {
4202 expected_buffer_rows.push(None);
4203 }
4204 block_row += block.height();
4205 }
4206 } else {
4207 break;
4208 }
4209 }
4210
4211 // Skip lines within replace blocks, then create empty lines for the replace block's height
4212 let mut is_in_replace_block = false;
4213 if let Some((BlockPlacement::Replace(replace_range), block)) =
4214 sorted_blocks_iter.peek()
4215 && wrap_row >= *replace_range.start()
4216 {
4217 is_in_replace_block = true;
4218
4219 if wrap_row == *replace_range.start() {
4220 if matches!(block, Block::FoldedBuffer { .. }) {
4221 expected_buffer_rows.push(None);
4222 } else {
4223 expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]);
4224 }
4225 }
4226
4227 if wrap_row == *replace_range.end() {
4228 expected_block_positions.push((block_row, block.id()));
4229 let text = "\n".repeat((block.height() - 1) as usize);
4230 if block_row > 0 {
4231 expected_text.push('\n');
4232 }
4233 expected_text.push_str(&text);
4234
4235 for _ in 1..block.height() {
4236 expected_buffer_rows.push(None);
4237 }
4238 block_row += block.height();
4239
4240 sorted_blocks_iter.next();
4241 }
4242 }
4243
4244 if is_in_replace_block {
4245 expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
4246 } else {
4247 let buffer_row = input_buffer_rows[multibuffer_row as usize];
4248 let soft_wrapped = wraps_snapshot
4249 .to_tab_point(WrapPoint::new(wrap_row, 0))
4250 .column()
4251 > 0;
4252 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
4253 if block_row > 0 {
4254 expected_text.push('\n');
4255 }
4256 expected_text.push_str(input_line);
4257 block_row += 1;
4258 }
4259
4260 while let Some((placement, block)) = sorted_blocks_iter.peek() {
4261 if *placement.end() == wrap_row && block.place_below() {
4262 let (_, block) = sorted_blocks_iter.next().unwrap();
4263 expected_block_positions.push((block_row, block.id()));
4264 if block.height() > 0 {
4265 let text = "\n".repeat((block.height() - 1) as usize);
4266 if block_row > 0 {
4267 expected_text.push('\n')
4268 }
4269 expected_text.push_str(&text);
4270 for _ in 0..block.height() {
4271 expected_buffer_rows.push(None);
4272 }
4273 block_row += block.height();
4274 }
4275 } else {
4276 break;
4277 }
4278 }
4279 }
4280
4281 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
4282 let expected_row_count = expected_lines.len();
4283 log::info!("expected text: {expected_text:?}");
4284
4285 assert_eq!(
4286 blocks_snapshot.max_point().row + 1,
4287 expected_row_count as u32,
4288 "actual row count != expected row count",
4289 );
4290 assert_eq!(
4291 blocks_snapshot.text(),
4292 expected_text,
4293 "actual text != expected text",
4294 );
4295
4296 for start_row in 0..expected_row_count {
4297 let end_row = rng.random_range(start_row + 1..=expected_row_count);
4298 let mut expected_text = expected_lines[start_row..end_row].join("\n");
4299 if end_row < expected_row_count {
4300 expected_text.push('\n');
4301 }
4302
4303 let actual_text = blocks_snapshot
4304 .chunks(
4305 BlockRow(start_row as u32)..BlockRow(end_row as u32),
4306 LanguageAwareStyling {
4307 tree_sitter: false,
4308 diagnostics: false,
4309 },
4310 false,
4311 Highlights::default(),
4312 )
4313 .map(|chunk| chunk.text)
4314 .collect::<String>();
4315 assert_eq!(
4316 actual_text,
4317 expected_text,
4318 "incorrect text starting row row range {:?}",
4319 start_row..end_row
4320 );
4321 assert_eq!(
4322 blocks_snapshot
4323 .row_infos(BlockRow(start_row as u32))
4324 .map(|row_info| row_info.buffer_row)
4325 .collect::<Vec<_>>(),
4326 &expected_buffer_rows[start_row..],
4327 "incorrect buffer_rows starting at row {:?}",
4328 start_row
4329 );
4330 }
4331
4332 assert_eq!(
4333 blocks_snapshot
4334 .blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
4335 .map(|(row, block)| (row.0, block.id()))
4336 .collect::<Vec<_>>(),
4337 expected_block_positions,
4338 "invalid blocks_in_range({:?})",
4339 0..expected_row_count
4340 );
4341
4342 for (_, expected_block) in
4343 blocks_snapshot.blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
4344 {
4345 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
4346 assert_eq!(
4347 actual_block.map(|block| block.id()),
4348 Some(expected_block.id())
4349 );
4350 }
4351
4352 for (block_row, block_id) in expected_block_positions {
4353 if let BlockId::Custom(block_id) = block_id {
4354 assert_eq!(
4355 blocks_snapshot.row_for_block(block_id),
4356 Some(BlockRow(block_row))
4357 );
4358 }
4359 }
4360
4361 let mut expected_longest_rows = Vec::new();
4362 let mut longest_line_len = -1_isize;
4363 for (row, line) in expected_lines.iter().enumerate() {
4364 let row = row as u32;
4365
4366 assert_eq!(
4367 blocks_snapshot.line_len(BlockRow(row)),
4368 line.len() as u32,
4369 "invalid line len for row {}",
4370 row
4371 );
4372
4373 let line_char_count = line.chars().count() as isize;
4374 match line_char_count.cmp(&longest_line_len) {
4375 Ordering::Less => {}
4376 Ordering::Equal => expected_longest_rows.push(row),
4377 Ordering::Greater => {
4378 longest_line_len = line_char_count;
4379 expected_longest_rows.clear();
4380 expected_longest_rows.push(row);
4381 }
4382 }
4383 }
4384
4385 let longest_row = blocks_snapshot.longest_row();
4386 assert!(
4387 expected_longest_rows.contains(&longest_row.0),
4388 "incorrect longest row {}. expected {:?} with length {}",
4389 longest_row.0,
4390 expected_longest_rows,
4391 longest_line_len,
4392 );
4393
4394 for _ in 0..10 {
4395 let end_row = rng.random_range(1..=expected_lines.len());
4396 let start_row = rng.random_range(0..end_row);
4397
4398 let mut expected_longest_rows_in_range = vec![];
4399 let mut longest_line_len_in_range = 0;
4400
4401 let mut row = start_row as u32;
4402 for line in &expected_lines[start_row..end_row] {
4403 let line_char_count = line.chars().count() as isize;
4404 match line_char_count.cmp(&longest_line_len_in_range) {
4405 Ordering::Less => {}
4406 Ordering::Equal => expected_longest_rows_in_range.push(row),
4407 Ordering::Greater => {
4408 longest_line_len_in_range = line_char_count;
4409 expected_longest_rows_in_range.clear();
4410 expected_longest_rows_in_range.push(row);
4411 }
4412 }
4413 row += 1;
4414 }
4415
4416 let longest_row_in_range = blocks_snapshot
4417 .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
4418 assert!(
4419 expected_longest_rows_in_range.contains(&longest_row_in_range.0),
4420 "incorrect longest row {} in range {:?}. expected {:?} with length {}",
4421 longest_row.0,
4422 start_row..end_row,
4423 expected_longest_rows_in_range,
4424 longest_line_len_in_range,
4425 );
4426 }
4427
4428 // Ensure that conversion between block points and wrap points is stable.
4429 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row().0 {
4430 let wrap_point = WrapPoint::new(WrapRow(row), 0);
4431 let block_point = blocks_snapshot.to_block_point(wrap_point);
4432 let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
4433 let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
4434 assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
4435 assert_eq!(
4436 blocks_snapshot.to_block_point(right_wrap_point),
4437 block_point
4438 );
4439 }
4440
4441 let mut block_point = BlockPoint::new(BlockRow(0), 0);
4442 for c in expected_text.chars() {
4443 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
4444 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
4445 assert_eq!(
4446 blocks_snapshot
4447 .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
4448 left_point,
4449 "block point: {:?}, wrap point: {:?}",
4450 block_point,
4451 blocks_snapshot.to_wrap_point(left_point, Bias::Left)
4452 );
4453 assert_eq!(
4454 left_buffer_point,
4455 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
4456 "{:?} is not valid in buffer coordinates",
4457 left_point
4458 );
4459
4460 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
4461 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
4462 assert_eq!(
4463 blocks_snapshot
4464 .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
4465 right_point,
4466 "block point: {:?}, wrap point: {:?}",
4467 block_point,
4468 blocks_snapshot.to_wrap_point(right_point, Bias::Right)
4469 );
4470 assert_eq!(
4471 right_buffer_point,
4472 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
4473 "{:?} is not valid in buffer coordinates",
4474 right_point
4475 );
4476
4477 if c == '\n' {
4478 block_point.0 += Point::new(1, 0);
4479 } else {
4480 block_point.column += c.len_utf8() as u32;
4481 }
4482 }
4483
4484 for buffer_row in 0..=buffer_snapshot.max_point().row {
4485 let buffer_row = MultiBufferRow(buffer_row);
4486 assert_eq!(
4487 blocks_snapshot.is_line_replaced(buffer_row),
4488 expected_replaced_buffer_rows.contains(&buffer_row),
4489 "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
4490 );
4491 }
4492 }
4493 }
4494
4495 #[gpui::test]
4496 fn test_remove_intersecting_replace_blocks_edge_case(cx: &mut gpui::TestAppContext) {
4497 cx.update(init_test);
4498
4499 let text = "abc\ndef\nghi\njkl\nmno";
4500 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
4501 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4502 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4503 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
4504 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4505 let (_wrap_map, wraps_snapshot) =
4506 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4507 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
4508
4509 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
4510 let _block_id = writer.insert(vec![BlockProperties {
4511 style: BlockStyle::Fixed,
4512 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
4513 height: Some(1),
4514 render: Arc::new(|_| div().into_any()),
4515 priority: 0,
4516 }])[0];
4517
4518 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
4519 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
4520
4521 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
4522 writer.remove_intersecting_replace_blocks(
4523 [buffer_snapshot
4524 .anchor_after(Point::new(1, 0))
4525 .to_offset(&buffer_snapshot)
4526 ..buffer_snapshot
4527 .anchor_after(Point::new(1, 0))
4528 .to_offset(&buffer_snapshot)],
4529 false,
4530 );
4531 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
4532 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
4533 }
4534
4535 #[gpui::test]
4536 fn test_folded_buffer_with_near_blocks(cx: &mut gpui::TestAppContext) {
4537 cx.update(init_test);
4538
4539 let text = "line 1\nline 2\nline 3";
4540 let buffer = cx.update(|cx| {
4541 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(2, 6)])], cx)
4542 });
4543 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4544 let buffer_ids = buffer_snapshot
4545 .excerpts()
4546 .map(|excerpt| excerpt.context.start.buffer_id)
4547 .dedup()
4548 .collect::<Vec<_>>();
4549 assert_eq!(buffer_ids.len(), 1);
4550 let buffer_id = buffer_ids[0];
4551
4552 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4553 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
4554 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4555 let (_, wrap_snapshot) =
4556 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4557 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
4558
4559 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4560 writer.insert(vec![BlockProperties {
4561 style: BlockStyle::Fixed,
4562 placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(0, 0))),
4563 height: Some(1),
4564 render: Arc::new(|_| div().into_any()),
4565 priority: 0,
4566 }]);
4567
4568 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
4569 assert_eq!(blocks_snapshot.text(), "\nline 1\n\nline 2\nline 3");
4570
4571 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4572 buffer.read_with(cx, |buffer, cx| {
4573 writer.fold_buffers([buffer_id], buffer, cx);
4574 });
4575
4576 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
4577 assert_eq!(blocks_snapshot.text(), "");
4578 }
4579
4580 #[gpui::test]
4581 fn test_folded_buffer_with_near_blocks_on_last_line(cx: &mut gpui::TestAppContext) {
4582 cx.update(init_test);
4583
4584 let text = "line 1\nline 2\nline 3\nline 4";
4585 let buffer = cx.update(|cx| {
4586 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(3, 6)])], cx)
4587 });
4588 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4589 let buffer_ids = buffer_snapshot
4590 .excerpts()
4591 .map(|excerpt| excerpt.context.start.buffer_id)
4592 .dedup()
4593 .collect::<Vec<_>>();
4594 assert_eq!(buffer_ids.len(), 1);
4595 let buffer_id = buffer_ids[0];
4596
4597 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4598 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
4599 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4600 let (_, wrap_snapshot) =
4601 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4602 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
4603
4604 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4605 writer.insert(vec![BlockProperties {
4606 style: BlockStyle::Fixed,
4607 placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(3, 6))),
4608 height: Some(1),
4609 render: Arc::new(|_| div().into_any()),
4610 priority: 0,
4611 }]);
4612
4613 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
4614 assert_eq!(blocks_snapshot.text(), "\nline 1\nline 2\nline 3\nline 4\n");
4615
4616 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4617 buffer.read_with(cx, |buffer, cx| {
4618 writer.fold_buffers([buffer_id], buffer, cx);
4619 });
4620
4621 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
4622 assert_eq!(blocks_snapshot.text(), "");
4623 }
4624
4625 #[gpui::test]
4626 fn test_companion_spacer_blocks(cx: &mut gpui::TestAppContext) {
4627 cx.update(init_test);
4628
4629 let base_text = "aaa\nbbb\nccc\nddd\nddd\nddd\neee\n";
4630 let main_text = "aaa\nddd\nddd\nddd\nXXX\nYYY\nZZZ\neee\n";
4631
4632 let rhs_buffer = cx.new(|cx| Buffer::local(main_text, cx));
4633 let diff = cx.new(|cx| {
4634 BufferDiff::new_with_base_text(base_text, &rhs_buffer.read(cx).text_snapshot(), cx)
4635 });
4636 let lhs_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer().clone());
4637
4638 let lhs_multibuffer = cx.new(|cx| {
4639 let mut mb = MultiBuffer::new(Capability::ReadWrite);
4640 mb.set_excerpts_for_buffer(
4641 lhs_buffer.clone(),
4642 [Point::zero()..lhs_buffer.read(cx).max_point()],
4643 0,
4644 cx,
4645 );
4646 mb.add_inverted_diff(diff.clone(), rhs_buffer.clone(), cx);
4647 mb
4648 });
4649 let rhs_multibuffer = cx.new(|cx| {
4650 let mut mb = MultiBuffer::new(Capability::ReadWrite);
4651 mb.set_excerpts_for_buffer(
4652 rhs_buffer.clone(),
4653 [Point::zero()..rhs_buffer.read(cx).max_point()],
4654 0,
4655 cx,
4656 );
4657 mb.add_diff(diff.clone(), cx);
4658 mb
4659 });
4660 let subscription =
4661 rhs_multibuffer.update(cx, |rhs_multibuffer, _| rhs_multibuffer.subscribe());
4662
4663 let lhs_buffer_snapshot = cx.update(|cx| lhs_multibuffer.read(cx).snapshot(cx));
4664 let (mut _lhs_inlay_map, lhs_inlay_snapshot) = InlayMap::new(lhs_buffer_snapshot);
4665 let (mut _lhs_fold_map, lhs_fold_snapshot) = FoldMap::new(lhs_inlay_snapshot);
4666 let (mut _lhs_tab_map, lhs_tab_snapshot) =
4667 TabMap::new(lhs_fold_snapshot, 4.try_into().unwrap());
4668 let (_lhs_wrap_map, lhs_wrap_snapshot) =
4669 cx.update(|cx| WrapMap::new(lhs_tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4670 let lhs_block_map = BlockMap::new(lhs_wrap_snapshot.clone(), 0, 0);
4671
4672 let rhs_buffer_snapshot = cx.update(|cx| rhs_multibuffer.read(cx).snapshot(cx));
4673 let (mut rhs_inlay_map, rhs_inlay_snapshot) = InlayMap::new(rhs_buffer_snapshot);
4674 let (mut rhs_fold_map, rhs_fold_snapshot) = FoldMap::new(rhs_inlay_snapshot);
4675 let (mut rhs_tab_map, rhs_tab_snapshot) =
4676 TabMap::new(rhs_fold_snapshot, 4.try_into().unwrap());
4677 let (_rhs_wrap_map, rhs_wrap_snapshot) =
4678 cx.update(|cx| WrapMap::new(rhs_tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4679 let rhs_block_map = BlockMap::new(rhs_wrap_snapshot.clone(), 0, 0);
4680
4681 let rhs_entity_id = rhs_multibuffer.entity_id();
4682
4683 let companion = cx.new(|_| {
4684 Companion::new(
4685 rhs_entity_id,
4686 convert_rhs_rows_to_lhs,
4687 convert_lhs_rows_to_rhs,
4688 )
4689 });
4690
4691 let rhs_edits = Patch::new(vec![text::Edit {
4692 old: WrapRow(0)..rhs_wrap_snapshot.max_point().row(),
4693 new: WrapRow(0)..rhs_wrap_snapshot.max_point().row(),
4694 }]);
4695 let lhs_edits = Patch::new(vec![text::Edit {
4696 old: WrapRow(0)..lhs_wrap_snapshot.max_point().row(),
4697 new: WrapRow(0)..lhs_wrap_snapshot.max_point().row(),
4698 }]);
4699
4700 let rhs_snapshot = companion.read_with(cx, |companion, _cx| {
4701 rhs_block_map.read(
4702 rhs_wrap_snapshot.clone(),
4703 rhs_edits.clone(),
4704 Some(CompanionView::new(
4705 rhs_entity_id,
4706 &lhs_wrap_snapshot,
4707 &lhs_edits,
4708 companion,
4709 )),
4710 )
4711 });
4712
4713 let lhs_entity_id = lhs_multibuffer.entity_id();
4714 let lhs_snapshot = companion.read_with(cx, |companion, _cx| {
4715 lhs_block_map.read(
4716 lhs_wrap_snapshot.clone(),
4717 lhs_edits.clone(),
4718 Some(CompanionView::new(
4719 lhs_entity_id,
4720 &rhs_wrap_snapshot,
4721 &rhs_edits,
4722 companion,
4723 )),
4724 )
4725 });
4726
4727 // LHS:
4728 // aaa
4729 // - bbb
4730 // - ccc
4731 // ddd
4732 // ddd
4733 // ddd
4734 // <extra line>
4735 // <extra line>
4736 // <extra line>
4737 // *eee
4738 //
4739 // RHS:
4740 // aaa
4741 // <extra line>
4742 // <extra line>
4743 // ddd
4744 // ddd
4745 // ddd
4746 // + XXX
4747 // + YYY
4748 // + ZZZ
4749 // eee
4750
4751 assert_eq!(
4752 rhs_snapshot.snapshot.text(),
4753 "aaa\n\n\nddd\nddd\nddd\nXXX\nYYY\nZZZ\neee\n",
4754 "RHS should have 2 spacer lines after 'aaa' to align with LHS's deleted lines"
4755 );
4756
4757 assert_eq!(
4758 lhs_snapshot.snapshot.text(),
4759 "aaa\nbbb\nccc\nddd\nddd\nddd\n\n\n\neee\n",
4760 "LHS should have 3 spacer lines in place of RHS's inserted lines"
4761 );
4762
4763 // LHS:
4764 // aaa
4765 // - bbb
4766 // - ccc
4767 // ddd
4768 // ddd
4769 // ddd
4770 // <extra line>
4771 // <extra line>
4772 // <extra line>
4773 // eee
4774 //
4775 // RHS:
4776 // aaa
4777 // <extra line>
4778 // <extra line>
4779 // ddd
4780 // foo
4781 // foo
4782 // foo
4783 // ddd
4784 // ddd
4785 // + XXX
4786 // + YYY
4787 // + ZZZ
4788 // eee
4789
4790 let rhs_buffer_snapshot = rhs_multibuffer.update(cx, |multibuffer, cx| {
4791 multibuffer.edit(
4792 [(Point::new(2, 0)..Point::new(2, 0), "foo\nfoo\nfoo\n")],
4793 None,
4794 cx,
4795 );
4796 multibuffer.snapshot(cx)
4797 });
4798
4799 let (rhs_inlay_snapshot, rhs_inlay_edits) =
4800 rhs_inlay_map.sync(rhs_buffer_snapshot, subscription.consume().into_inner());
4801 let (rhs_fold_snapshot, rhs_fold_edits) =
4802 rhs_fold_map.read(rhs_inlay_snapshot, rhs_inlay_edits);
4803 let (rhs_tab_snapshot, rhs_tab_edits) =
4804 rhs_tab_map.sync(rhs_fold_snapshot, rhs_fold_edits, 4.try_into().unwrap());
4805 let (rhs_wrap_snapshot, rhs_wrap_edits) = _rhs_wrap_map.update(cx, |wrap_map, cx| {
4806 wrap_map.sync(rhs_tab_snapshot, rhs_tab_edits, cx)
4807 });
4808
4809 let rhs_snapshot = companion.read_with(cx, |companion, _cx| {
4810 rhs_block_map.read(
4811 rhs_wrap_snapshot.clone(),
4812 rhs_wrap_edits.clone(),
4813 Some(CompanionView::new(
4814 rhs_entity_id,
4815 &lhs_wrap_snapshot,
4816 &Default::default(),
4817 companion,
4818 )),
4819 )
4820 });
4821
4822 let lhs_snapshot = companion.read_with(cx, |companion, _cx| {
4823 lhs_block_map.read(
4824 lhs_wrap_snapshot.clone(),
4825 Default::default(),
4826 Some(CompanionView::new(
4827 lhs_entity_id,
4828 &rhs_wrap_snapshot,
4829 &rhs_wrap_edits,
4830 companion,
4831 )),
4832 )
4833 });
4834
4835 assert_eq!(
4836 rhs_snapshot.snapshot.text(),
4837 "aaa\n\n\nddd\nfoo\nfoo\nfoo\nddd\nddd\nXXX\nYYY\nZZZ\neee\n",
4838 "RHS should have the insertion"
4839 );
4840
4841 assert_eq!(
4842 lhs_snapshot.snapshot.text(),
4843 "aaa\nbbb\nccc\nddd\n\n\n\nddd\nddd\n\n\n\neee\n",
4844 "LHS should have 3 more spacer lines to balance the insertion"
4845 );
4846 }
4847
4848 fn init_test(cx: &mut gpui::App) {
4849 let settings = SettingsStore::test(cx);
4850 cx.set_global(settings);
4851 theme_settings::init(theme::LoadThemes::JustBase, cx);
4852 assets::Assets.load_test_fonts(cx);
4853 }
4854
4855 impl Block {
4856 fn as_custom(&self) -> Option<&CustomBlock> {
4857 match self {
4858 Block::Custom(block) => Some(block),
4859 _ => None,
4860 }
4861 }
4862 }
4863
4864 impl BlockSnapshot {
4865 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
4866 self.wrap_snapshot
4867 .to_point(self.to_wrap_point(point, bias), bias)
4868 }
4869 }
4870}