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