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