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