1use super::{
2 Highlights,
3 fold_map::Chunk,
4 wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
5};
6use crate::{EditorStyle, GutterDimensions};
7use collections::{Bound, HashMap, HashSet};
8use gpui::{AnyElement, App, EntityId, Pixels, Window};
9use language::{Patch, Point};
10use multi_buffer::{
11 Anchor, ExcerptId, ExcerptInfo, MultiBuffer, MultiBufferRow, MultiBufferSnapshot, RowInfo,
12 ToOffset, ToPoint as _,
13};
14use parking_lot::Mutex;
15use std::{
16 cell::RefCell,
17 cmp::{self, Ordering},
18 fmt::Debug,
19 ops::{Deref, DerefMut, Range, RangeBounds, RangeInclusive},
20 sync::{
21 Arc,
22 atomic::{AtomicUsize, Ordering::SeqCst},
23 },
24};
25use sum_tree::{Bias, Dimensions, SumTree, Summary, TreeMap};
26use text::{BufferId, Edit};
27use ui::ElementId;
28
29const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
30const BULLETS: &str = "********************************************************************************************************************************";
31
32/// Tracks custom blocks such as diagnostics that should be displayed within buffer.
33///
34/// See the [`display_map` module documentation](crate::display_map) for more information.
35pub struct BlockMap {
36 next_block_id: AtomicUsize,
37 wrap_snapshot: RefCell<WrapSnapshot>,
38 custom_blocks: Vec<Arc<CustomBlock>>,
39 custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
40 transforms: RefCell<SumTree<Transform>>,
41 buffer_header_height: u32,
42 excerpt_header_height: u32,
43 pub(super) folded_buffers: HashSet<BufferId>,
44 buffers_with_disabled_headers: HashSet<BufferId>,
45}
46
47pub struct BlockMapReader<'a> {
48 blocks: &'a Vec<Arc<CustomBlock>>,
49 pub snapshot: BlockSnapshot,
50}
51
52pub struct BlockMapWriter<'a>(&'a mut BlockMap);
53
54#[derive(Clone)]
55pub struct BlockSnapshot {
56 wrap_snapshot: WrapSnapshot,
57 transforms: SumTree<Transform>,
58 custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
59 pub(super) buffer_header_height: u32,
60 pub(super) excerpt_header_height: u32,
61}
62
63#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
64pub struct CustomBlockId(pub usize);
65
66impl From<CustomBlockId> for ElementId {
67 fn from(val: CustomBlockId) -> Self {
68 val.0.into()
69 }
70}
71
72#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
73pub struct BlockPoint(pub Point);
74
75#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
76pub struct BlockRow(pub(super) u32);
77
78#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
79struct WrapRow(u32);
80
81pub type RenderBlock = Arc<dyn Send + Sync + Fn(&mut BlockContext) -> AnyElement>;
82
83#[derive(Clone, Debug, Eq, PartialEq)]
84pub enum BlockPlacement<T> {
85 Above(T),
86 Below(T),
87 Near(T),
88 Replace(RangeInclusive<T>),
89}
90
91impl<T> BlockPlacement<T> {
92 pub fn start(&self) -> &T {
93 match self {
94 BlockPlacement::Above(position) => position,
95 BlockPlacement::Below(position) => position,
96 BlockPlacement::Near(position) => position,
97 BlockPlacement::Replace(range) => range.start(),
98 }
99 }
100
101 fn end(&self) -> &T {
102 match self {
103 BlockPlacement::Above(position) => position,
104 BlockPlacement::Below(position) => position,
105 BlockPlacement::Near(position) => position,
106 BlockPlacement::Replace(range) => range.end(),
107 }
108 }
109
110 pub fn as_ref(&self) -> BlockPlacement<&T> {
111 match self {
112 BlockPlacement::Above(position) => BlockPlacement::Above(position),
113 BlockPlacement::Below(position) => BlockPlacement::Below(position),
114 BlockPlacement::Near(position) => BlockPlacement::Near(position),
115 BlockPlacement::Replace(range) => BlockPlacement::Replace(range.start()..=range.end()),
116 }
117 }
118
119 pub fn map<R>(self, mut f: impl FnMut(T) -> R) -> BlockPlacement<R> {
120 match self {
121 BlockPlacement::Above(position) => BlockPlacement::Above(f(position)),
122 BlockPlacement::Below(position) => BlockPlacement::Below(f(position)),
123 BlockPlacement::Near(position) => BlockPlacement::Near(f(position)),
124 BlockPlacement::Replace(range) => {
125 let (start, end) = range.into_inner();
126 BlockPlacement::Replace(f(start)..=f(end))
127 }
128 }
129 }
130
131 fn tie_break(&self) -> u8 {
132 match self {
133 BlockPlacement::Replace(_) => 0,
134 BlockPlacement::Above(_) => 1,
135 BlockPlacement::Near(_) => 2,
136 BlockPlacement::Below(_) => 3,
137 }
138 }
139}
140
141impl BlockPlacement<Anchor> {
142 fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
143 self.start()
144 .cmp(other.start(), buffer)
145 .then_with(|| other.end().cmp(self.end(), buffer))
146 .then_with(|| self.tie_break().cmp(&other.tie_break()))
147 }
148
149 fn to_wrap_row(&self, wrap_snapshot: &WrapSnapshot) -> Option<BlockPlacement<WrapRow>> {
150 let buffer_snapshot = wrap_snapshot.buffer_snapshot();
151 match self {
152 BlockPlacement::Above(position) => {
153 let mut position = position.to_point(buffer_snapshot);
154 position.column = 0;
155 let wrap_row = WrapRow(wrap_snapshot.make_wrap_point(position, Bias::Left).row());
156 Some(BlockPlacement::Above(wrap_row))
157 }
158 BlockPlacement::Near(position) => {
159 let mut position = position.to_point(buffer_snapshot);
160 position.column = buffer_snapshot.line_len(MultiBufferRow(position.row));
161 let wrap_row = WrapRow(wrap_snapshot.make_wrap_point(position, Bias::Left).row());
162 Some(BlockPlacement::Near(wrap_row))
163 }
164 BlockPlacement::Below(position) => {
165 let mut position = position.to_point(buffer_snapshot);
166 position.column = buffer_snapshot.line_len(MultiBufferRow(position.row));
167 let wrap_row = WrapRow(wrap_snapshot.make_wrap_point(position, Bias::Left).row());
168 Some(BlockPlacement::Below(wrap_row))
169 }
170 BlockPlacement::Replace(range) => {
171 let mut start = range.start().to_point(buffer_snapshot);
172 let mut end = range.end().to_point(buffer_snapshot);
173 if start == end {
174 None
175 } else {
176 start.column = 0;
177 let start_wrap_row =
178 WrapRow(wrap_snapshot.make_wrap_point(start, Bias::Left).row());
179 end.column = buffer_snapshot.line_len(MultiBufferRow(end.row));
180 let end_wrap_row =
181 WrapRow(wrap_snapshot.make_wrap_point(end, Bias::Left).row());
182 Some(BlockPlacement::Replace(start_wrap_row..=end_wrap_row))
183 }
184 }
185 }
186 }
187}
188
189pub struct CustomBlock {
190 pub id: CustomBlockId,
191 pub placement: BlockPlacement<Anchor>,
192 pub height: Option<u32>,
193 style: BlockStyle,
194 render: Arc<Mutex<RenderBlock>>,
195 priority: usize,
196}
197
198#[derive(Clone)]
199pub struct BlockProperties<P> {
200 pub placement: BlockPlacement<P>,
201 // None if the block takes up no space
202 // (e.g. a horizontal line)
203 pub height: Option<u32>,
204 pub style: BlockStyle,
205 pub render: RenderBlock,
206 pub priority: usize,
207}
208
209impl<P: Debug> Debug for BlockProperties<P> {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 f.debug_struct("BlockProperties")
212 .field("placement", &self.placement)
213 .field("height", &self.height)
214 .field("style", &self.style)
215 .finish()
216 }
217}
218
219#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
220pub enum BlockStyle {
221 Fixed,
222 Flex,
223 Sticky,
224}
225
226#[derive(Debug, Default, Copy, Clone)]
227pub struct EditorMargins {
228 pub gutter: GutterDimensions,
229 pub right: Pixels,
230}
231
232#[derive(gpui::AppContext, gpui::VisualContext)]
233pub struct BlockContext<'a, 'b> {
234 #[window]
235 pub window: &'a mut Window,
236 #[app]
237 pub app: &'b mut App,
238 pub anchor_x: Pixels,
239 pub max_width: Pixels,
240 pub margins: &'b EditorMargins,
241 pub em_width: Pixels,
242 pub line_height: Pixels,
243 pub block_id: BlockId,
244 pub selected: bool,
245 pub editor_style: &'b EditorStyle,
246}
247
248#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
249pub enum BlockId {
250 ExcerptBoundary(ExcerptId),
251 FoldedBuffer(ExcerptId),
252 Custom(CustomBlockId),
253}
254
255impl From<BlockId> for ElementId {
256 fn from(value: BlockId) -> Self {
257 match value {
258 BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(),
259 BlockId::ExcerptBoundary(excerpt_id) => {
260 ("ExcerptBoundary", EntityId::from(excerpt_id)).into()
261 }
262 BlockId::FoldedBuffer(id) => ("FoldedBuffer", EntityId::from(id)).into(),
263 }
264 }
265}
266
267impl std::fmt::Display for BlockId {
268 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269 match self {
270 Self::Custom(id) => write!(f, "Block({id:?})"),
271 Self::ExcerptBoundary(id) => write!(f, "ExcerptHeader({id:?})"),
272 Self::FoldedBuffer(id) => write!(f, "FoldedBuffer({id:?})"),
273 }
274 }
275}
276
277#[derive(Clone, Debug)]
278struct Transform {
279 summary: TransformSummary,
280 block: Option<Block>,
281}
282
283#[derive(Clone)]
284pub enum Block {
285 Custom(Arc<CustomBlock>),
286 FoldedBuffer {
287 first_excerpt: ExcerptInfo,
288 height: u32,
289 },
290 ExcerptBoundary {
291 excerpt: ExcerptInfo,
292 height: u32,
293 },
294 BufferHeader {
295 excerpt: ExcerptInfo,
296 height: u32,
297 },
298}
299
300impl Block {
301 pub fn id(&self) -> BlockId {
302 match self {
303 Block::Custom(block) => BlockId::Custom(block.id),
304 Block::ExcerptBoundary {
305 excerpt: next_excerpt,
306 ..
307 } => BlockId::ExcerptBoundary(next_excerpt.id),
308 Block::FoldedBuffer { first_excerpt, .. } => BlockId::FoldedBuffer(first_excerpt.id),
309 Block::BufferHeader {
310 excerpt: next_excerpt,
311 ..
312 } => BlockId::ExcerptBoundary(next_excerpt.id),
313 }
314 }
315
316 pub fn has_height(&self) -> bool {
317 match self {
318 Block::Custom(block) => block.height.is_some(),
319 Block::ExcerptBoundary { .. }
320 | Block::FoldedBuffer { .. }
321 | Block::BufferHeader { .. } => true,
322 }
323 }
324
325 pub fn height(&self) -> u32 {
326 match self {
327 Block::Custom(block) => block.height.unwrap_or(0),
328 Block::ExcerptBoundary { height, .. }
329 | Block::FoldedBuffer { height, .. }
330 | Block::BufferHeader { height, .. } => *height,
331 }
332 }
333
334 pub fn style(&self) -> BlockStyle {
335 match self {
336 Block::Custom(block) => block.style,
337 Block::ExcerptBoundary { .. }
338 | Block::FoldedBuffer { .. }
339 | Block::BufferHeader { .. } => BlockStyle::Sticky,
340 }
341 }
342
343 fn place_above(&self) -> bool {
344 match self {
345 Block::Custom(block) => matches!(block.placement, BlockPlacement::Above(_)),
346 Block::FoldedBuffer { .. } => false,
347 Block::ExcerptBoundary { .. } => true,
348 Block::BufferHeader { .. } => true,
349 }
350 }
351
352 pub fn place_near(&self) -> bool {
353 match self {
354 Block::Custom(block) => matches!(block.placement, BlockPlacement::Near(_)),
355 Block::FoldedBuffer { .. } => false,
356 Block::ExcerptBoundary { .. } => false,
357 Block::BufferHeader { .. } => false,
358 }
359 }
360
361 fn place_below(&self) -> bool {
362 match self {
363 Block::Custom(block) => matches!(
364 block.placement,
365 BlockPlacement::Below(_) | BlockPlacement::Near(_)
366 ),
367 Block::FoldedBuffer { .. } => false,
368 Block::ExcerptBoundary { .. } => false,
369 Block::BufferHeader { .. } => false,
370 }
371 }
372
373 fn is_replacement(&self) -> bool {
374 match self {
375 Block::Custom(block) => matches!(block.placement, BlockPlacement::Replace(_)),
376 Block::FoldedBuffer { .. } => true,
377 Block::ExcerptBoundary { .. } => false,
378 Block::BufferHeader { .. } => false,
379 }
380 }
381
382 fn is_header(&self) -> bool {
383 match self {
384 Block::Custom(_) => false,
385 Block::FoldedBuffer { .. } => true,
386 Block::ExcerptBoundary { .. } => true,
387 Block::BufferHeader { .. } => true,
388 }
389 }
390
391 pub fn is_buffer_header(&self) -> bool {
392 match self {
393 Block::Custom(_) => false,
394 Block::FoldedBuffer { .. } => true,
395 Block::ExcerptBoundary { .. } => false,
396 Block::BufferHeader { .. } => true,
397 }
398 }
399}
400
401impl Debug for Block {
402 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
403 match self {
404 Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(),
405 Self::FoldedBuffer {
406 first_excerpt,
407 height,
408 } => f
409 .debug_struct("FoldedBuffer")
410 .field("first_excerpt", &first_excerpt)
411 .field("height", height)
412 .finish(),
413 Self::ExcerptBoundary { excerpt, height } => f
414 .debug_struct("ExcerptBoundary")
415 .field("excerpt", excerpt)
416 .field("height", height)
417 .finish(),
418 Self::BufferHeader { excerpt, height } => f
419 .debug_struct("BufferHeader")
420 .field("excerpt", excerpt)
421 .field("height", height)
422 .finish(),
423 }
424 }
425}
426
427#[derive(Clone, Debug, Default)]
428struct TransformSummary {
429 input_rows: u32,
430 output_rows: u32,
431 longest_row: u32,
432 longest_row_chars: u32,
433}
434
435pub struct BlockChunks<'a> {
436 transforms: sum_tree::Cursor<'a, Transform, Dimensions<BlockRow, WrapRow>>,
437 input_chunks: wrap_map::WrapChunks<'a>,
438 input_chunk: Chunk<'a>,
439 output_row: u32,
440 max_output_row: u32,
441 masked: bool,
442}
443
444#[derive(Clone)]
445pub struct BlockRows<'a> {
446 transforms: sum_tree::Cursor<'a, Transform, Dimensions<BlockRow, WrapRow>>,
447 input_rows: wrap_map::WrapRows<'a>,
448 output_row: BlockRow,
449 started: bool,
450}
451
452impl BlockMap {
453 pub fn new(
454 wrap_snapshot: WrapSnapshot,
455 buffer_header_height: u32,
456 excerpt_header_height: u32,
457 ) -> Self {
458 let row_count = wrap_snapshot.max_point().row() + 1;
459 let mut transforms = SumTree::default();
460 push_isomorphic(&mut transforms, row_count, &wrap_snapshot);
461 let map = Self {
462 next_block_id: AtomicUsize::new(0),
463 custom_blocks: Vec::new(),
464 custom_blocks_by_id: TreeMap::default(),
465 folded_buffers: HashSet::default(),
466 buffers_with_disabled_headers: HashSet::default(),
467 transforms: RefCell::new(transforms),
468 wrap_snapshot: RefCell::new(wrap_snapshot.clone()),
469 buffer_header_height,
470 excerpt_header_height,
471 };
472 map.sync(
473 &wrap_snapshot,
474 Patch::new(vec![Edit {
475 old: 0..row_count,
476 new: 0..row_count,
477 }]),
478 );
479 map
480 }
481
482 pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapReader<'_> {
483 self.sync(&wrap_snapshot, edits);
484 *self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone();
485 BlockMapReader {
486 blocks: &self.custom_blocks,
487 snapshot: BlockSnapshot {
488 wrap_snapshot,
489 transforms: self.transforms.borrow().clone(),
490 custom_blocks_by_id: self.custom_blocks_by_id.clone(),
491 buffer_header_height: self.buffer_header_height,
492 excerpt_header_height: self.excerpt_header_height,
493 },
494 }
495 }
496
497 pub fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapWriter<'_> {
498 self.sync(&wrap_snapshot, edits);
499 *self.wrap_snapshot.borrow_mut() = wrap_snapshot;
500 BlockMapWriter(self)
501 }
502
503 fn sync(&self, wrap_snapshot: &WrapSnapshot, mut edits: Patch<u32>) {
504 let buffer = wrap_snapshot.buffer_snapshot();
505
506 // Handle changing the last excerpt if it is empty.
507 if buffer.trailing_excerpt_update_count()
508 != self
509 .wrap_snapshot
510 .borrow()
511 .buffer_snapshot()
512 .trailing_excerpt_update_count()
513 {
514 let max_point = wrap_snapshot.max_point();
515 let edit_start = wrap_snapshot.prev_row_boundary(max_point);
516 let edit_end = max_point.row() + 1;
517 edits = edits.compose([WrapEdit {
518 old: edit_start..edit_end,
519 new: edit_start..edit_end,
520 }]);
521 }
522
523 let edits = edits.into_inner();
524 if edits.is_empty() {
525 return;
526 }
527
528 let mut transforms = self.transforms.borrow_mut();
529 let mut new_transforms = SumTree::default();
530 let mut cursor = transforms.cursor::<WrapRow>(&());
531 let mut last_block_ix = 0;
532 let mut blocks_in_edit = Vec::new();
533 let mut edits = edits.into_iter().peekable();
534
535 while let Some(edit) = edits.next() {
536 let mut old_start = WrapRow(edit.old.start);
537 let mut new_start = WrapRow(edit.new.start);
538
539 // Only preserve transforms that:
540 // * Strictly precedes this edit
541 // * Isomorphic transforms that end *at* the start of the edit
542 // * Below blocks that end at the start of the edit
543 // However, if we hit a replace block that ends at the start of the edit we want to reconstruct it.
544 new_transforms.append(cursor.slice(&old_start, Bias::Left), &());
545 if let Some(transform) = cursor.item()
546 && transform.summary.input_rows > 0
547 && cursor.end() == old_start
548 && transform.block.as_ref().is_none_or(|b| !b.is_replacement())
549 {
550 // Preserve the transform (push and next)
551 new_transforms.push(transform.clone(), &());
552 cursor.next();
553
554 // Preserve below blocks at end of edit
555 while let Some(transform) = cursor.item() {
556 if transform.block.as_ref().is_some_and(|b| b.place_below()) {
557 new_transforms.push(transform.clone(), &());
558 cursor.next();
559 } else {
560 break;
561 }
562 }
563 }
564
565 // Ensure the edit starts at a transform boundary.
566 // If the edit starts within an isomorphic transform, preserve its prefix
567 // If the edit lands within a replacement block, expand the edit to include the start of the replaced input range
568 let transform = cursor.item().unwrap();
569 let transform_rows_before_edit = old_start.0 - cursor.start().0;
570 if transform_rows_before_edit > 0 {
571 if transform.block.is_none() {
572 // Preserve any portion of the old isomorphic transform that precedes this edit.
573 push_isomorphic(
574 &mut new_transforms,
575 transform_rows_before_edit,
576 wrap_snapshot,
577 );
578 } else {
579 // We landed within a block that replaces some lines, so we
580 // extend the edit to start at the beginning of the
581 // replacement.
582 debug_assert!(transform.summary.input_rows > 0);
583 old_start.0 -= transform_rows_before_edit;
584 new_start.0 -= transform_rows_before_edit;
585 }
586 }
587
588 // Decide where the edit ends
589 // * It should end at a transform boundary
590 // * Coalesce edits that intersect the same transform
591 let mut old_end = WrapRow(edit.old.end);
592 let mut new_end = WrapRow(edit.new.end);
593 loop {
594 // Seek to the transform starting at or after the end of the edit
595 cursor.seek(&old_end, Bias::Left);
596 cursor.next();
597
598 // Extend edit to the end of the discarded transform so it is reconstructed in full
599 let transform_rows_after_edit = cursor.start().0 - old_end.0;
600 old_end.0 += transform_rows_after_edit;
601 new_end.0 += transform_rows_after_edit;
602
603 // Combine this edit with any subsequent edits that intersect the same transform.
604 while let Some(next_edit) = edits.peek() {
605 if next_edit.old.start <= cursor.start().0 {
606 old_end = WrapRow(next_edit.old.end);
607 new_end = WrapRow(next_edit.new.end);
608 cursor.seek(&old_end, Bias::Left);
609 cursor.next();
610 edits.next();
611 } else {
612 break;
613 }
614 }
615
616 if *cursor.start() == old_end {
617 break;
618 }
619 }
620
621 // Discard below blocks at the end of the edit. They'll be reconstructed.
622 while let Some(transform) = cursor.item() {
623 if transform.block.as_ref().is_some_and(|b| b.place_below()) {
624 cursor.next();
625 } else {
626 break;
627 }
628 }
629
630 // Find the blocks within this edited region.
631 let new_buffer_start =
632 wrap_snapshot.to_point(WrapPoint::new(new_start.0, 0), Bias::Left);
633 let start_bound = Bound::Included(new_buffer_start);
634 let start_block_ix =
635 match self.custom_blocks[last_block_ix..].binary_search_by(|probe| {
636 probe
637 .start()
638 .to_point(buffer)
639 .cmp(&new_buffer_start)
640 // Move left until we find the index of the first block starting within this edit
641 .then(Ordering::Greater)
642 }) {
643 Ok(ix) | Err(ix) => last_block_ix + ix,
644 };
645
646 let end_bound;
647 let end_block_ix = if new_end.0 > wrap_snapshot.max_point().row() {
648 end_bound = Bound::Unbounded;
649 self.custom_blocks.len()
650 } else {
651 let new_buffer_end =
652 wrap_snapshot.to_point(WrapPoint::new(new_end.0, 0), Bias::Left);
653 end_bound = Bound::Excluded(new_buffer_end);
654 match self.custom_blocks[start_block_ix..].binary_search_by(|probe| {
655 probe
656 .start()
657 .to_point(buffer)
658 .cmp(&new_buffer_end)
659 .then(Ordering::Greater)
660 }) {
661 Ok(ix) | Err(ix) => start_block_ix + ix,
662 }
663 };
664 last_block_ix = end_block_ix;
665
666 debug_assert!(blocks_in_edit.is_empty());
667
668 blocks_in_edit.extend(
669 self.custom_blocks[start_block_ix..end_block_ix]
670 .iter()
671 .filter_map(|block| {
672 let placement = block.placement.to_wrap_row(wrap_snapshot)?;
673 if let BlockPlacement::Above(row) = placement
674 && row < new_start
675 {
676 return None;
677 }
678 Some((placement, Block::Custom(block.clone())))
679 }),
680 );
681
682 blocks_in_edit.extend(self.header_and_footer_blocks(
683 buffer,
684 (start_bound, end_bound),
685 wrap_snapshot,
686 ));
687
688 BlockMap::sort_blocks(&mut blocks_in_edit);
689
690 // For each of these blocks, insert a new isomorphic transform preceding the block,
691 // and then insert the block itself.
692 for (block_placement, block) in blocks_in_edit.drain(..) {
693 let mut summary = TransformSummary {
694 input_rows: 0,
695 output_rows: block.height(),
696 longest_row: 0,
697 longest_row_chars: 0,
698 };
699
700 let rows_before_block;
701 match block_placement {
702 BlockPlacement::Above(position) => {
703 rows_before_block = position.0 - new_transforms.summary().input_rows;
704 }
705 BlockPlacement::Near(position) | BlockPlacement::Below(position) => {
706 if position.0 + 1 < new_transforms.summary().input_rows {
707 continue;
708 }
709 rows_before_block = (position.0 + 1) - new_transforms.summary().input_rows;
710 }
711 BlockPlacement::Replace(range) => {
712 rows_before_block = range.start().0 - new_transforms.summary().input_rows;
713 summary.input_rows = range.end().0 - range.start().0 + 1;
714 }
715 }
716
717 push_isomorphic(&mut new_transforms, rows_before_block, wrap_snapshot);
718 new_transforms.push(
719 Transform {
720 summary,
721 block: Some(block),
722 },
723 &(),
724 );
725 }
726
727 // Insert an isomorphic transform after the final block.
728 let rows_after_last_block = new_end
729 .0
730 .saturating_sub(new_transforms.summary().input_rows);
731 push_isomorphic(&mut new_transforms, rows_after_last_block, wrap_snapshot);
732 }
733
734 new_transforms.append(cursor.suffix(), &());
735 debug_assert_eq!(
736 new_transforms.summary().input_rows,
737 wrap_snapshot.max_point().row() + 1
738 );
739
740 drop(cursor);
741 *transforms = new_transforms;
742 }
743
744 pub fn replace_blocks(&mut self, mut renderers: HashMap<CustomBlockId, RenderBlock>) {
745 for block in &mut self.custom_blocks {
746 if let Some(render) = renderers.remove(&block.id) {
747 *block.render.lock() = render;
748 }
749 }
750 }
751
752 fn header_and_footer_blocks<'a, R, T>(
753 &'a self,
754 buffer: &'a multi_buffer::MultiBufferSnapshot,
755 range: R,
756 wrap_snapshot: &'a WrapSnapshot,
757 ) -> impl Iterator<Item = (BlockPlacement<WrapRow>, Block)> + 'a
758 where
759 R: RangeBounds<T>,
760 T: multi_buffer::ToOffset,
761 {
762 let mut boundaries = buffer.excerpt_boundaries_in_range(range).peekable();
763
764 std::iter::from_fn(move || {
765 loop {
766 let excerpt_boundary = boundaries.next()?;
767 let wrap_row = wrap_snapshot
768 .make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
769 .row();
770
771 let new_buffer_id = match (&excerpt_boundary.prev, &excerpt_boundary.next) {
772 (None, next) => Some(next.buffer_id),
773 (Some(prev), next) => {
774 if prev.buffer_id != next.buffer_id {
775 Some(next.buffer_id)
776 } else {
777 None
778 }
779 }
780 };
781
782 let mut height = 0;
783
784 if let Some(new_buffer_id) = new_buffer_id {
785 let first_excerpt = excerpt_boundary.next.clone();
786 if self.buffers_with_disabled_headers.contains(&new_buffer_id) {
787 continue;
788 }
789 if self.folded_buffers.contains(&new_buffer_id) && buffer.show_headers() {
790 let mut last_excerpt_end_row = first_excerpt.end_row;
791
792 while let Some(next_boundary) = boundaries.peek() {
793 if next_boundary.next.buffer_id == new_buffer_id {
794 last_excerpt_end_row = next_boundary.next.end_row;
795 } else {
796 break;
797 }
798
799 boundaries.next();
800 }
801
802 let wrap_end_row = wrap_snapshot
803 .make_wrap_point(
804 Point::new(
805 last_excerpt_end_row.0,
806 buffer.line_len(last_excerpt_end_row),
807 ),
808 Bias::Right,
809 )
810 .row();
811
812 return Some((
813 BlockPlacement::Replace(WrapRow(wrap_row)..=WrapRow(wrap_end_row)),
814 Block::FoldedBuffer {
815 height: height + self.buffer_header_height,
816 first_excerpt,
817 },
818 ));
819 }
820 }
821
822 let starts_new_buffer = new_buffer_id.is_some();
823 let block = if starts_new_buffer && buffer.show_headers() {
824 height += self.buffer_header_height;
825 Block::BufferHeader {
826 excerpt: excerpt_boundary.next,
827 height,
828 }
829 } else if excerpt_boundary.prev.is_some() {
830 height += self.excerpt_header_height;
831 Block::ExcerptBoundary {
832 excerpt: excerpt_boundary.next,
833 height,
834 }
835 } else {
836 continue;
837 };
838
839 return Some((BlockPlacement::Above(WrapRow(wrap_row)), block));
840 }
841 })
842 }
843
844 fn sort_blocks(blocks: &mut Vec<(BlockPlacement<WrapRow>, Block)>) {
845 blocks.sort_unstable_by(|(placement_a, block_a), (placement_b, block_b)| {
846 placement_a
847 .start()
848 .cmp(placement_b.start())
849 .then_with(|| placement_b.end().cmp(placement_a.end()))
850 .then_with(|| placement_a.tie_break().cmp(&placement_b.tie_break()))
851 .then_with(|| {
852 if block_a.is_header() {
853 Ordering::Less
854 } else if block_b.is_header() {
855 Ordering::Greater
856 } else {
857 Ordering::Equal
858 }
859 })
860 .then_with(|| match (block_a, block_b) {
861 (
862 Block::ExcerptBoundary {
863 excerpt: excerpt_a, ..
864 }
865 | Block::BufferHeader {
866 excerpt: excerpt_a, ..
867 },
868 Block::ExcerptBoundary {
869 excerpt: excerpt_b, ..
870 }
871 | Block::BufferHeader {
872 excerpt: excerpt_b, ..
873 },
874 ) => Some(excerpt_a.id).cmp(&Some(excerpt_b.id)),
875 (
876 Block::ExcerptBoundary { .. } | Block::BufferHeader { .. },
877 Block::Custom(_),
878 ) => Ordering::Less,
879 (
880 Block::Custom(_),
881 Block::ExcerptBoundary { .. } | Block::BufferHeader { .. },
882 ) => Ordering::Greater,
883 (Block::Custom(block_a), Block::Custom(block_b)) => block_a
884 .priority
885 .cmp(&block_b.priority)
886 .then_with(|| block_a.id.cmp(&block_b.id)),
887 _ => {
888 unreachable!()
889 }
890 })
891 });
892 blocks.dedup_by(|right, left| match (left.0.clone(), right.0.clone()) {
893 (BlockPlacement::Replace(range), BlockPlacement::Above(row))
894 | (BlockPlacement::Replace(range), BlockPlacement::Below(row)) => range.contains(&row),
895 (BlockPlacement::Replace(range_a), BlockPlacement::Replace(range_b)) => {
896 if range_a.end() >= range_b.start() && range_a.start() <= range_b.end() {
897 left.0 = BlockPlacement::Replace(
898 *range_a.start()..=*range_a.end().max(range_b.end()),
899 );
900 true
901 } else {
902 false
903 }
904 }
905 _ => false,
906 });
907 }
908}
909
910fn push_isomorphic(tree: &mut SumTree<Transform>, rows: u32, wrap_snapshot: &WrapSnapshot) {
911 if rows == 0 {
912 return;
913 }
914
915 let wrap_row_start = tree.summary().input_rows;
916 let wrap_row_end = wrap_row_start + rows;
917 let wrap_summary = wrap_snapshot.text_summary_for_range(wrap_row_start..wrap_row_end);
918 let summary = TransformSummary {
919 input_rows: rows,
920 output_rows: rows,
921 longest_row: wrap_summary.longest_row,
922 longest_row_chars: wrap_summary.longest_row_chars,
923 };
924 let mut merged = false;
925 tree.update_last(
926 |last_transform| {
927 if last_transform.block.is_none() {
928 last_transform.summary.add_summary(&summary, &());
929 merged = true;
930 }
931 },
932 &(),
933 );
934 if !merged {
935 tree.push(
936 Transform {
937 summary,
938 block: None,
939 },
940 &(),
941 );
942 }
943}
944
945impl BlockPoint {
946 pub fn new(row: u32, column: u32) -> Self {
947 Self(Point::new(row, column))
948 }
949}
950
951impl Deref for BlockPoint {
952 type Target = Point;
953
954 fn deref(&self) -> &Self::Target {
955 &self.0
956 }
957}
958
959impl std::ops::DerefMut for BlockPoint {
960 fn deref_mut(&mut self) -> &mut Self::Target {
961 &mut self.0
962 }
963}
964
965impl Deref for BlockMapReader<'_> {
966 type Target = BlockSnapshot;
967
968 fn deref(&self) -> &Self::Target {
969 &self.snapshot
970 }
971}
972
973impl DerefMut for BlockMapReader<'_> {
974 fn deref_mut(&mut self) -> &mut Self::Target {
975 &mut self.snapshot
976 }
977}
978
979impl BlockMapReader<'_> {
980 pub fn row_for_block(&self, block_id: CustomBlockId) -> Option<BlockRow> {
981 let block = self.blocks.iter().find(|block| block.id == block_id)?;
982 let buffer_row = block
983 .start()
984 .to_point(self.wrap_snapshot.buffer_snapshot())
985 .row;
986 let wrap_row = self
987 .wrap_snapshot
988 .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
989 .row();
990 let start_wrap_row = WrapRow(
991 self.wrap_snapshot
992 .prev_row_boundary(WrapPoint::new(wrap_row, 0)),
993 );
994 let end_wrap_row = WrapRow(
995 self.wrap_snapshot
996 .next_row_boundary(WrapPoint::new(wrap_row, 0))
997 .unwrap_or(self.wrap_snapshot.max_point().row() + 1),
998 );
999
1000 let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(&());
1001 cursor.seek(&start_wrap_row, Bias::Left);
1002 while let Some(transform) = cursor.item() {
1003 if cursor.start().0 > end_wrap_row {
1004 break;
1005 }
1006
1007 if let Some(BlockId::Custom(id)) = transform.block.as_ref().map(|block| block.id())
1008 && id == block_id
1009 {
1010 return Some(cursor.start().1);
1011 }
1012 cursor.next();
1013 }
1014
1015 None
1016 }
1017}
1018
1019impl BlockMapWriter<'_> {
1020 pub fn insert(
1021 &mut self,
1022 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
1023 ) -> Vec<CustomBlockId> {
1024 let blocks = blocks.into_iter();
1025 let mut ids = Vec::with_capacity(blocks.size_hint().1.unwrap_or(0));
1026 let mut edits = Patch::default();
1027 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
1028 let buffer = wrap_snapshot.buffer_snapshot();
1029
1030 let mut previous_wrap_row_range: Option<Range<u32>> = None;
1031 for block in blocks {
1032 if let BlockPlacement::Replace(_) = &block.placement {
1033 debug_assert!(block.height.unwrap() > 0);
1034 }
1035
1036 let id = CustomBlockId(self.0.next_block_id.fetch_add(1, SeqCst));
1037 ids.push(id);
1038
1039 let start = block.placement.start().to_point(buffer);
1040 let end = block.placement.end().to_point(buffer);
1041 let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
1042 let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
1043
1044 let (start_row, end_row) = {
1045 previous_wrap_row_range.take_if(|range| {
1046 !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
1047 });
1048 let range = previous_wrap_row_range.get_or_insert_with(|| {
1049 let start_row =
1050 wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1051 let end_row = wrap_snapshot
1052 .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1053 .unwrap_or(wrap_snapshot.max_point().row() + 1);
1054 start_row..end_row
1055 });
1056 (range.start, range.end)
1057 };
1058 let block_ix = match self
1059 .0
1060 .custom_blocks
1061 .binary_search_by(|probe| probe.placement.cmp(&block.placement, buffer))
1062 {
1063 Ok(ix) | Err(ix) => ix,
1064 };
1065 let new_block = Arc::new(CustomBlock {
1066 id,
1067 placement: block.placement,
1068 height: block.height,
1069 render: Arc::new(Mutex::new(block.render)),
1070 style: block.style,
1071 priority: block.priority,
1072 });
1073 self.0.custom_blocks.insert(block_ix, new_block.clone());
1074 self.0.custom_blocks_by_id.insert(id, new_block);
1075
1076 edits = edits.compose([Edit {
1077 old: start_row..end_row,
1078 new: start_row..end_row,
1079 }]);
1080 }
1081
1082 self.0.sync(wrap_snapshot, edits);
1083 ids
1084 }
1085
1086 pub fn resize(&mut self, mut heights: HashMap<CustomBlockId, u32>) {
1087 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
1088 let buffer = wrap_snapshot.buffer_snapshot();
1089 let mut edits = Patch::default();
1090 let mut last_block_buffer_row = None;
1091
1092 for block in &mut self.0.custom_blocks {
1093 if let Some(new_height) = heights.remove(&block.id) {
1094 if let BlockPlacement::Replace(_) = &block.placement {
1095 debug_assert!(new_height > 0);
1096 }
1097
1098 if block.height != Some(new_height) {
1099 let new_block = CustomBlock {
1100 id: block.id,
1101 placement: block.placement.clone(),
1102 height: Some(new_height),
1103 style: block.style,
1104 render: block.render.clone(),
1105 priority: block.priority,
1106 };
1107 let new_block = Arc::new(new_block);
1108 *block = new_block.clone();
1109 self.0.custom_blocks_by_id.insert(block.id, new_block);
1110
1111 let start_row = block.placement.start().to_point(buffer).row;
1112 let end_row = block.placement.end().to_point(buffer).row;
1113 if last_block_buffer_row != Some(end_row) {
1114 last_block_buffer_row = Some(end_row);
1115 let start_wrap_row = wrap_snapshot
1116 .make_wrap_point(Point::new(start_row, 0), Bias::Left)
1117 .row();
1118 let end_wrap_row = wrap_snapshot
1119 .make_wrap_point(Point::new(end_row, 0), Bias::Left)
1120 .row();
1121 let start =
1122 wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1123 let end = wrap_snapshot
1124 .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1125 .unwrap_or(wrap_snapshot.max_point().row() + 1);
1126 edits.push(Edit {
1127 old: start..end,
1128 new: start..end,
1129 })
1130 }
1131 }
1132 }
1133 }
1134
1135 self.0.sync(wrap_snapshot, edits);
1136 }
1137
1138 pub fn remove(&mut self, block_ids: HashSet<CustomBlockId>) {
1139 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
1140 let buffer = wrap_snapshot.buffer_snapshot();
1141 let mut edits = Patch::default();
1142 let mut last_block_buffer_row = None;
1143 let mut previous_wrap_row_range: Option<Range<u32>> = None;
1144 self.0.custom_blocks.retain(|block| {
1145 if block_ids.contains(&block.id) {
1146 let start = block.placement.start().to_point(buffer);
1147 let end = block.placement.end().to_point(buffer);
1148 if last_block_buffer_row != Some(end.row) {
1149 last_block_buffer_row = Some(end.row);
1150 let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
1151 let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
1152 let (start_row, end_row) = {
1153 previous_wrap_row_range.take_if(|range| {
1154 !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
1155 });
1156 let range = previous_wrap_row_range.get_or_insert_with(|| {
1157 let start_row =
1158 wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1159 let end_row = wrap_snapshot
1160 .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1161 .unwrap_or(wrap_snapshot.max_point().row() + 1);
1162 start_row..end_row
1163 });
1164 (range.start, range.end)
1165 };
1166
1167 edits.push(Edit {
1168 old: start_row..end_row,
1169 new: start_row..end_row,
1170 })
1171 }
1172 false
1173 } else {
1174 true
1175 }
1176 });
1177 self.0
1178 .custom_blocks_by_id
1179 .retain(|id, _| !block_ids.contains(id));
1180 self.0.sync(wrap_snapshot, edits);
1181 }
1182
1183 pub fn remove_intersecting_replace_blocks<T>(
1184 &mut self,
1185 ranges: impl IntoIterator<Item = Range<T>>,
1186 inclusive: bool,
1187 ) where
1188 T: ToOffset,
1189 {
1190 let wrap_snapshot = self.0.wrap_snapshot.borrow();
1191 let mut blocks_to_remove = HashSet::default();
1192 for range in ranges {
1193 let range = range.start.to_offset(wrap_snapshot.buffer_snapshot())
1194 ..range.end.to_offset(wrap_snapshot.buffer_snapshot());
1195 for block in self.blocks_intersecting_buffer_range(range, inclusive) {
1196 if matches!(block.placement, BlockPlacement::Replace(_)) {
1197 blocks_to_remove.insert(block.id);
1198 }
1199 }
1200 }
1201 drop(wrap_snapshot);
1202 self.remove(blocks_to_remove);
1203 }
1204
1205 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId) {
1206 self.0.buffers_with_disabled_headers.insert(buffer_id);
1207 }
1208
1209 pub fn fold_buffers(
1210 &mut self,
1211 buffer_ids: impl IntoIterator<Item = BufferId>,
1212 multi_buffer: &MultiBuffer,
1213 cx: &App,
1214 ) {
1215 self.fold_or_unfold_buffers(true, buffer_ids, multi_buffer, cx);
1216 }
1217
1218 pub fn unfold_buffers(
1219 &mut self,
1220 buffer_ids: impl IntoIterator<Item = BufferId>,
1221 multi_buffer: &MultiBuffer,
1222 cx: &App,
1223 ) {
1224 self.fold_or_unfold_buffers(false, buffer_ids, multi_buffer, cx);
1225 }
1226
1227 fn fold_or_unfold_buffers(
1228 &mut self,
1229 fold: bool,
1230 buffer_ids: impl IntoIterator<Item = BufferId>,
1231 multi_buffer: &MultiBuffer,
1232 cx: &App,
1233 ) {
1234 let mut ranges = Vec::new();
1235 for buffer_id in buffer_ids {
1236 if fold {
1237 self.0.folded_buffers.insert(buffer_id);
1238 } else {
1239 self.0.folded_buffers.remove(&buffer_id);
1240 }
1241 ranges.extend(multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx));
1242 }
1243 ranges.sort_unstable_by_key(|range| range.start);
1244
1245 let mut edits = Patch::default();
1246 let wrap_snapshot = self.0.wrap_snapshot.borrow().clone();
1247 for range in ranges {
1248 let last_edit_row = cmp::min(
1249 wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + 1,
1250 wrap_snapshot.max_point().row(),
1251 ) + 1;
1252 let range = wrap_snapshot.make_wrap_point(range.start, Bias::Left).row()..last_edit_row;
1253 edits.push(Edit {
1254 old: range.clone(),
1255 new: range,
1256 });
1257 }
1258
1259 self.0.sync(&wrap_snapshot, edits);
1260 }
1261
1262 fn blocks_intersecting_buffer_range(
1263 &self,
1264 range: Range<usize>,
1265 inclusive: bool,
1266 ) -> &[Arc<CustomBlock>] {
1267 let wrap_snapshot = self.0.wrap_snapshot.borrow();
1268 let buffer = wrap_snapshot.buffer_snapshot();
1269
1270 let start_block_ix = match self.0.custom_blocks.binary_search_by(|block| {
1271 let block_end = block.end().to_offset(buffer);
1272 block_end.cmp(&range.start).then_with(|| {
1273 if inclusive || (range.is_empty() && block.start().to_offset(buffer) == block_end) {
1274 Ordering::Greater
1275 } else {
1276 Ordering::Less
1277 }
1278 })
1279 }) {
1280 Ok(ix) | Err(ix) => ix,
1281 };
1282 let end_block_ix = match self.0.custom_blocks.binary_search_by(|block| {
1283 block
1284 .start()
1285 .to_offset(buffer)
1286 .cmp(&range.end)
1287 .then(if inclusive {
1288 Ordering::Less
1289 } else {
1290 Ordering::Greater
1291 })
1292 }) {
1293 Ok(ix) | Err(ix) => ix,
1294 };
1295
1296 &self.0.custom_blocks[start_block_ix..end_block_ix]
1297 }
1298}
1299
1300impl BlockSnapshot {
1301 #[cfg(test)]
1302 pub fn text(&self) -> String {
1303 self.chunks(
1304 0..self.transforms.summary().output_rows,
1305 false,
1306 false,
1307 Highlights::default(),
1308 )
1309 .map(|chunk| chunk.text)
1310 .collect()
1311 }
1312
1313 pub(crate) fn chunks<'a>(
1314 &'a self,
1315 rows: Range<u32>,
1316 language_aware: bool,
1317 masked: bool,
1318 highlights: Highlights<'a>,
1319 ) -> BlockChunks<'a> {
1320 let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
1321
1322 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
1323 cursor.seek(&BlockRow(rows.start), Bias::Right);
1324 let transform_output_start = cursor.start().0.0;
1325 let transform_input_start = cursor.start().1.0;
1326
1327 let mut input_start = transform_input_start;
1328 let mut input_end = transform_input_start;
1329 if let Some(transform) = cursor.item()
1330 && transform.block.is_none()
1331 {
1332 input_start += rows.start - transform_output_start;
1333 input_end += cmp::min(
1334 rows.end - transform_output_start,
1335 transform.summary.input_rows,
1336 );
1337 }
1338
1339 BlockChunks {
1340 input_chunks: self.wrap_snapshot.chunks(
1341 input_start..input_end,
1342 language_aware,
1343 highlights,
1344 ),
1345 input_chunk: Default::default(),
1346 transforms: cursor,
1347 output_row: rows.start,
1348 max_output_row,
1349 masked,
1350 }
1351 }
1352
1353 pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows<'_> {
1354 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
1355 cursor.seek(&start_row, Bias::Right);
1356 let Dimensions(output_start, input_start, _) = cursor.start();
1357 let overshoot = if cursor
1358 .item()
1359 .is_some_and(|transform| transform.block.is_none())
1360 {
1361 start_row.0 - output_start.0
1362 } else {
1363 0
1364 };
1365 let input_start_row = input_start.0 + overshoot;
1366 BlockRows {
1367 transforms: cursor,
1368 input_rows: self.wrap_snapshot.row_infos(input_start_row),
1369 output_row: start_row,
1370 started: false,
1371 }
1372 }
1373
1374 pub fn blocks_in_range(&self, rows: Range<u32>) -> impl Iterator<Item = (u32, &Block)> {
1375 let mut cursor = self.transforms.cursor::<BlockRow>(&());
1376 cursor.seek(&BlockRow(rows.start), Bias::Left);
1377 while cursor.start().0 < rows.start && cursor.end().0 <= rows.start {
1378 cursor.next();
1379 }
1380
1381 std::iter::from_fn(move || {
1382 while let Some(transform) = cursor.item() {
1383 let start_row = cursor.start().0;
1384 if start_row > rows.end
1385 || (start_row == rows.end
1386 && transform
1387 .block
1388 .as_ref()
1389 .is_some_and(|block| block.height() > 0))
1390 {
1391 break;
1392 }
1393 if let Some(block) = &transform.block {
1394 cursor.next();
1395 return Some((start_row, block));
1396 } else {
1397 cursor.next();
1398 }
1399 }
1400 None
1401 })
1402 }
1403
1404 pub fn sticky_header_excerpt(&self, position: f32) -> Option<StickyHeaderExcerpt<'_>> {
1405 let top_row = position as u32;
1406 let mut cursor = self.transforms.cursor::<BlockRow>(&());
1407 cursor.seek(&BlockRow(top_row), Bias::Right);
1408
1409 while let Some(transform) = cursor.item() {
1410 match &transform.block {
1411 Some(
1412 Block::ExcerptBoundary { excerpt, .. } | Block::BufferHeader { excerpt, .. },
1413 ) => {
1414 return Some(StickyHeaderExcerpt { excerpt });
1415 }
1416 Some(block) if block.is_buffer_header() => return None,
1417 _ => {
1418 cursor.prev();
1419 continue;
1420 }
1421 }
1422 }
1423
1424 None
1425 }
1426
1427 pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
1428 let buffer = self.wrap_snapshot.buffer_snapshot();
1429 let wrap_point = match block_id {
1430 BlockId::Custom(custom_block_id) => {
1431 let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
1432 return Some(Block::Custom(custom_block.clone()));
1433 }
1434 BlockId::ExcerptBoundary(next_excerpt_id) => {
1435 let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
1436 self.wrap_snapshot
1437 .make_wrap_point(excerpt_range.start, Bias::Left)
1438 }
1439 BlockId::FoldedBuffer(excerpt_id) => self
1440 .wrap_snapshot
1441 .make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left),
1442 };
1443 let wrap_row = WrapRow(wrap_point.row());
1444
1445 let mut cursor = self.transforms.cursor::<WrapRow>(&());
1446 cursor.seek(&wrap_row, Bias::Left);
1447
1448 while let Some(transform) = cursor.item() {
1449 if let Some(block) = transform.block.as_ref() {
1450 if block.id() == block_id {
1451 return Some(block.clone());
1452 }
1453 } else if *cursor.start() > wrap_row {
1454 break;
1455 }
1456
1457 cursor.next();
1458 }
1459
1460 None
1461 }
1462
1463 pub fn max_point(&self) -> BlockPoint {
1464 let row = self.transforms.summary().output_rows.saturating_sub(1);
1465 BlockPoint::new(row, self.line_len(BlockRow(row)))
1466 }
1467
1468 pub fn longest_row(&self) -> u32 {
1469 self.transforms.summary().longest_row
1470 }
1471
1472 pub fn longest_row_in_range(&self, range: Range<BlockRow>) -> BlockRow {
1473 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
1474 cursor.seek(&range.start, Bias::Right);
1475
1476 let mut longest_row = range.start;
1477 let mut longest_row_chars = 0;
1478 if let Some(transform) = cursor.item() {
1479 if transform.block.is_none() {
1480 let Dimensions(output_start, input_start, _) = cursor.start();
1481 let overshoot = range.start.0 - output_start.0;
1482 let wrap_start_row = input_start.0 + overshoot;
1483 let wrap_end_row = cmp::min(
1484 input_start.0 + (range.end.0 - output_start.0),
1485 cursor.end().1.0,
1486 );
1487 let summary = self
1488 .wrap_snapshot
1489 .text_summary_for_range(wrap_start_row..wrap_end_row);
1490 longest_row = BlockRow(range.start.0 + summary.longest_row);
1491 longest_row_chars = summary.longest_row_chars;
1492 }
1493 cursor.next();
1494 }
1495
1496 let cursor_start_row = cursor.start().0;
1497 if range.end > cursor_start_row {
1498 let summary = cursor.summary::<_, TransformSummary>(&range.end, Bias::Right);
1499 if summary.longest_row_chars > longest_row_chars {
1500 longest_row = BlockRow(cursor_start_row.0 + summary.longest_row);
1501 longest_row_chars = summary.longest_row_chars;
1502 }
1503
1504 if let Some(transform) = cursor.item()
1505 && transform.block.is_none()
1506 {
1507 let Dimensions(output_start, input_start, _) = cursor.start();
1508 let overshoot = range.end.0 - output_start.0;
1509 let wrap_start_row = input_start.0;
1510 let wrap_end_row = input_start.0 + overshoot;
1511 let summary = self
1512 .wrap_snapshot
1513 .text_summary_for_range(wrap_start_row..wrap_end_row);
1514 if summary.longest_row_chars > longest_row_chars {
1515 longest_row = BlockRow(output_start.0 + summary.longest_row);
1516 }
1517 }
1518 }
1519
1520 longest_row
1521 }
1522
1523 pub(super) fn line_len(&self, row: BlockRow) -> u32 {
1524 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
1525 cursor.seek(&BlockRow(row.0), Bias::Right);
1526 if let Some(transform) = cursor.item() {
1527 let Dimensions(output_start, input_start, _) = cursor.start();
1528 let overshoot = row.0 - output_start.0;
1529 if transform.block.is_some() {
1530 0
1531 } else {
1532 self.wrap_snapshot.line_len(input_start.0 + overshoot)
1533 }
1534 } else if row.0 == 0 {
1535 0
1536 } else {
1537 panic!("row out of range");
1538 }
1539 }
1540
1541 pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
1542 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
1543 cursor.seek(&row, Bias::Right);
1544 cursor.item().is_some_and(|t| t.block.is_some())
1545 }
1546
1547 pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool {
1548 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
1549 cursor.seek(&row, Bias::Right);
1550 let Some(transform) = cursor.item() else {
1551 return false;
1552 };
1553 matches!(transform.block, Some(Block::FoldedBuffer { .. }))
1554 }
1555
1556 pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool {
1557 let wrap_point = self
1558 .wrap_snapshot
1559 .make_wrap_point(Point::new(row.0, 0), Bias::Left);
1560 let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(&());
1561 cursor.seek(&WrapRow(wrap_point.row()), Bias::Right);
1562 cursor.item().is_some_and(|transform| {
1563 transform
1564 .block
1565 .as_ref()
1566 .is_some_and(|block| block.is_replacement())
1567 })
1568 }
1569
1570 pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
1571 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
1572 cursor.seek(&BlockRow(point.row), Bias::Right);
1573
1574 let max_input_row = WrapRow(self.transforms.summary().input_rows);
1575 let mut search_left =
1576 (bias == Bias::Left && cursor.start().1.0 > 0) || cursor.end().1 == max_input_row;
1577 let mut reversed = false;
1578
1579 loop {
1580 if let Some(transform) = cursor.item() {
1581 let Dimensions(output_start_row, input_start_row, _) = cursor.start();
1582 let Dimensions(output_end_row, input_end_row, _) = cursor.end();
1583 let output_start = Point::new(output_start_row.0, 0);
1584 let input_start = Point::new(input_start_row.0, 0);
1585 let input_end = Point::new(input_end_row.0, 0);
1586
1587 match transform.block.as_ref() {
1588 Some(block) => {
1589 if block.is_replacement()
1590 && (((bias == Bias::Left || search_left) && output_start <= point.0)
1591 || (!search_left && output_start >= point.0))
1592 {
1593 return BlockPoint(output_start);
1594 }
1595 }
1596 None => {
1597 let input_point = if point.row >= output_end_row.0 {
1598 let line_len = self.wrap_snapshot.line_len(input_end_row.0 - 1);
1599 self.wrap_snapshot
1600 .clip_point(WrapPoint::new(input_end_row.0 - 1, line_len), bias)
1601 } else {
1602 let output_overshoot = point.0.saturating_sub(output_start);
1603 self.wrap_snapshot
1604 .clip_point(WrapPoint(input_start + output_overshoot), bias)
1605 };
1606
1607 if (input_start..input_end).contains(&input_point.0) {
1608 let input_overshoot = input_point.0.saturating_sub(input_start);
1609 return BlockPoint(output_start + input_overshoot);
1610 }
1611 }
1612 }
1613
1614 if search_left {
1615 cursor.prev();
1616 } else {
1617 cursor.next();
1618 }
1619 } else if reversed {
1620 return self.max_point();
1621 } else {
1622 reversed = true;
1623 search_left = !search_left;
1624 cursor.seek(&BlockRow(point.row), Bias::Right);
1625 }
1626 }
1627 }
1628
1629 pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
1630 let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(&());
1631 cursor.seek(&WrapRow(wrap_point.row()), Bias::Right);
1632 if let Some(transform) = cursor.item() {
1633 if transform.block.is_some() {
1634 BlockPoint::new(cursor.start().1.0, 0)
1635 } else {
1636 let Dimensions(input_start_row, output_start_row, _) = cursor.start();
1637 let input_start = Point::new(input_start_row.0, 0);
1638 let output_start = Point::new(output_start_row.0, 0);
1639 let input_overshoot = wrap_point.0 - input_start;
1640 BlockPoint(output_start + input_overshoot)
1641 }
1642 } else {
1643 self.max_point()
1644 }
1645 }
1646
1647 pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
1648 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
1649 cursor.seek(&BlockRow(block_point.row), Bias::Right);
1650 if let Some(transform) = cursor.item() {
1651 match transform.block.as_ref() {
1652 Some(block) => {
1653 if block.place_below() {
1654 let wrap_row = cursor.start().1.0 - 1;
1655 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1656 } else if block.place_above() {
1657 WrapPoint::new(cursor.start().1.0, 0)
1658 } else if bias == Bias::Left {
1659 WrapPoint::new(cursor.start().1.0, 0)
1660 } else {
1661 let wrap_row = cursor.end().1.0 - 1;
1662 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1663 }
1664 }
1665 None => {
1666 let overshoot = block_point.row - cursor.start().0.0;
1667 let wrap_row = cursor.start().1.0 + overshoot;
1668 WrapPoint::new(wrap_row, block_point.column)
1669 }
1670 }
1671 } else {
1672 self.wrap_snapshot.max_point()
1673 }
1674 }
1675}
1676
1677impl BlockChunks<'_> {
1678 /// Go to the next transform
1679 fn advance(&mut self) {
1680 self.input_chunk = Chunk::default();
1681 self.transforms.next();
1682 while let Some(transform) = self.transforms.item() {
1683 if transform
1684 .block
1685 .as_ref()
1686 .is_some_and(|block| block.height() == 0)
1687 {
1688 self.transforms.next();
1689 } else {
1690 break;
1691 }
1692 }
1693
1694 if self
1695 .transforms
1696 .item()
1697 .is_some_and(|transform| transform.block.is_none())
1698 {
1699 let start_input_row = self.transforms.start().1.0;
1700 let start_output_row = self.transforms.start().0.0;
1701 if start_output_row < self.max_output_row {
1702 let end_input_row = cmp::min(
1703 self.transforms.end().1.0,
1704 start_input_row + (self.max_output_row - start_output_row),
1705 );
1706 self.input_chunks.seek(start_input_row..end_input_row);
1707 }
1708 }
1709 }
1710}
1711
1712pub struct StickyHeaderExcerpt<'a> {
1713 pub excerpt: &'a ExcerptInfo,
1714}
1715
1716impl<'a> Iterator for BlockChunks<'a> {
1717 type Item = Chunk<'a>;
1718
1719 fn next(&mut self) -> Option<Self::Item> {
1720 if self.output_row >= self.max_output_row {
1721 return None;
1722 }
1723
1724 let transform = self.transforms.item()?;
1725 if transform.block.is_some() {
1726 let block_start = self.transforms.start().0.0;
1727 let mut block_end = self.transforms.end().0.0;
1728 self.advance();
1729 if self.transforms.item().is_none() {
1730 block_end -= 1;
1731 }
1732
1733 let start_in_block = self.output_row - block_start;
1734 let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
1735 let line_count = end_in_block - start_in_block;
1736 self.output_row += line_count;
1737
1738 return Some(Chunk {
1739 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) },
1740 ..Default::default()
1741 });
1742 }
1743
1744 if self.input_chunk.text.is_empty() {
1745 if let Some(input_chunk) = self.input_chunks.next() {
1746 self.input_chunk = input_chunk;
1747 } else {
1748 if self.output_row < self.max_output_row {
1749 self.output_row += 1;
1750 self.advance();
1751 if self.transforms.item().is_some() {
1752 return Some(Chunk {
1753 text: "\n",
1754 ..Default::default()
1755 });
1756 }
1757 }
1758 return None;
1759 }
1760 }
1761
1762 let transform_end = self.transforms.end().0.0;
1763 let (prefix_rows, prefix_bytes) =
1764 offset_for_row(self.input_chunk.text, transform_end - self.output_row);
1765 self.output_row += prefix_rows;
1766
1767 let (mut prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
1768 self.input_chunk.text = suffix;
1769
1770 if self.masked {
1771 // Not great for multibyte text because to keep cursor math correct we
1772 // need to have the same number of bytes in the input as output.
1773 let chars = prefix.chars().count();
1774 let bullet_len = chars;
1775 prefix = &BULLETS[..bullet_len];
1776 }
1777
1778 let chunk = Chunk {
1779 text: prefix,
1780 ..self.input_chunk.clone()
1781 };
1782
1783 if self.output_row == transform_end {
1784 self.advance();
1785 }
1786
1787 Some(chunk)
1788 }
1789}
1790
1791impl Iterator for BlockRows<'_> {
1792 type Item = RowInfo;
1793
1794 fn next(&mut self) -> Option<Self::Item> {
1795 if self.started {
1796 self.output_row.0 += 1;
1797 } else {
1798 self.started = true;
1799 }
1800
1801 if self.output_row.0 >= self.transforms.end().0.0 {
1802 self.transforms.next();
1803 while let Some(transform) = self.transforms.item() {
1804 if transform
1805 .block
1806 .as_ref()
1807 .is_some_and(|block| block.height() == 0)
1808 {
1809 self.transforms.next();
1810 } else {
1811 break;
1812 }
1813 }
1814
1815 let transform = self.transforms.item()?;
1816 if transform
1817 .block
1818 .as_ref()
1819 .is_none_or(|block| block.is_replacement())
1820 {
1821 self.input_rows.seek(self.transforms.start().1.0);
1822 }
1823 }
1824
1825 let transform = self.transforms.item()?;
1826 if let Some(block) = transform.block.as_ref() {
1827 if block.is_replacement() && self.transforms.start().0 == self.output_row {
1828 if matches!(block, Block::FoldedBuffer { .. }) {
1829 Some(RowInfo::default())
1830 } else {
1831 Some(self.input_rows.next().unwrap())
1832 }
1833 } else {
1834 Some(RowInfo::default())
1835 }
1836 } else {
1837 Some(self.input_rows.next().unwrap())
1838 }
1839 }
1840}
1841
1842impl sum_tree::Item for Transform {
1843 type Summary = TransformSummary;
1844
1845 fn summary(&self, _cx: &()) -> Self::Summary {
1846 self.summary.clone()
1847 }
1848}
1849
1850impl sum_tree::Summary for TransformSummary {
1851 type Context = ();
1852
1853 fn zero(_cx: &()) -> Self {
1854 Default::default()
1855 }
1856
1857 fn add_summary(&mut self, summary: &Self, _: &()) {
1858 if summary.longest_row_chars > self.longest_row_chars {
1859 self.longest_row = self.output_rows + summary.longest_row;
1860 self.longest_row_chars = summary.longest_row_chars;
1861 }
1862 self.input_rows += summary.input_rows;
1863 self.output_rows += summary.output_rows;
1864 }
1865}
1866
1867impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
1868 fn zero(_cx: &()) -> Self {
1869 Default::default()
1870 }
1871
1872 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1873 self.0 += summary.input_rows;
1874 }
1875}
1876
1877impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
1878 fn zero(_cx: &()) -> Self {
1879 Default::default()
1880 }
1881
1882 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1883 self.0 += summary.output_rows;
1884 }
1885}
1886
1887impl Deref for BlockContext<'_, '_> {
1888 type Target = App;
1889
1890 fn deref(&self) -> &Self::Target {
1891 self.app
1892 }
1893}
1894
1895impl DerefMut for BlockContext<'_, '_> {
1896 fn deref_mut(&mut self) -> &mut Self::Target {
1897 self.app
1898 }
1899}
1900
1901impl CustomBlock {
1902 pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
1903 self.render.lock()(cx)
1904 }
1905
1906 pub fn start(&self) -> Anchor {
1907 *self.placement.start()
1908 }
1909
1910 pub fn end(&self) -> Anchor {
1911 *self.placement.end()
1912 }
1913
1914 pub fn style(&self) -> BlockStyle {
1915 self.style
1916 }
1917}
1918
1919impl Debug for CustomBlock {
1920 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1921 f.debug_struct("Block")
1922 .field("id", &self.id)
1923 .field("placement", &self.placement)
1924 .field("height", &self.height)
1925 .field("style", &self.style)
1926 .field("priority", &self.priority)
1927 .finish_non_exhaustive()
1928 }
1929}
1930
1931// Count the number of bytes prior to a target point. If the string doesn't contain the target
1932// point, return its total extent. Otherwise return the target point itself.
1933fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
1934 let mut row = 0;
1935 let mut offset = 0;
1936 for (ix, line) in s.split('\n').enumerate() {
1937 if ix > 0 {
1938 row += 1;
1939 offset += 1;
1940 }
1941 if row >= target {
1942 break;
1943 }
1944 offset += line.len();
1945 }
1946 (row, offset)
1947}
1948
1949#[cfg(test)]
1950mod tests {
1951 use super::*;
1952 use crate::{
1953 display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap},
1954 test::test_font,
1955 };
1956 use gpui::{App, AppContext as _, Element, div, font, px};
1957 use itertools::Itertools;
1958 use language::{Buffer, Capability};
1959 use multi_buffer::{ExcerptRange, MultiBuffer};
1960 use rand::prelude::*;
1961 use settings::SettingsStore;
1962 use std::env;
1963 use util::RandomCharIter;
1964
1965 #[gpui::test]
1966 fn test_offset_for_row() {
1967 assert_eq!(offset_for_row("", 0), (0, 0));
1968 assert_eq!(offset_for_row("", 1), (0, 0));
1969 assert_eq!(offset_for_row("abcd", 0), (0, 0));
1970 assert_eq!(offset_for_row("abcd", 1), (0, 4));
1971 assert_eq!(offset_for_row("\n", 0), (0, 0));
1972 assert_eq!(offset_for_row("\n", 1), (1, 1));
1973 assert_eq!(offset_for_row("abc\ndef\nghi", 0), (0, 0));
1974 assert_eq!(offset_for_row("abc\ndef\nghi", 1), (1, 4));
1975 assert_eq!(offset_for_row("abc\ndef\nghi", 2), (2, 8));
1976 assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11));
1977 }
1978
1979 #[gpui::test]
1980 fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
1981 cx.update(init_test);
1982
1983 let text = "aaa\nbbb\nccc\nddd";
1984
1985 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
1986 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1987 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1988 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1989 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
1990 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
1991 let (wrap_map, wraps_snapshot) =
1992 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
1993 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
1994
1995 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
1996 let block_ids = writer.insert(vec![
1997 BlockProperties {
1998 style: BlockStyle::Fixed,
1999 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2000 height: Some(1),
2001 render: Arc::new(|_| div().into_any()),
2002 priority: 0,
2003 },
2004 BlockProperties {
2005 style: BlockStyle::Fixed,
2006 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2007 height: Some(2),
2008 render: Arc::new(|_| div().into_any()),
2009 priority: 0,
2010 },
2011 BlockProperties {
2012 style: BlockStyle::Fixed,
2013 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2014 height: Some(3),
2015 render: Arc::new(|_| div().into_any()),
2016 priority: 0,
2017 },
2018 ]);
2019
2020 let snapshot = block_map.read(wraps_snapshot, Default::default());
2021 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2022
2023 let blocks = snapshot
2024 .blocks_in_range(0..8)
2025 .map(|(start_row, block)| {
2026 let block = block.as_custom().unwrap();
2027 (start_row..start_row + block.height.unwrap(), block.id)
2028 })
2029 .collect::<Vec<_>>();
2030
2031 // When multiple blocks are on the same line, the newer blocks appear first.
2032 assert_eq!(
2033 blocks,
2034 &[
2035 (1..2, block_ids[0]),
2036 (2..4, block_ids[1]),
2037 (7..10, block_ids[2]),
2038 ]
2039 );
2040
2041 assert_eq!(
2042 snapshot.to_block_point(WrapPoint::new(0, 3)),
2043 BlockPoint::new(0, 3)
2044 );
2045 assert_eq!(
2046 snapshot.to_block_point(WrapPoint::new(1, 0)),
2047 BlockPoint::new(4, 0)
2048 );
2049 assert_eq!(
2050 snapshot.to_block_point(WrapPoint::new(3, 3)),
2051 BlockPoint::new(6, 3)
2052 );
2053
2054 assert_eq!(
2055 snapshot.to_wrap_point(BlockPoint::new(0, 3), Bias::Left),
2056 WrapPoint::new(0, 3)
2057 );
2058 assert_eq!(
2059 snapshot.to_wrap_point(BlockPoint::new(1, 0), Bias::Left),
2060 WrapPoint::new(1, 0)
2061 );
2062 assert_eq!(
2063 snapshot.to_wrap_point(BlockPoint::new(3, 0), Bias::Left),
2064 WrapPoint::new(1, 0)
2065 );
2066 assert_eq!(
2067 snapshot.to_wrap_point(BlockPoint::new(7, 0), Bias::Left),
2068 WrapPoint::new(3, 3)
2069 );
2070
2071 assert_eq!(
2072 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left),
2073 BlockPoint::new(0, 3)
2074 );
2075 assert_eq!(
2076 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right),
2077 BlockPoint::new(4, 0)
2078 );
2079 assert_eq!(
2080 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left),
2081 BlockPoint::new(0, 3)
2082 );
2083 assert_eq!(
2084 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right),
2085 BlockPoint::new(4, 0)
2086 );
2087 assert_eq!(
2088 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Left),
2089 BlockPoint::new(4, 0)
2090 );
2091 assert_eq!(
2092 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Right),
2093 BlockPoint::new(4, 0)
2094 );
2095 assert_eq!(
2096 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Left),
2097 BlockPoint::new(6, 3)
2098 );
2099 assert_eq!(
2100 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Right),
2101 BlockPoint::new(6, 3)
2102 );
2103 assert_eq!(
2104 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Left),
2105 BlockPoint::new(6, 3)
2106 );
2107 assert_eq!(
2108 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Right),
2109 BlockPoint::new(6, 3)
2110 );
2111
2112 assert_eq!(
2113 snapshot
2114 .row_infos(BlockRow(0))
2115 .map(|row_info| row_info.buffer_row)
2116 .collect::<Vec<_>>(),
2117 &[
2118 Some(0),
2119 None,
2120 None,
2121 None,
2122 Some(1),
2123 Some(2),
2124 Some(3),
2125 None,
2126 None,
2127 None
2128 ]
2129 );
2130
2131 // Insert a line break, separating two block decorations into separate lines.
2132 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2133 buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
2134 buffer.snapshot(cx)
2135 });
2136
2137 let (inlay_snapshot, inlay_edits) =
2138 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
2139 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2140 let (tab_snapshot, tab_edits) =
2141 tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
2142 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2143 wrap_map.sync(tab_snapshot, tab_edits, cx)
2144 });
2145 let snapshot = block_map.read(wraps_snapshot, wrap_edits);
2146 assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
2147 }
2148
2149 #[gpui::test]
2150 fn test_multibuffer_headers_and_footers(cx: &mut App) {
2151 init_test(cx);
2152
2153 let buffer1 = cx.new(|cx| Buffer::local("Buffer 1", cx));
2154 let buffer2 = cx.new(|cx| Buffer::local("Buffer 2", cx));
2155 let buffer3 = cx.new(|cx| Buffer::local("Buffer 3", cx));
2156
2157 let mut excerpt_ids = Vec::new();
2158 let multi_buffer = cx.new(|cx| {
2159 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
2160 excerpt_ids.extend(multi_buffer.push_excerpts(
2161 buffer1.clone(),
2162 [ExcerptRange::new(0..buffer1.read(cx).len())],
2163 cx,
2164 ));
2165 excerpt_ids.extend(multi_buffer.push_excerpts(
2166 buffer2.clone(),
2167 [ExcerptRange::new(0..buffer2.read(cx).len())],
2168 cx,
2169 ));
2170 excerpt_ids.extend(multi_buffer.push_excerpts(
2171 buffer3.clone(),
2172 [ExcerptRange::new(0..buffer3.read(cx).len())],
2173 cx,
2174 ));
2175
2176 multi_buffer
2177 });
2178
2179 let font = test_font();
2180 let font_size = px(14.);
2181 let font_id = cx.text_system().resolve_font(&font);
2182 let mut wrap_width = px(0.);
2183 for c in "Buff".chars() {
2184 wrap_width += cx
2185 .text_system()
2186 .advance(font_id, font_size, c)
2187 .unwrap()
2188 .width;
2189 }
2190
2191 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2192 let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot);
2193 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2194 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2195 let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
2196
2197 let block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2198 let snapshot = block_map.read(wraps_snapshot, Default::default());
2199
2200 // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
2201 assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3");
2202
2203 let blocks: Vec<_> = snapshot
2204 .blocks_in_range(0..u32::MAX)
2205 .map(|(row, block)| (row..row + block.height(), block.id()))
2206 .collect();
2207 assert_eq!(
2208 blocks,
2209 vec![
2210 (0..1, BlockId::ExcerptBoundary(excerpt_ids[0])), // path, header
2211 (3..4, BlockId::ExcerptBoundary(excerpt_ids[1])), // path, header
2212 (6..7, BlockId::ExcerptBoundary(excerpt_ids[2])), // path, header
2213 ]
2214 );
2215 }
2216
2217 #[gpui::test]
2218 fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
2219 cx.update(init_test);
2220
2221 let text = "aaa\nbbb\nccc\nddd";
2222
2223 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2224 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2225 let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2226 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2227 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2228 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2229 let (_wrap_map, wraps_snapshot) =
2230 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2231 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2232
2233 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2234 let block_ids = writer.insert(vec![
2235 BlockProperties {
2236 style: BlockStyle::Fixed,
2237 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2238 height: Some(1),
2239 render: Arc::new(|_| div().into_any()),
2240 priority: 0,
2241 },
2242 BlockProperties {
2243 style: BlockStyle::Fixed,
2244 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2245 height: Some(2),
2246 render: Arc::new(|_| div().into_any()),
2247 priority: 0,
2248 },
2249 BlockProperties {
2250 style: BlockStyle::Fixed,
2251 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2252 height: Some(3),
2253 render: Arc::new(|_| div().into_any()),
2254 priority: 0,
2255 },
2256 ]);
2257
2258 {
2259 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2260 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2261
2262 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2263
2264 let mut new_heights = HashMap::default();
2265 new_heights.insert(block_ids[0], 2);
2266 block_map_writer.resize(new_heights);
2267 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2268 assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2269 }
2270
2271 {
2272 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2273
2274 let mut new_heights = HashMap::default();
2275 new_heights.insert(block_ids[0], 1);
2276 block_map_writer.resize(new_heights);
2277
2278 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2279 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2280 }
2281
2282 {
2283 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2284
2285 let mut new_heights = HashMap::default();
2286 new_heights.insert(block_ids[0], 0);
2287 block_map_writer.resize(new_heights);
2288
2289 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2290 assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
2291 }
2292
2293 {
2294 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2295
2296 let mut new_heights = HashMap::default();
2297 new_heights.insert(block_ids[0], 3);
2298 block_map_writer.resize(new_heights);
2299
2300 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2301 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2302 }
2303
2304 {
2305 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2306
2307 let mut new_heights = HashMap::default();
2308 new_heights.insert(block_ids[0], 3);
2309 block_map_writer.resize(new_heights);
2310
2311 let snapshot = block_map.read(wraps_snapshot, Default::default());
2312 // Same height as before, should remain the same
2313 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2314 }
2315 }
2316
2317 #[gpui::test]
2318 fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
2319 cx.update(init_test);
2320
2321 let text = "one two three\nfour five six\nseven eight";
2322
2323 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2324 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2325 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2326 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2327 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2328 let (_, wraps_snapshot) = cx.update(|cx| {
2329 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(90.)), cx)
2330 });
2331 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2332
2333 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2334 writer.insert(vec![
2335 BlockProperties {
2336 style: BlockStyle::Fixed,
2337 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
2338 render: Arc::new(|_| div().into_any()),
2339 height: Some(1),
2340 priority: 0,
2341 },
2342 BlockProperties {
2343 style: BlockStyle::Fixed,
2344 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
2345 render: Arc::new(|_| div().into_any()),
2346 height: Some(1),
2347 priority: 0,
2348 },
2349 ]);
2350
2351 // Blocks with an 'above' disposition go above their corresponding buffer line.
2352 // Blocks with a 'below' disposition go below their corresponding buffer line.
2353 let snapshot = block_map.read(wraps_snapshot, Default::default());
2354 assert_eq!(
2355 snapshot.text(),
2356 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
2357 );
2358 }
2359
2360 #[gpui::test]
2361 fn test_replace_lines(cx: &mut gpui::TestAppContext) {
2362 cx.update(init_test);
2363
2364 let text = "line1\nline2\nline3\nline4\nline5";
2365
2366 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2367 let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
2368 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2369 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2370 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2371 let tab_size = 1.try_into().unwrap();
2372 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
2373 let (wrap_map, wraps_snapshot) =
2374 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2375 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2376
2377 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2378 let replace_block_id = writer.insert(vec![BlockProperties {
2379 style: BlockStyle::Fixed,
2380 placement: BlockPlacement::Replace(
2381 buffer_snapshot.anchor_after(Point::new(1, 3))
2382 ..=buffer_snapshot.anchor_before(Point::new(3, 1)),
2383 ),
2384 height: Some(4),
2385 render: Arc::new(|_| div().into_any()),
2386 priority: 0,
2387 }])[0];
2388
2389 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
2390 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2391
2392 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2393 buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
2394 buffer.snapshot(cx)
2395 });
2396 let (inlay_snapshot, inlay_edits) =
2397 inlay_map.sync(buffer_snapshot, buffer_subscription.consume().into_inner());
2398 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2399 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2400 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2401 wrap_map.sync(tab_snapshot, tab_edits, cx)
2402 });
2403 let blocks_snapshot = block_map.read(wraps_snapshot, wrap_edits);
2404 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2405
2406 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2407 buffer.edit(
2408 [(
2409 Point::new(1, 5)..Point::new(1, 5),
2410 "\nline 2.1\nline2.2\nline 2.3\nline 2.4",
2411 )],
2412 None,
2413 cx,
2414 );
2415 buffer.snapshot(cx)
2416 });
2417 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
2418 buffer_snapshot.clone(),
2419 buffer_subscription.consume().into_inner(),
2420 );
2421 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2422 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2423 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2424 wrap_map.sync(tab_snapshot, tab_edits, cx)
2425 });
2426 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
2427 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2428
2429 // Blocks inserted right above the start or right below the end of the replaced region are hidden.
2430 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2431 writer.insert(vec![
2432 BlockProperties {
2433 style: BlockStyle::Fixed,
2434 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))),
2435 height: Some(1),
2436 render: Arc::new(|_| div().into_any()),
2437 priority: 0,
2438 },
2439 BlockProperties {
2440 style: BlockStyle::Fixed,
2441 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
2442 height: Some(1),
2443 render: Arc::new(|_| div().into_any()),
2444 priority: 0,
2445 },
2446 BlockProperties {
2447 style: BlockStyle::Fixed,
2448 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
2449 height: Some(1),
2450 render: Arc::new(|_| div().into_any()),
2451 priority: 0,
2452 },
2453 ]);
2454 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2455 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2456
2457 // Ensure blocks inserted *inside* replaced region are hidden.
2458 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2459 writer.insert(vec![
2460 BlockProperties {
2461 style: BlockStyle::Fixed,
2462 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
2463 height: Some(1),
2464 render: Arc::new(|_| div().into_any()),
2465 priority: 0,
2466 },
2467 BlockProperties {
2468 style: BlockStyle::Fixed,
2469 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
2470 height: Some(1),
2471 render: Arc::new(|_| div().into_any()),
2472 priority: 0,
2473 },
2474 BlockProperties {
2475 style: BlockStyle::Fixed,
2476 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
2477 height: Some(1),
2478 render: Arc::new(|_| div().into_any()),
2479 priority: 0,
2480 },
2481 ]);
2482 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2483 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2484
2485 // Removing the replace block shows all the hidden blocks again.
2486 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2487 writer.remove(HashSet::from_iter([replace_block_id]));
2488 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
2489 assert_eq!(
2490 blocks_snapshot.text(),
2491 "\nline1\n\nline2\n\n\nline 2.1\nline2.2\nline 2.3\nline 2.4\n\nline4\n\nline5"
2492 );
2493 }
2494
2495 #[gpui::test]
2496 fn test_custom_blocks_inside_buffer_folds(cx: &mut gpui::TestAppContext) {
2497 cx.update(init_test);
2498
2499 let text = "111\n222\n333\n444\n555\n666";
2500
2501 let buffer = cx.update(|cx| {
2502 MultiBuffer::build_multi(
2503 [
2504 (text, vec![Point::new(0, 0)..Point::new(0, 3)]),
2505 (
2506 text,
2507 vec![
2508 Point::new(1, 0)..Point::new(1, 3),
2509 Point::new(2, 0)..Point::new(2, 3),
2510 Point::new(3, 0)..Point::new(3, 3),
2511 ],
2512 ),
2513 (
2514 text,
2515 vec![
2516 Point::new(4, 0)..Point::new(4, 3),
2517 Point::new(5, 0)..Point::new(5, 3),
2518 ],
2519 ),
2520 ],
2521 cx,
2522 )
2523 });
2524 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2525 let buffer_ids = buffer_snapshot
2526 .excerpts()
2527 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2528 .dedup()
2529 .collect::<Vec<_>>();
2530 assert_eq!(buffer_ids.len(), 3);
2531 let buffer_id_1 = buffer_ids[0];
2532 let buffer_id_2 = buffer_ids[1];
2533 let buffer_id_3 = buffer_ids[2];
2534
2535 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2536 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2537 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2538 let (_, wrap_snapshot) =
2539 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2540 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
2541 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2542
2543 assert_eq!(
2544 blocks_snapshot.text(),
2545 "\n\n111\n\n\n222\n\n333\n\n444\n\n\n555\n\n666"
2546 );
2547 assert_eq!(
2548 blocks_snapshot
2549 .row_infos(BlockRow(0))
2550 .map(|i| i.buffer_row)
2551 .collect::<Vec<_>>(),
2552 vec![
2553 None,
2554 None,
2555 Some(0),
2556 None,
2557 None,
2558 Some(1),
2559 None,
2560 Some(2),
2561 None,
2562 Some(3),
2563 None,
2564 None,
2565 Some(4),
2566 None,
2567 Some(5),
2568 ]
2569 );
2570
2571 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2572 let excerpt_blocks_2 = writer.insert(vec![
2573 BlockProperties {
2574 style: BlockStyle::Fixed,
2575 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2576 height: Some(1),
2577 render: Arc::new(|_| div().into_any()),
2578 priority: 0,
2579 },
2580 BlockProperties {
2581 style: BlockStyle::Fixed,
2582 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 0))),
2583 height: Some(1),
2584 render: Arc::new(|_| div().into_any()),
2585 priority: 0,
2586 },
2587 BlockProperties {
2588 style: BlockStyle::Fixed,
2589 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 0))),
2590 height: Some(1),
2591 render: Arc::new(|_| div().into_any()),
2592 priority: 0,
2593 },
2594 ]);
2595 let excerpt_blocks_3 = writer.insert(vec![
2596 BlockProperties {
2597 style: BlockStyle::Fixed,
2598 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))),
2599 height: Some(1),
2600 render: Arc::new(|_| div().into_any()),
2601 priority: 0,
2602 },
2603 BlockProperties {
2604 style: BlockStyle::Fixed,
2605 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))),
2606 height: Some(1),
2607 render: Arc::new(|_| div().into_any()),
2608 priority: 0,
2609 },
2610 ]);
2611
2612 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2613 assert_eq!(
2614 blocks_snapshot.text(),
2615 "\n\n111\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
2616 );
2617 assert_eq!(
2618 blocks_snapshot
2619 .row_infos(BlockRow(0))
2620 .map(|i| i.buffer_row)
2621 .collect::<Vec<_>>(),
2622 vec![
2623 None,
2624 None,
2625 Some(0),
2626 None,
2627 None,
2628 None,
2629 Some(1),
2630 None,
2631 None,
2632 Some(2),
2633 None,
2634 Some(3),
2635 None,
2636 None,
2637 None,
2638 None,
2639 Some(4),
2640 None,
2641 Some(5),
2642 None,
2643 ]
2644 );
2645
2646 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2647 buffer.read_with(cx, |buffer, cx| {
2648 writer.fold_buffers([buffer_id_1], buffer, cx);
2649 });
2650 let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
2651 style: BlockStyle::Fixed,
2652 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))),
2653 height: Some(1),
2654 render: Arc::new(|_| div().into_any()),
2655 priority: 0,
2656 }]);
2657 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2658 let blocks = blocks_snapshot
2659 .blocks_in_range(0..u32::MAX)
2660 .collect::<Vec<_>>();
2661 for (_, block) in &blocks {
2662 if let BlockId::Custom(custom_block_id) = block.id() {
2663 assert!(
2664 !excerpt_blocks_1.contains(&custom_block_id),
2665 "Should have no blocks from the folded buffer"
2666 );
2667 assert!(
2668 excerpt_blocks_2.contains(&custom_block_id)
2669 || excerpt_blocks_3.contains(&custom_block_id),
2670 "Should have only blocks from unfolded buffers"
2671 );
2672 }
2673 }
2674 assert_eq!(
2675 1,
2676 blocks
2677 .iter()
2678 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2679 .count(),
2680 "Should have one folded block, producing a header of the second buffer"
2681 );
2682 assert_eq!(
2683 blocks_snapshot.text(),
2684 "\n\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
2685 );
2686 assert_eq!(
2687 blocks_snapshot
2688 .row_infos(BlockRow(0))
2689 .map(|i| i.buffer_row)
2690 .collect::<Vec<_>>(),
2691 vec![
2692 None,
2693 None,
2694 None,
2695 None,
2696 None,
2697 Some(1),
2698 None,
2699 None,
2700 Some(2),
2701 None,
2702 Some(3),
2703 None,
2704 None,
2705 None,
2706 None,
2707 Some(4),
2708 None,
2709 Some(5),
2710 None,
2711 ]
2712 );
2713
2714 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2715 buffer.read_with(cx, |buffer, cx| {
2716 writer.fold_buffers([buffer_id_2], buffer, cx);
2717 });
2718 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2719 let blocks = blocks_snapshot
2720 .blocks_in_range(0..u32::MAX)
2721 .collect::<Vec<_>>();
2722 for (_, block) in &blocks {
2723 if let BlockId::Custom(custom_block_id) = block.id() {
2724 assert!(
2725 !excerpt_blocks_1.contains(&custom_block_id),
2726 "Should have no blocks from the folded buffer_1"
2727 );
2728 assert!(
2729 !excerpt_blocks_2.contains(&custom_block_id),
2730 "Should have no blocks from the folded buffer_2"
2731 );
2732 assert!(
2733 excerpt_blocks_3.contains(&custom_block_id),
2734 "Should have only blocks from unfolded buffers"
2735 );
2736 }
2737 }
2738 assert_eq!(
2739 2,
2740 blocks
2741 .iter()
2742 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2743 .count(),
2744 "Should have two folded blocks, producing headers"
2745 );
2746 assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n555\n\n666\n");
2747 assert_eq!(
2748 blocks_snapshot
2749 .row_infos(BlockRow(0))
2750 .map(|i| i.buffer_row)
2751 .collect::<Vec<_>>(),
2752 vec![
2753 None,
2754 None,
2755 None,
2756 None,
2757 None,
2758 None,
2759 None,
2760 Some(4),
2761 None,
2762 Some(5),
2763 None,
2764 ]
2765 );
2766
2767 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2768 buffer.read_with(cx, |buffer, cx| {
2769 writer.unfold_buffers([buffer_id_1], buffer, cx);
2770 });
2771 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2772 let blocks = blocks_snapshot
2773 .blocks_in_range(0..u32::MAX)
2774 .collect::<Vec<_>>();
2775 for (_, block) in &blocks {
2776 if let BlockId::Custom(custom_block_id) = block.id() {
2777 assert!(
2778 !excerpt_blocks_2.contains(&custom_block_id),
2779 "Should have no blocks from the folded buffer_2"
2780 );
2781 assert!(
2782 excerpt_blocks_1.contains(&custom_block_id)
2783 || excerpt_blocks_3.contains(&custom_block_id),
2784 "Should have only blocks from unfolded buffers"
2785 );
2786 }
2787 }
2788 assert_eq!(
2789 1,
2790 blocks
2791 .iter()
2792 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2793 .count(),
2794 "Should be back to a single folded buffer, producing a header for buffer_2"
2795 );
2796 assert_eq!(
2797 blocks_snapshot.text(),
2798 "\n\n\n111\n\n\n\n\n\n555\n\n666\n",
2799 "Should have extra newline for 111 buffer, due to a new block added when it was folded"
2800 );
2801 assert_eq!(
2802 blocks_snapshot
2803 .row_infos(BlockRow(0))
2804 .map(|i| i.buffer_row)
2805 .collect::<Vec<_>>(),
2806 vec![
2807 None,
2808 None,
2809 None,
2810 Some(0),
2811 None,
2812 None,
2813 None,
2814 None,
2815 None,
2816 Some(4),
2817 None,
2818 Some(5),
2819 None,
2820 ]
2821 );
2822
2823 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2824 buffer.read_with(cx, |buffer, cx| {
2825 writer.fold_buffers([buffer_id_3], buffer, cx);
2826 });
2827 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
2828 let blocks = blocks_snapshot
2829 .blocks_in_range(0..u32::MAX)
2830 .collect::<Vec<_>>();
2831 for (_, block) in &blocks {
2832 if let BlockId::Custom(custom_block_id) = block.id() {
2833 assert!(
2834 excerpt_blocks_1.contains(&custom_block_id),
2835 "Should have no blocks from the folded buffer_1"
2836 );
2837 assert!(
2838 !excerpt_blocks_2.contains(&custom_block_id),
2839 "Should have only blocks from unfolded buffers"
2840 );
2841 assert!(
2842 !excerpt_blocks_3.contains(&custom_block_id),
2843 "Should have only blocks from unfolded buffers"
2844 );
2845 }
2846 }
2847
2848 assert_eq!(
2849 blocks_snapshot.text(),
2850 "\n\n\n111\n\n\n\n",
2851 "Should have a single, first buffer left after folding"
2852 );
2853 assert_eq!(
2854 blocks_snapshot
2855 .row_infos(BlockRow(0))
2856 .map(|i| i.buffer_row)
2857 .collect::<Vec<_>>(),
2858 vec![None, None, None, Some(0), None, None, None, None,]
2859 );
2860 }
2861
2862 #[gpui::test]
2863 fn test_basic_buffer_fold(cx: &mut gpui::TestAppContext) {
2864 cx.update(init_test);
2865
2866 let text = "111";
2867
2868 let buffer = cx.update(|cx| {
2869 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(0, 3)])], cx)
2870 });
2871 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2872 let buffer_ids = buffer_snapshot
2873 .excerpts()
2874 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2875 .dedup()
2876 .collect::<Vec<_>>();
2877 assert_eq!(buffer_ids.len(), 1);
2878 let buffer_id = buffer_ids[0];
2879
2880 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
2881 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2882 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2883 let (_, wrap_snapshot) =
2884 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2885 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
2886 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2887
2888 assert_eq!(blocks_snapshot.text(), "\n\n111");
2889
2890 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2891 buffer.read_with(cx, |buffer, cx| {
2892 writer.fold_buffers([buffer_id], buffer, cx);
2893 });
2894 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
2895 let blocks = blocks_snapshot
2896 .blocks_in_range(0..u32::MAX)
2897 .collect::<Vec<_>>();
2898 assert_eq!(
2899 1,
2900 blocks
2901 .iter()
2902 .filter(|(_, block)| { matches!(block, Block::FoldedBuffer { .. }) })
2903 .count(),
2904 "Should have one folded block, producing a header of the second buffer"
2905 );
2906 assert_eq!(blocks_snapshot.text(), "\n");
2907 assert_eq!(
2908 blocks_snapshot
2909 .row_infos(BlockRow(0))
2910 .map(|i| i.buffer_row)
2911 .collect::<Vec<_>>(),
2912 vec![None, None],
2913 "When fully folded, should be no buffer rows"
2914 );
2915 }
2916
2917 #[gpui::test(iterations = 100)]
2918 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
2919 cx.update(init_test);
2920
2921 let operations = env::var("OPERATIONS")
2922 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2923 .unwrap_or(10);
2924
2925 let wrap_width = if rng.random_bool(0.2) {
2926 None
2927 } else {
2928 Some(px(rng.random_range(0.0..=100.0)))
2929 };
2930 let tab_size = 1.try_into().unwrap();
2931 let font_size = px(14.0);
2932 let buffer_start_header_height = rng.random_range(1..=5);
2933 let excerpt_header_height = rng.random_range(1..=5);
2934
2935 log::info!("Wrap width: {:?}", wrap_width);
2936 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
2937 let is_singleton = rng.random();
2938 let buffer = if is_singleton {
2939 let len = rng.random_range(0..10);
2940 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
2941 log::info!("initial singleton buffer text: {:?}", text);
2942 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
2943 } else {
2944 cx.update(|cx| {
2945 let multibuffer = MultiBuffer::build_random(&mut rng, cx);
2946 log::info!(
2947 "initial multi-buffer text: {:?}",
2948 multibuffer.read(cx).read(cx).text()
2949 );
2950 multibuffer
2951 })
2952 };
2953
2954 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2955 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2956 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2957 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2958 let font = test_font();
2959 let (wrap_map, wraps_snapshot) =
2960 cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
2961 let mut block_map = BlockMap::new(
2962 wraps_snapshot,
2963 buffer_start_header_height,
2964 excerpt_header_height,
2965 );
2966
2967 for _ in 0..operations {
2968 let mut buffer_edits = Vec::new();
2969 match rng.random_range(0..=100) {
2970 0..=19 => {
2971 let wrap_width = if rng.random_bool(0.2) {
2972 None
2973 } else {
2974 Some(px(rng.random_range(0.0..=100.0)))
2975 };
2976 log::info!("Setting wrap width to {:?}", wrap_width);
2977 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
2978 }
2979 20..=39 => {
2980 let block_count = rng.random_range(1..=5);
2981 let block_properties = (0..block_count)
2982 .map(|_| {
2983 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
2984 let offset =
2985 buffer.clip_offset(rng.random_range(0..=buffer.len()), Bias::Left);
2986 let mut min_height = 0;
2987 let placement = match rng.random_range(0..3) {
2988 0 => {
2989 min_height = 1;
2990 let start = buffer.anchor_after(offset);
2991 let end = buffer.anchor_after(buffer.clip_offset(
2992 rng.random_range(offset..=buffer.len()),
2993 Bias::Left,
2994 ));
2995 BlockPlacement::Replace(start..=end)
2996 }
2997 1 => BlockPlacement::Above(buffer.anchor_after(offset)),
2998 _ => BlockPlacement::Below(buffer.anchor_after(offset)),
2999 };
3000
3001 let height = rng.random_range(min_height..5);
3002 BlockProperties {
3003 style: BlockStyle::Fixed,
3004 placement,
3005 height: Some(height),
3006 render: Arc::new(|_| div().into_any()),
3007 priority: 0,
3008 }
3009 })
3010 .collect::<Vec<_>>();
3011
3012 let (inlay_snapshot, inlay_edits) =
3013 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3014 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3015 let (tab_snapshot, tab_edits) =
3016 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3017 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3018 wrap_map.sync(tab_snapshot, tab_edits, cx)
3019 });
3020 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3021 let block_ids =
3022 block_map.insert(block_properties.iter().map(|props| BlockProperties {
3023 placement: props.placement.clone(),
3024 height: props.height,
3025 style: props.style,
3026 render: Arc::new(|_| div().into_any()),
3027 priority: 0,
3028 }));
3029
3030 for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3031 log::info!(
3032 "inserted block {:?} with height {:?} and id {:?}",
3033 block_properties
3034 .placement
3035 .as_ref()
3036 .map(|p| p.to_point(&buffer_snapshot)),
3037 block_properties.height,
3038 block_id
3039 );
3040 }
3041 }
3042 40..=59 if !block_map.custom_blocks.is_empty() => {
3043 let block_count = rng.random_range(1..=4.min(block_map.custom_blocks.len()));
3044 let block_ids_to_remove = block_map
3045 .custom_blocks
3046 .choose_multiple(&mut rng, block_count)
3047 .map(|block| block.id)
3048 .collect::<HashSet<_>>();
3049
3050 let (inlay_snapshot, inlay_edits) =
3051 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3052 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3053 let (tab_snapshot, tab_edits) =
3054 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3055 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3056 wrap_map.sync(tab_snapshot, tab_edits, cx)
3057 });
3058 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3059 log::info!(
3060 "removing {} blocks: {:?}",
3061 block_ids_to_remove.len(),
3062 block_ids_to_remove
3063 );
3064 block_map.remove(block_ids_to_remove);
3065 }
3066 60..=79 => {
3067 if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
3068 log::info!("Noop fold/unfold operation on a singleton buffer");
3069 continue;
3070 }
3071 let (inlay_snapshot, inlay_edits) =
3072 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3073 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3074 let (tab_snapshot, tab_edits) =
3075 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3076 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3077 wrap_map.sync(tab_snapshot, tab_edits, cx)
3078 });
3079 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3080 let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
3081 let folded_buffers = block_map
3082 .0
3083 .folded_buffers
3084 .iter()
3085 .cloned()
3086 .collect::<Vec<_>>();
3087 let mut unfolded_buffers = buffer.excerpt_buffer_ids();
3088 unfolded_buffers.dedup();
3089 log::debug!("All buffers {unfolded_buffers:?}");
3090 log::debug!("Folded buffers {folded_buffers:?}");
3091 unfolded_buffers
3092 .retain(|buffer_id| !block_map.0.folded_buffers.contains(buffer_id));
3093 (unfolded_buffers, folded_buffers)
3094 });
3095 let mut folded_count = folded_buffers.len();
3096 let mut unfolded_count = unfolded_buffers.len();
3097
3098 let fold = !unfolded_buffers.is_empty() && rng.random_bool(0.5);
3099 let unfold = !folded_buffers.is_empty() && rng.random_bool(0.5);
3100 if !fold && !unfold {
3101 log::info!(
3102 "Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3103 );
3104 continue;
3105 }
3106
3107 buffer.update(cx, |buffer, cx| {
3108 if fold {
3109 let buffer_to_fold =
3110 unfolded_buffers[rng.random_range(0..unfolded_buffers.len())];
3111 log::info!("Folding {buffer_to_fold:?}");
3112 let related_excerpts = buffer_snapshot
3113 .excerpts()
3114 .filter_map(|(excerpt_id, buffer, range)| {
3115 if buffer.remote_id() == buffer_to_fold {
3116 Some((
3117 excerpt_id,
3118 buffer
3119 .text_for_range(range.context)
3120 .collect::<String>(),
3121 ))
3122 } else {
3123 None
3124 }
3125 })
3126 .collect::<Vec<_>>();
3127 log::info!(
3128 "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
3129 );
3130 folded_count += 1;
3131 unfolded_count -= 1;
3132 block_map.fold_buffers([buffer_to_fold], buffer, cx);
3133 }
3134 if unfold {
3135 let buffer_to_unfold =
3136 folded_buffers[rng.random_range(0..folded_buffers.len())];
3137 log::info!("Unfolding {buffer_to_unfold:?}");
3138 unfolded_count += 1;
3139 folded_count -= 1;
3140 block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
3141 }
3142 log::info!(
3143 "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3144 );
3145 });
3146 }
3147 _ => {
3148 buffer.update(cx, |buffer, cx| {
3149 let mutation_count = rng.random_range(1..=5);
3150 let subscription = buffer.subscribe();
3151 buffer.randomly_mutate(&mut rng, mutation_count, cx);
3152 buffer_snapshot = buffer.snapshot(cx);
3153 buffer_edits.extend(subscription.consume());
3154 log::info!("buffer text: {:?}", buffer_snapshot.text());
3155 });
3156 }
3157 }
3158
3159 let (inlay_snapshot, inlay_edits) =
3160 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
3161 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3162 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3163 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3164 wrap_map.sync(tab_snapshot, tab_edits, cx)
3165 });
3166 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
3167 assert_eq!(
3168 blocks_snapshot.transforms.summary().input_rows,
3169 wraps_snapshot.max_point().row() + 1
3170 );
3171 log::info!("wrapped text: {:?}", wraps_snapshot.text());
3172 log::info!("blocks text: {:?}", blocks_snapshot.text());
3173
3174 let mut expected_blocks = Vec::new();
3175 expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
3176 Some((
3177 block.placement.to_wrap_row(&wraps_snapshot)?,
3178 Block::Custom(block.clone()),
3179 ))
3180 }));
3181
3182 // Note that this needs to be synced with the related section in BlockMap::sync
3183 expected_blocks.extend(block_map.header_and_footer_blocks(
3184 &buffer_snapshot,
3185 0..,
3186 &wraps_snapshot,
3187 ));
3188
3189 BlockMap::sort_blocks(&mut expected_blocks);
3190
3191 for (placement, block) in &expected_blocks {
3192 log::info!(
3193 "Block {:?} placement: {:?} Height: {:?}",
3194 block.id(),
3195 placement,
3196 block.height()
3197 );
3198 }
3199
3200 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
3201
3202 let input_buffer_rows = buffer_snapshot
3203 .row_infos(MultiBufferRow(0))
3204 .map(|row| row.buffer_row)
3205 .collect::<Vec<_>>();
3206 let mut expected_buffer_rows = Vec::new();
3207 let mut expected_text = String::new();
3208 let mut expected_block_positions = Vec::new();
3209 let mut expected_replaced_buffer_rows = HashSet::default();
3210 let input_text = wraps_snapshot.text();
3211
3212 // Loop over the input lines, creating (N - 1) empty lines for
3213 // blocks of height N.
3214 //
3215 // It's important to note that output *starts* as one empty line,
3216 // so we special case row 0 to assume a leading '\n'.
3217 //
3218 // Linehood is the birthright of strings.
3219 let input_text_lines = input_text.split('\n').enumerate().peekable();
3220 let mut block_row = 0;
3221 for (wrap_row, input_line) in input_text_lines {
3222 let wrap_row = wrap_row as u32;
3223 let multibuffer_row = wraps_snapshot
3224 .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
3225 .row;
3226
3227 // Create empty lines for the above block
3228 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3229 if placement.start().0 == wrap_row && block.place_above() {
3230 let (_, block) = sorted_blocks_iter.next().unwrap();
3231 expected_block_positions.push((block_row, block.id()));
3232 if block.height() > 0 {
3233 let text = "\n".repeat((block.height() - 1) as usize);
3234 if block_row > 0 {
3235 expected_text.push('\n')
3236 }
3237 expected_text.push_str(&text);
3238 for _ in 0..block.height() {
3239 expected_buffer_rows.push(None);
3240 }
3241 block_row += block.height();
3242 }
3243 } else {
3244 break;
3245 }
3246 }
3247
3248 // Skip lines within replace blocks, then create empty lines for the replace block's height
3249 let mut is_in_replace_block = false;
3250 if let Some((BlockPlacement::Replace(replace_range), block)) =
3251 sorted_blocks_iter.peek()
3252 && wrap_row >= replace_range.start().0
3253 {
3254 is_in_replace_block = true;
3255
3256 if wrap_row == replace_range.start().0 {
3257 if matches!(block, Block::FoldedBuffer { .. }) {
3258 expected_buffer_rows.push(None);
3259 } else {
3260 expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]);
3261 }
3262 }
3263
3264 if wrap_row == replace_range.end().0 {
3265 expected_block_positions.push((block_row, block.id()));
3266 let text = "\n".repeat((block.height() - 1) as usize);
3267 if block_row > 0 {
3268 expected_text.push('\n');
3269 }
3270 expected_text.push_str(&text);
3271
3272 for _ in 1..block.height() {
3273 expected_buffer_rows.push(None);
3274 }
3275 block_row += block.height();
3276
3277 sorted_blocks_iter.next();
3278 }
3279 }
3280
3281 if is_in_replace_block {
3282 expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
3283 } else {
3284 let buffer_row = input_buffer_rows[multibuffer_row as usize];
3285 let soft_wrapped = wraps_snapshot
3286 .to_tab_point(WrapPoint::new(wrap_row, 0))
3287 .column()
3288 > 0;
3289 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
3290 if block_row > 0 {
3291 expected_text.push('\n');
3292 }
3293 expected_text.push_str(input_line);
3294 block_row += 1;
3295 }
3296
3297 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3298 if placement.end().0 == wrap_row && block.place_below() {
3299 let (_, block) = sorted_blocks_iter.next().unwrap();
3300 expected_block_positions.push((block_row, block.id()));
3301 if block.height() > 0 {
3302 let text = "\n".repeat((block.height() - 1) as usize);
3303 if block_row > 0 {
3304 expected_text.push('\n')
3305 }
3306 expected_text.push_str(&text);
3307 for _ in 0..block.height() {
3308 expected_buffer_rows.push(None);
3309 }
3310 block_row += block.height();
3311 }
3312 } else {
3313 break;
3314 }
3315 }
3316 }
3317
3318 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
3319 let expected_row_count = expected_lines.len();
3320 log::info!("expected text: {expected_text:?}");
3321
3322 assert_eq!(
3323 blocks_snapshot.max_point().row + 1,
3324 expected_row_count as u32,
3325 "actual row count != expected row count",
3326 );
3327 assert_eq!(
3328 blocks_snapshot.text(),
3329 expected_text,
3330 "actual text != expected text",
3331 );
3332
3333 for start_row in 0..expected_row_count {
3334 let end_row = rng.random_range(start_row + 1..=expected_row_count);
3335 let mut expected_text = expected_lines[start_row..end_row].join("\n");
3336 if end_row < expected_row_count {
3337 expected_text.push('\n');
3338 }
3339
3340 let actual_text = blocks_snapshot
3341 .chunks(
3342 start_row as u32..end_row as u32,
3343 false,
3344 false,
3345 Highlights::default(),
3346 )
3347 .map(|chunk| chunk.text)
3348 .collect::<String>();
3349 assert_eq!(
3350 actual_text,
3351 expected_text,
3352 "incorrect text starting row row range {:?}",
3353 start_row..end_row
3354 );
3355 assert_eq!(
3356 blocks_snapshot
3357 .row_infos(BlockRow(start_row as u32))
3358 .map(|row_info| row_info.buffer_row)
3359 .collect::<Vec<_>>(),
3360 &expected_buffer_rows[start_row..],
3361 "incorrect buffer_rows starting at row {:?}",
3362 start_row
3363 );
3364 }
3365
3366 assert_eq!(
3367 blocks_snapshot
3368 .blocks_in_range(0..(expected_row_count as u32))
3369 .map(|(row, block)| (row, block.id()))
3370 .collect::<Vec<_>>(),
3371 expected_block_positions,
3372 "invalid blocks_in_range({:?})",
3373 0..expected_row_count
3374 );
3375
3376 for (_, expected_block) in
3377 blocks_snapshot.blocks_in_range(0..(expected_row_count as u32))
3378 {
3379 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
3380 assert_eq!(
3381 actual_block.map(|block| block.id()),
3382 Some(expected_block.id())
3383 );
3384 }
3385
3386 for (block_row, block_id) in expected_block_positions {
3387 if let BlockId::Custom(block_id) = block_id {
3388 assert_eq!(
3389 blocks_snapshot.row_for_block(block_id),
3390 Some(BlockRow(block_row))
3391 );
3392 }
3393 }
3394
3395 let mut expected_longest_rows = Vec::new();
3396 let mut longest_line_len = -1_isize;
3397 for (row, line) in expected_lines.iter().enumerate() {
3398 let row = row as u32;
3399
3400 assert_eq!(
3401 blocks_snapshot.line_len(BlockRow(row)),
3402 line.len() as u32,
3403 "invalid line len for row {}",
3404 row
3405 );
3406
3407 let line_char_count = line.chars().count() as isize;
3408 match line_char_count.cmp(&longest_line_len) {
3409 Ordering::Less => {}
3410 Ordering::Equal => expected_longest_rows.push(row),
3411 Ordering::Greater => {
3412 longest_line_len = line_char_count;
3413 expected_longest_rows.clear();
3414 expected_longest_rows.push(row);
3415 }
3416 }
3417 }
3418
3419 let longest_row = blocks_snapshot.longest_row();
3420 assert!(
3421 expected_longest_rows.contains(&longest_row),
3422 "incorrect longest row {}. expected {:?} with length {}",
3423 longest_row,
3424 expected_longest_rows,
3425 longest_line_len,
3426 );
3427
3428 for _ in 0..10 {
3429 let end_row = rng.random_range(1..=expected_lines.len());
3430 let start_row = rng.random_range(0..end_row);
3431
3432 let mut expected_longest_rows_in_range = vec![];
3433 let mut longest_line_len_in_range = 0;
3434
3435 let mut row = start_row as u32;
3436 for line in &expected_lines[start_row..end_row] {
3437 let line_char_count = line.chars().count() as isize;
3438 match line_char_count.cmp(&longest_line_len_in_range) {
3439 Ordering::Less => {}
3440 Ordering::Equal => expected_longest_rows_in_range.push(row),
3441 Ordering::Greater => {
3442 longest_line_len_in_range = line_char_count;
3443 expected_longest_rows_in_range.clear();
3444 expected_longest_rows_in_range.push(row);
3445 }
3446 }
3447 row += 1;
3448 }
3449
3450 let longest_row_in_range = blocks_snapshot
3451 .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
3452 assert!(
3453 expected_longest_rows_in_range.contains(&longest_row_in_range.0),
3454 "incorrect longest row {} in range {:?}. expected {:?} with length {}",
3455 longest_row,
3456 start_row..end_row,
3457 expected_longest_rows_in_range,
3458 longest_line_len_in_range,
3459 );
3460 }
3461
3462 // Ensure that conversion between block points and wrap points is stable.
3463 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
3464 let wrap_point = WrapPoint::new(row, 0);
3465 let block_point = blocks_snapshot.to_block_point(wrap_point);
3466 let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
3467 let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
3468 assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
3469 assert_eq!(
3470 blocks_snapshot.to_block_point(right_wrap_point),
3471 block_point
3472 );
3473 }
3474
3475 let mut block_point = BlockPoint::new(0, 0);
3476 for c in expected_text.chars() {
3477 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
3478 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
3479 assert_eq!(
3480 blocks_snapshot
3481 .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
3482 left_point,
3483 "block point: {:?}, wrap point: {:?}",
3484 block_point,
3485 blocks_snapshot.to_wrap_point(left_point, Bias::Left)
3486 );
3487 assert_eq!(
3488 left_buffer_point,
3489 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
3490 "{:?} is not valid in buffer coordinates",
3491 left_point
3492 );
3493
3494 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
3495 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
3496 assert_eq!(
3497 blocks_snapshot
3498 .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
3499 right_point,
3500 "block point: {:?}, wrap point: {:?}",
3501 block_point,
3502 blocks_snapshot.to_wrap_point(right_point, Bias::Right)
3503 );
3504 assert_eq!(
3505 right_buffer_point,
3506 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
3507 "{:?} is not valid in buffer coordinates",
3508 right_point
3509 );
3510
3511 if c == '\n' {
3512 block_point.0 += Point::new(1, 0);
3513 } else {
3514 block_point.column += c.len_utf8() as u32;
3515 }
3516 }
3517
3518 for buffer_row in 0..=buffer_snapshot.max_point().row {
3519 let buffer_row = MultiBufferRow(buffer_row);
3520 assert_eq!(
3521 blocks_snapshot.is_line_replaced(buffer_row),
3522 expected_replaced_buffer_rows.contains(&buffer_row),
3523 "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
3524 );
3525 }
3526 }
3527 }
3528
3529 #[gpui::test]
3530 fn test_remove_intersecting_replace_blocks_edge_case(cx: &mut gpui::TestAppContext) {
3531 cx.update(init_test);
3532
3533 let text = "abc\ndef\nghi\njkl\nmno";
3534 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3535 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3536 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3537 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3538 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3539 let (_wrap_map, wraps_snapshot) =
3540 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3541 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3542
3543 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
3544 let _block_id = writer.insert(vec![BlockProperties {
3545 style: BlockStyle::Fixed,
3546 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3547 height: Some(1),
3548 render: Arc::new(|_| div().into_any()),
3549 priority: 0,
3550 }])[0];
3551
3552 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
3553 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
3554
3555 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
3556 writer.remove_intersecting_replace_blocks(
3557 [buffer_snapshot.anchor_after(Point::new(1, 0))
3558 ..buffer_snapshot.anchor_after(Point::new(1, 0))],
3559 false,
3560 );
3561 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
3562 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
3563 }
3564
3565 fn init_test(cx: &mut gpui::App) {
3566 let settings = SettingsStore::test(cx);
3567 cx.set_global(settings);
3568 theme::init(theme::LoadThemes::JustBase, cx);
3569 assets::Assets.load_test_fonts(cx);
3570 }
3571
3572 impl Block {
3573 fn as_custom(&self) -> Option<&CustomBlock> {
3574 match self {
3575 Block::Custom(block) => Some(block),
3576 _ => None,
3577 }
3578 }
3579 }
3580
3581 impl BlockSnapshot {
3582 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
3583 self.wrap_snapshot
3584 .to_point(self.to_wrap_point(point, bias), bias)
3585 }
3586 }
3587}