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