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