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