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