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