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