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, 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(gpui::AppContext, gpui::VisualContext)]
227pub struct BlockContext<'a, 'b> {
228 #[window]
229 pub window: &'a mut Window,
230 #[app]
231 pub app: &'b mut App,
232 pub anchor_x: Pixels,
233 pub max_width: Pixels,
234 pub gutter_dimensions: &'b GutterDimensions,
235 pub em_width: Pixels,
236 pub line_height: Pixels,
237 pub block_id: BlockId,
238 pub selected: bool,
239 pub editor_style: &'b EditorStyle,
240}
241
242#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
243pub enum BlockId {
244 ExcerptBoundary(ExcerptId),
245 FoldedBuffer(ExcerptId),
246 Custom(CustomBlockId),
247}
248
249impl From<BlockId> for ElementId {
250 fn from(value: BlockId) -> Self {
251 match value {
252 BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(),
253 BlockId::ExcerptBoundary(excerpt_id) => {
254 ("ExcerptBoundary", EntityId::from(excerpt_id)).into()
255 }
256 BlockId::FoldedBuffer(id) => ("FoldedBuffer", EntityId::from(id)).into(),
257 }
258 }
259}
260
261impl std::fmt::Display for BlockId {
262 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
263 match self {
264 Self::Custom(id) => write!(f, "Block({id:?})"),
265 Self::ExcerptBoundary(id) => write!(f, "ExcerptHeader({id:?})"),
266 Self::FoldedBuffer(id) => write!(f, "FoldedBuffer({id:?})"),
267 }
268 }
269}
270
271#[derive(Clone, Debug)]
272struct Transform {
273 summary: TransformSummary,
274 block: Option<Block>,
275}
276
277#[allow(clippy::large_enum_variant)]
278#[derive(Clone)]
279pub enum Block {
280 Custom(Arc<CustomBlock>),
281 FoldedBuffer {
282 first_excerpt: ExcerptInfo,
283 height: u32,
284 },
285 ExcerptBoundary {
286 excerpt: ExcerptInfo,
287 height: u32,
288 starts_new_buffer: bool,
289 },
290}
291
292impl Block {
293 pub fn id(&self) -> BlockId {
294 match self {
295 Block::Custom(block) => BlockId::Custom(block.id),
296 Block::ExcerptBoundary {
297 excerpt: next_excerpt,
298 ..
299 } => BlockId::ExcerptBoundary(next_excerpt.id),
300 Block::FoldedBuffer { first_excerpt, .. } => BlockId::FoldedBuffer(first_excerpt.id),
301 }
302 }
303
304 pub fn has_height(&self) -> bool {
305 match self {
306 Block::Custom(block) => block.height.is_some(),
307 Block::ExcerptBoundary { .. } | Block::FoldedBuffer { .. } => true,
308 }
309 }
310
311 pub fn height(&self) -> u32 {
312 match self {
313 Block::Custom(block) => block.height.unwrap_or(0),
314 Block::ExcerptBoundary { height, .. } | Block::FoldedBuffer { height, .. } => *height,
315 }
316 }
317
318 pub fn style(&self) -> BlockStyle {
319 match self {
320 Block::Custom(block) => block.style,
321 Block::ExcerptBoundary { .. } | Block::FoldedBuffer { .. } => BlockStyle::Sticky,
322 }
323 }
324
325 fn place_above(&self) -> bool {
326 match self {
327 Block::Custom(block) => matches!(block.placement, BlockPlacement::Above(_)),
328 Block::FoldedBuffer { .. } => false,
329 Block::ExcerptBoundary { .. } => true,
330 }
331 }
332
333 pub fn place_near(&self) -> bool {
334 match self {
335 Block::Custom(block) => matches!(block.placement, BlockPlacement::Near(_)),
336 Block::FoldedBuffer { .. } => false,
337 Block::ExcerptBoundary { .. } => false,
338 }
339 }
340
341 fn place_below(&self) -> bool {
342 match self {
343 Block::Custom(block) => matches!(
344 block.placement,
345 BlockPlacement::Below(_) | BlockPlacement::Near(_)
346 ),
347 Block::FoldedBuffer { .. } => false,
348 Block::ExcerptBoundary { .. } => false,
349 }
350 }
351
352 fn is_replacement(&self) -> bool {
353 match self {
354 Block::Custom(block) => matches!(block.placement, BlockPlacement::Replace(_)),
355 Block::FoldedBuffer { .. } => true,
356 Block::ExcerptBoundary { .. } => false,
357 }
358 }
359
360 fn is_header(&self) -> bool {
361 match self {
362 Block::Custom(_) => false,
363 Block::FoldedBuffer { .. } => true,
364 Block::ExcerptBoundary { .. } => true,
365 }
366 }
367
368 pub fn is_buffer_header(&self) -> bool {
369 match self {
370 Block::Custom(_) => false,
371 Block::FoldedBuffer { .. } => true,
372 Block::ExcerptBoundary {
373 starts_new_buffer, ..
374 } => *starts_new_buffer,
375 }
376 }
377}
378
379impl Debug for Block {
380 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
381 match self {
382 Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(),
383 Self::FoldedBuffer {
384 first_excerpt,
385 height,
386 } => f
387 .debug_struct("FoldedBuffer")
388 .field("first_excerpt", &first_excerpt)
389 .field("height", height)
390 .finish(),
391 Self::ExcerptBoundary {
392 starts_new_buffer,
393 excerpt,
394 height,
395 } => f
396 .debug_struct("ExcerptBoundary")
397 .field("excerpt", excerpt)
398 .field("starts_new_buffer", starts_new_buffer)
399 .field("height", height)
400 .finish(),
401 }
402 }
403}
404
405#[derive(Clone, Debug, Default)]
406struct TransformSummary {
407 input_rows: u32,
408 output_rows: u32,
409 longest_row: u32,
410 longest_row_chars: u32,
411}
412
413pub struct BlockChunks<'a> {
414 transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
415 input_chunks: wrap_map::WrapChunks<'a>,
416 input_chunk: Chunk<'a>,
417 output_row: u32,
418 max_output_row: u32,
419 masked: bool,
420}
421
422#[derive(Clone)]
423pub struct BlockRows<'a> {
424 transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
425 input_rows: wrap_map::WrapRows<'a>,
426 output_row: BlockRow,
427 started: bool,
428}
429
430impl BlockMap {
431 pub fn new(
432 wrap_snapshot: WrapSnapshot,
433 buffer_header_height: u32,
434 excerpt_header_height: u32,
435 ) -> Self {
436 let row_count = wrap_snapshot.max_point().row() + 1;
437 let mut transforms = SumTree::default();
438 push_isomorphic(&mut transforms, row_count, &wrap_snapshot);
439 let map = Self {
440 next_block_id: AtomicUsize::new(0),
441 custom_blocks: Vec::new(),
442 custom_blocks_by_id: TreeMap::default(),
443 folded_buffers: HashSet::default(),
444 buffers_with_disabled_headers: HashSet::default(),
445 transforms: RefCell::new(transforms),
446 wrap_snapshot: RefCell::new(wrap_snapshot.clone()),
447 buffer_header_height,
448 excerpt_header_height,
449 };
450 map.sync(
451 &wrap_snapshot,
452 Patch::new(vec![Edit {
453 old: 0..row_count,
454 new: 0..row_count,
455 }]),
456 );
457 map
458 }
459
460 pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapReader {
461 self.sync(&wrap_snapshot, edits);
462 *self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone();
463 BlockMapReader {
464 blocks: &self.custom_blocks,
465 snapshot: BlockSnapshot {
466 wrap_snapshot,
467 transforms: self.transforms.borrow().clone(),
468 custom_blocks_by_id: self.custom_blocks_by_id.clone(),
469 buffer_header_height: self.buffer_header_height,
470 excerpt_header_height: self.excerpt_header_height,
471 },
472 }
473 }
474
475 pub fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapWriter {
476 self.sync(&wrap_snapshot, edits);
477 *self.wrap_snapshot.borrow_mut() = wrap_snapshot;
478 BlockMapWriter(self)
479 }
480
481 fn sync(&self, wrap_snapshot: &WrapSnapshot, mut edits: Patch<u32>) {
482 let buffer = wrap_snapshot.buffer_snapshot();
483
484 // Handle changing the last excerpt if it is empty.
485 if buffer.trailing_excerpt_update_count()
486 != self
487 .wrap_snapshot
488 .borrow()
489 .buffer_snapshot()
490 .trailing_excerpt_update_count()
491 {
492 let max_point = wrap_snapshot.max_point();
493 let edit_start = wrap_snapshot.prev_row_boundary(max_point);
494 let edit_end = max_point.row() + 1;
495 edits = edits.compose([WrapEdit {
496 old: edit_start..edit_end,
497 new: edit_start..edit_end,
498 }]);
499 }
500
501 let edits = edits.into_inner();
502 if edits.is_empty() {
503 return;
504 }
505
506 let mut transforms = self.transforms.borrow_mut();
507 let mut new_transforms = SumTree::default();
508 let mut cursor = transforms.cursor::<WrapRow>(&());
509 let mut last_block_ix = 0;
510 let mut blocks_in_edit = Vec::new();
511 let mut edits = edits.into_iter().peekable();
512
513 while let Some(edit) = edits.next() {
514 let mut old_start = WrapRow(edit.old.start);
515 let mut new_start = WrapRow(edit.new.start);
516
517 // Only preserve transforms that:
518 // * Strictly precedes this edit
519 // * Isomorphic transforms that end *at* the start of the edit
520 // * Below blocks that end at the start of the edit
521 // However, if we hit a replace block that ends at the start of the edit we want to reconstruct it.
522 new_transforms.append(cursor.slice(&old_start, Bias::Left, &()), &());
523 if let Some(transform) = cursor.item() {
524 if transform.summary.input_rows > 0
525 && cursor.end(&()) == old_start
526 && transform
527 .block
528 .as_ref()
529 .map_or(true, |b| !b.is_replacement())
530 {
531 // Preserve the transform (push and next)
532 new_transforms.push(transform.clone(), &());
533 cursor.next(&());
534
535 // Preserve below blocks at end of edit
536 while let Some(transform) = cursor.item() {
537 if transform.block.as_ref().map_or(false, |b| b.place_below()) {
538 new_transforms.push(transform.clone(), &());
539 cursor.next(&());
540 } else {
541 break;
542 }
543 }
544 }
545 }
546
547 // Ensure the edit starts at a transform boundary.
548 // If the edit starts within an isomorphic transform, preserve its prefix
549 // If the edit lands within a replacement block, expand the edit to include the start of the replaced input range
550 let transform = cursor.item().unwrap();
551 let transform_rows_before_edit = old_start.0 - cursor.start().0;
552 if transform_rows_before_edit > 0 {
553 if transform.block.is_none() {
554 // Preserve any portion of the old isomorphic transform that precedes this edit.
555 push_isomorphic(
556 &mut new_transforms,
557 transform_rows_before_edit,
558 wrap_snapshot,
559 );
560 } else {
561 // We landed within a block that replaces some lines, so we
562 // extend the edit to start at the beginning of the
563 // replacement.
564 debug_assert!(transform.summary.input_rows > 0);
565 old_start.0 -= transform_rows_before_edit;
566 new_start.0 -= transform_rows_before_edit;
567 }
568 }
569
570 // Decide where the edit ends
571 // * It should end at a transform boundary
572 // * Coalesce edits that intersect the same transform
573 let mut old_end = WrapRow(edit.old.end);
574 let mut new_end = WrapRow(edit.new.end);
575 loop {
576 // Seek to the transform starting at or after the end of the edit
577 cursor.seek(&old_end, Bias::Left, &());
578 cursor.next(&());
579
580 // Extend edit to the end of the discarded transform so it is reconstructed in full
581 let transform_rows_after_edit = cursor.start().0 - old_end.0;
582 old_end.0 += transform_rows_after_edit;
583 new_end.0 += transform_rows_after_edit;
584
585 // Combine this edit with any subsequent edits that intersect the same transform.
586 while let Some(next_edit) = edits.peek() {
587 if next_edit.old.start <= cursor.start().0 {
588 old_end = WrapRow(next_edit.old.end);
589 new_end = WrapRow(next_edit.new.end);
590 cursor.seek(&old_end, Bias::Left, &());
591 cursor.next(&());
592 edits.next();
593 } else {
594 break;
595 }
596 }
597
598 if *cursor.start() == old_end {
599 break;
600 }
601 }
602
603 // Discard below blocks at the end of the edit. They'll be reconstructed.
604 while let Some(transform) = cursor.item() {
605 if transform.block.as_ref().map_or(false, |b| b.place_below()) {
606 cursor.next(&());
607 } else {
608 break;
609 }
610 }
611
612 // Find the blocks within this edited region.
613 let new_buffer_start =
614 wrap_snapshot.to_point(WrapPoint::new(new_start.0, 0), Bias::Left);
615 let start_bound = Bound::Included(new_buffer_start);
616 let start_block_ix =
617 match self.custom_blocks[last_block_ix..].binary_search_by(|probe| {
618 probe
619 .start()
620 .to_point(buffer)
621 .cmp(&new_buffer_start)
622 // Move left until we find the index of the first block starting within this edit
623 .then(Ordering::Greater)
624 }) {
625 Ok(ix) | Err(ix) => last_block_ix + ix,
626 };
627
628 let end_bound;
629 let end_block_ix = if new_end.0 > wrap_snapshot.max_point().row() {
630 end_bound = Bound::Unbounded;
631 self.custom_blocks.len()
632 } else {
633 let new_buffer_end =
634 wrap_snapshot.to_point(WrapPoint::new(new_end.0, 0), Bias::Left);
635 end_bound = Bound::Excluded(new_buffer_end);
636 match self.custom_blocks[start_block_ix..].binary_search_by(|probe| {
637 probe
638 .start()
639 .to_point(buffer)
640 .cmp(&new_buffer_end)
641 .then(Ordering::Greater)
642 }) {
643 Ok(ix) | Err(ix) => start_block_ix + ix,
644 }
645 };
646 last_block_ix = end_block_ix;
647
648 debug_assert!(blocks_in_edit.is_empty());
649
650 blocks_in_edit.extend(
651 self.custom_blocks[start_block_ix..end_block_ix]
652 .iter()
653 .filter_map(|block| {
654 let placement = block.placement.to_wrap_row(wrap_snapshot)?;
655 if let BlockPlacement::Above(row) = placement {
656 if row < new_start {
657 return None;
658 }
659 }
660 Some((placement, Block::Custom(block.clone())))
661 }),
662 );
663
664 if buffer.show_headers() {
665 blocks_in_edit.extend(self.header_and_footer_blocks(
666 buffer,
667 (start_bound, end_bound),
668 wrap_snapshot,
669 ));
670 }
671
672 BlockMap::sort_blocks(&mut blocks_in_edit);
673
674 // For each of these blocks, insert a new isomorphic transform preceding the block,
675 // and then insert the block itself.
676 for (block_placement, block) in blocks_in_edit.drain(..) {
677 let mut summary = TransformSummary {
678 input_rows: 0,
679 output_rows: block.height(),
680 longest_row: 0,
681 longest_row_chars: 0,
682 };
683
684 let rows_before_block;
685 match block_placement {
686 BlockPlacement::Above(position) => {
687 rows_before_block = position.0 - new_transforms.summary().input_rows;
688 }
689 BlockPlacement::Near(position) | BlockPlacement::Below(position) => {
690 if position.0 + 1 < new_transforms.summary().input_rows {
691 continue;
692 }
693 rows_before_block = (position.0 + 1) - new_transforms.summary().input_rows;
694 }
695 BlockPlacement::Replace(range) => {
696 rows_before_block = range.start().0 - new_transforms.summary().input_rows;
697 summary.input_rows = range.end().0 - range.start().0 + 1;
698 }
699 }
700
701 push_isomorphic(&mut new_transforms, rows_before_block, wrap_snapshot);
702 new_transforms.push(
703 Transform {
704 summary,
705 block: Some(block),
706 },
707 &(),
708 );
709 }
710
711 // Insert an isomorphic transform after the final block.
712 let rows_after_last_block = new_end
713 .0
714 .saturating_sub(new_transforms.summary().input_rows);
715 push_isomorphic(&mut new_transforms, rows_after_last_block, wrap_snapshot);
716 }
717
718 new_transforms.append(cursor.suffix(&()), &());
719 debug_assert_eq!(
720 new_transforms.summary().input_rows,
721 wrap_snapshot.max_point().row() + 1
722 );
723
724 drop(cursor);
725 *transforms = new_transforms;
726 }
727
728 pub fn replace_blocks(&mut self, mut renderers: HashMap<CustomBlockId, RenderBlock>) {
729 for block in &mut self.custom_blocks {
730 if let Some(render) = renderers.remove(&block.id) {
731 *block.render.lock() = render;
732 }
733 }
734 }
735
736 fn header_and_footer_blocks<'a, R, T>(
737 &'a self,
738 buffer: &'a multi_buffer::MultiBufferSnapshot,
739 range: R,
740 wrap_snapshot: &'a WrapSnapshot,
741 ) -> impl Iterator<Item = (BlockPlacement<WrapRow>, Block)> + 'a
742 where
743 R: RangeBounds<T>,
744 T: multi_buffer::ToOffset,
745 {
746 let mut boundaries = buffer.excerpt_boundaries_in_range(range).peekable();
747
748 std::iter::from_fn(move || {
749 loop {
750 let excerpt_boundary = boundaries.next()?;
751 let wrap_row = wrap_snapshot
752 .make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
753 .row();
754
755 let new_buffer_id = match (&excerpt_boundary.prev, &excerpt_boundary.next) {
756 (None, next) => Some(next.buffer_id),
757 (Some(prev), next) => {
758 if prev.buffer_id != next.buffer_id {
759 Some(next.buffer_id)
760 } else {
761 None
762 }
763 }
764 };
765
766 let mut height = 0;
767
768 if let Some(new_buffer_id) = new_buffer_id {
769 let first_excerpt = excerpt_boundary.next.clone();
770 if self.buffers_with_disabled_headers.contains(&new_buffer_id) {
771 continue;
772 }
773 if self.folded_buffers.contains(&new_buffer_id) {
774 let mut last_excerpt_end_row = first_excerpt.end_row;
775
776 while let Some(next_boundary) = boundaries.peek() {
777 if next_boundary.next.buffer_id == new_buffer_id {
778 last_excerpt_end_row = next_boundary.next.end_row;
779 } else {
780 break;
781 }
782
783 boundaries.next();
784 }
785
786 let wrap_end_row = wrap_snapshot
787 .make_wrap_point(
788 Point::new(
789 last_excerpt_end_row.0,
790 buffer.line_len(last_excerpt_end_row),
791 ),
792 Bias::Right,
793 )
794 .row();
795
796 return Some((
797 BlockPlacement::Replace(WrapRow(wrap_row)..=WrapRow(wrap_end_row)),
798 Block::FoldedBuffer {
799 height: height + self.buffer_header_height,
800 first_excerpt,
801 },
802 ));
803 }
804 }
805
806 if new_buffer_id.is_some() {
807 height += self.buffer_header_height;
808 } else {
809 height += self.excerpt_header_height;
810 }
811
812 return Some((
813 BlockPlacement::Above(WrapRow(wrap_row)),
814 Block::ExcerptBoundary {
815 excerpt: excerpt_boundary.next,
816 height,
817 starts_new_buffer: new_buffer_id.is_some(),
818 },
819 ));
820 }
821 })
822 }
823
824 fn sort_blocks(blocks: &mut Vec<(BlockPlacement<WrapRow>, Block)>) {
825 blocks.sort_unstable_by(|(placement_a, block_a), (placement_b, block_b)| {
826 placement_a
827 .start()
828 .cmp(placement_b.start())
829 .then_with(|| placement_b.end().cmp(placement_a.end()))
830 .then_with(|| {
831 if block_a.is_header() {
832 Ordering::Less
833 } else if block_b.is_header() {
834 Ordering::Greater
835 } else {
836 Ordering::Equal
837 }
838 })
839 .then_with(|| placement_a.sort_order().cmp(&placement_b.sort_order()))
840 .then_with(|| match (block_a, block_b) {
841 (
842 Block::ExcerptBoundary {
843 excerpt: excerpt_a, ..
844 },
845 Block::ExcerptBoundary {
846 excerpt: excerpt_b, ..
847 },
848 ) => Some(excerpt_a.id).cmp(&Some(excerpt_b.id)),
849 (Block::ExcerptBoundary { .. }, Block::Custom(_)) => Ordering::Less,
850 (Block::Custom(_), Block::ExcerptBoundary { .. }) => Ordering::Greater,
851 (Block::Custom(block_a), Block::Custom(block_b)) => block_a
852 .priority
853 .cmp(&block_b.priority)
854 .then_with(|| block_a.id.cmp(&block_b.id)),
855 _ => {
856 unreachable!()
857 }
858 })
859 });
860 blocks.dedup_by(|right, left| match (left.0.clone(), right.0.clone()) {
861 (BlockPlacement::Replace(range), BlockPlacement::Above(row))
862 | (BlockPlacement::Replace(range), BlockPlacement::Below(row)) => range.contains(&row),
863 (BlockPlacement::Replace(range_a), BlockPlacement::Replace(range_b)) => {
864 if range_a.end() >= range_b.start() && range_a.start() <= range_b.end() {
865 left.0 = BlockPlacement::Replace(
866 *range_a.start()..=*range_a.end().max(range_b.end()),
867 );
868 true
869 } else {
870 false
871 }
872 }
873 _ => false,
874 });
875 }
876}
877
878fn push_isomorphic(tree: &mut SumTree<Transform>, rows: u32, wrap_snapshot: &WrapSnapshot) {
879 if rows == 0 {
880 return;
881 }
882
883 let wrap_row_start = tree.summary().input_rows;
884 let wrap_row_end = wrap_row_start + rows;
885 let wrap_summary = wrap_snapshot.text_summary_for_range(wrap_row_start..wrap_row_end);
886 let summary = TransformSummary {
887 input_rows: rows,
888 output_rows: rows,
889 longest_row: wrap_summary.longest_row,
890 longest_row_chars: wrap_summary.longest_row_chars,
891 };
892 let mut merged = false;
893 tree.update_last(
894 |last_transform| {
895 if last_transform.block.is_none() {
896 last_transform.summary.add_summary(&summary, &());
897 merged = true;
898 }
899 },
900 &(),
901 );
902 if !merged {
903 tree.push(
904 Transform {
905 summary,
906 block: None,
907 },
908 &(),
909 );
910 }
911}
912
913impl BlockPoint {
914 pub fn new(row: u32, column: u32) -> Self {
915 Self(Point::new(row, column))
916 }
917}
918
919impl Deref for BlockPoint {
920 type Target = Point;
921
922 fn deref(&self) -> &Self::Target {
923 &self.0
924 }
925}
926
927impl std::ops::DerefMut for BlockPoint {
928 fn deref_mut(&mut self) -> &mut Self::Target {
929 &mut self.0
930 }
931}
932
933impl Deref for BlockMapReader<'_> {
934 type Target = BlockSnapshot;
935
936 fn deref(&self) -> &Self::Target {
937 &self.snapshot
938 }
939}
940
941impl DerefMut for BlockMapReader<'_> {
942 fn deref_mut(&mut self) -> &mut Self::Target {
943 &mut self.snapshot
944 }
945}
946
947impl BlockMapReader<'_> {
948 pub fn row_for_block(&self, block_id: CustomBlockId) -> Option<BlockRow> {
949 let block = self.blocks.iter().find(|block| block.id == block_id)?;
950 let buffer_row = block
951 .start()
952 .to_point(self.wrap_snapshot.buffer_snapshot())
953 .row;
954 let wrap_row = self
955 .wrap_snapshot
956 .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
957 .row();
958 let start_wrap_row = WrapRow(
959 self.wrap_snapshot
960 .prev_row_boundary(WrapPoint::new(wrap_row, 0)),
961 );
962 let end_wrap_row = WrapRow(
963 self.wrap_snapshot
964 .next_row_boundary(WrapPoint::new(wrap_row, 0))
965 .unwrap_or(self.wrap_snapshot.max_point().row() + 1),
966 );
967
968 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
969 cursor.seek(&start_wrap_row, Bias::Left, &());
970 while let Some(transform) = cursor.item() {
971 if cursor.start().0 > end_wrap_row {
972 break;
973 }
974
975 if let Some(BlockId::Custom(id)) = transform.block.as_ref().map(|block| block.id()) {
976 if id == block_id {
977 return Some(cursor.start().1);
978 }
979 }
980 cursor.next(&());
981 }
982
983 None
984 }
985}
986
987impl BlockMapWriter<'_> {
988 pub fn insert(
989 &mut self,
990 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
991 ) -> Vec<CustomBlockId> {
992 let blocks = blocks.into_iter();
993 let mut ids = Vec::with_capacity(blocks.size_hint().1.unwrap_or(0));
994 let mut edits = Patch::default();
995 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
996 let buffer = wrap_snapshot.buffer_snapshot();
997
998 let mut previous_wrap_row_range: Option<Range<u32>> = None;
999 for block in blocks {
1000 if let BlockPlacement::Replace(_) = &block.placement {
1001 debug_assert!(block.height.unwrap() > 0);
1002 }
1003
1004 let id = CustomBlockId(self.0.next_block_id.fetch_add(1, SeqCst));
1005 ids.push(id);
1006
1007 let start = block.placement.start().to_point(buffer);
1008 let end = block.placement.end().to_point(buffer);
1009 let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
1010 let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
1011
1012 let (start_row, end_row) = {
1013 previous_wrap_row_range.take_if(|range| {
1014 !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
1015 });
1016 let range = previous_wrap_row_range.get_or_insert_with(|| {
1017 let start_row =
1018 wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1019 let end_row = wrap_snapshot
1020 .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1021 .unwrap_or(wrap_snapshot.max_point().row() + 1);
1022 start_row..end_row
1023 });
1024 (range.start, range.end)
1025 };
1026 let block_ix = match self
1027 .0
1028 .custom_blocks
1029 .binary_search_by(|probe| probe.placement.cmp(&block.placement, buffer))
1030 {
1031 Ok(ix) | Err(ix) => ix,
1032 };
1033 let new_block = Arc::new(CustomBlock {
1034 id,
1035 placement: block.placement,
1036 height: block.height,
1037 render: Arc::new(Mutex::new(block.render)),
1038 style: block.style,
1039 priority: block.priority,
1040 });
1041 self.0.custom_blocks.insert(block_ix, new_block.clone());
1042 self.0.custom_blocks_by_id.insert(id, new_block);
1043
1044 edits = edits.compose([Edit {
1045 old: start_row..end_row,
1046 new: start_row..end_row,
1047 }]);
1048 }
1049
1050 self.0.sync(wrap_snapshot, edits);
1051 ids
1052 }
1053
1054 pub fn resize(&mut self, mut heights: HashMap<CustomBlockId, u32>) {
1055 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
1056 let buffer = wrap_snapshot.buffer_snapshot();
1057 let mut edits = Patch::default();
1058 let mut last_block_buffer_row = None;
1059
1060 for block in &mut self.0.custom_blocks {
1061 if let Some(new_height) = heights.remove(&block.id) {
1062 if let BlockPlacement::Replace(_) = &block.placement {
1063 debug_assert!(new_height > 0);
1064 }
1065
1066 if block.height != Some(new_height) {
1067 let new_block = CustomBlock {
1068 id: block.id,
1069 placement: block.placement.clone(),
1070 height: Some(new_height),
1071 style: block.style,
1072 render: block.render.clone(),
1073 priority: block.priority,
1074 };
1075 let new_block = Arc::new(new_block);
1076 *block = new_block.clone();
1077 self.0.custom_blocks_by_id.insert(block.id, new_block);
1078
1079 let start_row = block.placement.start().to_point(buffer).row;
1080 let end_row = block.placement.end().to_point(buffer).row;
1081 if last_block_buffer_row != Some(end_row) {
1082 last_block_buffer_row = Some(end_row);
1083 let start_wrap_row = wrap_snapshot
1084 .make_wrap_point(Point::new(start_row, 0), Bias::Left)
1085 .row();
1086 let end_wrap_row = wrap_snapshot
1087 .make_wrap_point(Point::new(end_row, 0), Bias::Left)
1088 .row();
1089 let start =
1090 wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1091 let end = wrap_snapshot
1092 .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1093 .unwrap_or(wrap_snapshot.max_point().row() + 1);
1094 edits.push(Edit {
1095 old: start..end,
1096 new: start..end,
1097 })
1098 }
1099 }
1100 }
1101 }
1102
1103 self.0.sync(wrap_snapshot, edits);
1104 }
1105
1106 pub fn remove(&mut self, block_ids: HashSet<CustomBlockId>) {
1107 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
1108 let buffer = wrap_snapshot.buffer_snapshot();
1109 let mut edits = Patch::default();
1110 let mut last_block_buffer_row = None;
1111 let mut previous_wrap_row_range: Option<Range<u32>> = None;
1112 self.0.custom_blocks.retain(|block| {
1113 if block_ids.contains(&block.id) {
1114 let start = block.placement.start().to_point(buffer);
1115 let end = block.placement.end().to_point(buffer);
1116 if last_block_buffer_row != Some(end.row) {
1117 last_block_buffer_row = Some(end.row);
1118 let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
1119 let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
1120 let (start_row, end_row) = {
1121 previous_wrap_row_range.take_if(|range| {
1122 !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
1123 });
1124 let range = previous_wrap_row_range.get_or_insert_with(|| {
1125 let start_row =
1126 wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
1127 let end_row = wrap_snapshot
1128 .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
1129 .unwrap_or(wrap_snapshot.max_point().row() + 1);
1130 start_row..end_row
1131 });
1132 (range.start, range.end)
1133 };
1134
1135 edits.push(Edit {
1136 old: start_row..end_row,
1137 new: start_row..end_row,
1138 })
1139 }
1140 false
1141 } else {
1142 true
1143 }
1144 });
1145 self.0
1146 .custom_blocks_by_id
1147 .retain(|id, _| !block_ids.contains(id));
1148 self.0.sync(wrap_snapshot, edits);
1149 }
1150
1151 pub fn remove_intersecting_replace_blocks<T>(
1152 &mut self,
1153 ranges: impl IntoIterator<Item = Range<T>>,
1154 inclusive: bool,
1155 ) where
1156 T: ToOffset,
1157 {
1158 let wrap_snapshot = self.0.wrap_snapshot.borrow();
1159 let mut blocks_to_remove = HashSet::default();
1160 for range in ranges {
1161 let range = range.start.to_offset(wrap_snapshot.buffer_snapshot())
1162 ..range.end.to_offset(wrap_snapshot.buffer_snapshot());
1163 for block in self.blocks_intersecting_buffer_range(range, inclusive) {
1164 if matches!(block.placement, BlockPlacement::Replace(_)) {
1165 blocks_to_remove.insert(block.id);
1166 }
1167 }
1168 }
1169 drop(wrap_snapshot);
1170 self.remove(blocks_to_remove);
1171 }
1172
1173 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId) {
1174 self.0.buffers_with_disabled_headers.insert(buffer_id);
1175 }
1176
1177 pub fn fold_buffers(
1178 &mut self,
1179 buffer_ids: impl IntoIterator<Item = BufferId>,
1180 multi_buffer: &MultiBuffer,
1181 cx: &App,
1182 ) {
1183 self.fold_or_unfold_buffers(true, buffer_ids, multi_buffer, cx);
1184 }
1185
1186 pub fn unfold_buffers(
1187 &mut self,
1188 buffer_ids: impl IntoIterator<Item = BufferId>,
1189 multi_buffer: &MultiBuffer,
1190 cx: &App,
1191 ) {
1192 self.fold_or_unfold_buffers(false, buffer_ids, multi_buffer, cx);
1193 }
1194
1195 fn fold_or_unfold_buffers(
1196 &mut self,
1197 fold: bool,
1198 buffer_ids: impl IntoIterator<Item = BufferId>,
1199 multi_buffer: &MultiBuffer,
1200 cx: &App,
1201 ) {
1202 let mut ranges = Vec::new();
1203 for buffer_id in buffer_ids {
1204 if fold {
1205 self.0.folded_buffers.insert(buffer_id);
1206 } else {
1207 self.0.folded_buffers.remove(&buffer_id);
1208 }
1209 ranges.extend(multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx));
1210 }
1211 ranges.sort_unstable_by_key(|range| range.start);
1212
1213 let mut edits = Patch::default();
1214 let wrap_snapshot = self.0.wrap_snapshot.borrow().clone();
1215 for range in ranges {
1216 let last_edit_row = cmp::min(
1217 wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + 1,
1218 wrap_snapshot.max_point().row(),
1219 ) + 1;
1220 let range = wrap_snapshot.make_wrap_point(range.start, Bias::Left).row()..last_edit_row;
1221 edits.push(Edit {
1222 old: range.clone(),
1223 new: range,
1224 });
1225 }
1226
1227 self.0.sync(&wrap_snapshot, edits);
1228 }
1229
1230 fn blocks_intersecting_buffer_range(
1231 &self,
1232 range: Range<usize>,
1233 inclusive: bool,
1234 ) -> &[Arc<CustomBlock>] {
1235 let wrap_snapshot = self.0.wrap_snapshot.borrow();
1236 let buffer = wrap_snapshot.buffer_snapshot();
1237
1238 let start_block_ix = match self.0.custom_blocks.binary_search_by(|block| {
1239 let block_end = block.end().to_offset(buffer);
1240 block_end.cmp(&range.start).then_with(|| {
1241 if inclusive || (range.is_empty() && block.start().to_offset(buffer) == block_end) {
1242 Ordering::Greater
1243 } else {
1244 Ordering::Less
1245 }
1246 })
1247 }) {
1248 Ok(ix) | Err(ix) => ix,
1249 };
1250 let end_block_ix = match self.0.custom_blocks.binary_search_by(|block| {
1251 block
1252 .start()
1253 .to_offset(buffer)
1254 .cmp(&range.end)
1255 .then(if inclusive {
1256 Ordering::Less
1257 } else {
1258 Ordering::Greater
1259 })
1260 }) {
1261 Ok(ix) | Err(ix) => ix,
1262 };
1263
1264 &self.0.custom_blocks[start_block_ix..end_block_ix]
1265 }
1266}
1267
1268impl BlockSnapshot {
1269 #[cfg(test)]
1270 pub fn text(&self) -> String {
1271 self.chunks(
1272 0..self.transforms.summary().output_rows,
1273 false,
1274 false,
1275 Highlights::default(),
1276 )
1277 .map(|chunk| chunk.text)
1278 .collect()
1279 }
1280
1281 pub(crate) fn chunks<'a>(
1282 &'a self,
1283 rows: Range<u32>,
1284 language_aware: bool,
1285 masked: bool,
1286 highlights: Highlights<'a>,
1287 ) -> BlockChunks<'a> {
1288 let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
1289
1290 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1291 cursor.seek(&BlockRow(rows.start), Bias::Right, &());
1292 let transform_output_start = cursor.start().0.0;
1293 let transform_input_start = cursor.start().1.0;
1294
1295 let mut input_start = transform_input_start;
1296 let mut input_end = transform_input_start;
1297 if let Some(transform) = cursor.item() {
1298 if transform.block.is_none() {
1299 input_start += rows.start - transform_output_start;
1300 input_end += cmp::min(
1301 rows.end - transform_output_start,
1302 transform.summary.input_rows,
1303 );
1304 }
1305 }
1306
1307 BlockChunks {
1308 input_chunks: self.wrap_snapshot.chunks(
1309 input_start..input_end,
1310 language_aware,
1311 highlights,
1312 ),
1313 input_chunk: Default::default(),
1314 transforms: cursor,
1315 output_row: rows.start,
1316 max_output_row,
1317 masked,
1318 }
1319 }
1320
1321 pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows {
1322 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1323 cursor.seek(&start_row, Bias::Right, &());
1324 let (output_start, input_start) = cursor.start();
1325 let overshoot = if cursor
1326 .item()
1327 .map_or(false, |transform| transform.block.is_none())
1328 {
1329 start_row.0 - output_start.0
1330 } else {
1331 0
1332 };
1333 let input_start_row = input_start.0 + overshoot;
1334 BlockRows {
1335 transforms: cursor,
1336 input_rows: self.wrap_snapshot.row_infos(input_start_row),
1337 output_row: start_row,
1338 started: false,
1339 }
1340 }
1341
1342 pub fn blocks_in_range(&self, rows: Range<u32>) -> impl Iterator<Item = (u32, &Block)> {
1343 let mut cursor = self.transforms.cursor::<BlockRow>(&());
1344 cursor.seek(&BlockRow(rows.start), Bias::Left, &());
1345 while cursor.start().0 < rows.start && cursor.end(&()).0 <= rows.start {
1346 cursor.next(&());
1347 }
1348
1349 std::iter::from_fn(move || {
1350 while let Some(transform) = cursor.item() {
1351 let start_row = cursor.start().0;
1352 if start_row > rows.end
1353 || (start_row == rows.end
1354 && transform
1355 .block
1356 .as_ref()
1357 .map_or(false, |block| block.height() > 0))
1358 {
1359 break;
1360 }
1361 if let Some(block) = &transform.block {
1362 cursor.next(&());
1363 return Some((start_row, block));
1364 } else {
1365 cursor.next(&());
1366 }
1367 }
1368 None
1369 })
1370 }
1371
1372 pub fn sticky_header_excerpt(&self, position: f32) -> Option<StickyHeaderExcerpt<'_>> {
1373 let top_row = position as u32;
1374 let mut cursor = self.transforms.cursor::<BlockRow>(&());
1375 cursor.seek(&BlockRow(top_row), Bias::Right, &());
1376
1377 while let Some(transform) = cursor.item() {
1378 match &transform.block {
1379 Some(Block::ExcerptBoundary { excerpt, .. }) => {
1380 return Some(StickyHeaderExcerpt { excerpt });
1381 }
1382 Some(block) if block.is_buffer_header() => return None,
1383 _ => {
1384 cursor.prev(&());
1385 continue;
1386 }
1387 }
1388 }
1389
1390 None
1391 }
1392
1393 pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
1394 let buffer = self.wrap_snapshot.buffer_snapshot();
1395 let wrap_point = match block_id {
1396 BlockId::Custom(custom_block_id) => {
1397 let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
1398 return Some(Block::Custom(custom_block.clone()));
1399 }
1400 BlockId::ExcerptBoundary(next_excerpt_id) => {
1401 let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
1402 self.wrap_snapshot
1403 .make_wrap_point(excerpt_range.start, Bias::Left)
1404 }
1405 BlockId::FoldedBuffer(excerpt_id) => self
1406 .wrap_snapshot
1407 .make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left),
1408 };
1409 let wrap_row = WrapRow(wrap_point.row());
1410
1411 let mut cursor = self.transforms.cursor::<WrapRow>(&());
1412 cursor.seek(&wrap_row, Bias::Left, &());
1413
1414 while let Some(transform) = cursor.item() {
1415 if let Some(block) = transform.block.as_ref() {
1416 if block.id() == block_id {
1417 return Some(block.clone());
1418 }
1419 } else if *cursor.start() > wrap_row {
1420 break;
1421 }
1422
1423 cursor.next(&());
1424 }
1425
1426 None
1427 }
1428
1429 pub fn max_point(&self) -> BlockPoint {
1430 let row = self.transforms.summary().output_rows.saturating_sub(1);
1431 BlockPoint::new(row, self.line_len(BlockRow(row)))
1432 }
1433
1434 pub fn longest_row(&self) -> u32 {
1435 self.transforms.summary().longest_row
1436 }
1437
1438 pub fn longest_row_in_range(&self, range: Range<BlockRow>) -> BlockRow {
1439 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1440 cursor.seek(&range.start, Bias::Right, &());
1441
1442 let mut longest_row = range.start;
1443 let mut longest_row_chars = 0;
1444 if let Some(transform) = cursor.item() {
1445 if transform.block.is_none() {
1446 let (output_start, input_start) = cursor.start();
1447 let overshoot = range.start.0 - output_start.0;
1448 let wrap_start_row = input_start.0 + overshoot;
1449 let wrap_end_row = cmp::min(
1450 input_start.0 + (range.end.0 - output_start.0),
1451 cursor.end(&()).1.0,
1452 );
1453 let summary = self
1454 .wrap_snapshot
1455 .text_summary_for_range(wrap_start_row..wrap_end_row);
1456 longest_row = BlockRow(range.start.0 + summary.longest_row);
1457 longest_row_chars = summary.longest_row_chars;
1458 }
1459 cursor.next(&());
1460 }
1461
1462 let cursor_start_row = cursor.start().0;
1463 if range.end > cursor_start_row {
1464 let summary = cursor.summary::<_, TransformSummary>(&range.end, Bias::Right, &());
1465 if summary.longest_row_chars > longest_row_chars {
1466 longest_row = BlockRow(cursor_start_row.0 + summary.longest_row);
1467 longest_row_chars = summary.longest_row_chars;
1468 }
1469
1470 if let Some(transform) = cursor.item() {
1471 if transform.block.is_none() {
1472 let (output_start, input_start) = cursor.start();
1473 let overshoot = range.end.0 - output_start.0;
1474 let wrap_start_row = input_start.0;
1475 let wrap_end_row = input_start.0 + overshoot;
1476 let summary = self
1477 .wrap_snapshot
1478 .text_summary_for_range(wrap_start_row..wrap_end_row);
1479 if summary.longest_row_chars > longest_row_chars {
1480 longest_row = BlockRow(output_start.0 + summary.longest_row);
1481 }
1482 }
1483 }
1484 }
1485
1486 longest_row
1487 }
1488
1489 pub(super) fn line_len(&self, row: BlockRow) -> u32 {
1490 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1491 cursor.seek(&BlockRow(row.0), Bias::Right, &());
1492 if let Some(transform) = cursor.item() {
1493 let (output_start, input_start) = cursor.start();
1494 let overshoot = row.0 - output_start.0;
1495 if transform.block.is_some() {
1496 0
1497 } else {
1498 self.wrap_snapshot.line_len(input_start.0 + overshoot)
1499 }
1500 } else if row.0 == 0 {
1501 0
1502 } else {
1503 panic!("row out of range");
1504 }
1505 }
1506
1507 pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
1508 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1509 cursor.seek(&row, Bias::Right, &());
1510 cursor.item().map_or(false, |t| t.block.is_some())
1511 }
1512
1513 pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool {
1514 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1515 cursor.seek(&row, Bias::Right, &());
1516 let Some(transform) = cursor.item() else {
1517 return false;
1518 };
1519 matches!(transform.block, Some(Block::FoldedBuffer { .. }))
1520 }
1521
1522 pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool {
1523 let wrap_point = self
1524 .wrap_snapshot
1525 .make_wrap_point(Point::new(row.0, 0), Bias::Left);
1526 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
1527 cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
1528 cursor.item().map_or(false, |transform| {
1529 transform
1530 .block
1531 .as_ref()
1532 .map_or(false, |block| block.is_replacement())
1533 })
1534 }
1535
1536 pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
1537 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1538 cursor.seek(&BlockRow(point.row), Bias::Right, &());
1539
1540 let max_input_row = WrapRow(self.transforms.summary().input_rows);
1541 let mut search_left =
1542 (bias == Bias::Left && cursor.start().1.0 > 0) || cursor.end(&()).1 == max_input_row;
1543 let mut reversed = false;
1544
1545 loop {
1546 if let Some(transform) = cursor.item() {
1547 let (output_start_row, input_start_row) = cursor.start();
1548 let (output_end_row, input_end_row) = cursor.end(&());
1549 let output_start = Point::new(output_start_row.0, 0);
1550 let input_start = Point::new(input_start_row.0, 0);
1551 let input_end = Point::new(input_end_row.0, 0);
1552
1553 match transform.block.as_ref() {
1554 Some(block) => {
1555 if block.is_replacement() {
1556 if ((bias == Bias::Left || search_left) && output_start <= point.0)
1557 || (!search_left && output_start >= point.0)
1558 {
1559 return BlockPoint(output_start);
1560 }
1561 }
1562 }
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::<(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 (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::<(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 .map_or(false, |block| block.height() == 0)
1654 {
1655 self.transforms.next(&());
1656 } else {
1657 break;
1658 }
1659 }
1660
1661 if self
1662 .transforms
1663 .item()
1664 .map_or(false, |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 .map_or(false, |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 .map_or(true, |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.clone());
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.clone(), 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 _font_id = cx.text_system().font_id(&font("Helvetica")).unwrap();
2289
2290 let text = "one two three\nfour five six\nseven eight";
2291
2292 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2293 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2294 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2295 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2296 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2297 let (_, wraps_snapshot) = cx.update(|cx| {
2298 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(90.)), cx)
2299 });
2300 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2301
2302 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2303 writer.insert(vec![
2304 BlockProperties {
2305 style: BlockStyle::Fixed,
2306 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
2307 render: Arc::new(|_| div().into_any()),
2308 height: Some(1),
2309 priority: 0,
2310 },
2311 BlockProperties {
2312 style: BlockStyle::Fixed,
2313 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
2314 render: Arc::new(|_| div().into_any()),
2315 height: Some(1),
2316 priority: 0,
2317 },
2318 ]);
2319
2320 // Blocks with an 'above' disposition go above their corresponding buffer line.
2321 // Blocks with a 'below' disposition go below their corresponding buffer line.
2322 let snapshot = block_map.read(wraps_snapshot, Default::default());
2323 assert_eq!(
2324 snapshot.text(),
2325 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
2326 );
2327 }
2328
2329 #[gpui::test]
2330 fn test_replace_lines(cx: &mut gpui::TestAppContext) {
2331 cx.update(init_test);
2332
2333 let text = "line1\nline2\nline3\nline4\nline5";
2334
2335 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2336 let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
2337 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2338 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2339 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2340 let tab_size = 1.try_into().unwrap();
2341 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
2342 let (wrap_map, wraps_snapshot) =
2343 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2344 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
2345
2346 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2347 let replace_block_id = writer.insert(vec![BlockProperties {
2348 style: BlockStyle::Fixed,
2349 placement: BlockPlacement::Replace(
2350 buffer_snapshot.anchor_after(Point::new(1, 3))
2351 ..=buffer_snapshot.anchor_before(Point::new(3, 1)),
2352 ),
2353 height: Some(4),
2354 render: Arc::new(|_| div().into_any()),
2355 priority: 0,
2356 }])[0];
2357
2358 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
2359 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2360
2361 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2362 buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
2363 buffer.snapshot(cx)
2364 });
2365 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
2366 buffer_snapshot.clone(),
2367 buffer_subscription.consume().into_inner(),
2368 );
2369 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2370 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2371 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2372 wrap_map.sync(tab_snapshot, tab_edits, cx)
2373 });
2374 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
2375 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2376
2377 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2378 buffer.edit(
2379 [(
2380 Point::new(1, 5)..Point::new(1, 5),
2381 "\nline 2.1\nline2.2\nline 2.3\nline 2.4",
2382 )],
2383 None,
2384 cx,
2385 );
2386 buffer.snapshot(cx)
2387 });
2388 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
2389 buffer_snapshot.clone(),
2390 buffer_subscription.consume().into_inner(),
2391 );
2392 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2393 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2394 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2395 wrap_map.sync(tab_snapshot, tab_edits, cx)
2396 });
2397 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
2398 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2399
2400 // Blocks inserted right above the start or right below the end of the replaced region are hidden.
2401 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2402 writer.insert(vec![
2403 BlockProperties {
2404 style: BlockStyle::Fixed,
2405 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))),
2406 height: Some(1),
2407 render: Arc::new(|_| div().into_any()),
2408 priority: 0,
2409 },
2410 BlockProperties {
2411 style: BlockStyle::Fixed,
2412 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
2413 height: Some(1),
2414 render: Arc::new(|_| div().into_any()),
2415 priority: 0,
2416 },
2417 BlockProperties {
2418 style: BlockStyle::Fixed,
2419 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
2420 height: Some(1),
2421 render: Arc::new(|_| div().into_any()),
2422 priority: 0,
2423 },
2424 ]);
2425 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2426 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2427
2428 // Ensure blocks inserted *inside* replaced region are hidden.
2429 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2430 writer.insert(vec![
2431 BlockProperties {
2432 style: BlockStyle::Fixed,
2433 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
2434 height: Some(1),
2435 render: Arc::new(|_| div().into_any()),
2436 priority: 0,
2437 },
2438 BlockProperties {
2439 style: BlockStyle::Fixed,
2440 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
2441 height: Some(1),
2442 render: Arc::new(|_| div().into_any()),
2443 priority: 0,
2444 },
2445 BlockProperties {
2446 style: BlockStyle::Fixed,
2447 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
2448 height: Some(1),
2449 render: Arc::new(|_| div().into_any()),
2450 priority: 0,
2451 },
2452 ]);
2453 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2454 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2455
2456 // Removing the replace block shows all the hidden blocks again.
2457 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2458 writer.remove(HashSet::from_iter([replace_block_id]));
2459 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2460 assert_eq!(
2461 blocks_snapshot.text(),
2462 "\nline1\n\nline2\n\n\nline 2.1\nline2.2\nline 2.3\nline 2.4\n\nline4\n\nline5"
2463 );
2464 }
2465
2466 #[gpui::test]
2467 fn test_custom_blocks_inside_buffer_folds(cx: &mut gpui::TestAppContext) {
2468 cx.update(init_test);
2469
2470 let text = "111\n222\n333\n444\n555\n666";
2471
2472 let buffer = cx.update(|cx| {
2473 MultiBuffer::build_multi(
2474 [
2475 (text, vec![Point::new(0, 0)..Point::new(0, 3)]),
2476 (
2477 text,
2478 vec![
2479 Point::new(1, 0)..Point::new(1, 3),
2480 Point::new(2, 0)..Point::new(2, 3),
2481 Point::new(3, 0)..Point::new(3, 3),
2482 ],
2483 ),
2484 (
2485 text,
2486 vec![
2487 Point::new(4, 0)..Point::new(4, 3),
2488 Point::new(5, 0)..Point::new(5, 3),
2489 ],
2490 ),
2491 ],
2492 cx,
2493 )
2494 });
2495 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2496 let buffer_ids = buffer_snapshot
2497 .excerpts()
2498 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2499 .dedup()
2500 .collect::<Vec<_>>();
2501 assert_eq!(buffer_ids.len(), 3);
2502 let buffer_id_1 = buffer_ids[0];
2503 let buffer_id_2 = buffer_ids[1];
2504 let buffer_id_3 = buffer_ids[2];
2505
2506 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2507 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2508 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2509 let (_, wrap_snapshot) =
2510 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2511 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
2512 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2513
2514 assert_eq!(
2515 blocks_snapshot.text(),
2516 "\n\n111\n\n\n222\n\n333\n\n444\n\n\n555\n\n666"
2517 );
2518 assert_eq!(
2519 blocks_snapshot
2520 .row_infos(BlockRow(0))
2521 .map(|i| i.buffer_row)
2522 .collect::<Vec<_>>(),
2523 vec![
2524 None,
2525 None,
2526 Some(0),
2527 None,
2528 None,
2529 Some(1),
2530 None,
2531 Some(2),
2532 None,
2533 Some(3),
2534 None,
2535 None,
2536 Some(4),
2537 None,
2538 Some(5),
2539 ]
2540 );
2541
2542 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2543 let excerpt_blocks_2 = writer.insert(vec![
2544 BlockProperties {
2545 style: BlockStyle::Fixed,
2546 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2547 height: Some(1),
2548 render: Arc::new(|_| div().into_any()),
2549 priority: 0,
2550 },
2551 BlockProperties {
2552 style: BlockStyle::Fixed,
2553 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 0))),
2554 height: Some(1),
2555 render: Arc::new(|_| div().into_any()),
2556 priority: 0,
2557 },
2558 BlockProperties {
2559 style: BlockStyle::Fixed,
2560 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 0))),
2561 height: Some(1),
2562 render: Arc::new(|_| div().into_any()),
2563 priority: 0,
2564 },
2565 ]);
2566 let excerpt_blocks_3 = writer.insert(vec![
2567 BlockProperties {
2568 style: BlockStyle::Fixed,
2569 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))),
2570 height: Some(1),
2571 render: Arc::new(|_| div().into_any()),
2572 priority: 0,
2573 },
2574 BlockProperties {
2575 style: BlockStyle::Fixed,
2576 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))),
2577 height: Some(1),
2578 render: Arc::new(|_| div().into_any()),
2579 priority: 0,
2580 },
2581 ]);
2582
2583 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2584 assert_eq!(
2585 blocks_snapshot.text(),
2586 "\n\n111\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
2587 );
2588 assert_eq!(
2589 blocks_snapshot
2590 .row_infos(BlockRow(0))
2591 .map(|i| i.buffer_row)
2592 .collect::<Vec<_>>(),
2593 vec![
2594 None,
2595 None,
2596 Some(0),
2597 None,
2598 None,
2599 None,
2600 Some(1),
2601 None,
2602 None,
2603 Some(2),
2604 None,
2605 Some(3),
2606 None,
2607 None,
2608 None,
2609 None,
2610 Some(4),
2611 None,
2612 Some(5),
2613 None,
2614 ]
2615 );
2616
2617 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2618 buffer.read_with(cx, |buffer, cx| {
2619 writer.fold_buffers([buffer_id_1], buffer, cx);
2620 });
2621 let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
2622 style: BlockStyle::Fixed,
2623 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))),
2624 height: Some(1),
2625 render: Arc::new(|_| div().into_any()),
2626 priority: 0,
2627 }]);
2628 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2629 let blocks = blocks_snapshot
2630 .blocks_in_range(0..u32::MAX)
2631 .collect::<Vec<_>>();
2632 for (_, block) in &blocks {
2633 if let BlockId::Custom(custom_block_id) = block.id() {
2634 assert!(
2635 !excerpt_blocks_1.contains(&custom_block_id),
2636 "Should have no blocks from the folded buffer"
2637 );
2638 assert!(
2639 excerpt_blocks_2.contains(&custom_block_id)
2640 || excerpt_blocks_3.contains(&custom_block_id),
2641 "Should have only blocks from unfolded buffers"
2642 );
2643 }
2644 }
2645 assert_eq!(
2646 1,
2647 blocks
2648 .iter()
2649 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2650 .count(),
2651 "Should have one folded block, producing a header of the second buffer"
2652 );
2653 assert_eq!(
2654 blocks_snapshot.text(),
2655 "\n\n\n\n\n222\n\n\n333\n\n444\n\n\n\n\n555\n\n666\n"
2656 );
2657 assert_eq!(
2658 blocks_snapshot
2659 .row_infos(BlockRow(0))
2660 .map(|i| i.buffer_row)
2661 .collect::<Vec<_>>(),
2662 vec![
2663 None,
2664 None,
2665 None,
2666 None,
2667 None,
2668 Some(1),
2669 None,
2670 None,
2671 Some(2),
2672 None,
2673 Some(3),
2674 None,
2675 None,
2676 None,
2677 None,
2678 Some(4),
2679 None,
2680 Some(5),
2681 None,
2682 ]
2683 );
2684
2685 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2686 buffer.read_with(cx, |buffer, cx| {
2687 writer.fold_buffers([buffer_id_2], buffer, cx);
2688 });
2689 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2690 let blocks = blocks_snapshot
2691 .blocks_in_range(0..u32::MAX)
2692 .collect::<Vec<_>>();
2693 for (_, block) in &blocks {
2694 if let BlockId::Custom(custom_block_id) = block.id() {
2695 assert!(
2696 !excerpt_blocks_1.contains(&custom_block_id),
2697 "Should have no blocks from the folded buffer_1"
2698 );
2699 assert!(
2700 !excerpt_blocks_2.contains(&custom_block_id),
2701 "Should have no blocks from the folded buffer_2"
2702 );
2703 assert!(
2704 excerpt_blocks_3.contains(&custom_block_id),
2705 "Should have only blocks from unfolded buffers"
2706 );
2707 }
2708 }
2709 assert_eq!(
2710 2,
2711 blocks
2712 .iter()
2713 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2714 .count(),
2715 "Should have two folded blocks, producing headers"
2716 );
2717 assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n555\n\n666\n");
2718 assert_eq!(
2719 blocks_snapshot
2720 .row_infos(BlockRow(0))
2721 .map(|i| i.buffer_row)
2722 .collect::<Vec<_>>(),
2723 vec![
2724 None,
2725 None,
2726 None,
2727 None,
2728 None,
2729 None,
2730 None,
2731 Some(4),
2732 None,
2733 Some(5),
2734 None,
2735 ]
2736 );
2737
2738 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2739 buffer.read_with(cx, |buffer, cx| {
2740 writer.unfold_buffers([buffer_id_1], buffer, cx);
2741 });
2742 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2743 let blocks = blocks_snapshot
2744 .blocks_in_range(0..u32::MAX)
2745 .collect::<Vec<_>>();
2746 for (_, block) in &blocks {
2747 if let BlockId::Custom(custom_block_id) = block.id() {
2748 assert!(
2749 !excerpt_blocks_2.contains(&custom_block_id),
2750 "Should have no blocks from the folded buffer_2"
2751 );
2752 assert!(
2753 excerpt_blocks_1.contains(&custom_block_id)
2754 || excerpt_blocks_3.contains(&custom_block_id),
2755 "Should have only blocks from unfolded buffers"
2756 );
2757 }
2758 }
2759 assert_eq!(
2760 1,
2761 blocks
2762 .iter()
2763 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2764 .count(),
2765 "Should be back to a single folded buffer, producing a header for buffer_2"
2766 );
2767 assert_eq!(
2768 blocks_snapshot.text(),
2769 "\n\n\n111\n\n\n\n\n\n555\n\n666\n",
2770 "Should have extra newline for 111 buffer, due to a new block added when it was folded"
2771 );
2772 assert_eq!(
2773 blocks_snapshot
2774 .row_infos(BlockRow(0))
2775 .map(|i| i.buffer_row)
2776 .collect::<Vec<_>>(),
2777 vec![
2778 None,
2779 None,
2780 None,
2781 Some(0),
2782 None,
2783 None,
2784 None,
2785 None,
2786 None,
2787 Some(4),
2788 None,
2789 Some(5),
2790 None,
2791 ]
2792 );
2793
2794 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2795 buffer.read_with(cx, |buffer, cx| {
2796 writer.fold_buffers([buffer_id_3], buffer, cx);
2797 });
2798 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2799 let blocks = blocks_snapshot
2800 .blocks_in_range(0..u32::MAX)
2801 .collect::<Vec<_>>();
2802 for (_, block) in &blocks {
2803 if let BlockId::Custom(custom_block_id) = block.id() {
2804 assert!(
2805 excerpt_blocks_1.contains(&custom_block_id),
2806 "Should have no blocks from the folded buffer_1"
2807 );
2808 assert!(
2809 !excerpt_blocks_2.contains(&custom_block_id),
2810 "Should have only blocks from unfolded buffers"
2811 );
2812 assert!(
2813 !excerpt_blocks_3.contains(&custom_block_id),
2814 "Should have only blocks from unfolded buffers"
2815 );
2816 }
2817 }
2818
2819 assert_eq!(
2820 blocks_snapshot.text(),
2821 "\n\n\n111\n\n\n\n",
2822 "Should have a single, first buffer left after folding"
2823 );
2824 assert_eq!(
2825 blocks_snapshot
2826 .row_infos(BlockRow(0))
2827 .map(|i| i.buffer_row)
2828 .collect::<Vec<_>>(),
2829 vec![None, None, None, Some(0), None, None, None, None,]
2830 );
2831 }
2832
2833 #[gpui::test]
2834 fn test_basic_buffer_fold(cx: &mut gpui::TestAppContext) {
2835 cx.update(init_test);
2836
2837 let text = "111";
2838
2839 let buffer = cx.update(|cx| {
2840 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(0, 3)])], cx)
2841 });
2842 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2843 let buffer_ids = buffer_snapshot
2844 .excerpts()
2845 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2846 .dedup()
2847 .collect::<Vec<_>>();
2848 assert_eq!(buffer_ids.len(), 1);
2849 let buffer_id = buffer_ids[0];
2850
2851 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2852 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2853 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2854 let (_, wrap_snapshot) =
2855 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2856 let mut block_map = BlockMap::new(wrap_snapshot.clone(), 2, 1);
2857 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2858
2859 assert_eq!(blocks_snapshot.text(), "\n\n111");
2860
2861 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2862 buffer.read_with(cx, |buffer, cx| {
2863 writer.fold_buffers([buffer_id], buffer, cx);
2864 });
2865 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2866 let blocks = blocks_snapshot
2867 .blocks_in_range(0..u32::MAX)
2868 .collect::<Vec<_>>();
2869 assert_eq!(
2870 1,
2871 blocks
2872 .iter()
2873 .filter(|(_, block)| {
2874 match block {
2875 Block::FoldedBuffer { .. } => true,
2876 _ => false,
2877 }
2878 })
2879 .count(),
2880 "Should have one folded block, producing a header of the second buffer"
2881 );
2882 assert_eq!(blocks_snapshot.text(), "\n");
2883 assert_eq!(
2884 blocks_snapshot
2885 .row_infos(BlockRow(0))
2886 .map(|i| i.buffer_row)
2887 .collect::<Vec<_>>(),
2888 vec![None, None],
2889 "When fully folded, should be no buffer rows"
2890 );
2891 }
2892
2893 #[gpui::test(iterations = 100)]
2894 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
2895 cx.update(init_test);
2896
2897 let operations = env::var("OPERATIONS")
2898 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2899 .unwrap_or(10);
2900
2901 let wrap_width = if rng.gen_bool(0.2) {
2902 None
2903 } else {
2904 Some(px(rng.gen_range(0.0..=100.0)))
2905 };
2906 let tab_size = 1.try_into().unwrap();
2907 let font_size = px(14.0);
2908 let buffer_start_header_height = rng.gen_range(1..=5);
2909 let excerpt_header_height = rng.gen_range(1..=5);
2910
2911 log::info!("Wrap width: {:?}", wrap_width);
2912 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
2913 let is_singleton = rng.r#gen();
2914 let buffer = if is_singleton {
2915 let len = rng.gen_range(0..10);
2916 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
2917 log::info!("initial singleton buffer text: {:?}", text);
2918 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
2919 } else {
2920 cx.update(|cx| {
2921 let multibuffer = MultiBuffer::build_random(&mut rng, cx);
2922 log::info!(
2923 "initial multi-buffer text: {:?}",
2924 multibuffer.read(cx).read(cx).text()
2925 );
2926 multibuffer
2927 })
2928 };
2929
2930 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2931 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2932 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2933 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2934 let font = test_font();
2935 let (wrap_map, wraps_snapshot) =
2936 cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
2937 let mut block_map = BlockMap::new(
2938 wraps_snapshot,
2939 buffer_start_header_height,
2940 excerpt_header_height,
2941 );
2942
2943 for _ in 0..operations {
2944 let mut buffer_edits = Vec::new();
2945 match rng.gen_range(0..=100) {
2946 0..=19 => {
2947 let wrap_width = if rng.gen_bool(0.2) {
2948 None
2949 } else {
2950 Some(px(rng.gen_range(0.0..=100.0)))
2951 };
2952 log::info!("Setting wrap width to {:?}", wrap_width);
2953 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
2954 }
2955 20..=39 => {
2956 let block_count = rng.gen_range(1..=5);
2957 let block_properties = (0..block_count)
2958 .map(|_| {
2959 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
2960 let offset =
2961 buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left);
2962 let mut min_height = 0;
2963 let placement = match rng.gen_range(0..3) {
2964 0 => {
2965 min_height = 1;
2966 let start = buffer.anchor_after(offset);
2967 let end = buffer.anchor_after(buffer.clip_offset(
2968 rng.gen_range(offset..=buffer.len()),
2969 Bias::Left,
2970 ));
2971 BlockPlacement::Replace(start..=end)
2972 }
2973 1 => BlockPlacement::Above(buffer.anchor_after(offset)),
2974 _ => BlockPlacement::Below(buffer.anchor_after(offset)),
2975 };
2976
2977 let height = rng.gen_range(min_height..5);
2978 BlockProperties {
2979 style: BlockStyle::Fixed,
2980 placement,
2981 height: Some(height),
2982 render: Arc::new(|_| div().into_any()),
2983 priority: 0,
2984 }
2985 })
2986 .collect::<Vec<_>>();
2987
2988 let (inlay_snapshot, inlay_edits) =
2989 inlay_map.sync(buffer_snapshot.clone(), vec![]);
2990 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2991 let (tab_snapshot, tab_edits) =
2992 tab_map.sync(fold_snapshot, fold_edits, tab_size);
2993 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2994 wrap_map.sync(tab_snapshot, tab_edits, cx)
2995 });
2996 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
2997 let block_ids =
2998 block_map.insert(block_properties.iter().map(|props| BlockProperties {
2999 placement: props.placement.clone(),
3000 height: props.height,
3001 style: props.style,
3002 render: Arc::new(|_| div().into_any()),
3003 priority: 0,
3004 }));
3005
3006 for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3007 log::info!(
3008 "inserted block {:?} with height {:?} and id {:?}",
3009 block_properties
3010 .placement
3011 .as_ref()
3012 .map(|p| p.to_point(&buffer_snapshot)),
3013 block_properties.height,
3014 block_id
3015 );
3016 }
3017 }
3018 40..=59 if !block_map.custom_blocks.is_empty() => {
3019 let block_count = rng.gen_range(1..=4.min(block_map.custom_blocks.len()));
3020 let block_ids_to_remove = block_map
3021 .custom_blocks
3022 .choose_multiple(&mut rng, block_count)
3023 .map(|block| block.id)
3024 .collect::<HashSet<_>>();
3025
3026 let (inlay_snapshot, inlay_edits) =
3027 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3028 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3029 let (tab_snapshot, tab_edits) =
3030 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3031 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3032 wrap_map.sync(tab_snapshot, tab_edits, cx)
3033 });
3034 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3035 log::info!(
3036 "removing {} blocks: {:?}",
3037 block_ids_to_remove.len(),
3038 block_ids_to_remove
3039 );
3040 block_map.remove(block_ids_to_remove);
3041 }
3042 60..=79 => {
3043 if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
3044 log::info!("Noop fold/unfold operation on a singleton buffer");
3045 continue;
3046 }
3047 let (inlay_snapshot, inlay_edits) =
3048 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3049 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3050 let (tab_snapshot, tab_edits) =
3051 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3052 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3053 wrap_map.sync(tab_snapshot, tab_edits, cx)
3054 });
3055 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3056 let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
3057 let folded_buffers = block_map
3058 .0
3059 .folded_buffers
3060 .iter()
3061 .cloned()
3062 .collect::<Vec<_>>();
3063 let mut unfolded_buffers = buffer.excerpt_buffer_ids();
3064 unfolded_buffers.dedup();
3065 log::debug!("All buffers {unfolded_buffers:?}");
3066 log::debug!("Folded buffers {folded_buffers:?}");
3067 unfolded_buffers
3068 .retain(|buffer_id| !block_map.0.folded_buffers.contains(buffer_id));
3069 (unfolded_buffers, folded_buffers)
3070 });
3071 let mut folded_count = folded_buffers.len();
3072 let mut unfolded_count = unfolded_buffers.len();
3073
3074 let fold = !unfolded_buffers.is_empty() && rng.gen_bool(0.5);
3075 let unfold = !folded_buffers.is_empty() && rng.gen_bool(0.5);
3076 if !fold && !unfold {
3077 log::info!(
3078 "Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3079 );
3080 continue;
3081 }
3082
3083 buffer.update(cx, |buffer, cx| {
3084 if fold {
3085 let buffer_to_fold =
3086 unfolded_buffers[rng.gen_range(0..unfolded_buffers.len())];
3087 log::info!("Folding {buffer_to_fold:?}");
3088 let related_excerpts = buffer_snapshot
3089 .excerpts()
3090 .filter_map(|(excerpt_id, buffer, range)| {
3091 if buffer.remote_id() == buffer_to_fold {
3092 Some((
3093 excerpt_id,
3094 buffer
3095 .text_for_range(range.context)
3096 .collect::<String>(),
3097 ))
3098 } else {
3099 None
3100 }
3101 })
3102 .collect::<Vec<_>>();
3103 log::info!(
3104 "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
3105 );
3106 folded_count += 1;
3107 unfolded_count -= 1;
3108 block_map.fold_buffers([buffer_to_fold], buffer, cx);
3109 }
3110 if unfold {
3111 let buffer_to_unfold =
3112 folded_buffers[rng.gen_range(0..folded_buffers.len())];
3113 log::info!("Unfolding {buffer_to_unfold:?}");
3114 unfolded_count += 1;
3115 folded_count -= 1;
3116 block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
3117 }
3118 log::info!(
3119 "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3120 );
3121 });
3122 }
3123 _ => {
3124 buffer.update(cx, |buffer, cx| {
3125 let mutation_count = rng.gen_range(1..=5);
3126 let subscription = buffer.subscribe();
3127 buffer.randomly_mutate(&mut rng, mutation_count, cx);
3128 buffer_snapshot = buffer.snapshot(cx);
3129 buffer_edits.extend(subscription.consume());
3130 log::info!("buffer text: {:?}", buffer_snapshot.text());
3131 });
3132 }
3133 }
3134
3135 let (inlay_snapshot, inlay_edits) =
3136 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
3137 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3138 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3139 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3140 wrap_map.sync(tab_snapshot, tab_edits, cx)
3141 });
3142 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
3143 assert_eq!(
3144 blocks_snapshot.transforms.summary().input_rows,
3145 wraps_snapshot.max_point().row() + 1
3146 );
3147 log::info!("wrapped text: {:?}", wraps_snapshot.text());
3148 log::info!("blocks text: {:?}", blocks_snapshot.text());
3149
3150 let mut expected_blocks = Vec::new();
3151 expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
3152 Some((
3153 block.placement.to_wrap_row(&wraps_snapshot)?,
3154 Block::Custom(block.clone()),
3155 ))
3156 }));
3157
3158 // Note that this needs to be synced with the related section in BlockMap::sync
3159 expected_blocks.extend(block_map.header_and_footer_blocks(
3160 &buffer_snapshot,
3161 0..,
3162 &wraps_snapshot,
3163 ));
3164
3165 BlockMap::sort_blocks(&mut expected_blocks);
3166
3167 for (placement, block) in &expected_blocks {
3168 log::info!(
3169 "Block {:?} placement: {:?} Height: {:?}",
3170 block.id(),
3171 placement,
3172 block.height()
3173 );
3174 }
3175
3176 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
3177
3178 let input_buffer_rows = buffer_snapshot
3179 .row_infos(MultiBufferRow(0))
3180 .map(|row| row.buffer_row)
3181 .collect::<Vec<_>>();
3182 let mut expected_buffer_rows = Vec::new();
3183 let mut expected_text = String::new();
3184 let mut expected_block_positions = Vec::new();
3185 let mut expected_replaced_buffer_rows = HashSet::default();
3186 let input_text = wraps_snapshot.text();
3187
3188 // Loop over the input lines, creating (N - 1) empty lines for
3189 // blocks of height N.
3190 //
3191 // It's important to note that output *starts* as one empty line,
3192 // so we special case row 0 to assume a leading '\n'.
3193 //
3194 // Linehood is the birthright of strings.
3195 let mut input_text_lines = input_text.split('\n').enumerate().peekable();
3196 let mut block_row = 0;
3197 while let Some((wrap_row, input_line)) = input_text_lines.next() {
3198 let wrap_row = wrap_row as u32;
3199 let multibuffer_row = wraps_snapshot
3200 .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
3201 .row;
3202
3203 // Create empty lines for the above block
3204 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3205 if placement.start().0 == wrap_row && block.place_above() {
3206 let (_, block) = sorted_blocks_iter.next().unwrap();
3207 expected_block_positions.push((block_row, block.id()));
3208 if block.height() > 0 {
3209 let text = "\n".repeat((block.height() - 1) as usize);
3210 if block_row > 0 {
3211 expected_text.push('\n')
3212 }
3213 expected_text.push_str(&text);
3214 for _ in 0..block.height() {
3215 expected_buffer_rows.push(None);
3216 }
3217 block_row += block.height();
3218 }
3219 } else {
3220 break;
3221 }
3222 }
3223
3224 // Skip lines within replace blocks, then create empty lines for the replace block's height
3225 let mut is_in_replace_block = false;
3226 if let Some((BlockPlacement::Replace(replace_range), block)) =
3227 sorted_blocks_iter.peek()
3228 {
3229 if wrap_row >= replace_range.start().0 {
3230 is_in_replace_block = true;
3231
3232 if wrap_row == replace_range.start().0 {
3233 if matches!(block, Block::FoldedBuffer { .. }) {
3234 expected_buffer_rows.push(None);
3235 } else {
3236 expected_buffer_rows
3237 .push(input_buffer_rows[multibuffer_row as usize]);
3238 }
3239 }
3240
3241 if wrap_row == replace_range.end().0 {
3242 expected_block_positions.push((block_row, block.id()));
3243 let text = "\n".repeat((block.height() - 1) as usize);
3244 if block_row > 0 {
3245 expected_text.push('\n');
3246 }
3247 expected_text.push_str(&text);
3248
3249 for _ in 1..block.height() {
3250 expected_buffer_rows.push(None);
3251 }
3252 block_row += block.height();
3253
3254 sorted_blocks_iter.next();
3255 }
3256 }
3257 }
3258
3259 if is_in_replace_block {
3260 expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
3261 } else {
3262 let buffer_row = input_buffer_rows[multibuffer_row as usize];
3263 let soft_wrapped = wraps_snapshot
3264 .to_tab_point(WrapPoint::new(wrap_row, 0))
3265 .column()
3266 > 0;
3267 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
3268 if block_row > 0 {
3269 expected_text.push('\n');
3270 }
3271 expected_text.push_str(input_line);
3272 block_row += 1;
3273 }
3274
3275 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3276 if placement.end().0 == wrap_row && block.place_below() {
3277 let (_, block) = sorted_blocks_iter.next().unwrap();
3278 expected_block_positions.push((block_row, block.id()));
3279 if block.height() > 0 {
3280 let text = "\n".repeat((block.height() - 1) as usize);
3281 if block_row > 0 {
3282 expected_text.push('\n')
3283 }
3284 expected_text.push_str(&text);
3285 for _ in 0..block.height() {
3286 expected_buffer_rows.push(None);
3287 }
3288 block_row += block.height();
3289 }
3290 } else {
3291 break;
3292 }
3293 }
3294 }
3295
3296 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
3297 let expected_row_count = expected_lines.len();
3298 log::info!("expected text: {expected_text:?}");
3299
3300 assert_eq!(
3301 blocks_snapshot.max_point().row + 1,
3302 expected_row_count as u32,
3303 "actual row count != expected row count",
3304 );
3305 assert_eq!(
3306 blocks_snapshot.text(),
3307 expected_text,
3308 "actual text != expected text",
3309 );
3310
3311 for start_row in 0..expected_row_count {
3312 let end_row = rng.gen_range(start_row + 1..=expected_row_count);
3313 let mut expected_text = expected_lines[start_row..end_row].join("\n");
3314 if end_row < expected_row_count {
3315 expected_text.push('\n');
3316 }
3317
3318 let actual_text = blocks_snapshot
3319 .chunks(
3320 start_row as u32..end_row as u32,
3321 false,
3322 false,
3323 Highlights::default(),
3324 )
3325 .map(|chunk| chunk.text)
3326 .collect::<String>();
3327 assert_eq!(
3328 actual_text,
3329 expected_text,
3330 "incorrect text starting row row range {:?}",
3331 start_row..end_row
3332 );
3333 assert_eq!(
3334 blocks_snapshot
3335 .row_infos(BlockRow(start_row as u32))
3336 .map(|row_info| row_info.buffer_row)
3337 .collect::<Vec<_>>(),
3338 &expected_buffer_rows[start_row..],
3339 "incorrect buffer_rows starting at row {:?}",
3340 start_row
3341 );
3342 }
3343
3344 assert_eq!(
3345 blocks_snapshot
3346 .blocks_in_range(0..(expected_row_count as u32))
3347 .map(|(row, block)| (row, block.id()))
3348 .collect::<Vec<_>>(),
3349 expected_block_positions,
3350 "invalid blocks_in_range({:?})",
3351 0..expected_row_count
3352 );
3353
3354 for (_, expected_block) in
3355 blocks_snapshot.blocks_in_range(0..(expected_row_count as u32))
3356 {
3357 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
3358 assert_eq!(
3359 actual_block.map(|block| block.id()),
3360 Some(expected_block.id())
3361 );
3362 }
3363
3364 for (block_row, block_id) in expected_block_positions {
3365 if let BlockId::Custom(block_id) = block_id {
3366 assert_eq!(
3367 blocks_snapshot.row_for_block(block_id),
3368 Some(BlockRow(block_row))
3369 );
3370 }
3371 }
3372
3373 let mut expected_longest_rows = Vec::new();
3374 let mut longest_line_len = -1_isize;
3375 for (row, line) in expected_lines.iter().enumerate() {
3376 let row = row as u32;
3377
3378 assert_eq!(
3379 blocks_snapshot.line_len(BlockRow(row)),
3380 line.len() as u32,
3381 "invalid line len for row {}",
3382 row
3383 );
3384
3385 let line_char_count = line.chars().count() as isize;
3386 match line_char_count.cmp(&longest_line_len) {
3387 Ordering::Less => {}
3388 Ordering::Equal => expected_longest_rows.push(row),
3389 Ordering::Greater => {
3390 longest_line_len = line_char_count;
3391 expected_longest_rows.clear();
3392 expected_longest_rows.push(row);
3393 }
3394 }
3395 }
3396
3397 let longest_row = blocks_snapshot.longest_row();
3398 assert!(
3399 expected_longest_rows.contains(&longest_row),
3400 "incorrect longest row {}. expected {:?} with length {}",
3401 longest_row,
3402 expected_longest_rows,
3403 longest_line_len,
3404 );
3405
3406 for _ in 0..10 {
3407 let end_row = rng.gen_range(1..=expected_lines.len());
3408 let start_row = rng.gen_range(0..end_row);
3409
3410 let mut expected_longest_rows_in_range = vec![];
3411 let mut longest_line_len_in_range = 0;
3412
3413 let mut row = start_row as u32;
3414 for line in &expected_lines[start_row..end_row] {
3415 let line_char_count = line.chars().count() as isize;
3416 match line_char_count.cmp(&longest_line_len_in_range) {
3417 Ordering::Less => {}
3418 Ordering::Equal => expected_longest_rows_in_range.push(row),
3419 Ordering::Greater => {
3420 longest_line_len_in_range = line_char_count;
3421 expected_longest_rows_in_range.clear();
3422 expected_longest_rows_in_range.push(row);
3423 }
3424 }
3425 row += 1;
3426 }
3427
3428 let longest_row_in_range = blocks_snapshot
3429 .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
3430 assert!(
3431 expected_longest_rows_in_range.contains(&longest_row_in_range.0),
3432 "incorrect longest row {} in range {:?}. expected {:?} with length {}",
3433 longest_row,
3434 start_row..end_row,
3435 expected_longest_rows_in_range,
3436 longest_line_len_in_range,
3437 );
3438 }
3439
3440 // Ensure that conversion between block points and wrap points is stable.
3441 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
3442 let wrap_point = WrapPoint::new(row, 0);
3443 let block_point = blocks_snapshot.to_block_point(wrap_point);
3444 let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
3445 let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
3446 assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
3447 assert_eq!(
3448 blocks_snapshot.to_block_point(right_wrap_point),
3449 block_point
3450 );
3451 }
3452
3453 let mut block_point = BlockPoint::new(0, 0);
3454 for c in expected_text.chars() {
3455 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
3456 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
3457 assert_eq!(
3458 blocks_snapshot
3459 .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
3460 left_point,
3461 "block point: {:?}, wrap point: {:?}",
3462 block_point,
3463 blocks_snapshot.to_wrap_point(left_point, Bias::Left)
3464 );
3465 assert_eq!(
3466 left_buffer_point,
3467 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
3468 "{:?} is not valid in buffer coordinates",
3469 left_point
3470 );
3471
3472 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
3473 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
3474 assert_eq!(
3475 blocks_snapshot
3476 .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
3477 right_point,
3478 "block point: {:?}, wrap point: {:?}",
3479 block_point,
3480 blocks_snapshot.to_wrap_point(right_point, Bias::Right)
3481 );
3482 assert_eq!(
3483 right_buffer_point,
3484 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
3485 "{:?} is not valid in buffer coordinates",
3486 right_point
3487 );
3488
3489 if c == '\n' {
3490 block_point.0 += Point::new(1, 0);
3491 } else {
3492 block_point.column += c.len_utf8() as u32;
3493 }
3494 }
3495
3496 for buffer_row in 0..=buffer_snapshot.max_point().row {
3497 let buffer_row = MultiBufferRow(buffer_row);
3498 assert_eq!(
3499 blocks_snapshot.is_line_replaced(buffer_row),
3500 expected_replaced_buffer_rows.contains(&buffer_row),
3501 "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
3502 );
3503 }
3504 }
3505 }
3506
3507 #[gpui::test]
3508 fn test_remove_intersecting_replace_blocks_edge_case(cx: &mut gpui::TestAppContext) {
3509 cx.update(init_test);
3510
3511 let text = "abc\ndef\nghi\njkl\nmno";
3512 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
3513 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3514 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3515 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3516 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3517 let (_wrap_map, wraps_snapshot) =
3518 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
3519 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
3520
3521 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
3522 let _block_id = writer.insert(vec![BlockProperties {
3523 style: BlockStyle::Fixed,
3524 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
3525 height: Some(1),
3526 render: Arc::new(|_| div().into_any()),
3527 priority: 0,
3528 }])[0];
3529
3530 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
3531 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
3532
3533 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
3534 writer.remove_intersecting_replace_blocks(
3535 [buffer_snapshot.anchor_after(Point::new(1, 0))
3536 ..buffer_snapshot.anchor_after(Point::new(1, 0))],
3537 false,
3538 );
3539 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
3540 assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
3541 }
3542
3543 fn init_test(cx: &mut gpui::App) {
3544 let settings = SettingsStore::test(cx);
3545 cx.set_global(settings);
3546 theme::init(theme::LoadThemes::JustBase, cx);
3547 assets::Assets.load_test_fonts(cx);
3548 }
3549
3550 impl Block {
3551 fn as_custom(&self) -> Option<&CustomBlock> {
3552 match self {
3553 Block::Custom(block) => Some(block),
3554 _ => None,
3555 }
3556 }
3557 }
3558
3559 impl BlockSnapshot {
3560 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
3561 self.wrap_snapshot
3562 .to_point(self.to_wrap_point(point, bias), bias)
3563 }
3564 }
3565}