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