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