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