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 if range.is_empty() && !inclusive {
1268 return &[];
1269 }
1270 let wrap_snapshot = self.0.wrap_snapshot.borrow();
1271 let buffer = wrap_snapshot.buffer_snapshot();
1272
1273 let start_block_ix = match self.0.custom_blocks.binary_search_by(|block| {
1274 let block_end = block.end().to_offset(buffer);
1275 block_end.cmp(&range.start).then(Ordering::Greater)
1276 }) {
1277 Ok(ix) | Err(ix) => ix,
1278 };
1279 let end_block_ix = match self.0.custom_blocks[start_block_ix..].binary_search_by(|block| {
1280 let block_start = block.start().to_offset(buffer);
1281 block_start.cmp(&range.end).then(if inclusive {
1282 Ordering::Less
1283 } else {
1284 Ordering::Greater
1285 })
1286 }) {
1287 Ok(ix) | Err(ix) => ix,
1288 };
1289
1290 &self.0.custom_blocks[start_block_ix..][..end_block_ix]
1291 }
1292}
1293
1294impl BlockSnapshot {
1295 #[cfg(test)]
1296 pub fn text(&self) -> String {
1297 self.chunks(
1298 0..self.transforms.summary().output_rows,
1299 false,
1300 false,
1301 Highlights::default(),
1302 )
1303 .map(|chunk| chunk.text)
1304 .collect()
1305 }
1306
1307 pub(crate) fn chunks<'a>(
1308 &'a self,
1309 rows: Range<u32>,
1310 language_aware: bool,
1311 masked: bool,
1312 highlights: Highlights<'a>,
1313 ) -> BlockChunks<'a> {
1314 let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
1315
1316 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
1317 cursor.seek(&BlockRow(rows.start), Bias::Right);
1318 let transform_output_start = cursor.start().0.0;
1319 let transform_input_start = cursor.start().1.0;
1320
1321 let mut input_start = transform_input_start;
1322 let mut input_end = transform_input_start;
1323 if let Some(transform) = cursor.item()
1324 && transform.block.is_none()
1325 {
1326 input_start += rows.start - transform_output_start;
1327 input_end += cmp::min(
1328 rows.end - transform_output_start,
1329 transform.summary.input_rows,
1330 );
1331 }
1332
1333 BlockChunks {
1334 input_chunks: self.wrap_snapshot.chunks(
1335 input_start..input_end,
1336 language_aware,
1337 highlights,
1338 ),
1339 input_chunk: Default::default(),
1340 transforms: cursor,
1341 output_row: rows.start,
1342 max_output_row,
1343 masked,
1344 }
1345 }
1346
1347 pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows<'_> {
1348 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
1349 cursor.seek(&start_row, Bias::Right);
1350 let Dimensions(output_start, input_start, _) = cursor.start();
1351 let overshoot = if cursor
1352 .item()
1353 .is_some_and(|transform| transform.block.is_none())
1354 {
1355 start_row.0 - output_start.0
1356 } else {
1357 0
1358 };
1359 let input_start_row = input_start.0 + overshoot;
1360 BlockRows {
1361 transforms: cursor,
1362 input_rows: self.wrap_snapshot.row_infos(input_start_row),
1363 output_row: start_row,
1364 started: false,
1365 }
1366 }
1367
1368 pub fn blocks_in_range(&self, rows: Range<u32>) -> impl Iterator<Item = (u32, &Block)> {
1369 let mut cursor = self.transforms.cursor::<BlockRow>(&());
1370 cursor.seek(&BlockRow(rows.start), Bias::Left);
1371 while cursor.start().0 < rows.start && cursor.end().0 <= rows.start {
1372 cursor.next();
1373 }
1374
1375 std::iter::from_fn(move || {
1376 while let Some(transform) = cursor.item() {
1377 let start_row = cursor.start().0;
1378 if start_row > rows.end
1379 || (start_row == rows.end
1380 && transform
1381 .block
1382 .as_ref()
1383 .is_some_and(|block| block.height() > 0))
1384 {
1385 break;
1386 }
1387 if let Some(block) = &transform.block {
1388 cursor.next();
1389 return Some((start_row, block));
1390 } else {
1391 cursor.next();
1392 }
1393 }
1394 None
1395 })
1396 }
1397
1398 pub fn sticky_header_excerpt(&self, position: f32) -> Option<StickyHeaderExcerpt<'_>> {
1399 let top_row = position as u32;
1400 let mut cursor = self.transforms.cursor::<BlockRow>(&());
1401 cursor.seek(&BlockRow(top_row), Bias::Right);
1402
1403 while let Some(transform) = cursor.item() {
1404 match &transform.block {
1405 Some(
1406 Block::ExcerptBoundary { excerpt, .. } | Block::BufferHeader { excerpt, .. },
1407 ) => {
1408 return Some(StickyHeaderExcerpt { excerpt });
1409 }
1410 Some(block) if block.is_buffer_header() => return None,
1411 _ => {
1412 cursor.prev();
1413 continue;
1414 }
1415 }
1416 }
1417
1418 None
1419 }
1420
1421 pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
1422 let buffer = self.wrap_snapshot.buffer_snapshot();
1423 let wrap_point = match block_id {
1424 BlockId::Custom(custom_block_id) => {
1425 let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
1426 return Some(Block::Custom(custom_block.clone()));
1427 }
1428 BlockId::ExcerptBoundary(next_excerpt_id) => {
1429 let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
1430 self.wrap_snapshot
1431 .make_wrap_point(excerpt_range.start, Bias::Left)
1432 }
1433 BlockId::FoldedBuffer(excerpt_id) => self
1434 .wrap_snapshot
1435 .make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left),
1436 };
1437 let wrap_row = WrapRow(wrap_point.row());
1438
1439 let mut cursor = self.transforms.cursor::<WrapRow>(&());
1440 cursor.seek(&wrap_row, Bias::Left);
1441
1442 while let Some(transform) = cursor.item() {
1443 if let Some(block) = transform.block.as_ref() {
1444 if block.id() == block_id {
1445 return Some(block.clone());
1446 }
1447 } else if *cursor.start() > wrap_row {
1448 break;
1449 }
1450
1451 cursor.next();
1452 }
1453
1454 None
1455 }
1456
1457 pub fn max_point(&self) -> BlockPoint {
1458 let row = self.transforms.summary().output_rows.saturating_sub(1);
1459 BlockPoint::new(row, self.line_len(BlockRow(row)))
1460 }
1461
1462 pub fn longest_row(&self) -> u32 {
1463 self.transforms.summary().longest_row
1464 }
1465
1466 pub fn longest_row_in_range(&self, range: Range<BlockRow>) -> BlockRow {
1467 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
1468 cursor.seek(&range.start, Bias::Right);
1469
1470 let mut longest_row = range.start;
1471 let mut longest_row_chars = 0;
1472 if let Some(transform) = cursor.item() {
1473 if transform.block.is_none() {
1474 let Dimensions(output_start, input_start, _) = cursor.start();
1475 let overshoot = range.start.0 - output_start.0;
1476 let wrap_start_row = input_start.0 + overshoot;
1477 let wrap_end_row = cmp::min(
1478 input_start.0 + (range.end.0 - output_start.0),
1479 cursor.end().1.0,
1480 );
1481 let summary = self
1482 .wrap_snapshot
1483 .text_summary_for_range(wrap_start_row..wrap_end_row);
1484 longest_row = BlockRow(range.start.0 + summary.longest_row);
1485 longest_row_chars = summary.longest_row_chars;
1486 }
1487 cursor.next();
1488 }
1489
1490 let cursor_start_row = cursor.start().0;
1491 if range.end > cursor_start_row {
1492 let summary = cursor.summary::<_, TransformSummary>(&range.end, Bias::Right);
1493 if summary.longest_row_chars > longest_row_chars {
1494 longest_row = BlockRow(cursor_start_row.0 + summary.longest_row);
1495 longest_row_chars = summary.longest_row_chars;
1496 }
1497
1498 if let Some(transform) = cursor.item()
1499 && transform.block.is_none()
1500 {
1501 let Dimensions(output_start, input_start, _) = cursor.start();
1502 let overshoot = range.end.0 - output_start.0;
1503 let wrap_start_row = input_start.0;
1504 let wrap_end_row = input_start.0 + overshoot;
1505 let summary = self
1506 .wrap_snapshot
1507 .text_summary_for_range(wrap_start_row..wrap_end_row);
1508 if summary.longest_row_chars > longest_row_chars {
1509 longest_row = BlockRow(output_start.0 + summary.longest_row);
1510 }
1511 }
1512 }
1513
1514 longest_row
1515 }
1516
1517 pub(super) fn line_len(&self, row: BlockRow) -> u32 {
1518 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
1519 cursor.seek(&BlockRow(row.0), Bias::Right);
1520 if let Some(transform) = cursor.item() {
1521 let Dimensions(output_start, input_start, _) = cursor.start();
1522 let overshoot = row.0 - output_start.0;
1523 if transform.block.is_some() {
1524 0
1525 } else {
1526 self.wrap_snapshot.line_len(input_start.0 + overshoot)
1527 }
1528 } else if row.0 == 0 {
1529 0
1530 } else {
1531 panic!("row out of range");
1532 }
1533 }
1534
1535 pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
1536 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
1537 cursor.seek(&row, Bias::Right);
1538 cursor.item().is_some_and(|t| t.block.is_some())
1539 }
1540
1541 pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool {
1542 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
1543 cursor.seek(&row, Bias::Right);
1544 let Some(transform) = cursor.item() else {
1545 return false;
1546 };
1547 matches!(transform.block, Some(Block::FoldedBuffer { .. }))
1548 }
1549
1550 pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool {
1551 let wrap_point = self
1552 .wrap_snapshot
1553 .make_wrap_point(Point::new(row.0, 0), Bias::Left);
1554 let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(&());
1555 cursor.seek(&WrapRow(wrap_point.row()), Bias::Right);
1556 cursor.item().is_some_and(|transform| {
1557 transform
1558 .block
1559 .as_ref()
1560 .is_some_and(|block| block.is_replacement())
1561 })
1562 }
1563
1564 pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
1565 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
1566 cursor.seek(&BlockRow(point.row), Bias::Right);
1567
1568 let max_input_row = WrapRow(self.transforms.summary().input_rows);
1569 let mut search_left =
1570 (bias == Bias::Left && cursor.start().1.0 > 0) || cursor.end().1 == max_input_row;
1571 let mut reversed = false;
1572
1573 loop {
1574 if let Some(transform) = cursor.item() {
1575 let Dimensions(output_start_row, input_start_row, _) = cursor.start();
1576 let Dimensions(output_end_row, input_end_row, _) = cursor.end();
1577 let output_start = Point::new(output_start_row.0, 0);
1578 let input_start = Point::new(input_start_row.0, 0);
1579 let input_end = Point::new(input_end_row.0, 0);
1580
1581 match transform.block.as_ref() {
1582 Some(block) => {
1583 if block.is_replacement()
1584 && (((bias == Bias::Left || search_left) && output_start <= point.0)
1585 || (!search_left && output_start >= point.0))
1586 {
1587 return BlockPoint(output_start);
1588 }
1589 }
1590 None => {
1591 let input_point = if point.row >= output_end_row.0 {
1592 let line_len = self.wrap_snapshot.line_len(input_end_row.0 - 1);
1593 self.wrap_snapshot
1594 .clip_point(WrapPoint::new(input_end_row.0 - 1, line_len), bias)
1595 } else {
1596 let output_overshoot = point.0.saturating_sub(output_start);
1597 self.wrap_snapshot
1598 .clip_point(WrapPoint(input_start + output_overshoot), bias)
1599 };
1600
1601 if (input_start..input_end).contains(&input_point.0) {
1602 let input_overshoot = input_point.0.saturating_sub(input_start);
1603 return BlockPoint(output_start + input_overshoot);
1604 }
1605 }
1606 }
1607
1608 if search_left {
1609 cursor.prev();
1610 } else {
1611 cursor.next();
1612 }
1613 } else if reversed {
1614 return self.max_point();
1615 } else {
1616 reversed = true;
1617 search_left = !search_left;
1618 cursor.seek(&BlockRow(point.row), Bias::Right);
1619 }
1620 }
1621 }
1622
1623 pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
1624 let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(&());
1625 cursor.seek(&WrapRow(wrap_point.row()), Bias::Right);
1626 if let Some(transform) = cursor.item() {
1627 if transform.block.is_some() {
1628 BlockPoint::new(cursor.start().1.0, 0)
1629 } else {
1630 let Dimensions(input_start_row, output_start_row, _) = cursor.start();
1631 let input_start = Point::new(input_start_row.0, 0);
1632 let output_start = Point::new(output_start_row.0, 0);
1633 let input_overshoot = wrap_point.0 - input_start;
1634 BlockPoint(output_start + input_overshoot)
1635 }
1636 } else {
1637 self.max_point()
1638 }
1639 }
1640
1641 pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
1642 let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(&());
1643 cursor.seek(&BlockRow(block_point.row), Bias::Right);
1644 if let Some(transform) = cursor.item() {
1645 match transform.block.as_ref() {
1646 Some(block) => {
1647 if block.place_below() {
1648 let wrap_row = cursor.start().1.0 - 1;
1649 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1650 } else if block.place_above() {
1651 WrapPoint::new(cursor.start().1.0, 0)
1652 } else if bias == Bias::Left {
1653 WrapPoint::new(cursor.start().1.0, 0)
1654 } else {
1655 let wrap_row = cursor.end().1.0 - 1;
1656 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1657 }
1658 }
1659 None => {
1660 let overshoot = block_point.row - cursor.start().0.0;
1661 let wrap_row = cursor.start().1.0 + overshoot;
1662 WrapPoint::new(wrap_row, block_point.column)
1663 }
1664 }
1665 } else {
1666 self.wrap_snapshot.max_point()
1667 }
1668 }
1669}
1670
1671impl BlockChunks<'_> {
1672 /// Go to the next transform
1673 fn advance(&mut self) {
1674 self.input_chunk = Chunk::default();
1675 self.transforms.next();
1676 while let Some(transform) = self.transforms.item() {
1677 if transform
1678 .block
1679 .as_ref()
1680 .is_some_and(|block| block.height() == 0)
1681 {
1682 self.transforms.next();
1683 } else {
1684 break;
1685 }
1686 }
1687
1688 if self
1689 .transforms
1690 .item()
1691 .is_some_and(|transform| transform.block.is_none())
1692 {
1693 let start_input_row = self.transforms.start().1.0;
1694 let start_output_row = self.transforms.start().0.0;
1695 if start_output_row < self.max_output_row {
1696 let end_input_row = cmp::min(
1697 self.transforms.end().1.0,
1698 start_input_row + (self.max_output_row - start_output_row),
1699 );
1700 self.input_chunks.seek(start_input_row..end_input_row);
1701 }
1702 }
1703 }
1704}
1705
1706pub struct StickyHeaderExcerpt<'a> {
1707 pub excerpt: &'a ExcerptInfo,
1708}
1709
1710impl<'a> Iterator for BlockChunks<'a> {
1711 type Item = Chunk<'a>;
1712
1713 fn next(&mut self) -> Option<Self::Item> {
1714 if self.output_row >= self.max_output_row {
1715 return None;
1716 }
1717
1718 let transform = self.transforms.item()?;
1719 if transform.block.is_some() {
1720 let block_start = self.transforms.start().0.0;
1721 let mut block_end = self.transforms.end().0.0;
1722 self.advance();
1723 if self.transforms.item().is_none() {
1724 block_end -= 1;
1725 }
1726
1727 let start_in_block = self.output_row - block_start;
1728 let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
1729 let line_count = end_in_block - start_in_block;
1730 self.output_row += line_count;
1731
1732 return Some(Chunk {
1733 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) },
1734 chars: (1 << line_count) - 1,
1735 ..Default::default()
1736 });
1737 }
1738
1739 if self.input_chunk.text.is_empty() {
1740 if let Some(input_chunk) = self.input_chunks.next() {
1741 self.input_chunk = input_chunk;
1742 } else {
1743 if self.output_row < self.max_output_row {
1744 self.output_row += 1;
1745 self.advance();
1746 if self.transforms.item().is_some() {
1747 return Some(Chunk {
1748 text: "\n",
1749 ..Default::default()
1750 });
1751 }
1752 }
1753 return None;
1754 }
1755 }
1756
1757 let transform_end = self.transforms.end().0.0;
1758 let (prefix_rows, prefix_bytes) =
1759 offset_for_row(self.input_chunk.text, transform_end - self.output_row);
1760 self.output_row += prefix_rows;
1761
1762 let (mut prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
1763 self.input_chunk.text = suffix;
1764 self.input_chunk.tabs >>= prefix_bytes.saturating_sub(1);
1765 self.input_chunk.chars >>= prefix_bytes.saturating_sub(1);
1766
1767 let mut tabs = self.input_chunk.tabs;
1768 let mut chars = self.input_chunk.chars;
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_count = prefix.chars().count();
1774 let bullet_len = chars_count;
1775 prefix = &BULLETS[..bullet_len];
1776 chars = (1 << bullet_len) - 1;
1777 tabs = 0;
1778 }
1779
1780 let chunk = Chunk {
1781 text: prefix,
1782 tabs,
1783 chars,
1784 ..self.input_chunk.clone()
1785 };
1786
1787 if self.output_row == transform_end {
1788 self.advance();
1789 }
1790
1791 Some(chunk)
1792 }
1793}
1794
1795impl Iterator for BlockRows<'_> {
1796 type Item = RowInfo;
1797
1798 fn next(&mut self) -> Option<Self::Item> {
1799 if self.started {
1800 self.output_row.0 += 1;
1801 } else {
1802 self.started = true;
1803 }
1804
1805 if self.output_row.0 >= self.transforms.end().0.0 {
1806 self.transforms.next();
1807 while let Some(transform) = self.transforms.item() {
1808 if transform
1809 .block
1810 .as_ref()
1811 .is_some_and(|block| block.height() == 0)
1812 {
1813 self.transforms.next();
1814 } else {
1815 break;
1816 }
1817 }
1818
1819 let transform = self.transforms.item()?;
1820 if transform
1821 .block
1822 .as_ref()
1823 .is_none_or(|block| block.is_replacement())
1824 {
1825 self.input_rows.seek(self.transforms.start().1.0);
1826 }
1827 }
1828
1829 let transform = self.transforms.item()?;
1830 if let Some(block) = transform.block.as_ref() {
1831 if block.is_replacement() && self.transforms.start().0 == self.output_row {
1832 if matches!(block, Block::FoldedBuffer { .. }) {
1833 Some(RowInfo::default())
1834 } else {
1835 Some(self.input_rows.next().unwrap())
1836 }
1837 } else {
1838 Some(RowInfo::default())
1839 }
1840 } else {
1841 Some(self.input_rows.next().unwrap())
1842 }
1843 }
1844}
1845
1846impl sum_tree::Item for Transform {
1847 type Summary = TransformSummary;
1848
1849 fn summary(&self, _cx: &()) -> Self::Summary {
1850 self.summary.clone()
1851 }
1852}
1853
1854impl sum_tree::Summary for TransformSummary {
1855 type Context = ();
1856
1857 fn zero(_cx: &()) -> Self {
1858 Default::default()
1859 }
1860
1861 fn add_summary(&mut self, summary: &Self, _: &()) {
1862 if summary.longest_row_chars > self.longest_row_chars {
1863 self.longest_row = self.output_rows + summary.longest_row;
1864 self.longest_row_chars = summary.longest_row_chars;
1865 }
1866 self.input_rows += summary.input_rows;
1867 self.output_rows += summary.output_rows;
1868 }
1869}
1870
1871impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
1872 fn zero(_cx: &()) -> Self {
1873 Default::default()
1874 }
1875
1876 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1877 self.0 += summary.input_rows;
1878 }
1879}
1880
1881impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
1882 fn zero(_cx: &()) -> Self {
1883 Default::default()
1884 }
1885
1886 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1887 self.0 += summary.output_rows;
1888 }
1889}
1890
1891impl Deref for BlockContext<'_, '_> {
1892 type Target = App;
1893
1894 fn deref(&self) -> &Self::Target {
1895 self.app
1896 }
1897}
1898
1899impl DerefMut for BlockContext<'_, '_> {
1900 fn deref_mut(&mut self) -> &mut Self::Target {
1901 self.app
1902 }
1903}
1904
1905impl CustomBlock {
1906 pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
1907 self.render.lock()(cx)
1908 }
1909
1910 pub fn start(&self) -> Anchor {
1911 *self.placement.start()
1912 }
1913
1914 pub fn end(&self) -> Anchor {
1915 *self.placement.end()
1916 }
1917
1918 pub fn style(&self) -> BlockStyle {
1919 self.style
1920 }
1921}
1922
1923impl Debug for CustomBlock {
1924 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1925 f.debug_struct("Block")
1926 .field("id", &self.id)
1927 .field("placement", &self.placement)
1928 .field("height", &self.height)
1929 .field("style", &self.style)
1930 .field("priority", &self.priority)
1931 .finish_non_exhaustive()
1932 }
1933}
1934
1935// Count the number of bytes prior to a target point. If the string doesn't contain the target
1936// point, return its total extent. Otherwise return the target point itself.
1937fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
1938 let mut row = 0;
1939 let mut offset = 0;
1940 for (ix, line) in s.split('\n').enumerate() {
1941 if ix > 0 {
1942 row += 1;
1943 offset += 1;
1944 }
1945 if row >= target {
1946 break;
1947 }
1948 offset += line.len();
1949 }
1950 (row, offset)
1951}
1952
1953#[cfg(test)]
1954mod tests {
1955 use super::*;
1956 use crate::{
1957 display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap},
1958 test::test_font,
1959 };
1960 use gpui::{App, AppContext as _, Element, div, font, px};
1961 use itertools::Itertools;
1962 use language::{Buffer, Capability};
1963 use multi_buffer::{ExcerptRange, MultiBuffer};
1964 use rand::prelude::*;
1965 use settings::SettingsStore;
1966 use std::env;
1967 use util::RandomCharIter;
1968
1969 #[gpui::test]
1970 fn test_offset_for_row() {
1971 assert_eq!(offset_for_row("", 0), (0, 0));
1972 assert_eq!(offset_for_row("", 1), (0, 0));
1973 assert_eq!(offset_for_row("abcd", 0), (0, 0));
1974 assert_eq!(offset_for_row("abcd", 1), (0, 4));
1975 assert_eq!(offset_for_row("\n", 0), (0, 0));
1976 assert_eq!(offset_for_row("\n", 1), (1, 1));
1977 assert_eq!(offset_for_row("abc\ndef\nghi", 0), (0, 0));
1978 assert_eq!(offset_for_row("abc\ndef\nghi", 1), (1, 4));
1979 assert_eq!(offset_for_row("abc\ndef\nghi", 2), (2, 8));
1980 assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11));
1981 }
1982
1983 #[gpui::test]
1984 fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
1985 cx.update(init_test);
1986
1987 let text = "aaa\nbbb\nccc\nddd";
1988
1989 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
1990 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1991 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1992 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1993 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
1994 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
1995 let (wrap_map, wraps_snapshot) =
1996 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
1997 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
1998
1999 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2000 let block_ids = writer.insert(vec![
2001 BlockProperties {
2002 style: BlockStyle::Fixed,
2003 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2004 height: Some(1),
2005 render: Arc::new(|_| div().into_any()),
2006 priority: 0,
2007 },
2008 BlockProperties {
2009 style: BlockStyle::Fixed,
2010 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2011 height: Some(2),
2012 render: Arc::new(|_| div().into_any()),
2013 priority: 0,
2014 },
2015 BlockProperties {
2016 style: BlockStyle::Fixed,
2017 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2018 height: Some(3),
2019 render: Arc::new(|_| div().into_any()),
2020 priority: 0,
2021 },
2022 ]);
2023
2024 let snapshot = block_map.read(wraps_snapshot, Default::default());
2025 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2026
2027 let blocks = snapshot
2028 .blocks_in_range(0..8)
2029 .map(|(start_row, block)| {
2030 let block = block.as_custom().unwrap();
2031 (start_row..start_row + block.height.unwrap(), block.id)
2032 })
2033 .collect::<Vec<_>>();
2034
2035 // When multiple blocks are on the same line, the newer blocks appear first.
2036 assert_eq!(
2037 blocks,
2038 &[
2039 (1..2, block_ids[0]),
2040 (2..4, block_ids[1]),
2041 (7..10, block_ids[2]),
2042 ]
2043 );
2044
2045 assert_eq!(
2046 snapshot.to_block_point(WrapPoint::new(0, 3)),
2047 BlockPoint::new(0, 3)
2048 );
2049 assert_eq!(
2050 snapshot.to_block_point(WrapPoint::new(1, 0)),
2051 BlockPoint::new(4, 0)
2052 );
2053 assert_eq!(
2054 snapshot.to_block_point(WrapPoint::new(3, 3)),
2055 BlockPoint::new(6, 3)
2056 );
2057
2058 assert_eq!(
2059 snapshot.to_wrap_point(BlockPoint::new(0, 3), Bias::Left),
2060 WrapPoint::new(0, 3)
2061 );
2062 assert_eq!(
2063 snapshot.to_wrap_point(BlockPoint::new(1, 0), Bias::Left),
2064 WrapPoint::new(1, 0)
2065 );
2066 assert_eq!(
2067 snapshot.to_wrap_point(BlockPoint::new(3, 0), Bias::Left),
2068 WrapPoint::new(1, 0)
2069 );
2070 assert_eq!(
2071 snapshot.to_wrap_point(BlockPoint::new(7, 0), Bias::Left),
2072 WrapPoint::new(3, 3)
2073 );
2074
2075 assert_eq!(
2076 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left),
2077 BlockPoint::new(0, 3)
2078 );
2079 assert_eq!(
2080 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right),
2081 BlockPoint::new(4, 0)
2082 );
2083 assert_eq!(
2084 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left),
2085 BlockPoint::new(0, 3)
2086 );
2087 assert_eq!(
2088 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right),
2089 BlockPoint::new(4, 0)
2090 );
2091 assert_eq!(
2092 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Left),
2093 BlockPoint::new(4, 0)
2094 );
2095 assert_eq!(
2096 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Right),
2097 BlockPoint::new(4, 0)
2098 );
2099 assert_eq!(
2100 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Left),
2101 BlockPoint::new(6, 3)
2102 );
2103 assert_eq!(
2104 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Right),
2105 BlockPoint::new(6, 3)
2106 );
2107 assert_eq!(
2108 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Left),
2109 BlockPoint::new(6, 3)
2110 );
2111 assert_eq!(
2112 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Right),
2113 BlockPoint::new(6, 3)
2114 );
2115
2116 assert_eq!(
2117 snapshot
2118 .row_infos(BlockRow(0))
2119 .map(|row_info| row_info.buffer_row)
2120 .collect::<Vec<_>>(),
2121 &[
2122 Some(0),
2123 None,
2124 None,
2125 None,
2126 Some(1),
2127 Some(2),
2128 Some(3),
2129 None,
2130 None,
2131 None
2132 ]
2133 );
2134
2135 // Insert a line break, separating two block decorations into separate lines.
2136 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2137 buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
2138 buffer.snapshot(cx)
2139 });
2140
2141 let (inlay_snapshot, inlay_edits) =
2142 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
2143 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2144 let (tab_snapshot, tab_edits) =
2145 tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
2146 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2147 wrap_map.sync(tab_snapshot, tab_edits, cx)
2148 });
2149 let snapshot = block_map.read(wraps_snapshot, wrap_edits);
2150 assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
2151 }
2152
2153 #[gpui::test]
2154 fn test_multibuffer_headers_and_footers(cx: &mut App) {
2155 init_test(cx);
2156
2157 let buffer1 = cx.new(|cx| Buffer::local("Buffer 1", cx));
2158 let buffer2 = cx.new(|cx| Buffer::local("Buffer 2", cx));
2159 let buffer3 = cx.new(|cx| Buffer::local("Buffer 3", cx));
2160
2161 let mut excerpt_ids = Vec::new();
2162 let multi_buffer = cx.new(|cx| {
2163 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
2164 excerpt_ids.extend(multi_buffer.push_excerpts(
2165 buffer1.clone(),
2166 [ExcerptRange::new(0..buffer1.read(cx).len())],
2167 cx,
2168 ));
2169 excerpt_ids.extend(multi_buffer.push_excerpts(
2170 buffer2.clone(),
2171 [ExcerptRange::new(0..buffer2.read(cx).len())],
2172 cx,
2173 ));
2174 excerpt_ids.extend(multi_buffer.push_excerpts(
2175 buffer3.clone(),
2176 [ExcerptRange::new(0..buffer3.read(cx).len())],
2177 cx,
2178 ));
2179
2180 multi_buffer
2181 });
2182
2183 let font = test_font();
2184 let font_size = px(14.);
2185 let font_id = cx.text_system().resolve_font(&font);
2186 let mut wrap_width = px(0.);
2187 for c in "Buff".chars() {
2188 wrap_width += cx
2189 .text_system()
2190 .advance(font_id, font_size, c)
2191 .unwrap()
2192 .width;
2193 }
2194
2195 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2196 let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot);
2197 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2198 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2199 let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
2200
2201 let block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2202 let snapshot = block_map.read(wraps_snapshot, Default::default());
2203
2204 // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
2205 assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3");
2206
2207 let blocks: Vec<_> = snapshot
2208 .blocks_in_range(0..u32::MAX)
2209 .map(|(row, block)| (row..row + block.height(), block.id()))
2210 .collect();
2211 assert_eq!(
2212 blocks,
2213 vec![
2214 (0..1, BlockId::ExcerptBoundary(excerpt_ids[0])), // path, header
2215 (3..4, BlockId::ExcerptBoundary(excerpt_ids[1])), // path, header
2216 (6..7, BlockId::ExcerptBoundary(excerpt_ids[2])), // path, header
2217 ]
2218 );
2219 }
2220
2221 #[gpui::test]
2222 fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
2223 cx.update(init_test);
2224
2225 let text = "aaa\nbbb\nccc\nddd";
2226
2227 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2228 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2229 let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2230 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2231 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2232 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2233 let (_wrap_map, wraps_snapshot) =
2234 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2235 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2236
2237 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2238 let block_ids = writer.insert(vec![
2239 BlockProperties {
2240 style: BlockStyle::Fixed,
2241 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2242 height: Some(1),
2243 render: Arc::new(|_| div().into_any()),
2244 priority: 0,
2245 },
2246 BlockProperties {
2247 style: BlockStyle::Fixed,
2248 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2249 height: Some(2),
2250 render: Arc::new(|_| div().into_any()),
2251 priority: 0,
2252 },
2253 BlockProperties {
2254 style: BlockStyle::Fixed,
2255 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2256 height: Some(3),
2257 render: Arc::new(|_| div().into_any()),
2258 priority: 0,
2259 },
2260 ]);
2261
2262 {
2263 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2264 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2265
2266 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2267
2268 let mut new_heights = HashMap::default();
2269 new_heights.insert(block_ids[0], 2);
2270 block_map_writer.resize(new_heights);
2271 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2272 assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2273 }
2274
2275 {
2276 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2277
2278 let mut new_heights = HashMap::default();
2279 new_heights.insert(block_ids[0], 1);
2280 block_map_writer.resize(new_heights);
2281
2282 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2283 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2284 }
2285
2286 {
2287 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2288
2289 let mut new_heights = HashMap::default();
2290 new_heights.insert(block_ids[0], 0);
2291 block_map_writer.resize(new_heights);
2292
2293 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2294 assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
2295 }
2296
2297 {
2298 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2299
2300 let mut new_heights = HashMap::default();
2301 new_heights.insert(block_ids[0], 3);
2302 block_map_writer.resize(new_heights);
2303
2304 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2305 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2306 }
2307
2308 {
2309 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2310
2311 let mut new_heights = HashMap::default();
2312 new_heights.insert(block_ids[0], 3);
2313 block_map_writer.resize(new_heights);
2314
2315 let snapshot = block_map.read(wraps_snapshot, Default::default());
2316 // Same height as before, should remain the same
2317 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2318 }
2319 }
2320
2321 #[gpui::test]
2322 fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
2323 cx.update(init_test);
2324
2325 let text = "one two three\nfour five six\nseven eight";
2326
2327 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2328 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2329 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2330 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2331 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2332 let (_, wraps_snapshot) = cx.update(|cx| {
2333 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(90.)), cx)
2334 });
2335 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2336
2337 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2338 writer.insert(vec![
2339 BlockProperties {
2340 style: BlockStyle::Fixed,
2341 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
2342 render: Arc::new(|_| div().into_any()),
2343 height: Some(1),
2344 priority: 0,
2345 },
2346 BlockProperties {
2347 style: BlockStyle::Fixed,
2348 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
2349 render: Arc::new(|_| div().into_any()),
2350 height: Some(1),
2351 priority: 0,
2352 },
2353 ]);
2354
2355 // Blocks with an 'above' disposition go above their corresponding buffer line.
2356 // Blocks with a 'below' disposition go below their corresponding buffer line.
2357 let snapshot = block_map.read(wraps_snapshot, Default::default());
2358 assert_eq!(
2359 snapshot.text(),
2360 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
2361 );
2362 }
2363
2364 #[gpui::test]
2365 fn test_replace_lines(cx: &mut gpui::TestAppContext) {
2366 cx.update(init_test);
2367
2368 let text = "line1\nline2\nline3\nline4\nline5";
2369
2370 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2371 let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
2372 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2373 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2374 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2375 let tab_size = 1.try_into().unwrap();
2376 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
2377 let (wrap_map, wraps_snapshot) =
2378 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2379 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2380
2381 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2382 let replace_block_id = writer.insert(vec![BlockProperties {
2383 style: BlockStyle::Fixed,
2384 placement: BlockPlacement::Replace(
2385 buffer_snapshot.anchor_after(Point::new(1, 3))
2386 ..=buffer_snapshot.anchor_before(Point::new(3, 1)),
2387 ),
2388 height: Some(4),
2389 render: Arc::new(|_| div().into_any()),
2390 priority: 0,
2391 }])[0];
2392
2393 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
2394 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2395
2396 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2397 buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
2398 buffer.snapshot(cx)
2399 });
2400 let (inlay_snapshot, inlay_edits) =
2401 inlay_map.sync(buffer_snapshot, buffer_subscription.consume().into_inner());
2402 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2403 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2404 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2405 wrap_map.sync(tab_snapshot, tab_edits, cx)
2406 });
2407 let blocks_snapshot = block_map.read(wraps_snapshot, wrap_edits);
2408 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2409
2410 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2411 buffer.edit(
2412 [(
2413 Point::new(1, 5)..Point::new(1, 5),
2414 "\nline 2.1\nline2.2\nline 2.3\nline 2.4",
2415 )],
2416 None,
2417 cx,
2418 );
2419 buffer.snapshot(cx)
2420 });
2421 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
2422 buffer_snapshot.clone(),
2423 buffer_subscription.consume().into_inner(),
2424 );
2425 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2426 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2427 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2428 wrap_map.sync(tab_snapshot, tab_edits, cx)
2429 });
2430 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
2431 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2432
2433 // Blocks inserted right above the start or right below the end of the replaced region are hidden.
2434 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2435 writer.insert(vec![
2436 BlockProperties {
2437 style: BlockStyle::Fixed,
2438 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))),
2439 height: Some(1),
2440 render: Arc::new(|_| div().into_any()),
2441 priority: 0,
2442 },
2443 BlockProperties {
2444 style: BlockStyle::Fixed,
2445 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
2446 height: Some(1),
2447 render: Arc::new(|_| div().into_any()),
2448 priority: 0,
2449 },
2450 BlockProperties {
2451 style: BlockStyle::Fixed,
2452 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
2453 height: Some(1),
2454 render: Arc::new(|_| div().into_any()),
2455 priority: 0,
2456 },
2457 ]);
2458 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2459 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2460
2461 // Ensure blocks inserted *inside* replaced region are hidden.
2462 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2463 writer.insert(vec![
2464 BlockProperties {
2465 style: BlockStyle::Fixed,
2466 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
2467 height: Some(1),
2468 render: Arc::new(|_| div().into_any()),
2469 priority: 0,
2470 },
2471 BlockProperties {
2472 style: BlockStyle::Fixed,
2473 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
2474 height: Some(1),
2475 render: Arc::new(|_| div().into_any()),
2476 priority: 0,
2477 },
2478 BlockProperties {
2479 style: BlockStyle::Fixed,
2480 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
2481 height: Some(1),
2482 render: Arc::new(|_| div().into_any()),
2483 priority: 0,
2484 },
2485 ]);
2486 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2487 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2488
2489 // Removing the replace block shows all the hidden blocks again.
2490 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2491 writer.remove(HashSet::from_iter([replace_block_id]));
2492 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
2493 assert_eq!(
2494 blocks_snapshot.text(),
2495 "\nline1\n\nline2\n\n\nline 2.1\nline2.2\nline 2.3\nline 2.4\n\nline4\n\nline5"
2496 );
2497 }
2498
2499 #[gpui::test]
2500 fn test_custom_blocks_inside_buffer_folds(cx: &mut gpui::TestAppContext) {
2501 cx.update(init_test);
2502
2503 let text = "111\n222\n333\n444\n555\n666";
2504
2505 let buffer = cx.update(|cx| {
2506 MultiBuffer::build_multi(
2507 [
2508 (text, vec![Point::new(0, 0)..Point::new(0, 3)]),
2509 (
2510 text,
2511 vec![
2512 Point::new(1, 0)..Point::new(1, 3),
2513 Point::new(2, 0)..Point::new(2, 3),
2514 Point::new(3, 0)..Point::new(3, 3),
2515 ],
2516 ),
2517 (
2518 text,
2519 vec![
2520 Point::new(4, 0)..Point::new(4, 3),
2521 Point::new(5, 0)..Point::new(5, 3),
2522 ],
2523 ),
2524 ],
2525 cx,
2526 )
2527 });
2528 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2529 let buffer_ids = buffer_snapshot
2530 .excerpts()
2531 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2532 .dedup()
2533 .collect::<Vec<_>>();
2534 assert_eq!(buffer_ids.len(), 3);
2535 let buffer_id_1 = buffer_ids[0];
2536 let buffer_id_2 = buffer_ids[1];
2537 let buffer_id_3 = buffer_ids[2];
2538
2539 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2540 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2541 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2542 let (_, wrap_snapshot) =
2543 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2544 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
2545 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2546
2547 assert_eq!(
2548 blocks_snapshot.text(),
2549 "\n\n111\n\n\n222\n\n333\n\n444\n\n\n555\n\n666"
2550 );
2551 assert_eq!(
2552 blocks_snapshot
2553 .row_infos(BlockRow(0))
2554 .map(|i| i.buffer_row)
2555 .collect::<Vec<_>>(),
2556 vec![
2557 None,
2558 None,
2559 Some(0),
2560 None,
2561 None,
2562 Some(1),
2563 None,
2564 Some(2),
2565 None,
2566 Some(3),
2567 None,
2568 None,
2569 Some(4),
2570 None,
2571 Some(5),
2572 ]
2573 );
2574
2575 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2576 let excerpt_blocks_2 = writer.insert(vec![
2577 BlockProperties {
2578 style: BlockStyle::Fixed,
2579 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2580 height: Some(1),
2581 render: Arc::new(|_| div().into_any()),
2582 priority: 0,
2583 },
2584 BlockProperties {
2585 style: BlockStyle::Fixed,
2586 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 0))),
2587 height: Some(1),
2588 render: Arc::new(|_| div().into_any()),
2589 priority: 0,
2590 },
2591 BlockProperties {
2592 style: BlockStyle::Fixed,
2593 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 0))),
2594 height: Some(1),
2595 render: Arc::new(|_| div().into_any()),
2596 priority: 0,
2597 },
2598 ]);
2599 let excerpt_blocks_3 = writer.insert(vec![
2600 BlockProperties {
2601 style: BlockStyle::Fixed,
2602 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))),
2603 height: Some(1),
2604 render: Arc::new(|_| div().into_any()),
2605 priority: 0,
2606 },
2607 BlockProperties {
2608 style: BlockStyle::Fixed,
2609 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))),
2610 height: Some(1),
2611 render: Arc::new(|_| div().into_any()),
2612 priority: 0,
2613 },
2614 ]);
2615
2616 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2617 assert_eq!(
2618 blocks_snapshot.text(),
2619 "\n\n111\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
2620 );
2621 assert_eq!(
2622 blocks_snapshot
2623 .row_infos(BlockRow(0))
2624 .map(|i| i.buffer_row)
2625 .collect::<Vec<_>>(),
2626 vec![
2627 None,
2628 None,
2629 Some(0),
2630 None,
2631 None,
2632 None,
2633 Some(1),
2634 None,
2635 None,
2636 Some(2),
2637 None,
2638 Some(3),
2639 None,
2640 None,
2641 None,
2642 None,
2643 Some(4),
2644 None,
2645 Some(5),
2646 None,
2647 ]
2648 );
2649
2650 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2651 buffer.read_with(cx, |buffer, cx| {
2652 writer.fold_buffers([buffer_id_1], buffer, cx);
2653 });
2654 let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
2655 style: BlockStyle::Fixed,
2656 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))),
2657 height: Some(1),
2658 render: Arc::new(|_| div().into_any()),
2659 priority: 0,
2660 }]);
2661 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2662 let blocks = blocks_snapshot
2663 .blocks_in_range(0..u32::MAX)
2664 .collect::<Vec<_>>();
2665 for (_, block) in &blocks {
2666 if let BlockId::Custom(custom_block_id) = block.id() {
2667 assert!(
2668 !excerpt_blocks_1.contains(&custom_block_id),
2669 "Should have no blocks from the folded buffer"
2670 );
2671 assert!(
2672 excerpt_blocks_2.contains(&custom_block_id)
2673 || excerpt_blocks_3.contains(&custom_block_id),
2674 "Should have only blocks from unfolded buffers"
2675 );
2676 }
2677 }
2678 assert_eq!(
2679 1,
2680 blocks
2681 .iter()
2682 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2683 .count(),
2684 "Should have one folded block, producing a header of the second buffer"
2685 );
2686 assert_eq!(
2687 blocks_snapshot.text(),
2688 "\n\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
2689 );
2690 assert_eq!(
2691 blocks_snapshot
2692 .row_infos(BlockRow(0))
2693 .map(|i| i.buffer_row)
2694 .collect::<Vec<_>>(),
2695 vec![
2696 None,
2697 None,
2698 None,
2699 None,
2700 None,
2701 Some(1),
2702 None,
2703 None,
2704 Some(2),
2705 None,
2706 Some(3),
2707 None,
2708 None,
2709 None,
2710 None,
2711 Some(4),
2712 None,
2713 Some(5),
2714 None,
2715 ]
2716 );
2717
2718 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2719 buffer.read_with(cx, |buffer, cx| {
2720 writer.fold_buffers([buffer_id_2], buffer, cx);
2721 });
2722 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2723 let blocks = blocks_snapshot
2724 .blocks_in_range(0..u32::MAX)
2725 .collect::<Vec<_>>();
2726 for (_, block) in &blocks {
2727 if let BlockId::Custom(custom_block_id) = block.id() {
2728 assert!(
2729 !excerpt_blocks_1.contains(&custom_block_id),
2730 "Should have no blocks from the folded buffer_1"
2731 );
2732 assert!(
2733 !excerpt_blocks_2.contains(&custom_block_id),
2734 "Should have no blocks from the folded buffer_2"
2735 );
2736 assert!(
2737 excerpt_blocks_3.contains(&custom_block_id),
2738 "Should have only blocks from unfolded buffers"
2739 );
2740 }
2741 }
2742 assert_eq!(
2743 2,
2744 blocks
2745 .iter()
2746 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2747 .count(),
2748 "Should have two folded blocks, producing headers"
2749 );
2750 assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n555\n\n666\n");
2751 assert_eq!(
2752 blocks_snapshot
2753 .row_infos(BlockRow(0))
2754 .map(|i| i.buffer_row)
2755 .collect::<Vec<_>>(),
2756 vec![
2757 None,
2758 None,
2759 None,
2760 None,
2761 None,
2762 None,
2763 None,
2764 Some(4),
2765 None,
2766 Some(5),
2767 None,
2768 ]
2769 );
2770
2771 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2772 buffer.read_with(cx, |buffer, cx| {
2773 writer.unfold_buffers([buffer_id_1], buffer, cx);
2774 });
2775 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2776 let blocks = blocks_snapshot
2777 .blocks_in_range(0..u32::MAX)
2778 .collect::<Vec<_>>();
2779 for (_, block) in &blocks {
2780 if let BlockId::Custom(custom_block_id) = block.id() {
2781 assert!(
2782 !excerpt_blocks_2.contains(&custom_block_id),
2783 "Should have no blocks from the folded buffer_2"
2784 );
2785 assert!(
2786 excerpt_blocks_1.contains(&custom_block_id)
2787 || excerpt_blocks_3.contains(&custom_block_id),
2788 "Should have only blocks from unfolded buffers"
2789 );
2790 }
2791 }
2792 assert_eq!(
2793 1,
2794 blocks
2795 .iter()
2796 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2797 .count(),
2798 "Should be back to a single folded buffer, producing a header for buffer_2"
2799 );
2800 assert_eq!(
2801 blocks_snapshot.text(),
2802 "\n\n\n111\n\n\n\n\n\n555\n\n666\n",
2803 "Should have extra newline for 111 buffer, due to a new block added when it was folded"
2804 );
2805 assert_eq!(
2806 blocks_snapshot
2807 .row_infos(BlockRow(0))
2808 .map(|i| i.buffer_row)
2809 .collect::<Vec<_>>(),
2810 vec![
2811 None,
2812 None,
2813 None,
2814 Some(0),
2815 None,
2816 None,
2817 None,
2818 None,
2819 None,
2820 Some(4),
2821 None,
2822 Some(5),
2823 None,
2824 ]
2825 );
2826
2827 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2828 buffer.read_with(cx, |buffer, cx| {
2829 writer.fold_buffers([buffer_id_3], buffer, cx);
2830 });
2831 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
2832 let blocks = blocks_snapshot
2833 .blocks_in_range(0..u32::MAX)
2834 .collect::<Vec<_>>();
2835 for (_, block) in &blocks {
2836 if let BlockId::Custom(custom_block_id) = block.id() {
2837 assert!(
2838 excerpt_blocks_1.contains(&custom_block_id),
2839 "Should have no blocks from the folded buffer_1"
2840 );
2841 assert!(
2842 !excerpt_blocks_2.contains(&custom_block_id),
2843 "Should have only blocks from unfolded buffers"
2844 );
2845 assert!(
2846 !excerpt_blocks_3.contains(&custom_block_id),
2847 "Should have only blocks from unfolded buffers"
2848 );
2849 }
2850 }
2851
2852 assert_eq!(
2853 blocks_snapshot.text(),
2854 "\n\n\n111\n\n\n\n",
2855 "Should have a single, first buffer left after folding"
2856 );
2857 assert_eq!(
2858 blocks_snapshot
2859 .row_infos(BlockRow(0))
2860 .map(|i| i.buffer_row)
2861 .collect::<Vec<_>>(),
2862 vec![None, None, None, Some(0), None, None, None, None,]
2863 );
2864 }
2865
2866 #[gpui::test]
2867 fn test_basic_buffer_fold(cx: &mut gpui::TestAppContext) {
2868 cx.update(init_test);
2869
2870 let text = "111";
2871
2872 let buffer = cx.update(|cx| {
2873 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(0, 3)])], cx)
2874 });
2875 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2876 let buffer_ids = buffer_snapshot
2877 .excerpts()
2878 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2879 .dedup()
2880 .collect::<Vec<_>>();
2881 assert_eq!(buffer_ids.len(), 1);
2882 let buffer_id = buffer_ids[0];
2883
2884 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot);
2885 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2886 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2887 let (_, wrap_snapshot) =
2888 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2889 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
2890 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2891
2892 assert_eq!(blocks_snapshot.text(), "\n\n111");
2893
2894 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2895 buffer.read_with(cx, |buffer, cx| {
2896 writer.fold_buffers([buffer_id], buffer, cx);
2897 });
2898 let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
2899 let blocks = blocks_snapshot
2900 .blocks_in_range(0..u32::MAX)
2901 .collect::<Vec<_>>();
2902 assert_eq!(
2903 1,
2904 blocks
2905 .iter()
2906 .filter(|(_, block)| { matches!(block, Block::FoldedBuffer { .. }) })
2907 .count(),
2908 "Should have one folded block, producing a header of the second buffer"
2909 );
2910 assert_eq!(blocks_snapshot.text(), "\n");
2911 assert_eq!(
2912 blocks_snapshot
2913 .row_infos(BlockRow(0))
2914 .map(|i| i.buffer_row)
2915 .collect::<Vec<_>>(),
2916 vec![None, None],
2917 "When fully folded, should be no buffer rows"
2918 );
2919 }
2920
2921 #[gpui::test(iterations = 100)]
2922 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
2923 cx.update(init_test);
2924
2925 let operations = env::var("OPERATIONS")
2926 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2927 .unwrap_or(10);
2928
2929 let wrap_width = if rng.random_bool(0.2) {
2930 None
2931 } else {
2932 Some(px(rng.random_range(0.0..=100.0)))
2933 };
2934 let tab_size = 1.try_into().unwrap();
2935 let font_size = px(14.0);
2936 let buffer_start_header_height = rng.random_range(1..=5);
2937 let excerpt_header_height = rng.random_range(1..=5);
2938
2939 log::info!("Wrap width: {:?}", wrap_width);
2940 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
2941 let is_singleton = rng.random();
2942 let buffer = if is_singleton {
2943 let len = rng.random_range(0..10);
2944 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
2945 log::info!("initial singleton buffer text: {:?}", text);
2946 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
2947 } else {
2948 cx.update(|cx| {
2949 let multibuffer = MultiBuffer::build_random(&mut rng, cx);
2950 log::info!(
2951 "initial multi-buffer text: {:?}",
2952 multibuffer.read(cx).read(cx).text()
2953 );
2954 multibuffer
2955 })
2956 };
2957
2958 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2959 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2960 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2961 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2962 let font = test_font();
2963 let (wrap_map, wraps_snapshot) =
2964 cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
2965 let mut block_map = BlockMap::new(
2966 wraps_snapshot,
2967 buffer_start_header_height,
2968 excerpt_header_height,
2969 );
2970
2971 for _ in 0..operations {
2972 let mut buffer_edits = Vec::new();
2973 match rng.random_range(0..=100) {
2974 0..=19 => {
2975 let wrap_width = if rng.random_bool(0.2) {
2976 None
2977 } else {
2978 Some(px(rng.random_range(0.0..=100.0)))
2979 };
2980 log::info!("Setting wrap width to {:?}", wrap_width);
2981 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
2982 }
2983 20..=39 => {
2984 let block_count = rng.random_range(1..=5);
2985 let block_properties = (0..block_count)
2986 .map(|_| {
2987 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
2988 let offset =
2989 buffer.clip_offset(rng.random_range(0..=buffer.len()), Bias::Left);
2990 let mut min_height = 0;
2991 let placement = match rng.random_range(0..3) {
2992 0 => {
2993 min_height = 1;
2994 let start = buffer.anchor_after(offset);
2995 let end = buffer.anchor_after(buffer.clip_offset(
2996 rng.random_range(offset..=buffer.len()),
2997 Bias::Left,
2998 ));
2999 BlockPlacement::Replace(start..=end)
3000 }
3001 1 => BlockPlacement::Above(buffer.anchor_after(offset)),
3002 _ => BlockPlacement::Below(buffer.anchor_after(offset)),
3003 };
3004
3005 let height = rng.random_range(min_height..5);
3006 BlockProperties {
3007 style: BlockStyle::Fixed,
3008 placement,
3009 height: Some(height),
3010 render: Arc::new(|_| div().into_any()),
3011 priority: 0,
3012 }
3013 })
3014 .collect::<Vec<_>>();
3015
3016 let (inlay_snapshot, inlay_edits) =
3017 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3018 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3019 let (tab_snapshot, tab_edits) =
3020 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3021 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3022 wrap_map.sync(tab_snapshot, tab_edits, cx)
3023 });
3024 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3025 let block_ids =
3026 block_map.insert(block_properties.iter().map(|props| BlockProperties {
3027 placement: props.placement.clone(),
3028 height: props.height,
3029 style: props.style,
3030 render: Arc::new(|_| div().into_any()),
3031 priority: 0,
3032 }));
3033
3034 for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3035 log::info!(
3036 "inserted block {:?} with height {:?} and id {:?}",
3037 block_properties
3038 .placement
3039 .as_ref()
3040 .map(|p| p.to_point(&buffer_snapshot)),
3041 block_properties.height,
3042 block_id
3043 );
3044 }
3045 }
3046 40..=59 if !block_map.custom_blocks.is_empty() => {
3047 let block_count = rng.random_range(1..=4.min(block_map.custom_blocks.len()));
3048 let block_ids_to_remove = block_map
3049 .custom_blocks
3050 .choose_multiple(&mut rng, block_count)
3051 .map(|block| block.id)
3052 .collect::<HashSet<_>>();
3053
3054 let (inlay_snapshot, inlay_edits) =
3055 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3056 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3057 let (tab_snapshot, tab_edits) =
3058 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3059 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3060 wrap_map.sync(tab_snapshot, tab_edits, cx)
3061 });
3062 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3063 log::info!(
3064 "removing {} blocks: {:?}",
3065 block_ids_to_remove.len(),
3066 block_ids_to_remove
3067 );
3068 block_map.remove(block_ids_to_remove);
3069 }
3070 60..=79 => {
3071 if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
3072 log::info!("Noop fold/unfold operation on a singleton buffer");
3073 continue;
3074 }
3075 let (inlay_snapshot, inlay_edits) =
3076 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3077 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3078 let (tab_snapshot, tab_edits) =
3079 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3080 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3081 wrap_map.sync(tab_snapshot, tab_edits, cx)
3082 });
3083 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3084 let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
3085 let folded_buffers = block_map
3086 .0
3087 .folded_buffers
3088 .iter()
3089 .cloned()
3090 .collect::<Vec<_>>();
3091 let mut unfolded_buffers = buffer.excerpt_buffer_ids();
3092 unfolded_buffers.dedup();
3093 log::debug!("All buffers {unfolded_buffers:?}");
3094 log::debug!("Folded buffers {folded_buffers:?}");
3095 unfolded_buffers
3096 .retain(|buffer_id| !block_map.0.folded_buffers.contains(buffer_id));
3097 (unfolded_buffers, folded_buffers)
3098 });
3099 let mut folded_count = folded_buffers.len();
3100 let mut unfolded_count = unfolded_buffers.len();
3101
3102 let fold = !unfolded_buffers.is_empty() && rng.random_bool(0.5);
3103 let unfold = !folded_buffers.is_empty() && rng.random_bool(0.5);
3104 if !fold && !unfold {
3105 log::info!(
3106 "Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3107 );
3108 continue;
3109 }
3110
3111 buffer.update(cx, |buffer, cx| {
3112 if fold {
3113 let buffer_to_fold =
3114 unfolded_buffers[rng.random_range(0..unfolded_buffers.len())];
3115 log::info!("Folding {buffer_to_fold:?}");
3116 let related_excerpts = buffer_snapshot
3117 .excerpts()
3118 .filter_map(|(excerpt_id, buffer, range)| {
3119 if buffer.remote_id() == buffer_to_fold {
3120 Some((
3121 excerpt_id,
3122 buffer
3123 .text_for_range(range.context)
3124 .collect::<String>(),
3125 ))
3126 } else {
3127 None
3128 }
3129 })
3130 .collect::<Vec<_>>();
3131 log::info!(
3132 "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
3133 );
3134 folded_count += 1;
3135 unfolded_count -= 1;
3136 block_map.fold_buffers([buffer_to_fold], buffer, cx);
3137 }
3138 if unfold {
3139 let buffer_to_unfold =
3140 folded_buffers[rng.random_range(0..folded_buffers.len())];
3141 log::info!("Unfolding {buffer_to_unfold:?}");
3142 unfolded_count += 1;
3143 folded_count -= 1;
3144 block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
3145 }
3146 log::info!(
3147 "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3148 );
3149 });
3150 }
3151 _ => {
3152 buffer.update(cx, |buffer, cx| {
3153 let mutation_count = rng.random_range(1..=5);
3154 let subscription = buffer.subscribe();
3155 buffer.randomly_mutate(&mut rng, mutation_count, cx);
3156 buffer_snapshot = buffer.snapshot(cx);
3157 buffer_edits.extend(subscription.consume());
3158 log::info!("buffer text: {:?}", buffer_snapshot.text());
3159 });
3160 }
3161 }
3162
3163 let (inlay_snapshot, inlay_edits) =
3164 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
3165 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3166 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3167 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3168 wrap_map.sync(tab_snapshot, tab_edits, cx)
3169 });
3170 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
3171 assert_eq!(
3172 blocks_snapshot.transforms.summary().input_rows,
3173 wraps_snapshot.max_point().row() + 1
3174 );
3175 log::info!("wrapped text: {:?}", wraps_snapshot.text());
3176 log::info!("blocks text: {:?}", blocks_snapshot.text());
3177
3178 let mut expected_blocks = Vec::new();
3179 expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
3180 Some((
3181 block.placement.to_wrap_row(&wraps_snapshot)?,
3182 Block::Custom(block.clone()),
3183 ))
3184 }));
3185
3186 // Note that this needs to be synced with the related section in BlockMap::sync
3187 expected_blocks.extend(block_map.header_and_footer_blocks(
3188 &buffer_snapshot,
3189 0..,
3190 &wraps_snapshot,
3191 ));
3192
3193 BlockMap::sort_blocks(&mut expected_blocks);
3194
3195 for (placement, block) in &expected_blocks {
3196 log::info!(
3197 "Block {:?} placement: {:?} Height: {:?}",
3198 block.id(),
3199 placement,
3200 block.height()
3201 );
3202 }
3203
3204 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
3205
3206 let input_buffer_rows = buffer_snapshot
3207 .row_infos(MultiBufferRow(0))
3208 .map(|row| row.buffer_row)
3209 .collect::<Vec<_>>();
3210 let mut expected_buffer_rows = Vec::new();
3211 let mut expected_text = String::new();
3212 let mut expected_block_positions = Vec::new();
3213 let mut expected_replaced_buffer_rows = HashSet::default();
3214 let input_text = wraps_snapshot.text();
3215
3216 // Loop over the input lines, creating (N - 1) empty lines for
3217 // blocks of height N.
3218 //
3219 // It's important to note that output *starts* as one empty line,
3220 // so we special case row 0 to assume a leading '\n'.
3221 //
3222 // Linehood is the birthright of strings.
3223 let input_text_lines = input_text.split('\n').enumerate().peekable();
3224 let mut block_row = 0;
3225 for (wrap_row, input_line) in input_text_lines {
3226 let wrap_row = wrap_row as u32;
3227 let multibuffer_row = wraps_snapshot
3228 .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
3229 .row;
3230
3231 // Create empty lines for the above block
3232 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3233 if placement.start().0 == wrap_row && block.place_above() {
3234 let (_, block) = sorted_blocks_iter.next().unwrap();
3235 expected_block_positions.push((block_row, block.id()));
3236 if block.height() > 0 {
3237 let text = "\n".repeat((block.height() - 1) as usize);
3238 if block_row > 0 {
3239 expected_text.push('\n')
3240 }
3241 expected_text.push_str(&text);
3242 for _ in 0..block.height() {
3243 expected_buffer_rows.push(None);
3244 }
3245 block_row += block.height();
3246 }
3247 } else {
3248 break;
3249 }
3250 }
3251
3252 // Skip lines within replace blocks, then create empty lines for the replace block's height
3253 let mut is_in_replace_block = false;
3254 if let Some((BlockPlacement::Replace(replace_range), block)) =
3255 sorted_blocks_iter.peek()
3256 && wrap_row >= replace_range.start().0
3257 {
3258 is_in_replace_block = true;
3259
3260 if wrap_row == replace_range.start().0 {
3261 if matches!(block, Block::FoldedBuffer { .. }) {
3262 expected_buffer_rows.push(None);
3263 } else {
3264 expected_buffer_rows.push(input_buffer_rows[multibuffer_row as usize]);
3265 }
3266 }
3267
3268 if wrap_row == replace_range.end().0 {
3269 expected_block_positions.push((block_row, block.id()));
3270 let text = "\n".repeat((block.height() - 1) as usize);
3271 if block_row > 0 {
3272 expected_text.push('\n');
3273 }
3274 expected_text.push_str(&text);
3275
3276 for _ in 1..block.height() {
3277 expected_buffer_rows.push(None);
3278 }
3279 block_row += block.height();
3280
3281 sorted_blocks_iter.next();
3282 }
3283 }
3284
3285 if is_in_replace_block {
3286 expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
3287 } else {
3288 let buffer_row = input_buffer_rows[multibuffer_row as usize];
3289 let soft_wrapped = wraps_snapshot
3290 .to_tab_point(WrapPoint::new(wrap_row, 0))
3291 .column()
3292 > 0;
3293 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
3294 if block_row > 0 {
3295 expected_text.push('\n');
3296 }
3297 expected_text.push_str(input_line);
3298 block_row += 1;
3299 }
3300
3301 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3302 if placement.end().0 == wrap_row && block.place_below() {
3303 let (_, block) = sorted_blocks_iter.next().unwrap();
3304 expected_block_positions.push((block_row, block.id()));
3305 if block.height() > 0 {
3306 let text = "\n".repeat((block.height() - 1) as usize);
3307 if block_row > 0 {
3308 expected_text.push('\n')
3309 }
3310 expected_text.push_str(&text);
3311 for _ in 0..block.height() {
3312 expected_buffer_rows.push(None);
3313 }
3314 block_row += block.height();
3315 }
3316 } else {
3317 break;
3318 }
3319 }
3320 }
3321
3322 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
3323 let expected_row_count = expected_lines.len();
3324 log::info!("expected text: {expected_text:?}");
3325
3326 assert_eq!(
3327 blocks_snapshot.max_point().row + 1,
3328 expected_row_count as u32,
3329 "actual row count != expected row count",
3330 );
3331 assert_eq!(
3332 blocks_snapshot.text(),
3333 expected_text,
3334 "actual text != expected text",
3335 );
3336
3337 for start_row in 0..expected_row_count {
3338 let end_row = rng.random_range(start_row + 1..=expected_row_count);
3339 let mut expected_text = expected_lines[start_row..end_row].join("\n");
3340 if end_row < expected_row_count {
3341 expected_text.push('\n');
3342 }
3343
3344 let actual_text = blocks_snapshot
3345 .chunks(
3346 start_row as u32..end_row as u32,
3347 false,
3348 false,
3349 Highlights::default(),
3350 )
3351 .map(|chunk| chunk.text)
3352 .collect::<String>();
3353 assert_eq!(
3354 actual_text,
3355 expected_text,
3356 "incorrect text starting row row range {:?}",
3357 start_row..end_row
3358 );
3359 assert_eq!(
3360 blocks_snapshot
3361 .row_infos(BlockRow(start_row as u32))
3362 .map(|row_info| row_info.buffer_row)
3363 .collect::<Vec<_>>(),
3364 &expected_buffer_rows[start_row..],
3365 "incorrect buffer_rows starting at row {:?}",
3366 start_row
3367 );
3368 }
3369
3370 assert_eq!(
3371 blocks_snapshot
3372 .blocks_in_range(0..(expected_row_count as u32))
3373 .map(|(row, block)| (row, block.id()))
3374 .collect::<Vec<_>>(),
3375 expected_block_positions,
3376 "invalid blocks_in_range({:?})",
3377 0..expected_row_count
3378 );
3379
3380 for (_, expected_block) in
3381 blocks_snapshot.blocks_in_range(0..(expected_row_count as u32))
3382 {
3383 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
3384 assert_eq!(
3385 actual_block.map(|block| block.id()),
3386 Some(expected_block.id())
3387 );
3388 }
3389
3390 for (block_row, block_id) in expected_block_positions {
3391 if let BlockId::Custom(block_id) = block_id {
3392 assert_eq!(
3393 blocks_snapshot.row_for_block(block_id),
3394 Some(BlockRow(block_row))
3395 );
3396 }
3397 }
3398
3399 let mut expected_longest_rows = Vec::new();
3400 let mut longest_line_len = -1_isize;
3401 for (row, line) in expected_lines.iter().enumerate() {
3402 let row = row as u32;
3403
3404 assert_eq!(
3405 blocks_snapshot.line_len(BlockRow(row)),
3406 line.len() as u32,
3407 "invalid line len for row {}",
3408 row
3409 );
3410
3411 let line_char_count = line.chars().count() as isize;
3412 match line_char_count.cmp(&longest_line_len) {
3413 Ordering::Less => {}
3414 Ordering::Equal => expected_longest_rows.push(row),
3415 Ordering::Greater => {
3416 longest_line_len = line_char_count;
3417 expected_longest_rows.clear();
3418 expected_longest_rows.push(row);
3419 }
3420 }
3421 }
3422
3423 let longest_row = blocks_snapshot.longest_row();
3424 assert!(
3425 expected_longest_rows.contains(&longest_row),
3426 "incorrect longest row {}. expected {:?} with length {}",
3427 longest_row,
3428 expected_longest_rows,
3429 longest_line_len,
3430 );
3431
3432 for _ in 0..10 {
3433 let end_row = rng.random_range(1..=expected_lines.len());
3434 let start_row = rng.random_range(0..end_row);
3435
3436 let mut expected_longest_rows_in_range = vec![];
3437 let mut longest_line_len_in_range = 0;
3438
3439 let mut row = start_row as u32;
3440 for line in &expected_lines[start_row..end_row] {
3441 let line_char_count = line.chars().count() as isize;
3442 match line_char_count.cmp(&longest_line_len_in_range) {
3443 Ordering::Less => {}
3444 Ordering::Equal => expected_longest_rows_in_range.push(row),
3445 Ordering::Greater => {
3446 longest_line_len_in_range = line_char_count;
3447 expected_longest_rows_in_range.clear();
3448 expected_longest_rows_in_range.push(row);
3449 }
3450 }
3451 row += 1;
3452 }
3453
3454 let longest_row_in_range = blocks_snapshot
3455 .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
3456 assert!(
3457 expected_longest_rows_in_range.contains(&longest_row_in_range.0),
3458 "incorrect longest row {} in range {:?}. expected {:?} with length {}",
3459 longest_row,
3460 start_row..end_row,
3461 expected_longest_rows_in_range,
3462 longest_line_len_in_range,
3463 );
3464 }
3465
3466 // Ensure that conversion between block points and wrap points is stable.
3467 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
3468 let wrap_point = WrapPoint::new(row, 0);
3469 let block_point = blocks_snapshot.to_block_point(wrap_point);
3470 let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
3471 let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
3472 assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
3473 assert_eq!(
3474 blocks_snapshot.to_block_point(right_wrap_point),
3475 block_point
3476 );
3477 }
3478
3479 let mut block_point = BlockPoint::new(0, 0);
3480 for c in expected_text.chars() {
3481 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
3482 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
3483 assert_eq!(
3484 blocks_snapshot
3485 .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
3486 left_point,
3487 "block point: {:?}, wrap point: {:?}",
3488 block_point,
3489 blocks_snapshot.to_wrap_point(left_point, Bias::Left)
3490 );
3491 assert_eq!(
3492 left_buffer_point,
3493 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
3494 "{:?} is not valid in buffer coordinates",
3495 left_point
3496 );
3497
3498 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
3499 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
3500 assert_eq!(
3501 blocks_snapshot
3502 .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
3503 right_point,
3504 "block point: {:?}, wrap point: {:?}",
3505 block_point,
3506 blocks_snapshot.to_wrap_point(right_point, Bias::Right)
3507 );
3508 assert_eq!(
3509 right_buffer_point,
3510 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
3511 "{:?} is not valid in buffer coordinates",
3512 right_point
3513 );
3514
3515 if c == '\n' {
3516 block_point.0 += Point::new(1, 0);
3517 } else {
3518 block_point.column += c.len_utf8() as u32;
3519 }
3520 }
3521
3522 for buffer_row in 0..=buffer_snapshot.max_point().row {
3523 let buffer_row = MultiBufferRow(buffer_row);
3524 assert_eq!(
3525 blocks_snapshot.is_line_replaced(buffer_row),
3526 expected_replaced_buffer_rows.contains(&buffer_row),
3527 "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
3528 );
3529 }
3530 }
3531 }
3532
3533 #[gpui::test]
3534 fn test_remove_intersecting_replace_blocks_edge_case(cx: &mut gpui::TestAppContext) {
3535 cx.update(init_test);
3536
3537 let text = "abc\ndef\nghi\njkl\nmno";
3538 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3539 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3540 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3541 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3542 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3543 let (_wrap_map, wraps_snapshot) =
3544 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3545 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3546
3547 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
3548 let _block_id = writer.insert(vec![BlockProperties {
3549 style: BlockStyle::Fixed,
3550 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3551 height: Some(1),
3552 render: Arc::new(|_| div().into_any()),
3553 priority: 0,
3554 }])[0];
3555
3556 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
3557 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
3558
3559 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
3560 writer.remove_intersecting_replace_blocks(
3561 [buffer_snapshot.anchor_after(Point::new(1, 0))
3562 ..buffer_snapshot.anchor_after(Point::new(1, 0))],
3563 false,
3564 );
3565 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
3566 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
3567 }
3568
3569 fn init_test(cx: &mut gpui::App) {
3570 let settings = SettingsStore::test(cx);
3571 cx.set_global(settings);
3572 theme::init(theme::LoadThemes::JustBase, cx);
3573 assets::Assets.load_test_fonts(cx);
3574 }
3575
3576 impl Block {
3577 fn as_custom(&self) -> Option<&CustomBlock> {
3578 match self {
3579 Block::Custom(block) => Some(block),
3580 _ => None,
3581 }
3582 }
3583 }
3584
3585 impl BlockSnapshot {
3586 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
3587 self.wrap_snapshot
3588 .to_point(self.to_wrap_point(point, bias), bias)
3589 }
3590 }
3591}