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