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, pred: &mut dyn 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 dyn FnMut(Point, Bias) -> WrapRow,
1274 companion_wrapper: &mut dyn 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 self.input_chunk.newlines >>= prefix_bytes.saturating_sub(1);
2634
2635 let mut tabs = self.input_chunk.tabs;
2636 let mut chars = self.input_chunk.chars;
2637 let mut newlines = self.input_chunk.newlines;
2638
2639 if self.masked {
2640 // Not great for multibyte text because to keep cursor math correct we
2641 // need to have the same number of chars in the input as output.
2642 let chars_count = prefix.chars().count();
2643 let bullet_len = chars_count;
2644 prefix = unsafe { std::str::from_utf8_unchecked(&BULLETS[..bullet_len]) };
2645 chars = 1u128.unbounded_shl(bullet_len as u32).wrapping_sub(1);
2646 tabs = 0;
2647 newlines = 0;
2648 }
2649
2650 let chunk = Chunk {
2651 text: prefix,
2652 tabs,
2653 chars,
2654 newlines,
2655 ..self.input_chunk.clone()
2656 };
2657
2658 if self.output_row == transform_end {
2659 self.advance();
2660 }
2661
2662 Some(chunk)
2663 }
2664}
2665
2666impl Iterator for BlockRows<'_> {
2667 type Item = RowInfo;
2668
2669 #[ztracing::instrument(skip_all)]
2670 fn next(&mut self) -> Option<Self::Item> {
2671 if self.started {
2672 self.output_row.0 += 1;
2673 } else {
2674 self.started = true;
2675 }
2676
2677 if self.output_row >= self.transforms.end().0 {
2678 self.transforms.next();
2679 while let Some(transform) = self.transforms.item() {
2680 if transform
2681 .block
2682 .as_ref()
2683 .is_some_and(|block| block.height() == 0)
2684 {
2685 self.transforms.next();
2686 } else {
2687 break;
2688 }
2689 }
2690
2691 let transform = self.transforms.item()?;
2692 if transform
2693 .block
2694 .as_ref()
2695 .is_none_or(|block| block.is_replacement())
2696 {
2697 self.input_rows.seek(self.transforms.start().1);
2698 }
2699 }
2700
2701 let transform = self.transforms.item()?;
2702 if transform.block.as_ref().is_none_or(|block| {
2703 block.is_replacement()
2704 && self.transforms.start().0 == self.output_row
2705 && matches!(block, Block::FoldedBuffer { .. }).not()
2706 }) {
2707 self.input_rows.next()
2708 } else {
2709 Some(RowInfo::default())
2710 }
2711 }
2712}
2713
2714impl sum_tree::Item for Transform {
2715 type Summary = TransformSummary;
2716
2717 fn summary(&self, _cx: ()) -> Self::Summary {
2718 self.summary.clone()
2719 }
2720}
2721
2722impl sum_tree::ContextLessSummary for TransformSummary {
2723 fn zero() -> Self {
2724 Default::default()
2725 }
2726
2727 fn add_summary(&mut self, summary: &Self) {
2728 if summary.longest_row_chars > self.longest_row_chars {
2729 self.longest_row = self.output_rows + summary.longest_row;
2730 self.longest_row_chars = summary.longest_row_chars;
2731 }
2732 self.input_rows += summary.input_rows;
2733 self.output_rows += summary.output_rows;
2734 }
2735}
2736
2737impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
2738 fn zero(_cx: ()) -> Self {
2739 Default::default()
2740 }
2741
2742 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
2743 *self += summary.input_rows;
2744 }
2745}
2746
2747impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
2748 fn zero(_cx: ()) -> Self {
2749 Default::default()
2750 }
2751
2752 fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) {
2753 *self += summary.output_rows;
2754 }
2755}
2756
2757impl Deref for BlockContext<'_, '_> {
2758 type Target = App;
2759
2760 fn deref(&self) -> &Self::Target {
2761 self.app
2762 }
2763}
2764
2765impl DerefMut for BlockContext<'_, '_> {
2766 fn deref_mut(&mut self) -> &mut Self::Target {
2767 self.app
2768 }
2769}
2770
2771impl CustomBlock {
2772 #[ztracing::instrument(skip_all)]
2773 pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
2774 self.render.lock()(cx)
2775 }
2776
2777 #[ztracing::instrument(skip_all)]
2778 pub fn start(&self) -> Anchor {
2779 *self.placement.start()
2780 }
2781
2782 #[ztracing::instrument(skip_all)]
2783 pub fn end(&self) -> Anchor {
2784 *self.placement.end()
2785 }
2786
2787 pub fn style(&self) -> BlockStyle {
2788 self.style
2789 }
2790
2791 pub fn properties(&self) -> BlockProperties<Anchor> {
2792 BlockProperties {
2793 placement: self.placement.clone(),
2794 height: self.height,
2795 style: self.style,
2796 render: Arc::new(|_| {
2797 // Not used
2798 gpui::Empty.into_any_element()
2799 }),
2800 priority: self.priority,
2801 }
2802 }
2803}
2804
2805impl Debug for CustomBlock {
2806 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2807 f.debug_struct("Block")
2808 .field("id", &self.id)
2809 .field("placement", &self.placement)
2810 .field("height", &self.height)
2811 .field("style", &self.style)
2812 .field("priority", &self.priority)
2813 .finish_non_exhaustive()
2814 }
2815}
2816
2817// Count the number of bytes prior to a target point. If the string doesn't contain the target
2818// point, return its total extent. Otherwise return the target point itself.
2819fn offset_for_row(s: &str, target: RowDelta) -> (RowDelta, usize) {
2820 let mut row = 0;
2821 let mut offset = 0;
2822 for (ix, line) in s.split('\n').enumerate() {
2823 if ix > 0 {
2824 row += 1;
2825 offset += 1;
2826 }
2827 if row >= target.0 {
2828 break;
2829 }
2830 offset += line.len();
2831 }
2832 (RowDelta(row), offset)
2833}
2834
2835#[cfg(test)]
2836mod tests {
2837 use super::*;
2838 use crate::{
2839 display_map::{
2840 Companion, fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap,
2841 },
2842 split::{convert_lhs_rows_to_rhs, convert_rhs_rows_to_lhs},
2843 test::test_font,
2844 };
2845 use buffer_diff::BufferDiff;
2846 use gpui::{App, AppContext as _, Element, div, font, px};
2847 use itertools::Itertools;
2848 use language::{Buffer, Capability};
2849 use multi_buffer::{ExcerptRange, MultiBuffer};
2850 use rand::prelude::*;
2851 use settings::SettingsStore;
2852 use std::env;
2853 use util::RandomCharIter;
2854
2855 #[gpui::test]
2856 fn test_offset_for_row() {
2857 assert_eq!(offset_for_row("", RowDelta(0)), (RowDelta(0), 0));
2858 assert_eq!(offset_for_row("", RowDelta(1)), (RowDelta(0), 0));
2859 assert_eq!(offset_for_row("abcd", RowDelta(0)), (RowDelta(0), 0));
2860 assert_eq!(offset_for_row("abcd", RowDelta(1)), (RowDelta(0), 4));
2861 assert_eq!(offset_for_row("\n", RowDelta(0)), (RowDelta(0), 0));
2862 assert_eq!(offset_for_row("\n", RowDelta(1)), (RowDelta(1), 1));
2863 assert_eq!(
2864 offset_for_row("abc\ndef\nghi", RowDelta(0)),
2865 (RowDelta(0), 0)
2866 );
2867 assert_eq!(
2868 offset_for_row("abc\ndef\nghi", RowDelta(1)),
2869 (RowDelta(1), 4)
2870 );
2871 assert_eq!(
2872 offset_for_row("abc\ndef\nghi", RowDelta(2)),
2873 (RowDelta(2), 8)
2874 );
2875 assert_eq!(
2876 offset_for_row("abc\ndef\nghi", RowDelta(3)),
2877 (RowDelta(2), 11)
2878 );
2879 }
2880
2881 #[gpui::test]
2882 fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
2883 cx.update(init_test);
2884
2885 let text = "aaa\nbbb\nccc\nddd";
2886
2887 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2888 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2889 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2890 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2891 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2892 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2893 let (wrap_map, wraps_snapshot) =
2894 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2895 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2896
2897 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
2898 let block_ids = writer.insert(vec![
2899 BlockProperties {
2900 style: BlockStyle::Fixed,
2901 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2902 height: Some(1),
2903 render: Arc::new(|_| div().into_any()),
2904 priority: 0,
2905 },
2906 BlockProperties {
2907 style: BlockStyle::Fixed,
2908 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2909 height: Some(2),
2910 render: Arc::new(|_| div().into_any()),
2911 priority: 0,
2912 },
2913 BlockProperties {
2914 style: BlockStyle::Fixed,
2915 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2916 height: Some(3),
2917 render: Arc::new(|_| div().into_any()),
2918 priority: 0,
2919 },
2920 ]);
2921
2922 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
2923 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2924
2925 let blocks = snapshot
2926 .blocks_in_range(BlockRow(0)..BlockRow(8))
2927 .map(|(start_row, block)| {
2928 let block = block.as_custom().unwrap();
2929 (start_row.0..start_row.0 + block.height.unwrap(), block.id)
2930 })
2931 .collect::<Vec<_>>();
2932
2933 // When multiple blocks are on the same line, the newer blocks appear first.
2934 assert_eq!(
2935 blocks,
2936 &[
2937 (1..2, block_ids[0]),
2938 (2..4, block_ids[1]),
2939 (7..10, block_ids[2]),
2940 ]
2941 );
2942
2943 assert_eq!(
2944 snapshot.to_block_point(WrapPoint::new(WrapRow(0), 3)),
2945 BlockPoint::new(BlockRow(0), 3)
2946 );
2947 assert_eq!(
2948 snapshot.to_block_point(WrapPoint::new(WrapRow(1), 0)),
2949 BlockPoint::new(BlockRow(4), 0)
2950 );
2951 assert_eq!(
2952 snapshot.to_block_point(WrapPoint::new(WrapRow(3), 3)),
2953 BlockPoint::new(BlockRow(6), 3)
2954 );
2955
2956 assert_eq!(
2957 snapshot.to_wrap_point(BlockPoint::new(BlockRow(0), 3), Bias::Left),
2958 WrapPoint::new(WrapRow(0), 3)
2959 );
2960 assert_eq!(
2961 snapshot.to_wrap_point(BlockPoint::new(BlockRow(1), 0), Bias::Left),
2962 WrapPoint::new(WrapRow(1), 0)
2963 );
2964 assert_eq!(
2965 snapshot.to_wrap_point(BlockPoint::new(BlockRow(3), 0), Bias::Left),
2966 WrapPoint::new(WrapRow(1), 0)
2967 );
2968 assert_eq!(
2969 snapshot.to_wrap_point(BlockPoint::new(BlockRow(7), 0), Bias::Left),
2970 WrapPoint::new(WrapRow(3), 3)
2971 );
2972
2973 assert_eq!(
2974 snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Left),
2975 BlockPoint::new(BlockRow(0), 3)
2976 );
2977 assert_eq!(
2978 snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Right),
2979 BlockPoint::new(BlockRow(4), 0)
2980 );
2981 assert_eq!(
2982 snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Left),
2983 BlockPoint::new(BlockRow(0), 3)
2984 );
2985 assert_eq!(
2986 snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Right),
2987 BlockPoint::new(BlockRow(4), 0)
2988 );
2989 assert_eq!(
2990 snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Left),
2991 BlockPoint::new(BlockRow(4), 0)
2992 );
2993 assert_eq!(
2994 snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Right),
2995 BlockPoint::new(BlockRow(4), 0)
2996 );
2997 assert_eq!(
2998 snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Left),
2999 BlockPoint::new(BlockRow(6), 3)
3000 );
3001 assert_eq!(
3002 snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Right),
3003 BlockPoint::new(BlockRow(6), 3)
3004 );
3005 assert_eq!(
3006 snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Left),
3007 BlockPoint::new(BlockRow(6), 3)
3008 );
3009 assert_eq!(
3010 snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Right),
3011 BlockPoint::new(BlockRow(6), 3)
3012 );
3013
3014 assert_eq!(
3015 snapshot
3016 .row_infos(BlockRow(0))
3017 .map(|row_info| row_info.buffer_row)
3018 .collect::<Vec<_>>(),
3019 &[
3020 Some(0),
3021 None,
3022 None,
3023 None,
3024 Some(1),
3025 Some(2),
3026 Some(3),
3027 None,
3028 None,
3029 None
3030 ]
3031 );
3032
3033 // Insert a line break, separating two block decorations into separate lines.
3034 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3035 buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
3036 buffer.snapshot(cx)
3037 });
3038
3039 let (inlay_snapshot, inlay_edits) =
3040 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
3041 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3042 let (tab_snapshot, tab_edits) =
3043 tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
3044 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3045 wrap_map.sync(tab_snapshot, tab_edits, cx)
3046 });
3047 let snapshot = block_map.read(wraps_snapshot, wrap_edits, None);
3048 assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
3049 }
3050
3051 #[gpui::test]
3052 fn test_multibuffer_headers_and_footers(cx: &mut App) {
3053 init_test(cx);
3054
3055 let buffer1 = cx.new(|cx| Buffer::local("Buffer 1", cx));
3056 let buffer2 = cx.new(|cx| Buffer::local("Buffer 2", cx));
3057 let buffer3 = cx.new(|cx| Buffer::local("Buffer 3", cx));
3058
3059 let mut excerpt_ids = Vec::new();
3060 let multi_buffer = cx.new(|cx| {
3061 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
3062 excerpt_ids.extend(multi_buffer.push_excerpts(
3063 buffer1.clone(),
3064 [ExcerptRange::new(0..buffer1.read(cx).len())],
3065 cx,
3066 ));
3067 excerpt_ids.extend(multi_buffer.push_excerpts(
3068 buffer2.clone(),
3069 [ExcerptRange::new(0..buffer2.read(cx).len())],
3070 cx,
3071 ));
3072 excerpt_ids.extend(multi_buffer.push_excerpts(
3073 buffer3.clone(),
3074 [ExcerptRange::new(0..buffer3.read(cx).len())],
3075 cx,
3076 ));
3077
3078 multi_buffer
3079 });
3080
3081 let font = test_font();
3082 let font_size = px(14.);
3083 let font_id = cx.text_system().resolve_font(&font);
3084 let mut wrap_width = px(0.);
3085 for c in "Buff".chars() {
3086 wrap_width += cx
3087 .text_system()
3088 .advance(font_id, font_size, c)
3089 .unwrap()
3090 .width;
3091 }
3092
3093 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
3094 let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot);
3095 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3096 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3097 let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
3098
3099 let block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3100 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3101
3102 // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
3103 assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3");
3104
3105 let blocks: Vec<_> = snapshot
3106 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3107 .map(|(row, block)| (row.0..row.0 + block.height(), block.id()))
3108 .collect();
3109 assert_eq!(
3110 blocks,
3111 vec![
3112 (0..1, BlockId::ExcerptBoundary(excerpt_ids[0])), // path, header
3113 (3..4, BlockId::ExcerptBoundary(excerpt_ids[1])), // path, header
3114 (6..7, BlockId::ExcerptBoundary(excerpt_ids[2])), // path, header
3115 ]
3116 );
3117 }
3118
3119 #[gpui::test]
3120 fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
3121 cx.update(init_test);
3122
3123 let text = "aaa\nbbb\nccc\nddd";
3124
3125 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3126 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3127 let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
3128 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3129 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3130 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
3131 let (_wrap_map, wraps_snapshot) =
3132 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3133 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3134
3135 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3136 let block_ids = writer.insert(vec![
3137 BlockProperties {
3138 style: BlockStyle::Fixed,
3139 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3140 height: Some(1),
3141 render: Arc::new(|_| div().into_any()),
3142 priority: 0,
3143 },
3144 BlockProperties {
3145 style: BlockStyle::Fixed,
3146 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
3147 height: Some(2),
3148 render: Arc::new(|_| div().into_any()),
3149 priority: 0,
3150 },
3151 BlockProperties {
3152 style: BlockStyle::Fixed,
3153 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
3154 height: Some(3),
3155 render: Arc::new(|_| div().into_any()),
3156 priority: 0,
3157 },
3158 ]);
3159
3160 {
3161 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3162 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
3163
3164 let mut block_map_writer =
3165 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3166
3167 let mut new_heights = HashMap::default();
3168 new_heights.insert(block_ids[0], 2);
3169 block_map_writer.resize(new_heights);
3170 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3171 assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3172 }
3173
3174 {
3175 let mut block_map_writer =
3176 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3177
3178 let mut new_heights = HashMap::default();
3179 new_heights.insert(block_ids[0], 1);
3180 block_map_writer.resize(new_heights);
3181
3182 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3183 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
3184 }
3185
3186 {
3187 let mut block_map_writer =
3188 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3189
3190 let mut new_heights = HashMap::default();
3191 new_heights.insert(block_ids[0], 0);
3192 block_map_writer.resize(new_heights);
3193
3194 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3195 assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
3196 }
3197
3198 {
3199 let mut block_map_writer =
3200 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3201
3202 let mut new_heights = HashMap::default();
3203 new_heights.insert(block_ids[0], 3);
3204 block_map_writer.resize(new_heights);
3205
3206 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3207 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3208 }
3209
3210 {
3211 let mut block_map_writer =
3212 block_map.write(wraps_snapshot.clone(), Default::default(), None);
3213
3214 let mut new_heights = HashMap::default();
3215 new_heights.insert(block_ids[0], 3);
3216 block_map_writer.resize(new_heights);
3217
3218 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3219 // Same height as before, should remain the same
3220 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
3221 }
3222 }
3223
3224 #[gpui::test]
3225 fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
3226 cx.update(init_test);
3227
3228 let text = "one two three\nfour five six\nseven eight";
3229
3230 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3231 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3232 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3233 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3234 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3235 let (_, wraps_snapshot) = cx.update(|cx| {
3236 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(90.)), cx)
3237 });
3238 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3239
3240 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3241 writer.insert(vec![
3242 BlockProperties {
3243 style: BlockStyle::Fixed,
3244 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
3245 render: Arc::new(|_| div().into_any()),
3246 height: Some(1),
3247 priority: 0,
3248 },
3249 BlockProperties {
3250 style: BlockStyle::Fixed,
3251 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
3252 render: Arc::new(|_| div().into_any()),
3253 height: Some(1),
3254 priority: 0,
3255 },
3256 ]);
3257
3258 // Blocks with an 'above' disposition go above their corresponding buffer line.
3259 // Blocks with a 'below' disposition go below their corresponding buffer line.
3260 let snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3261 assert_eq!(
3262 snapshot.text(),
3263 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
3264 );
3265 }
3266
3267 #[gpui::test]
3268 fn test_replace_lines(cx: &mut gpui::TestAppContext) {
3269 cx.update(init_test);
3270
3271 let text = "line1\nline2\nline3\nline4\nline5";
3272
3273 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3274 let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
3275 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3276 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3277 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3278 let tab_size = 1.try_into().unwrap();
3279 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
3280 let (wrap_map, wraps_snapshot) =
3281 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3282 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3283
3284 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3285 let replace_block_id = writer.insert(vec![BlockProperties {
3286 style: BlockStyle::Fixed,
3287 placement: BlockPlacement::Replace(
3288 buffer_snapshot.anchor_after(Point::new(1, 3))
3289 ..=buffer_snapshot.anchor_before(Point::new(3, 1)),
3290 ),
3291 height: Some(4),
3292 render: Arc::new(|_| div().into_any()),
3293 priority: 0,
3294 }])[0];
3295
3296 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3297 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3298
3299 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3300 buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
3301 buffer.snapshot(cx)
3302 });
3303 let (inlay_snapshot, inlay_edits) =
3304 inlay_map.sync(buffer_snapshot, buffer_subscription.consume().into_inner());
3305 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3306 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3307 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3308 wrap_map.sync(tab_snapshot, tab_edits, cx)
3309 });
3310 let blocks_snapshot = block_map.read(wraps_snapshot, wrap_edits, None);
3311 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3312
3313 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
3314 buffer.edit(
3315 [(
3316 Point::new(1, 5)..Point::new(1, 5),
3317 "\nline 2.1\nline2.2\nline 2.3\nline 2.4",
3318 )],
3319 None,
3320 cx,
3321 );
3322 buffer.snapshot(cx)
3323 });
3324 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
3325 buffer_snapshot.clone(),
3326 buffer_subscription.consume().into_inner(),
3327 );
3328 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3329 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3330 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3331 wrap_map.sync(tab_snapshot, tab_edits, cx)
3332 });
3333 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, None);
3334 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
3335
3336 // Blocks inserted right above the start or right below the end of the replaced region are hidden.
3337 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3338 writer.insert(vec![
3339 BlockProperties {
3340 style: BlockStyle::Fixed,
3341 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))),
3342 height: Some(1),
3343 render: Arc::new(|_| div().into_any()),
3344 priority: 0,
3345 },
3346 BlockProperties {
3347 style: BlockStyle::Fixed,
3348 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
3349 height: Some(1),
3350 render: Arc::new(|_| div().into_any()),
3351 priority: 0,
3352 },
3353 BlockProperties {
3354 style: BlockStyle::Fixed,
3355 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
3356 height: Some(1),
3357 render: Arc::new(|_| div().into_any()),
3358 priority: 0,
3359 },
3360 ]);
3361 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3362 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
3363
3364 // Ensure blocks inserted *inside* replaced region are hidden.
3365 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3366 writer.insert(vec![
3367 BlockProperties {
3368 style: BlockStyle::Fixed,
3369 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
3370 height: Some(1),
3371 render: Arc::new(|_| div().into_any()),
3372 priority: 0,
3373 },
3374 BlockProperties {
3375 style: BlockStyle::Fixed,
3376 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
3377 height: Some(1),
3378 render: Arc::new(|_| div().into_any()),
3379 priority: 0,
3380 },
3381 BlockProperties {
3382 style: BlockStyle::Fixed,
3383 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
3384 height: Some(1),
3385 render: Arc::new(|_| div().into_any()),
3386 priority: 0,
3387 },
3388 ]);
3389 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
3390 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
3391
3392 // Removing the replace block shows all the hidden blocks again.
3393 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
3394 writer.remove(HashSet::from_iter([replace_block_id]));
3395 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
3396 assert_eq!(
3397 blocks_snapshot.text(),
3398 "\nline1\n\nline2\n\n\nline 2.1\nline2.2\nline 2.3\nline 2.4\n\nline4\n\nline5"
3399 );
3400 }
3401
3402 #[gpui::test]
3403 fn test_custom_blocks_inside_buffer_folds(cx: &mut gpui::TestAppContext) {
3404 cx.update(init_test);
3405
3406 let text = "111\n222\n333\n444\n555\n666";
3407
3408 let buffer = cx.update(|cx| {
3409 MultiBuffer::build_multi(
3410 [
3411 (text, vec![Point::new(0, 0)..Point::new(0, 3)]),
3412 (
3413 text,
3414 vec![
3415 Point::new(1, 0)..Point::new(1, 3),
3416 Point::new(2, 0)..Point::new(2, 3),
3417 Point::new(3, 0)..Point::new(3, 3),
3418 ],
3419 ),
3420 (
3421 text,
3422 vec![
3423 Point::new(4, 0)..Point::new(4, 3),
3424 Point::new(5, 0)..Point::new(5, 3),
3425 ],
3426 ),
3427 ],
3428 cx,
3429 )
3430 });
3431 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3432 let buffer_ids = buffer_snapshot
3433 .excerpts()
3434 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3435 .dedup()
3436 .collect::<Vec<_>>();
3437 assert_eq!(buffer_ids.len(), 3);
3438 let buffer_id_1 = buffer_ids[0];
3439 let buffer_id_2 = buffer_ids[1];
3440 let buffer_id_3 = buffer_ids[2];
3441
3442 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3443 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3444 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3445 let (_, wrap_snapshot) =
3446 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3447 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
3448 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3449
3450 assert_eq!(
3451 blocks_snapshot.text(),
3452 "\n\n111\n\n\n222\n\n333\n\n444\n\n\n555\n\n666"
3453 );
3454 assert_eq!(
3455 blocks_snapshot
3456 .row_infos(BlockRow(0))
3457 .map(|i| i.buffer_row)
3458 .collect::<Vec<_>>(),
3459 vec![
3460 None,
3461 None,
3462 Some(0),
3463 None,
3464 None,
3465 Some(1),
3466 None,
3467 Some(2),
3468 None,
3469 Some(3),
3470 None,
3471 None,
3472 Some(4),
3473 None,
3474 Some(5),
3475 ]
3476 );
3477
3478 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3479 let excerpt_blocks_2 = writer.insert(vec![
3480 BlockProperties {
3481 style: BlockStyle::Fixed,
3482 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3483 height: Some(1),
3484 render: Arc::new(|_| div().into_any()),
3485 priority: 0,
3486 },
3487 BlockProperties {
3488 style: BlockStyle::Fixed,
3489 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 0))),
3490 height: Some(1),
3491 render: Arc::new(|_| div().into_any()),
3492 priority: 0,
3493 },
3494 BlockProperties {
3495 style: BlockStyle::Fixed,
3496 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 0))),
3497 height: Some(1),
3498 render: Arc::new(|_| div().into_any()),
3499 priority: 0,
3500 },
3501 ]);
3502 let excerpt_blocks_3 = writer.insert(vec![
3503 BlockProperties {
3504 style: BlockStyle::Fixed,
3505 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))),
3506 height: Some(1),
3507 render: Arc::new(|_| div().into_any()),
3508 priority: 0,
3509 },
3510 BlockProperties {
3511 style: BlockStyle::Fixed,
3512 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))),
3513 height: Some(1),
3514 render: Arc::new(|_| div().into_any()),
3515 priority: 0,
3516 },
3517 ]);
3518
3519 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3520 assert_eq!(
3521 blocks_snapshot.text(),
3522 "\n\n111\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
3523 );
3524 assert_eq!(
3525 blocks_snapshot
3526 .row_infos(BlockRow(0))
3527 .map(|i| i.buffer_row)
3528 .collect::<Vec<_>>(),
3529 vec![
3530 None,
3531 None,
3532 Some(0),
3533 None,
3534 None,
3535 None,
3536 Some(1),
3537 None,
3538 None,
3539 Some(2),
3540 None,
3541 Some(3),
3542 None,
3543 None,
3544 None,
3545 None,
3546 Some(4),
3547 None,
3548 Some(5),
3549 None,
3550 ]
3551 );
3552
3553 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3554 buffer.read_with(cx, |buffer, cx| {
3555 writer.fold_buffers([buffer_id_1], buffer, cx);
3556 });
3557 let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
3558 style: BlockStyle::Fixed,
3559 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))),
3560 height: Some(1),
3561 render: Arc::new(|_| div().into_any()),
3562 priority: 0,
3563 }]);
3564 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3565 let blocks = blocks_snapshot
3566 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3567 .collect::<Vec<_>>();
3568 for (_, block) in &blocks {
3569 if let BlockId::Custom(custom_block_id) = block.id() {
3570 assert!(
3571 !excerpt_blocks_1.contains(&custom_block_id),
3572 "Should have no blocks from the folded buffer"
3573 );
3574 assert!(
3575 excerpt_blocks_2.contains(&custom_block_id)
3576 || excerpt_blocks_3.contains(&custom_block_id),
3577 "Should have only blocks from unfolded buffers"
3578 );
3579 }
3580 }
3581 assert_eq!(
3582 1,
3583 blocks
3584 .iter()
3585 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3586 .count(),
3587 "Should have one folded block, producing a header of the second buffer"
3588 );
3589 assert_eq!(
3590 blocks_snapshot.text(),
3591 "\n\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
3592 );
3593 assert_eq!(
3594 blocks_snapshot
3595 .row_infos(BlockRow(0))
3596 .map(|i| i.buffer_row)
3597 .collect::<Vec<_>>(),
3598 vec![
3599 None,
3600 None,
3601 None,
3602 None,
3603 None,
3604 Some(1),
3605 None,
3606 None,
3607 Some(2),
3608 None,
3609 Some(3),
3610 None,
3611 None,
3612 None,
3613 None,
3614 Some(4),
3615 None,
3616 Some(5),
3617 None,
3618 ]
3619 );
3620
3621 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3622 buffer.read_with(cx, |buffer, cx| {
3623 writer.fold_buffers([buffer_id_2], buffer, cx);
3624 });
3625 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3626 let blocks = blocks_snapshot
3627 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3628 .collect::<Vec<_>>();
3629 for (_, block) in &blocks {
3630 if let BlockId::Custom(custom_block_id) = block.id() {
3631 assert!(
3632 !excerpt_blocks_1.contains(&custom_block_id),
3633 "Should have no blocks from the folded buffer_1"
3634 );
3635 assert!(
3636 !excerpt_blocks_2.contains(&custom_block_id),
3637 "Should have no blocks from the folded buffer_2"
3638 );
3639 assert!(
3640 excerpt_blocks_3.contains(&custom_block_id),
3641 "Should have only blocks from unfolded buffers"
3642 );
3643 }
3644 }
3645 assert_eq!(
3646 2,
3647 blocks
3648 .iter()
3649 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3650 .count(),
3651 "Should have two folded blocks, producing headers"
3652 );
3653 assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n555\n\n666\n");
3654 assert_eq!(
3655 blocks_snapshot
3656 .row_infos(BlockRow(0))
3657 .map(|i| i.buffer_row)
3658 .collect::<Vec<_>>(),
3659 vec![
3660 None,
3661 None,
3662 None,
3663 None,
3664 None,
3665 None,
3666 None,
3667 Some(4),
3668 None,
3669 Some(5),
3670 None,
3671 ]
3672 );
3673
3674 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3675 buffer.read_with(cx, |buffer, cx| {
3676 writer.unfold_buffers([buffer_id_1], buffer, cx);
3677 });
3678 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3679 let blocks = blocks_snapshot
3680 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3681 .collect::<Vec<_>>();
3682 for (_, block) in &blocks {
3683 if let BlockId::Custom(custom_block_id) = block.id() {
3684 assert!(
3685 !excerpt_blocks_2.contains(&custom_block_id),
3686 "Should have no blocks from the folded buffer_2"
3687 );
3688 assert!(
3689 excerpt_blocks_1.contains(&custom_block_id)
3690 || excerpt_blocks_3.contains(&custom_block_id),
3691 "Should have only blocks from unfolded buffers"
3692 );
3693 }
3694 }
3695 assert_eq!(
3696 1,
3697 blocks
3698 .iter()
3699 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
3700 .count(),
3701 "Should be back to a single folded buffer, producing a header for buffer_2"
3702 );
3703 assert_eq!(
3704 blocks_snapshot.text(),
3705 "\n\n\n111\n\n\n\n\n\n555\n\n666\n",
3706 "Should have extra newline for 111 buffer, due to a new block added when it was folded"
3707 );
3708 assert_eq!(
3709 blocks_snapshot
3710 .row_infos(BlockRow(0))
3711 .map(|i| i.buffer_row)
3712 .collect::<Vec<_>>(),
3713 vec![
3714 None,
3715 None,
3716 None,
3717 Some(0),
3718 None,
3719 None,
3720 None,
3721 None,
3722 None,
3723 Some(4),
3724 None,
3725 Some(5),
3726 None,
3727 ]
3728 );
3729
3730 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3731 buffer.read_with(cx, |buffer, cx| {
3732 writer.fold_buffers([buffer_id_3], buffer, cx);
3733 });
3734 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
3735 let blocks = blocks_snapshot
3736 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3737 .collect::<Vec<_>>();
3738 for (_, block) in &blocks {
3739 if let BlockId::Custom(custom_block_id) = block.id() {
3740 assert!(
3741 excerpt_blocks_1.contains(&custom_block_id),
3742 "Should have no blocks from the folded buffer_1"
3743 );
3744 assert!(
3745 !excerpt_blocks_2.contains(&custom_block_id),
3746 "Should have only blocks from unfolded buffers"
3747 );
3748 assert!(
3749 !excerpt_blocks_3.contains(&custom_block_id),
3750 "Should have only blocks from unfolded buffers"
3751 );
3752 }
3753 }
3754
3755 assert_eq!(
3756 blocks_snapshot.text(),
3757 "\n\n\n111\n\n\n\n",
3758 "Should have a single, first buffer left after folding"
3759 );
3760 assert_eq!(
3761 blocks_snapshot
3762 .row_infos(BlockRow(0))
3763 .map(|i| i.buffer_row)
3764 .collect::<Vec<_>>(),
3765 vec![None, None, None, Some(0), None, None, None, None,]
3766 );
3767 }
3768
3769 #[gpui::test]
3770 fn test_basic_buffer_fold(cx: &mut gpui::TestAppContext) {
3771 cx.update(init_test);
3772
3773 let text = "111";
3774
3775 let buffer = cx.update(|cx| {
3776 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(0, 3)])], cx)
3777 });
3778 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3779 let buffer_ids = buffer_snapshot
3780 .excerpts()
3781 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
3782 .dedup()
3783 .collect::<Vec<_>>();
3784 assert_eq!(buffer_ids.len(), 1);
3785 let buffer_id = buffer_ids[0];
3786
3787 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
3788 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
3789 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3790 let (_, wrap_snapshot) =
3791 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3792 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
3793 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
3794
3795 assert_eq!(blocks_snapshot.text(), "\n\n111");
3796
3797 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
3798 buffer.read_with(cx, |buffer, cx| {
3799 writer.fold_buffers([buffer_id], buffer, cx);
3800 });
3801 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
3802 let blocks = blocks_snapshot
3803 .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX))
3804 .collect::<Vec<_>>();
3805 assert_eq!(
3806 1,
3807 blocks
3808 .iter()
3809 .filter(|(_, block)| { matches!(block, Block::FoldedBuffer { .. }) })
3810 .count(),
3811 "Should have one folded block, producing a header of the second buffer"
3812 );
3813 assert_eq!(blocks_snapshot.text(), "\n");
3814 assert_eq!(
3815 blocks_snapshot
3816 .row_infos(BlockRow(0))
3817 .map(|i| i.buffer_row)
3818 .collect::<Vec<_>>(),
3819 vec![None, None],
3820 "When fully folded, should be no buffer rows"
3821 );
3822 }
3823
3824 #[gpui::test(iterations = 60)]
3825 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
3826 cx.update(init_test);
3827
3828 let operations = env::var("OPERATIONS")
3829 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3830 .unwrap_or(10);
3831
3832 let wrap_width = if rng.random_bool(0.2) {
3833 None
3834 } else {
3835 Some(px(rng.random_range(0.0..=100.0)))
3836 };
3837 let tab_size = 1.try_into().unwrap();
3838 let font_size = px(14.0);
3839 let buffer_start_header_height = rng.random_range(1..=5);
3840 let excerpt_header_height = rng.random_range(1..=5);
3841
3842 log::info!("Wrap width: {:?}", wrap_width);
3843 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
3844 let is_singleton = rng.random();
3845 let buffer = if is_singleton {
3846 let len = rng.random_range(0..10);
3847 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
3848 log::info!("initial singleton buffer text: {:?}", text);
3849 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
3850 } else {
3851 cx.update(|cx| {
3852 let multibuffer = MultiBuffer::build_random(&mut rng, cx);
3853 log::info!(
3854 "initial multi-buffer text: {:?}",
3855 multibuffer.read(cx).read(cx).text()
3856 );
3857 multibuffer
3858 })
3859 };
3860
3861 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3862 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3863 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3864 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3865 let font = test_font();
3866 let (wrap_map, wraps_snapshot) =
3867 cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
3868 let mut block_map = BlockMap::new(
3869 wraps_snapshot,
3870 buffer_start_header_height,
3871 excerpt_header_height,
3872 );
3873
3874 for _ in 0..operations {
3875 let mut buffer_edits = Vec::new();
3876 match rng.random_range(0..=100) {
3877 0..=19 => {
3878 let wrap_width = if rng.random_bool(0.2) {
3879 None
3880 } else {
3881 Some(px(rng.random_range(0.0..=100.0)))
3882 };
3883 log::info!("Setting wrap width to {:?}", wrap_width);
3884 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
3885 }
3886 20..=39 => {
3887 let block_count = rng.random_range(1..=5);
3888 let block_properties = (0..block_count)
3889 .map(|_| {
3890 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
3891 let offset = buffer.clip_offset(
3892 rng.random_range(MultiBufferOffset(0)..=buffer.len()),
3893 Bias::Left,
3894 );
3895 let mut min_height = 0;
3896 let placement = match rng.random_range(0..3) {
3897 0 => {
3898 min_height = 1;
3899 let start = buffer.anchor_after(offset);
3900 let end = buffer.anchor_after(buffer.clip_offset(
3901 rng.random_range(offset..=buffer.len()),
3902 Bias::Left,
3903 ));
3904 BlockPlacement::Replace(start..=end)
3905 }
3906 1 => BlockPlacement::Above(buffer.anchor_after(offset)),
3907 _ => BlockPlacement::Below(buffer.anchor_after(offset)),
3908 };
3909
3910 let height = rng.random_range(min_height..512);
3911 BlockProperties {
3912 style: BlockStyle::Fixed,
3913 placement,
3914 height: Some(height),
3915 render: Arc::new(|_| div().into_any()),
3916 priority: 0,
3917 }
3918 })
3919 .collect::<Vec<_>>();
3920
3921 let (inlay_snapshot, inlay_edits) =
3922 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3923 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3924 let (tab_snapshot, tab_edits) =
3925 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3926 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3927 wrap_map.sync(tab_snapshot, tab_edits, cx)
3928 });
3929 let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3930 let block_ids =
3931 block_map.insert(block_properties.iter().map(|props| BlockProperties {
3932 placement: props.placement.clone(),
3933 height: props.height,
3934 style: props.style,
3935 render: Arc::new(|_| div().into_any()),
3936 priority: 0,
3937 }));
3938
3939 for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3940 log::info!(
3941 "inserted block {:?} with height {:?} and id {:?}",
3942 block_properties
3943 .placement
3944 .as_ref()
3945 .map(|p| p.to_point(&buffer_snapshot)),
3946 block_properties.height,
3947 block_id
3948 );
3949 }
3950 }
3951 40..=59 if !block_map.custom_blocks.is_empty() => {
3952 let block_count = rng.random_range(1..=4.min(block_map.custom_blocks.len()));
3953 let block_ids_to_remove = block_map
3954 .custom_blocks
3955 .choose_multiple(&mut rng, block_count)
3956 .map(|block| block.id)
3957 .collect::<HashSet<_>>();
3958
3959 let (inlay_snapshot, inlay_edits) =
3960 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3961 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3962 let (tab_snapshot, tab_edits) =
3963 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3964 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3965 wrap_map.sync(tab_snapshot, tab_edits, cx)
3966 });
3967 let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3968 log::info!(
3969 "removing {} blocks: {:?}",
3970 block_ids_to_remove.len(),
3971 block_ids_to_remove
3972 );
3973 block_map.remove(block_ids_to_remove);
3974 }
3975 60..=79 => {
3976 if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
3977 log::info!("Noop fold/unfold operation on a singleton buffer");
3978 continue;
3979 }
3980 let (inlay_snapshot, inlay_edits) =
3981 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3982 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3983 let (tab_snapshot, tab_edits) =
3984 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3985 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3986 wrap_map.sync(tab_snapshot, tab_edits, cx)
3987 });
3988 let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
3989 let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
3990 let folded_buffers: Vec<_> =
3991 block_map.block_map.folded_buffers.iter().cloned().collect();
3992 let mut unfolded_buffers = buffer.excerpt_buffer_ids();
3993 unfolded_buffers.dedup();
3994 log::debug!("All buffers {unfolded_buffers:?}");
3995 log::debug!("Folded buffers {folded_buffers:?}");
3996 unfolded_buffers.retain(|buffer_id| {
3997 !block_map.block_map.folded_buffers.contains(buffer_id)
3998 });
3999 (unfolded_buffers, folded_buffers)
4000 });
4001 let mut folded_count = folded_buffers.len();
4002 let mut unfolded_count = unfolded_buffers.len();
4003
4004 let fold = !unfolded_buffers.is_empty() && rng.random_bool(0.5);
4005 let unfold = !folded_buffers.is_empty() && rng.random_bool(0.5);
4006 if !fold && !unfold {
4007 log::info!(
4008 "Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
4009 );
4010 continue;
4011 }
4012
4013 buffer.update(cx, |buffer, cx| {
4014 if fold {
4015 let buffer_to_fold =
4016 unfolded_buffers[rng.random_range(0..unfolded_buffers.len())];
4017 log::info!("Folding {buffer_to_fold:?}");
4018 let related_excerpts = buffer_snapshot
4019 .excerpts()
4020 .filter_map(|(excerpt_id, buffer, range)| {
4021 if buffer.remote_id() == buffer_to_fold {
4022 Some((
4023 excerpt_id,
4024 buffer
4025 .text_for_range(range.context)
4026 .collect::<String>(),
4027 ))
4028 } else {
4029 None
4030 }
4031 })
4032 .collect::<Vec<_>>();
4033 log::info!(
4034 "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
4035 );
4036 folded_count += 1;
4037 unfolded_count -= 1;
4038 block_map.fold_buffers([buffer_to_fold], buffer, cx);
4039 }
4040 if unfold {
4041 let buffer_to_unfold =
4042 folded_buffers[rng.random_range(0..folded_buffers.len())];
4043 log::info!("Unfolding {buffer_to_unfold:?}");
4044 unfolded_count += 1;
4045 folded_count -= 1;
4046 block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
4047 }
4048 log::info!(
4049 "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
4050 );
4051 });
4052 }
4053 _ => {
4054 buffer.update(cx, |buffer, cx| {
4055 let mutation_count = rng.random_range(1..=5);
4056 let subscription = buffer.subscribe();
4057 buffer.randomly_mutate(&mut rng, mutation_count, cx);
4058 buffer_snapshot = buffer.snapshot(cx);
4059 buffer_edits.extend(subscription.consume());
4060 log::info!("buffer text: {:?}", buffer_snapshot.text());
4061 });
4062 }
4063 }
4064
4065 let (inlay_snapshot, inlay_edits) =
4066 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
4067 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
4068 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
4069 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
4070 wrap_map.sync(tab_snapshot, tab_edits, cx)
4071 });
4072 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, None);
4073 assert_eq!(
4074 blocks_snapshot.transforms.summary().input_rows,
4075 wraps_snapshot.max_point().row() + RowDelta(1)
4076 );
4077 log::info!("wrapped text: {:?}", wraps_snapshot.text());
4078 log::info!("blocks text: {:?}", blocks_snapshot.text());
4079
4080 let mut expected_blocks = Vec::new();
4081 expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
4082 Some((
4083 block.placement.to_wrap_row(&wraps_snapshot)?,
4084 Block::Custom(block.clone()),
4085 ))
4086 }));
4087
4088 let mut inlay_point_cursor = wraps_snapshot.inlay_point_cursor();
4089 let mut tab_point_cursor = wraps_snapshot.tab_point_cursor();
4090 let mut fold_point_cursor = wraps_snapshot.fold_point_cursor();
4091 let mut wrap_point_cursor = wraps_snapshot.wrap_point_cursor();
4092
4093 // Note that this needs to be synced with the related section in BlockMap::sync
4094 expected_blocks.extend(block_map.header_and_footer_blocks(
4095 &buffer_snapshot,
4096 MultiBufferOffset(0)..,
4097 |point, bias| {
4098 wrap_point_cursor
4099 .map(
4100 tab_point_cursor.map(
4101 fold_point_cursor.map(inlay_point_cursor.map(point, bias), bias),
4102 ),
4103 )
4104 .row()
4105 },
4106 ));
4107
4108 BlockMap::sort_blocks(&mut expected_blocks);
4109
4110 for (placement, block) in &expected_blocks {
4111 log::info!(
4112 "Block {:?} placement: {:?} Height: {:?}",
4113 block.id(),
4114 placement,
4115 block.height()
4116 );
4117 }
4118
4119 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
4120
4121 let input_buffer_rows = buffer_snapshot
4122 .row_infos(MultiBufferRow(0))
4123 .map(|row| row.buffer_row)
4124 .collect::<Vec<_>>();
4125 let mut expected_buffer_rows = Vec::new();
4126 let mut expected_text = String::new();
4127 let mut expected_block_positions = Vec::new();
4128 let mut expected_replaced_buffer_rows = HashSet::default();
4129 let input_text = wraps_snapshot.text();
4130
4131 // Loop over the input lines, creating (N - 1) empty lines for
4132 // blocks of height N.
4133 //
4134 // It's important to note that output *starts* as one empty line,
4135 // so we special case row 0 to assume a leading '\n'.
4136 //
4137 // Linehood is the birthright of strings.
4138 let input_text_lines = input_text.split('\n').enumerate().peekable();
4139 let mut block_row = 0;
4140 for (wrap_row, input_line) in input_text_lines {
4141 let wrap_row = WrapRow(wrap_row as u32);
4142 let multibuffer_row = wraps_snapshot
4143 .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
4144 .row;
4145
4146 // Create empty lines for the above block
4147 while let Some((placement, block)) = sorted_blocks_iter.peek() {
4148 if *placement.start() == wrap_row && block.place_above() {
4149 let (_, block) = sorted_blocks_iter.next().unwrap();
4150 expected_block_positions.push((block_row, block.id()));
4151 if block.height() > 0 {
4152 let text = "\n".repeat((block.height() - 1) as usize);
4153 if block_row > 0 {
4154 expected_text.push('\n')
4155 }
4156 expected_text.push_str(&text);
4157 for _ in 0..block.height() {
4158 expected_buffer_rows.push(None);
4159 }
4160 block_row += block.height();
4161 }
4162 } else {
4163 break;
4164 }
4165 }
4166
4167 // Skip lines within replace blocks, then create empty lines for the replace block's height
4168 let mut is_in_replace_block = false;
4169 if let Some((BlockPlacement::Replace(replace_range), block)) =
4170 sorted_blocks_iter.peek()
4171 && wrap_row >= *replace_range.start()
4172 {
4173 is_in_replace_block = true;
4174
4175 if wrap_row == *replace_range.start() {
4176 if matches!(block, Block::FoldedBuffer { .. }) {
4177 expected_buffer_rows.push(None);
4178 } else {
4179 expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]);
4180 }
4181 }
4182
4183 if wrap_row == *replace_range.end() {
4184 expected_block_positions.push((block_row, block.id()));
4185 let text = "\n".repeat((block.height() - 1) as usize);
4186 if block_row > 0 {
4187 expected_text.push('\n');
4188 }
4189 expected_text.push_str(&text);
4190
4191 for _ in 1..block.height() {
4192 expected_buffer_rows.push(None);
4193 }
4194 block_row += block.height();
4195
4196 sorted_blocks_iter.next();
4197 }
4198 }
4199
4200 if is_in_replace_block {
4201 expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
4202 } else {
4203 let buffer_row = input_buffer_rows[multibuffer_row as usize];
4204 let soft_wrapped = wraps_snapshot
4205 .to_tab_point(WrapPoint::new(wrap_row, 0))
4206 .column()
4207 > 0;
4208 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
4209 if block_row > 0 {
4210 expected_text.push('\n');
4211 }
4212 expected_text.push_str(input_line);
4213 block_row += 1;
4214 }
4215
4216 while let Some((placement, block)) = sorted_blocks_iter.peek() {
4217 if *placement.end() == wrap_row && block.place_below() {
4218 let (_, block) = sorted_blocks_iter.next().unwrap();
4219 expected_block_positions.push((block_row, block.id()));
4220 if block.height() > 0 {
4221 let text = "\n".repeat((block.height() - 1) as usize);
4222 if block_row > 0 {
4223 expected_text.push('\n')
4224 }
4225 expected_text.push_str(&text);
4226 for _ in 0..block.height() {
4227 expected_buffer_rows.push(None);
4228 }
4229 block_row += block.height();
4230 }
4231 } else {
4232 break;
4233 }
4234 }
4235 }
4236
4237 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
4238 let expected_row_count = expected_lines.len();
4239 log::info!("expected text: {expected_text:?}");
4240
4241 assert_eq!(
4242 blocks_snapshot.max_point().row + 1,
4243 expected_row_count as u32,
4244 "actual row count != expected row count",
4245 );
4246 assert_eq!(
4247 blocks_snapshot.text(),
4248 expected_text,
4249 "actual text != expected text",
4250 );
4251
4252 for start_row in 0..expected_row_count {
4253 let end_row = rng.random_range(start_row + 1..=expected_row_count);
4254 let mut expected_text = expected_lines[start_row..end_row].join("\n");
4255 if end_row < expected_row_count {
4256 expected_text.push('\n');
4257 }
4258
4259 let actual_text = blocks_snapshot
4260 .chunks(
4261 BlockRow(start_row as u32)..BlockRow(end_row as u32),
4262 false,
4263 false,
4264 Highlights::default(),
4265 )
4266 .map(|chunk| chunk.text)
4267 .collect::<String>();
4268 assert_eq!(
4269 actual_text,
4270 expected_text,
4271 "incorrect text starting row row range {:?}",
4272 start_row..end_row
4273 );
4274 assert_eq!(
4275 blocks_snapshot
4276 .row_infos(BlockRow(start_row as u32))
4277 .map(|row_info| row_info.buffer_row)
4278 .collect::<Vec<_>>(),
4279 &expected_buffer_rows[start_row..],
4280 "incorrect buffer_rows starting at row {:?}",
4281 start_row
4282 );
4283 }
4284
4285 assert_eq!(
4286 blocks_snapshot
4287 .blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
4288 .map(|(row, block)| (row.0, block.id()))
4289 .collect::<Vec<_>>(),
4290 expected_block_positions,
4291 "invalid blocks_in_range({:?})",
4292 0..expected_row_count
4293 );
4294
4295 for (_, expected_block) in
4296 blocks_snapshot.blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32))
4297 {
4298 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
4299 assert_eq!(
4300 actual_block.map(|block| block.id()),
4301 Some(expected_block.id())
4302 );
4303 }
4304
4305 for (block_row, block_id) in expected_block_positions {
4306 if let BlockId::Custom(block_id) = block_id {
4307 assert_eq!(
4308 blocks_snapshot.row_for_block(block_id),
4309 Some(BlockRow(block_row))
4310 );
4311 }
4312 }
4313
4314 let mut expected_longest_rows = Vec::new();
4315 let mut longest_line_len = -1_isize;
4316 for (row, line) in expected_lines.iter().enumerate() {
4317 let row = row as u32;
4318
4319 assert_eq!(
4320 blocks_snapshot.line_len(BlockRow(row)),
4321 line.len() as u32,
4322 "invalid line len for row {}",
4323 row
4324 );
4325
4326 let line_char_count = line.chars().count() as isize;
4327 match line_char_count.cmp(&longest_line_len) {
4328 Ordering::Less => {}
4329 Ordering::Equal => expected_longest_rows.push(row),
4330 Ordering::Greater => {
4331 longest_line_len = line_char_count;
4332 expected_longest_rows.clear();
4333 expected_longest_rows.push(row);
4334 }
4335 }
4336 }
4337
4338 let longest_row = blocks_snapshot.longest_row();
4339 assert!(
4340 expected_longest_rows.contains(&longest_row.0),
4341 "incorrect longest row {}. expected {:?} with length {}",
4342 longest_row.0,
4343 expected_longest_rows,
4344 longest_line_len,
4345 );
4346
4347 for _ in 0..10 {
4348 let end_row = rng.random_range(1..=expected_lines.len());
4349 let start_row = rng.random_range(0..end_row);
4350
4351 let mut expected_longest_rows_in_range = vec![];
4352 let mut longest_line_len_in_range = 0;
4353
4354 let mut row = start_row as u32;
4355 for line in &expected_lines[start_row..end_row] {
4356 let line_char_count = line.chars().count() as isize;
4357 match line_char_count.cmp(&longest_line_len_in_range) {
4358 Ordering::Less => {}
4359 Ordering::Equal => expected_longest_rows_in_range.push(row),
4360 Ordering::Greater => {
4361 longest_line_len_in_range = line_char_count;
4362 expected_longest_rows_in_range.clear();
4363 expected_longest_rows_in_range.push(row);
4364 }
4365 }
4366 row += 1;
4367 }
4368
4369 let longest_row_in_range = blocks_snapshot
4370 .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
4371 assert!(
4372 expected_longest_rows_in_range.contains(&longest_row_in_range.0),
4373 "incorrect longest row {} in range {:?}. expected {:?} with length {}",
4374 longest_row.0,
4375 start_row..end_row,
4376 expected_longest_rows_in_range,
4377 longest_line_len_in_range,
4378 );
4379 }
4380
4381 // Ensure that conversion between block points and wrap points is stable.
4382 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row().0 {
4383 let wrap_point = WrapPoint::new(WrapRow(row), 0);
4384 let block_point = blocks_snapshot.to_block_point(wrap_point);
4385 let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
4386 let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
4387 assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
4388 assert_eq!(
4389 blocks_snapshot.to_block_point(right_wrap_point),
4390 block_point
4391 );
4392 }
4393
4394 let mut block_point = BlockPoint::new(BlockRow(0), 0);
4395 for c in expected_text.chars() {
4396 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
4397 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
4398 assert_eq!(
4399 blocks_snapshot
4400 .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
4401 left_point,
4402 "block point: {:?}, wrap point: {:?}",
4403 block_point,
4404 blocks_snapshot.to_wrap_point(left_point, Bias::Left)
4405 );
4406 assert_eq!(
4407 left_buffer_point,
4408 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
4409 "{:?} is not valid in buffer coordinates",
4410 left_point
4411 );
4412
4413 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
4414 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
4415 assert_eq!(
4416 blocks_snapshot
4417 .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
4418 right_point,
4419 "block point: {:?}, wrap point: {:?}",
4420 block_point,
4421 blocks_snapshot.to_wrap_point(right_point, Bias::Right)
4422 );
4423 assert_eq!(
4424 right_buffer_point,
4425 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
4426 "{:?} is not valid in buffer coordinates",
4427 right_point
4428 );
4429
4430 if c == '\n' {
4431 block_point.0 += Point::new(1, 0);
4432 } else {
4433 block_point.column += c.len_utf8() as u32;
4434 }
4435 }
4436
4437 for buffer_row in 0..=buffer_snapshot.max_point().row {
4438 let buffer_row = MultiBufferRow(buffer_row);
4439 assert_eq!(
4440 blocks_snapshot.is_line_replaced(buffer_row),
4441 expected_replaced_buffer_rows.contains(&buffer_row),
4442 "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
4443 );
4444 }
4445 }
4446 }
4447
4448 #[gpui::test]
4449 fn test_remove_intersecting_replace_blocks_edge_case(cx: &mut gpui::TestAppContext) {
4450 cx.update(init_test);
4451
4452 let text = "abc\ndef\nghi\njkl\nmno";
4453 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
4454 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4455 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4456 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
4457 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4458 let (_wrap_map, wraps_snapshot) =
4459 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4460 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
4461
4462 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
4463 let _block_id = writer.insert(vec![BlockProperties {
4464 style: BlockStyle::Fixed,
4465 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
4466 height: Some(1),
4467 render: Arc::new(|_| div().into_any()),
4468 priority: 0,
4469 }])[0];
4470
4471 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default(), None);
4472 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
4473
4474 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default(), None);
4475 writer.remove_intersecting_replace_blocks(
4476 [buffer_snapshot
4477 .anchor_after(Point::new(1, 0))
4478 .to_offset(&buffer_snapshot)
4479 ..buffer_snapshot
4480 .anchor_after(Point::new(1, 0))
4481 .to_offset(&buffer_snapshot)],
4482 false,
4483 );
4484 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default(), None);
4485 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
4486 }
4487
4488 #[gpui::test]
4489 fn test_folded_buffer_with_near_blocks(cx: &mut gpui::TestAppContext) {
4490 cx.update(init_test);
4491
4492 let text = "line 1\nline 2\nline 3";
4493 let buffer = cx.update(|cx| {
4494 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(2, 6)])], cx)
4495 });
4496 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4497 let buffer_ids = buffer_snapshot
4498 .excerpts()
4499 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
4500 .dedup()
4501 .collect::<Vec<_>>();
4502 assert_eq!(buffer_ids.len(), 1);
4503 let buffer_id = buffer_ids[0];
4504
4505 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4506 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
4507 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4508 let (_, wrap_snapshot) =
4509 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4510 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
4511
4512 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4513 writer.insert(vec![BlockProperties {
4514 style: BlockStyle::Fixed,
4515 placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(0, 0))),
4516 height: Some(1),
4517 render: Arc::new(|_| div().into_any()),
4518 priority: 0,
4519 }]);
4520
4521 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
4522 assert_eq!(blocks_snapshot.text(), "\nline 1\n\nline 2\nline 3");
4523
4524 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4525 buffer.read_with(cx, |buffer, cx| {
4526 writer.fold_buffers([buffer_id], buffer, cx);
4527 });
4528
4529 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
4530 assert_eq!(blocks_snapshot.text(), "");
4531 }
4532
4533 #[gpui::test]
4534 fn test_folded_buffer_with_near_blocks_on_last_line(cx: &mut gpui::TestAppContext) {
4535 cx.update(init_test);
4536
4537 let text = "line 1\nline 2\nline 3\nline 4";
4538 let buffer = cx.update(|cx| {
4539 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(3, 6)])], cx)
4540 });
4541 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
4542 let buffer_ids = buffer_snapshot
4543 .excerpts()
4544 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
4545 .dedup()
4546 .collect::<Vec<_>>();
4547 assert_eq!(buffer_ids.len(), 1);
4548 let buffer_id = buffer_ids[0];
4549
4550 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
4551 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
4552 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
4553 let (_, wrap_snapshot) =
4554 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4555 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
4556
4557 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4558 writer.insert(vec![BlockProperties {
4559 style: BlockStyle::Fixed,
4560 placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(3, 6))),
4561 height: Some(1),
4562 render: Arc::new(|_| div().into_any()),
4563 priority: 0,
4564 }]);
4565
4566 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default(), None);
4567 assert_eq!(blocks_snapshot.text(), "\nline 1\nline 2\nline 3\nline 4\n");
4568
4569 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default(), None);
4570 buffer.read_with(cx, |buffer, cx| {
4571 writer.fold_buffers([buffer_id], buffer, cx);
4572 });
4573
4574 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default(), None);
4575 assert_eq!(blocks_snapshot.text(), "");
4576 }
4577
4578 #[gpui::test]
4579 fn test_companion_spacer_blocks(cx: &mut gpui::TestAppContext) {
4580 cx.update(init_test);
4581
4582 let base_text = "aaa\nbbb\nccc\nddd\nddd\nddd\neee\n";
4583 let main_text = "aaa\nddd\nddd\nddd\nXXX\nYYY\nZZZ\neee\n";
4584
4585 let rhs_buffer = cx.new(|cx| Buffer::local(main_text, cx));
4586 let diff = cx.new(|cx| {
4587 BufferDiff::new_with_base_text(base_text, &rhs_buffer.read(cx).text_snapshot(), cx)
4588 });
4589 let lhs_buffer = diff.read_with(cx, |diff, _| diff.base_text_buffer());
4590
4591 let lhs_multibuffer = cx.new(|cx| {
4592 let mut mb = MultiBuffer::new(Capability::ReadWrite);
4593 mb.push_excerpts(
4594 lhs_buffer.clone(),
4595 [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
4596 cx,
4597 );
4598 mb.add_inverted_diff(diff.clone(), rhs_buffer.clone(), cx);
4599 mb
4600 });
4601 let rhs_multibuffer = cx.new(|cx| {
4602 let mut mb = MultiBuffer::new(Capability::ReadWrite);
4603 mb.push_excerpts(
4604 rhs_buffer.clone(),
4605 [ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
4606 cx,
4607 );
4608 mb.add_diff(diff.clone(), cx);
4609 mb
4610 });
4611 let subscription =
4612 rhs_multibuffer.update(cx, |rhs_multibuffer, _| rhs_multibuffer.subscribe());
4613
4614 let lhs_excerpt_id =
4615 lhs_multibuffer.read_with(cx, |mb, cx| mb.snapshot(cx).excerpts().next().unwrap().0);
4616 let rhs_excerpt_id =
4617 rhs_multibuffer.read_with(cx, |mb, cx| mb.snapshot(cx).excerpts().next().unwrap().0);
4618
4619 let lhs_buffer_snapshot = cx.update(|cx| lhs_multibuffer.read(cx).snapshot(cx));
4620 let (mut _lhs_inlay_map, lhs_inlay_snapshot) = InlayMap::new(lhs_buffer_snapshot);
4621 let (mut _lhs_fold_map, lhs_fold_snapshot) = FoldMap::new(lhs_inlay_snapshot);
4622 let (mut _lhs_tab_map, lhs_tab_snapshot) =
4623 TabMap::new(lhs_fold_snapshot, 4.try_into().unwrap());
4624 let (_lhs_wrap_map, lhs_wrap_snapshot) =
4625 cx.update(|cx| WrapMap::new(lhs_tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4626 let lhs_block_map = BlockMap::new(lhs_wrap_snapshot.clone(), 0, 0);
4627
4628 let rhs_buffer_snapshot = cx.update(|cx| rhs_multibuffer.read(cx).snapshot(cx));
4629 let (mut rhs_inlay_map, rhs_inlay_snapshot) = InlayMap::new(rhs_buffer_snapshot);
4630 let (mut rhs_fold_map, rhs_fold_snapshot) = FoldMap::new(rhs_inlay_snapshot);
4631 let (mut rhs_tab_map, rhs_tab_snapshot) =
4632 TabMap::new(rhs_fold_snapshot, 4.try_into().unwrap());
4633 let (_rhs_wrap_map, rhs_wrap_snapshot) =
4634 cx.update(|cx| WrapMap::new(rhs_tab_snapshot, font("Helvetica"), px(14.0), None, cx));
4635 let rhs_block_map = BlockMap::new(rhs_wrap_snapshot.clone(), 0, 0);
4636
4637 let rhs_entity_id = rhs_multibuffer.entity_id();
4638
4639 let companion = cx.new(|_| {
4640 let mut c = Companion::new(
4641 rhs_entity_id,
4642 convert_rhs_rows_to_lhs,
4643 convert_lhs_rows_to_rhs,
4644 );
4645 c.add_excerpt_mapping(lhs_excerpt_id, rhs_excerpt_id);
4646 c
4647 });
4648
4649 let rhs_edits = Patch::new(vec![text::Edit {
4650 old: WrapRow(0)..rhs_wrap_snapshot.max_point().row(),
4651 new: WrapRow(0)..rhs_wrap_snapshot.max_point().row(),
4652 }]);
4653 let lhs_edits = Patch::new(vec![text::Edit {
4654 old: WrapRow(0)..lhs_wrap_snapshot.max_point().row(),
4655 new: WrapRow(0)..lhs_wrap_snapshot.max_point().row(),
4656 }]);
4657
4658 let rhs_snapshot = companion.read_with(cx, |companion, _cx| {
4659 rhs_block_map.read(
4660 rhs_wrap_snapshot.clone(),
4661 rhs_edits.clone(),
4662 Some(CompanionView::new(
4663 rhs_entity_id,
4664 &lhs_wrap_snapshot,
4665 &lhs_edits,
4666 companion,
4667 )),
4668 )
4669 });
4670
4671 let lhs_entity_id = lhs_multibuffer.entity_id();
4672 let lhs_snapshot = companion.read_with(cx, |companion, _cx| {
4673 lhs_block_map.read(
4674 lhs_wrap_snapshot.clone(),
4675 lhs_edits.clone(),
4676 Some(CompanionView::new(
4677 lhs_entity_id,
4678 &rhs_wrap_snapshot,
4679 &rhs_edits,
4680 companion,
4681 )),
4682 )
4683 });
4684
4685 // LHS:
4686 // aaa
4687 // - bbb
4688 // - ccc
4689 // ddd
4690 // ddd
4691 // ddd
4692 // <extra line>
4693 // <extra line>
4694 // <extra line>
4695 // *eee
4696 //
4697 // RHS:
4698 // aaa
4699 // <extra line>
4700 // <extra line>
4701 // ddd
4702 // ddd
4703 // ddd
4704 // + XXX
4705 // + YYY
4706 // + ZZZ
4707 // eee
4708
4709 assert_eq!(
4710 rhs_snapshot.snapshot.text(),
4711 "aaa\n\n\nddd\nddd\nddd\nXXX\nYYY\nZZZ\neee\n",
4712 "RHS should have 2 spacer lines after 'aaa' to align with LHS's deleted lines"
4713 );
4714
4715 assert_eq!(
4716 lhs_snapshot.snapshot.text(),
4717 "aaa\nbbb\nccc\nddd\nddd\nddd\n\n\n\neee\n",
4718 "LHS should have 3 spacer lines in place of RHS's inserted lines"
4719 );
4720
4721 // LHS:
4722 // aaa
4723 // - bbb
4724 // - ccc
4725 // ddd
4726 // ddd
4727 // ddd
4728 // <extra line>
4729 // <extra line>
4730 // <extra line>
4731 // eee
4732 //
4733 // RHS:
4734 // aaa
4735 // <extra line>
4736 // <extra line>
4737 // ddd
4738 // foo
4739 // foo
4740 // foo
4741 // ddd
4742 // ddd
4743 // + XXX
4744 // + YYY
4745 // + ZZZ
4746 // eee
4747
4748 let rhs_buffer_snapshot = rhs_multibuffer.update(cx, |multibuffer, cx| {
4749 multibuffer.edit(
4750 [(Point::new(2, 0)..Point::new(2, 0), "foo\nfoo\nfoo\n")],
4751 None,
4752 cx,
4753 );
4754 multibuffer.snapshot(cx)
4755 });
4756
4757 let (rhs_inlay_snapshot, rhs_inlay_edits) =
4758 rhs_inlay_map.sync(rhs_buffer_snapshot, subscription.consume().into_inner());
4759 let (rhs_fold_snapshot, rhs_fold_edits) =
4760 rhs_fold_map.read(rhs_inlay_snapshot, rhs_inlay_edits);
4761 let (rhs_tab_snapshot, rhs_tab_edits) =
4762 rhs_tab_map.sync(rhs_fold_snapshot, rhs_fold_edits, 4.try_into().unwrap());
4763 let (rhs_wrap_snapshot, rhs_wrap_edits) = _rhs_wrap_map.update(cx, |wrap_map, cx| {
4764 wrap_map.sync(rhs_tab_snapshot, rhs_tab_edits, cx)
4765 });
4766
4767 let rhs_snapshot = companion.read_with(cx, |companion, _cx| {
4768 rhs_block_map.read(
4769 rhs_wrap_snapshot.clone(),
4770 rhs_wrap_edits.clone(),
4771 Some(CompanionView::new(
4772 rhs_entity_id,
4773 &lhs_wrap_snapshot,
4774 &Default::default(),
4775 companion,
4776 )),
4777 )
4778 });
4779
4780 let lhs_snapshot = companion.read_with(cx, |companion, _cx| {
4781 lhs_block_map.read(
4782 lhs_wrap_snapshot.clone(),
4783 Default::default(),
4784 Some(CompanionView::new(
4785 lhs_entity_id,
4786 &rhs_wrap_snapshot,
4787 &rhs_wrap_edits,
4788 companion,
4789 )),
4790 )
4791 });
4792
4793 assert_eq!(
4794 rhs_snapshot.snapshot.text(),
4795 "aaa\n\n\nddd\nfoo\nfoo\nfoo\nddd\nddd\nXXX\nYYY\nZZZ\neee\n",
4796 "RHS should have the insertion"
4797 );
4798
4799 assert_eq!(
4800 lhs_snapshot.snapshot.text(),
4801 "aaa\nbbb\nccc\nddd\n\n\n\nddd\nddd\n\n\n\neee\n",
4802 "LHS should have 3 more spacer lines to balance the insertion"
4803 );
4804 }
4805
4806 fn init_test(cx: &mut gpui::App) {
4807 let settings = SettingsStore::test(cx);
4808 cx.set_global(settings);
4809 theme::init(theme::LoadThemes::JustBase, cx);
4810 assets::Assets.load_test_fonts(cx);
4811 }
4812
4813 impl Block {
4814 fn as_custom(&self) -> Option<&CustomBlock> {
4815 match self {
4816 Block::Custom(block) => Some(block),
4817 _ => None,
4818 }
4819 }
4820 }
4821
4822 impl BlockSnapshot {
4823 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
4824 self.wrap_snapshot
4825 .to_point(self.to_wrap_point(point, bias), bias)
4826 }
4827 }
4828}