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