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