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