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