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<'a> Deref for BlockMapReader<'a> {
1003 type Target = BlockSnapshot;
1004
1005 fn deref(&self) -> &Self::Target {
1006 &self.snapshot
1007 }
1008}
1009
1010impl<'a> DerefMut for BlockMapReader<'a> {
1011 fn deref_mut(&mut self) -> &mut Self::Target {
1012 &mut self.snapshot
1013 }
1014}
1015
1016impl<'a> BlockMapReader<'a> {
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<'a> BlockMapWriter<'a> {
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_buffer(&mut self, buffer_id: BufferId, multi_buffer: &MultiBuffer, cx: &App) {
1243 self.0.folded_buffers.insert(buffer_id);
1244 self.recompute_blocks_for_buffer(buffer_id, multi_buffer, cx);
1245 }
1246
1247 pub fn unfold_buffer(&mut self, buffer_id: BufferId, multi_buffer: &MultiBuffer, cx: &App) {
1248 self.0.folded_buffers.remove(&buffer_id);
1249 self.recompute_blocks_for_buffer(buffer_id, multi_buffer, cx);
1250 }
1251
1252 fn recompute_blocks_for_buffer(
1253 &mut self,
1254 buffer_id: BufferId,
1255 multi_buffer: &MultiBuffer,
1256 cx: &App,
1257 ) {
1258 let wrap_snapshot = self.0.wrap_snapshot.borrow().clone();
1259
1260 let mut edits = Patch::default();
1261 for range in multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx) {
1262 let last_edit_row = cmp::min(
1263 wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + 1,
1264 wrap_snapshot.max_point().row(),
1265 ) + 1;
1266 let range = wrap_snapshot.make_wrap_point(range.start, Bias::Left).row()..last_edit_row;
1267 edits.push(Edit {
1268 old: range.clone(),
1269 new: range,
1270 });
1271 }
1272
1273 self.0.sync(&wrap_snapshot, edits);
1274 }
1275
1276 fn blocks_intersecting_buffer_range(
1277 &self,
1278 range: Range<usize>,
1279 inclusive: bool,
1280 ) -> &[Arc<CustomBlock>] {
1281 let wrap_snapshot = self.0.wrap_snapshot.borrow();
1282 let buffer = wrap_snapshot.buffer_snapshot();
1283 let start_block_ix = match self.0.custom_blocks.binary_search_by(|probe| {
1284 probe
1285 .end()
1286 .to_offset(buffer)
1287 .cmp(&range.start)
1288 .then(if inclusive {
1289 Ordering::Greater
1290 } else {
1291 Ordering::Less
1292 })
1293 }) {
1294 Ok(ix) | Err(ix) => ix,
1295 };
1296 let end_block_ix = match self.0.custom_blocks.binary_search_by(|probe| {
1297 probe
1298 .start()
1299 .to_offset(buffer)
1300 .cmp(&range.end)
1301 .then(if inclusive {
1302 Ordering::Less
1303 } else {
1304 Ordering::Greater
1305 })
1306 }) {
1307 Ok(ix) | Err(ix) => ix,
1308 };
1309 &self.0.custom_blocks[start_block_ix..end_block_ix]
1310 }
1311}
1312
1313impl BlockSnapshot {
1314 #[cfg(test)]
1315 pub fn text(&self) -> String {
1316 self.chunks(
1317 0..self.transforms.summary().output_rows,
1318 false,
1319 false,
1320 Highlights::default(),
1321 )
1322 .map(|chunk| chunk.text)
1323 .collect()
1324 }
1325
1326 pub(crate) fn chunks<'a>(
1327 &'a self,
1328 rows: Range<u32>,
1329 language_aware: bool,
1330 masked: bool,
1331 highlights: Highlights<'a>,
1332 ) -> BlockChunks<'a> {
1333 let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
1334
1335 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1336 cursor.seek(&BlockRow(rows.start), Bias::Right, &());
1337 let transform_output_start = cursor.start().0 .0;
1338 let transform_input_start = cursor.start().1 .0;
1339
1340 let mut input_start = transform_input_start;
1341 let mut input_end = transform_input_start;
1342 if let Some(transform) = cursor.item() {
1343 if transform.block.is_none() {
1344 input_start += rows.start - transform_output_start;
1345 input_end += cmp::min(
1346 rows.end - transform_output_start,
1347 transform.summary.input_rows,
1348 );
1349 }
1350 }
1351
1352 BlockChunks {
1353 input_chunks: self.wrap_snapshot.chunks(
1354 input_start..input_end,
1355 language_aware,
1356 highlights,
1357 ),
1358 input_chunk: Default::default(),
1359 transforms: cursor,
1360 output_row: rows.start,
1361 max_output_row,
1362 masked,
1363 }
1364 }
1365
1366 pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows {
1367 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1368 cursor.seek(&start_row, Bias::Right, &());
1369 let (output_start, input_start) = cursor.start();
1370 let overshoot = if cursor
1371 .item()
1372 .map_or(false, |transform| transform.block.is_none())
1373 {
1374 start_row.0 - output_start.0
1375 } else {
1376 0
1377 };
1378 let input_start_row = input_start.0 + overshoot;
1379 BlockRows {
1380 transforms: cursor,
1381 input_rows: self.wrap_snapshot.row_infos(input_start_row),
1382 output_row: start_row,
1383 started: false,
1384 }
1385 }
1386
1387 pub fn blocks_in_range(&self, rows: Range<u32>) -> impl Iterator<Item = (u32, &Block)> {
1388 let mut cursor = self.transforms.cursor::<BlockRow>(&());
1389 cursor.seek(&BlockRow(rows.start), Bias::Left, &());
1390 while cursor.start().0 < rows.start && cursor.end(&()).0 <= rows.start {
1391 cursor.next(&());
1392 }
1393
1394 std::iter::from_fn(move || {
1395 while let Some(transform) = cursor.item() {
1396 let start_row = cursor.start().0;
1397 if start_row > rows.end
1398 || (start_row == rows.end
1399 && transform
1400 .block
1401 .as_ref()
1402 .map_or(false, |block| block.height() > 0))
1403 {
1404 break;
1405 }
1406 if let Some(block) = &transform.block {
1407 cursor.next(&());
1408 return Some((start_row, block));
1409 } else {
1410 cursor.next(&());
1411 }
1412 }
1413 None
1414 })
1415 }
1416
1417 pub fn sticky_header_excerpt(&self, top_row: u32) -> Option<StickyHeaderExcerpt<'_>> {
1418 let mut cursor = self.transforms.cursor::<BlockRow>(&());
1419 cursor.seek(&BlockRow(top_row), Bias::Left, &());
1420
1421 while let Some(transform) = cursor.item() {
1422 let start = cursor.start().0;
1423 let end = cursor.end(&()).0;
1424
1425 match &transform.block {
1426 Some(Block::ExcerptBoundary {
1427 prev_excerpt,
1428 next_excerpt,
1429 starts_new_buffer,
1430 show_excerpt_controls,
1431 ..
1432 }) => {
1433 let matches_start = if *show_excerpt_controls && prev_excerpt.is_some() {
1434 start < top_row
1435 } else {
1436 start <= top_row
1437 };
1438
1439 if matches_start && top_row <= end {
1440 return next_excerpt.as_ref().map(|excerpt| StickyHeaderExcerpt {
1441 next_buffer_row: None,
1442 next_excerpt_controls_present: *show_excerpt_controls,
1443 excerpt,
1444 });
1445 }
1446
1447 let next_buffer_row = if *starts_new_buffer { Some(end) } else { None };
1448
1449 return prev_excerpt.as_ref().map(|excerpt| StickyHeaderExcerpt {
1450 excerpt,
1451 next_buffer_row,
1452 next_excerpt_controls_present: *show_excerpt_controls,
1453 });
1454 }
1455 Some(Block::FoldedBuffer {
1456 prev_excerpt: Some(excerpt),
1457 ..
1458 }) if top_row <= start => {
1459 return Some(StickyHeaderExcerpt {
1460 next_buffer_row: Some(end),
1461 next_excerpt_controls_present: false,
1462 excerpt,
1463 });
1464 }
1465 Some(Block::FoldedBuffer { .. }) | Some(Block::Custom(_)) | None => {}
1466 }
1467
1468 // This is needed to iterate past None / FoldedBuffer / Custom blocks. For FoldedBuffer,
1469 // if scrolled slightly past the header of a folded block, the next block is needed for
1470 // the sticky header.
1471 cursor.next(&());
1472 }
1473
1474 None
1475 }
1476
1477 pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
1478 let buffer = self.wrap_snapshot.buffer_snapshot();
1479 let wrap_point = match block_id {
1480 BlockId::Custom(custom_block_id) => {
1481 let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
1482 return Some(Block::Custom(custom_block.clone()));
1483 }
1484 BlockId::ExcerptBoundary(next_excerpt_id) => {
1485 if let Some(next_excerpt_id) = next_excerpt_id {
1486 let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
1487 self.wrap_snapshot
1488 .make_wrap_point(excerpt_range.start, Bias::Left)
1489 } else {
1490 self.wrap_snapshot
1491 .make_wrap_point(buffer.max_point(), Bias::Left)
1492 }
1493 }
1494 BlockId::FoldedBuffer(excerpt_id) => self
1495 .wrap_snapshot
1496 .make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left),
1497 };
1498 let wrap_row = WrapRow(wrap_point.row());
1499
1500 let mut cursor = self.transforms.cursor::<WrapRow>(&());
1501 cursor.seek(&wrap_row, Bias::Left, &());
1502
1503 while let Some(transform) = cursor.item() {
1504 if let Some(block) = transform.block.as_ref() {
1505 if block.id() == block_id {
1506 return Some(block.clone());
1507 }
1508 } else if *cursor.start() > wrap_row {
1509 break;
1510 }
1511
1512 cursor.next(&());
1513 }
1514
1515 None
1516 }
1517
1518 pub fn max_point(&self) -> BlockPoint {
1519 let row = self.transforms.summary().output_rows.saturating_sub(1);
1520 BlockPoint::new(row, self.line_len(BlockRow(row)))
1521 }
1522
1523 pub fn longest_row(&self) -> u32 {
1524 self.transforms.summary().longest_row
1525 }
1526
1527 pub fn longest_row_in_range(&self, range: Range<BlockRow>) -> BlockRow {
1528 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1529 cursor.seek(&range.start, Bias::Right, &());
1530
1531 let mut longest_row = range.start;
1532 let mut longest_row_chars = 0;
1533 if let Some(transform) = cursor.item() {
1534 if transform.block.is_none() {
1535 let (output_start, input_start) = cursor.start();
1536 let overshoot = range.start.0 - output_start.0;
1537 let wrap_start_row = input_start.0 + overshoot;
1538 let wrap_end_row = cmp::min(
1539 input_start.0 + (range.end.0 - output_start.0),
1540 cursor.end(&()).1 .0,
1541 );
1542 let summary = self
1543 .wrap_snapshot
1544 .text_summary_for_range(wrap_start_row..wrap_end_row);
1545 longest_row = BlockRow(range.start.0 + summary.longest_row);
1546 longest_row_chars = summary.longest_row_chars;
1547 }
1548 cursor.next(&());
1549 }
1550
1551 let cursor_start_row = cursor.start().0;
1552 if range.end > cursor_start_row {
1553 let summary = cursor.summary::<_, TransformSummary>(&range.end, Bias::Right, &());
1554 if summary.longest_row_chars > longest_row_chars {
1555 longest_row = BlockRow(cursor_start_row.0 + summary.longest_row);
1556 longest_row_chars = summary.longest_row_chars;
1557 }
1558
1559 if let Some(transform) = cursor.item() {
1560 if transform.block.is_none() {
1561 let (output_start, input_start) = cursor.start();
1562 let overshoot = range.end.0 - output_start.0;
1563 let wrap_start_row = input_start.0;
1564 let wrap_end_row = input_start.0 + overshoot;
1565 let summary = self
1566 .wrap_snapshot
1567 .text_summary_for_range(wrap_start_row..wrap_end_row);
1568 if summary.longest_row_chars > longest_row_chars {
1569 longest_row = BlockRow(output_start.0 + summary.longest_row);
1570 }
1571 }
1572 }
1573 }
1574
1575 longest_row
1576 }
1577
1578 pub(super) fn line_len(&self, row: BlockRow) -> u32 {
1579 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1580 cursor.seek(&BlockRow(row.0), Bias::Right, &());
1581 if let Some(transform) = cursor.item() {
1582 let (output_start, input_start) = cursor.start();
1583 let overshoot = row.0 - output_start.0;
1584 if transform.block.is_some() {
1585 0
1586 } else {
1587 self.wrap_snapshot.line_len(input_start.0 + overshoot)
1588 }
1589 } else if row.0 == 0 {
1590 0
1591 } else {
1592 panic!("row out of range");
1593 }
1594 }
1595
1596 pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
1597 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1598 cursor.seek(&row, Bias::Right, &());
1599 cursor.item().map_or(false, |t| t.block.is_some())
1600 }
1601
1602 pub(super) fn is_line_replaced(&self, row: MultiBufferRow) -> bool {
1603 let wrap_point = self
1604 .wrap_snapshot
1605 .make_wrap_point(Point::new(row.0, 0), Bias::Left);
1606 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
1607 cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
1608 cursor.item().map_or(false, |transform| {
1609 transform
1610 .block
1611 .as_ref()
1612 .map_or(false, |block| block.is_replacement())
1613 })
1614 }
1615
1616 pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
1617 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1618 cursor.seek(&BlockRow(point.row), Bias::Right, &());
1619
1620 let max_input_row = WrapRow(self.transforms.summary().input_rows);
1621 let mut search_left =
1622 (bias == Bias::Left && cursor.start().1 .0 > 0) || cursor.end(&()).1 == max_input_row;
1623 let mut reversed = false;
1624
1625 loop {
1626 if let Some(transform) = cursor.item() {
1627 let (output_start_row, input_start_row) = cursor.start();
1628 let (output_end_row, input_end_row) = cursor.end(&());
1629 let output_start = Point::new(output_start_row.0, 0);
1630 let input_start = Point::new(input_start_row.0, 0);
1631 let input_end = Point::new(input_end_row.0, 0);
1632
1633 match transform.block.as_ref() {
1634 Some(block) => {
1635 if block.is_replacement() {
1636 if ((bias == Bias::Left || search_left) && output_start <= point.0)
1637 || (!search_left && output_start >= point.0)
1638 {
1639 return BlockPoint(output_start);
1640 }
1641 }
1642 }
1643 None => {
1644 let input_point = if point.row >= output_end_row.0 {
1645 let line_len = self.wrap_snapshot.line_len(input_end_row.0 - 1);
1646 self.wrap_snapshot
1647 .clip_point(WrapPoint::new(input_end_row.0 - 1, line_len), bias)
1648 } else {
1649 let output_overshoot = point.0.saturating_sub(output_start);
1650 self.wrap_snapshot
1651 .clip_point(WrapPoint(input_start + output_overshoot), bias)
1652 };
1653
1654 if (input_start..input_end).contains(&input_point.0) {
1655 let input_overshoot = input_point.0.saturating_sub(input_start);
1656 return BlockPoint(output_start + input_overshoot);
1657 }
1658 }
1659 }
1660
1661 if search_left {
1662 cursor.prev(&());
1663 } else {
1664 cursor.next(&());
1665 }
1666 } else if reversed {
1667 return self.max_point();
1668 } else {
1669 reversed = true;
1670 search_left = !search_left;
1671 cursor.seek(&BlockRow(point.row), Bias::Right, &());
1672 }
1673 }
1674 }
1675
1676 pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
1677 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
1678 cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
1679 if let Some(transform) = cursor.item() {
1680 if transform.block.is_some() {
1681 BlockPoint::new(cursor.start().1 .0, 0)
1682 } else {
1683 let (input_start_row, output_start_row) = cursor.start();
1684 let input_start = Point::new(input_start_row.0, 0);
1685 let output_start = Point::new(output_start_row.0, 0);
1686 let input_overshoot = wrap_point.0 - input_start;
1687 BlockPoint(output_start + input_overshoot)
1688 }
1689 } else {
1690 self.max_point()
1691 }
1692 }
1693
1694 pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
1695 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1696 cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
1697 if let Some(transform) = cursor.item() {
1698 match transform.block.as_ref() {
1699 Some(block) => {
1700 if block.place_below() {
1701 let wrap_row = cursor.start().1 .0 - 1;
1702 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1703 } else if block.place_above() {
1704 WrapPoint::new(cursor.start().1 .0, 0)
1705 } else if bias == Bias::Left {
1706 WrapPoint::new(cursor.start().1 .0, 0)
1707 } else {
1708 let wrap_row = cursor.end(&()).1 .0 - 1;
1709 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1710 }
1711 }
1712 None => {
1713 let overshoot = block_point.row - cursor.start().0 .0;
1714 let wrap_row = cursor.start().1 .0 + overshoot;
1715 WrapPoint::new(wrap_row, block_point.column)
1716 }
1717 }
1718 } else {
1719 self.wrap_snapshot.max_point()
1720 }
1721 }
1722}
1723
1724impl<'a> BlockChunks<'a> {
1725 /// Go to the next transform
1726 fn advance(&mut self) {
1727 self.input_chunk = Chunk::default();
1728 self.transforms.next(&());
1729 while let Some(transform) = self.transforms.item() {
1730 if transform
1731 .block
1732 .as_ref()
1733 .map_or(false, |block| block.height() == 0)
1734 {
1735 self.transforms.next(&());
1736 } else {
1737 break;
1738 }
1739 }
1740
1741 if self
1742 .transforms
1743 .item()
1744 .map_or(false, |transform| transform.block.is_none())
1745 {
1746 let start_input_row = self.transforms.start().1 .0;
1747 let start_output_row = self.transforms.start().0 .0;
1748 if start_output_row < self.max_output_row {
1749 let end_input_row = cmp::min(
1750 self.transforms.end(&()).1 .0,
1751 start_input_row + (self.max_output_row - start_output_row),
1752 );
1753 self.input_chunks.seek(start_input_row..end_input_row);
1754 }
1755 }
1756 }
1757}
1758
1759pub struct StickyHeaderExcerpt<'a> {
1760 pub excerpt: &'a ExcerptInfo,
1761 pub next_excerpt_controls_present: bool,
1762 pub next_buffer_row: Option<u32>,
1763}
1764
1765impl<'a> Iterator for BlockChunks<'a> {
1766 type Item = Chunk<'a>;
1767
1768 fn next(&mut self) -> Option<Self::Item> {
1769 if self.output_row >= self.max_output_row {
1770 return None;
1771 }
1772
1773 let transform = self.transforms.item()?;
1774 if transform.block.is_some() {
1775 let block_start = self.transforms.start().0 .0;
1776 let mut block_end = self.transforms.end(&()).0 .0;
1777 self.advance();
1778 if self.transforms.item().is_none() {
1779 block_end -= 1;
1780 }
1781
1782 let start_in_block = self.output_row - block_start;
1783 let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
1784 let line_count = end_in_block - start_in_block;
1785 self.output_row += line_count;
1786
1787 return Some(Chunk {
1788 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) },
1789 ..Default::default()
1790 });
1791 }
1792
1793 if self.input_chunk.text.is_empty() {
1794 if let Some(input_chunk) = self.input_chunks.next() {
1795 self.input_chunk = input_chunk;
1796 } else {
1797 if self.output_row < self.max_output_row {
1798 self.output_row += 1;
1799 self.advance();
1800 if self.transforms.item().is_some() {
1801 return Some(Chunk {
1802 text: "\n",
1803 ..Default::default()
1804 });
1805 }
1806 }
1807 return None;
1808 }
1809 }
1810
1811 let transform_end = self.transforms.end(&()).0 .0;
1812 let (prefix_rows, prefix_bytes) =
1813 offset_for_row(self.input_chunk.text, transform_end - self.output_row);
1814 self.output_row += prefix_rows;
1815
1816 let (mut prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
1817 self.input_chunk.text = suffix;
1818
1819 if self.masked {
1820 // Not great for multibyte text because to keep cursor math correct we
1821 // need to have the same number of bytes in the input as output.
1822 let chars = prefix.chars().count();
1823 let bullet_len = chars;
1824 prefix = &BULLETS[..bullet_len];
1825 }
1826
1827 let chunk = Chunk {
1828 text: prefix,
1829 ..self.input_chunk.clone()
1830 };
1831
1832 if self.output_row == transform_end {
1833 self.advance();
1834 }
1835
1836 Some(chunk)
1837 }
1838}
1839
1840impl<'a> Iterator for BlockRows<'a> {
1841 type Item = RowInfo;
1842
1843 fn next(&mut self) -> Option<Self::Item> {
1844 if self.started {
1845 self.output_row.0 += 1;
1846 } else {
1847 self.started = true;
1848 }
1849
1850 if self.output_row.0 >= self.transforms.end(&()).0 .0 {
1851 self.transforms.next(&());
1852 while let Some(transform) = self.transforms.item() {
1853 if transform
1854 .block
1855 .as_ref()
1856 .map_or(false, |block| block.height() == 0)
1857 {
1858 self.transforms.next(&());
1859 } else {
1860 break;
1861 }
1862 }
1863
1864 let transform = self.transforms.item()?;
1865 if transform
1866 .block
1867 .as_ref()
1868 .map_or(true, |block| block.is_replacement())
1869 {
1870 self.input_rows.seek(self.transforms.start().1 .0);
1871 }
1872 }
1873
1874 let transform = self.transforms.item()?;
1875 if let Some(block) = transform.block.as_ref() {
1876 if block.is_replacement() && self.transforms.start().0 == self.output_row {
1877 if matches!(block, Block::FoldedBuffer { .. }) {
1878 Some(RowInfo::default())
1879 } else {
1880 Some(self.input_rows.next().unwrap())
1881 }
1882 } else {
1883 Some(RowInfo::default())
1884 }
1885 } else {
1886 Some(self.input_rows.next().unwrap())
1887 }
1888 }
1889}
1890
1891impl sum_tree::Item for Transform {
1892 type Summary = TransformSummary;
1893
1894 fn summary(&self, _cx: &()) -> Self::Summary {
1895 self.summary.clone()
1896 }
1897}
1898
1899impl sum_tree::Summary for TransformSummary {
1900 type Context = ();
1901
1902 fn zero(_cx: &()) -> Self {
1903 Default::default()
1904 }
1905
1906 fn add_summary(&mut self, summary: &Self, _: &()) {
1907 if summary.longest_row_chars > self.longest_row_chars {
1908 self.longest_row = self.output_rows + summary.longest_row;
1909 self.longest_row_chars = summary.longest_row_chars;
1910 }
1911 self.input_rows += summary.input_rows;
1912 self.output_rows += summary.output_rows;
1913 }
1914}
1915
1916impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
1917 fn zero(_cx: &()) -> Self {
1918 Default::default()
1919 }
1920
1921 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1922 self.0 += summary.input_rows;
1923 }
1924}
1925
1926impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
1927 fn zero(_cx: &()) -> Self {
1928 Default::default()
1929 }
1930
1931 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1932 self.0 += summary.output_rows;
1933 }
1934}
1935
1936impl<'a> Deref for BlockContext<'a, '_> {
1937 type Target = App;
1938
1939 fn deref(&self) -> &Self::Target {
1940 self.app
1941 }
1942}
1943
1944impl DerefMut for BlockContext<'_, '_> {
1945 fn deref_mut(&mut self) -> &mut Self::Target {
1946 self.app
1947 }
1948}
1949
1950impl CustomBlock {
1951 pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
1952 self.render.lock()(cx)
1953 }
1954
1955 pub fn start(&self) -> Anchor {
1956 *self.placement.start()
1957 }
1958
1959 pub fn end(&self) -> Anchor {
1960 *self.placement.end()
1961 }
1962
1963 pub fn style(&self) -> BlockStyle {
1964 self.style
1965 }
1966}
1967
1968impl Debug for CustomBlock {
1969 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1970 f.debug_struct("Block")
1971 .field("id", &self.id)
1972 .field("placement", &self.placement)
1973 .field("height", &self.height)
1974 .field("style", &self.style)
1975 .field("priority", &self.priority)
1976 .finish_non_exhaustive()
1977 }
1978}
1979
1980// Count the number of bytes prior to a target point. If the string doesn't contain the target
1981// point, return its total extent. Otherwise return the target point itself.
1982fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
1983 let mut row = 0;
1984 let mut offset = 0;
1985 for (ix, line) in s.split('\n').enumerate() {
1986 if ix > 0 {
1987 row += 1;
1988 offset += 1;
1989 }
1990 if row >= target {
1991 break;
1992 }
1993 offset += line.len();
1994 }
1995 (row, offset)
1996}
1997
1998#[cfg(test)]
1999mod tests {
2000 use super::*;
2001 use crate::{
2002 display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap},
2003 test::test_font,
2004 };
2005 use gpui::{div, font, px, App, AppContext as _, Element};
2006 use itertools::Itertools;
2007 use language::{Buffer, Capability};
2008 use multi_buffer::{ExcerptRange, MultiBuffer};
2009 use rand::prelude::*;
2010 use settings::SettingsStore;
2011 use std::env;
2012 use util::RandomCharIter;
2013
2014 #[gpui::test]
2015 fn test_offset_for_row() {
2016 assert_eq!(offset_for_row("", 0), (0, 0));
2017 assert_eq!(offset_for_row("", 1), (0, 0));
2018 assert_eq!(offset_for_row("abcd", 0), (0, 0));
2019 assert_eq!(offset_for_row("abcd", 1), (0, 4));
2020 assert_eq!(offset_for_row("\n", 0), (0, 0));
2021 assert_eq!(offset_for_row("\n", 1), (1, 1));
2022 assert_eq!(offset_for_row("abc\ndef\nghi", 0), (0, 0));
2023 assert_eq!(offset_for_row("abc\ndef\nghi", 1), (1, 4));
2024 assert_eq!(offset_for_row("abc\ndef\nghi", 2), (2, 8));
2025 assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11));
2026 }
2027
2028 #[gpui::test]
2029 fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
2030 cx.update(init_test);
2031
2032 let text = "aaa\nbbb\nccc\nddd";
2033
2034 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2035 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2036 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2037 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2038 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2039 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2040 let (wrap_map, wraps_snapshot) =
2041 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2042 let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
2043
2044 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2045 let block_ids = writer.insert(vec![
2046 BlockProperties {
2047 style: BlockStyle::Fixed,
2048 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2049 height: 1,
2050 render: Arc::new(|_| div().into_any()),
2051 priority: 0,
2052 },
2053 BlockProperties {
2054 style: BlockStyle::Fixed,
2055 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2056 height: 2,
2057 render: Arc::new(|_| div().into_any()),
2058 priority: 0,
2059 },
2060 BlockProperties {
2061 style: BlockStyle::Fixed,
2062 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2063 height: 3,
2064 render: Arc::new(|_| div().into_any()),
2065 priority: 0,
2066 },
2067 ]);
2068
2069 let snapshot = block_map.read(wraps_snapshot, Default::default());
2070 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2071
2072 let blocks = snapshot
2073 .blocks_in_range(0..8)
2074 .map(|(start_row, block)| {
2075 let block = block.as_custom().unwrap();
2076 (start_row..start_row + block.height, block.id)
2077 })
2078 .collect::<Vec<_>>();
2079
2080 // When multiple blocks are on the same line, the newer blocks appear first.
2081 assert_eq!(
2082 blocks,
2083 &[
2084 (1..2, block_ids[0]),
2085 (2..4, block_ids[1]),
2086 (7..10, block_ids[2]),
2087 ]
2088 );
2089
2090 assert_eq!(
2091 snapshot.to_block_point(WrapPoint::new(0, 3)),
2092 BlockPoint::new(0, 3)
2093 );
2094 assert_eq!(
2095 snapshot.to_block_point(WrapPoint::new(1, 0)),
2096 BlockPoint::new(4, 0)
2097 );
2098 assert_eq!(
2099 snapshot.to_block_point(WrapPoint::new(3, 3)),
2100 BlockPoint::new(6, 3)
2101 );
2102
2103 assert_eq!(
2104 snapshot.to_wrap_point(BlockPoint::new(0, 3), Bias::Left),
2105 WrapPoint::new(0, 3)
2106 );
2107 assert_eq!(
2108 snapshot.to_wrap_point(BlockPoint::new(1, 0), Bias::Left),
2109 WrapPoint::new(1, 0)
2110 );
2111 assert_eq!(
2112 snapshot.to_wrap_point(BlockPoint::new(3, 0), Bias::Left),
2113 WrapPoint::new(1, 0)
2114 );
2115 assert_eq!(
2116 snapshot.to_wrap_point(BlockPoint::new(7, 0), Bias::Left),
2117 WrapPoint::new(3, 3)
2118 );
2119
2120 assert_eq!(
2121 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left),
2122 BlockPoint::new(0, 3)
2123 );
2124 assert_eq!(
2125 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right),
2126 BlockPoint::new(4, 0)
2127 );
2128 assert_eq!(
2129 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left),
2130 BlockPoint::new(0, 3)
2131 );
2132 assert_eq!(
2133 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right),
2134 BlockPoint::new(4, 0)
2135 );
2136 assert_eq!(
2137 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Left),
2138 BlockPoint::new(4, 0)
2139 );
2140 assert_eq!(
2141 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Right),
2142 BlockPoint::new(4, 0)
2143 );
2144 assert_eq!(
2145 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Left),
2146 BlockPoint::new(6, 3)
2147 );
2148 assert_eq!(
2149 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Right),
2150 BlockPoint::new(6, 3)
2151 );
2152 assert_eq!(
2153 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Left),
2154 BlockPoint::new(6, 3)
2155 );
2156 assert_eq!(
2157 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Right),
2158 BlockPoint::new(6, 3)
2159 );
2160
2161 assert_eq!(
2162 snapshot
2163 .row_infos(BlockRow(0))
2164 .map(|row_info| row_info.buffer_row)
2165 .collect::<Vec<_>>(),
2166 &[
2167 Some(0),
2168 None,
2169 None,
2170 None,
2171 Some(1),
2172 Some(2),
2173 Some(3),
2174 None,
2175 None,
2176 None
2177 ]
2178 );
2179
2180 // Insert a line break, separating two block decorations into separate lines.
2181 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2182 buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
2183 buffer.snapshot(cx)
2184 });
2185
2186 let (inlay_snapshot, inlay_edits) =
2187 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
2188 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2189 let (tab_snapshot, tab_edits) =
2190 tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
2191 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2192 wrap_map.sync(tab_snapshot, tab_edits, cx)
2193 });
2194 let snapshot = block_map.read(wraps_snapshot, wrap_edits);
2195 assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
2196 }
2197
2198 #[gpui::test]
2199 fn test_multibuffer_headers_and_footers(cx: &mut App) {
2200 init_test(cx);
2201
2202 let buffer1 = cx.new(|cx| Buffer::local("Buffer 1", cx));
2203 let buffer2 = cx.new(|cx| Buffer::local("Buffer 2", cx));
2204 let buffer3 = cx.new(|cx| Buffer::local("Buffer 3", cx));
2205
2206 let mut excerpt_ids = Vec::new();
2207 let multi_buffer = cx.new(|cx| {
2208 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
2209 excerpt_ids.extend(multi_buffer.push_excerpts(
2210 buffer1.clone(),
2211 [ExcerptRange {
2212 context: 0..buffer1.read(cx).len(),
2213 primary: None,
2214 }],
2215 cx,
2216 ));
2217 excerpt_ids.extend(multi_buffer.push_excerpts(
2218 buffer2.clone(),
2219 [ExcerptRange {
2220 context: 0..buffer2.read(cx).len(),
2221 primary: None,
2222 }],
2223 cx,
2224 ));
2225 excerpt_ids.extend(multi_buffer.push_excerpts(
2226 buffer3.clone(),
2227 [ExcerptRange {
2228 context: 0..buffer3.read(cx).len(),
2229 primary: None,
2230 }],
2231 cx,
2232 ));
2233
2234 multi_buffer
2235 });
2236
2237 let font = test_font();
2238 let font_size = px(14.);
2239 let font_id = cx.text_system().resolve_font(&font);
2240 let mut wrap_width = px(0.);
2241 for c in "Buff".chars() {
2242 wrap_width += cx
2243 .text_system()
2244 .advance(font_id, font_size, c)
2245 .unwrap()
2246 .width;
2247 }
2248
2249 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2250 let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot.clone());
2251 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2252 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2253 let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
2254
2255 let block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
2256 let snapshot = block_map.read(wraps_snapshot, Default::default());
2257
2258 // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
2259 assert_eq!(
2260 snapshot.text(),
2261 "\n\nBuff\ner 1\n\n\n\nBuff\ner 2\n\n\n\nBuff\ner 3\n"
2262 );
2263
2264 let blocks: Vec<_> = snapshot
2265 .blocks_in_range(0..u32::MAX)
2266 .map(|(row, block)| (row..row + block.height(), block.id()))
2267 .collect();
2268 assert_eq!(
2269 blocks,
2270 vec![
2271 (0..2, BlockId::ExcerptBoundary(Some(excerpt_ids[0]))), // path, header
2272 (4..7, BlockId::ExcerptBoundary(Some(excerpt_ids[1]))), // footer, path, header
2273 (9..12, BlockId::ExcerptBoundary(Some(excerpt_ids[2]))), // footer, path, header
2274 (14..15, BlockId::ExcerptBoundary(None)), // footer
2275 ]
2276 );
2277 }
2278
2279 #[gpui::test]
2280 fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
2281 cx.update(init_test);
2282
2283 let text = "aaa\nbbb\nccc\nddd";
2284
2285 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2286 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2287 let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2288 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2289 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2290 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2291 let (_wrap_map, wraps_snapshot) =
2292 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2293 let mut block_map = BlockMap::new(wraps_snapshot.clone(), false, 1, 1, 0);
2294
2295 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2296 let block_ids = writer.insert(vec![
2297 BlockProperties {
2298 style: BlockStyle::Fixed,
2299 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2300 height: 1,
2301 render: Arc::new(|_| div().into_any()),
2302 priority: 0,
2303 },
2304 BlockProperties {
2305 style: BlockStyle::Fixed,
2306 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2307 height: 2,
2308 render: Arc::new(|_| div().into_any()),
2309 priority: 0,
2310 },
2311 BlockProperties {
2312 style: BlockStyle::Fixed,
2313 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2314 height: 3,
2315 render: Arc::new(|_| div().into_any()),
2316 priority: 0,
2317 },
2318 ]);
2319
2320 {
2321 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2322 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2323
2324 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2325
2326 let mut new_heights = HashMap::default();
2327 new_heights.insert(block_ids[0], 2);
2328 block_map_writer.resize(new_heights);
2329 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2330 assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2331 }
2332
2333 {
2334 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2335
2336 let mut new_heights = HashMap::default();
2337 new_heights.insert(block_ids[0], 1);
2338 block_map_writer.resize(new_heights);
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
2344 {
2345 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2346
2347 let mut new_heights = HashMap::default();
2348 new_heights.insert(block_ids[0], 0);
2349 block_map_writer.resize(new_heights);
2350
2351 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2352 assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
2353 }
2354
2355 {
2356 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2357
2358 let mut new_heights = HashMap::default();
2359 new_heights.insert(block_ids[0], 3);
2360 block_map_writer.resize(new_heights);
2361
2362 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2363 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2364 }
2365
2366 {
2367 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2368
2369 let mut new_heights = HashMap::default();
2370 new_heights.insert(block_ids[0], 3);
2371 block_map_writer.resize(new_heights);
2372
2373 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2374 // Same height as before, should remain the same
2375 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2376 }
2377 }
2378
2379 #[cfg(target_os = "macos")]
2380 #[gpui::test]
2381 fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
2382 cx.update(init_test);
2383
2384 let _font_id = cx.text_system().font_id(&font("Helvetica")).unwrap();
2385
2386 let text = "one two three\nfour five six\nseven eight";
2387
2388 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2389 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2390 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2391 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2392 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2393 let (_, wraps_snapshot) = cx.update(|cx| {
2394 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(60.)), cx)
2395 });
2396 let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 0);
2397
2398 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2399 writer.insert(vec![
2400 BlockProperties {
2401 style: BlockStyle::Fixed,
2402 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
2403 render: Arc::new(|_| div().into_any()),
2404 height: 1,
2405 priority: 0,
2406 },
2407 BlockProperties {
2408 style: BlockStyle::Fixed,
2409 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
2410 render: Arc::new(|_| div().into_any()),
2411 height: 1,
2412 priority: 0,
2413 },
2414 ]);
2415
2416 // Blocks with an 'above' disposition go above their corresponding buffer line.
2417 // Blocks with a 'below' disposition go below their corresponding buffer line.
2418 let snapshot = block_map.read(wraps_snapshot, Default::default());
2419 assert_eq!(
2420 snapshot.text(),
2421 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
2422 );
2423 }
2424
2425 #[gpui::test]
2426 fn test_replace_lines(cx: &mut gpui::TestAppContext) {
2427 cx.update(init_test);
2428
2429 let text = "line1\nline2\nline3\nline4\nline5";
2430
2431 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2432 let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
2433 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2434 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2435 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2436 let tab_size = 1.try_into().unwrap();
2437 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
2438 let (wrap_map, wraps_snapshot) =
2439 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2440 let mut block_map = BlockMap::new(wraps_snapshot.clone(), false, 1, 1, 0);
2441
2442 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2443 let replace_block_id = writer.insert(vec![BlockProperties {
2444 style: BlockStyle::Fixed,
2445 placement: BlockPlacement::Replace(
2446 buffer_snapshot.anchor_after(Point::new(1, 3))
2447 ..=buffer_snapshot.anchor_before(Point::new(3, 1)),
2448 ),
2449 height: 4,
2450 render: Arc::new(|_| div().into_any()),
2451 priority: 0,
2452 }])[0];
2453
2454 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
2455 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2456
2457 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2458 buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
2459 buffer.snapshot(cx)
2460 });
2461 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
2462 buffer_snapshot.clone(),
2463 buffer_subscription.consume().into_inner(),
2464 );
2465 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2466 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2467 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2468 wrap_map.sync(tab_snapshot, tab_edits, cx)
2469 });
2470 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
2471 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2472
2473 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2474 buffer.edit(
2475 [(
2476 Point::new(1, 5)..Point::new(1, 5),
2477 "\nline 2.1\nline2.2\nline 2.3\nline 2.4",
2478 )],
2479 None,
2480 cx,
2481 );
2482 buffer.snapshot(cx)
2483 });
2484 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
2485 buffer_snapshot.clone(),
2486 buffer_subscription.consume().into_inner(),
2487 );
2488 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2489 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2490 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2491 wrap_map.sync(tab_snapshot, tab_edits, cx)
2492 });
2493 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
2494 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2495
2496 // Blocks inserted right above the start or right below the end of the replaced region are hidden.
2497 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2498 writer.insert(vec![
2499 BlockProperties {
2500 style: BlockStyle::Fixed,
2501 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))),
2502 height: 1,
2503 render: Arc::new(|_| div().into_any()),
2504 priority: 0,
2505 },
2506 BlockProperties {
2507 style: BlockStyle::Fixed,
2508 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
2509 height: 1,
2510 render: Arc::new(|_| div().into_any()),
2511 priority: 0,
2512 },
2513 BlockProperties {
2514 style: BlockStyle::Fixed,
2515 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
2516 height: 1,
2517 render: Arc::new(|_| div().into_any()),
2518 priority: 0,
2519 },
2520 ]);
2521 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2522 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2523
2524 // Ensure blocks inserted *inside* replaced region are hidden.
2525 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2526 writer.insert(vec![
2527 BlockProperties {
2528 style: BlockStyle::Fixed,
2529 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
2530 height: 1,
2531 render: Arc::new(|_| div().into_any()),
2532 priority: 0,
2533 },
2534 BlockProperties {
2535 style: BlockStyle::Fixed,
2536 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
2537 height: 1,
2538 render: Arc::new(|_| div().into_any()),
2539 priority: 0,
2540 },
2541 BlockProperties {
2542 style: BlockStyle::Fixed,
2543 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
2544 height: 1,
2545 render: Arc::new(|_| div().into_any()),
2546 priority: 0,
2547 },
2548 ]);
2549 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2550 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2551
2552 // Removing the replace block shows all the hidden blocks again.
2553 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2554 writer.remove(HashSet::from_iter([replace_block_id]));
2555 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2556 assert_eq!(
2557 blocks_snapshot.text(),
2558 "\nline1\n\nline2\n\n\nline 2.1\nline2.2\nline 2.3\nline 2.4\n\nline4\n\nline5"
2559 );
2560 }
2561
2562 #[gpui::test]
2563 fn test_custom_blocks_inside_buffer_folds(cx: &mut gpui::TestAppContext) {
2564 cx.update(init_test);
2565
2566 let text = "111\n222\n333\n444\n555\n666";
2567
2568 let buffer = cx.update(|cx| {
2569 MultiBuffer::build_multi(
2570 [
2571 (text, vec![Point::new(0, 0)..Point::new(0, 3)]),
2572 (
2573 text,
2574 vec![
2575 Point::new(1, 0)..Point::new(1, 3),
2576 Point::new(2, 0)..Point::new(2, 3),
2577 Point::new(3, 0)..Point::new(3, 3),
2578 ],
2579 ),
2580 (
2581 text,
2582 vec![
2583 Point::new(4, 0)..Point::new(4, 3),
2584 Point::new(5, 0)..Point::new(5, 3),
2585 ],
2586 ),
2587 ],
2588 cx,
2589 )
2590 });
2591 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2592 let buffer_ids = buffer_snapshot
2593 .excerpts()
2594 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2595 .dedup()
2596 .collect::<Vec<_>>();
2597 assert_eq!(buffer_ids.len(), 3);
2598 let buffer_id_1 = buffer_ids[0];
2599 let buffer_id_2 = buffer_ids[1];
2600 let buffer_id_3 = buffer_ids[2];
2601
2602 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2603 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2604 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2605 let (_, wrap_snapshot) =
2606 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2607 let mut block_map = BlockMap::new(wrap_snapshot.clone(), true, 2, 1, 1);
2608 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2609
2610 assert_eq!(
2611 blocks_snapshot.text(),
2612 "\n\n\n111\n\n\n\n\n222\n\n\n333\n\n\n444\n\n\n\n\n555\n\n\n666\n"
2613 );
2614 assert_eq!(
2615 blocks_snapshot
2616 .row_infos(BlockRow(0))
2617 .map(|i| i.buffer_row)
2618 .collect::<Vec<_>>(),
2619 vec![
2620 None,
2621 None,
2622 None,
2623 Some(0),
2624 None,
2625 None,
2626 None,
2627 None,
2628 Some(1),
2629 None,
2630 None,
2631 Some(2),
2632 None,
2633 None,
2634 Some(3),
2635 None,
2636 None,
2637 None,
2638 None,
2639 Some(4),
2640 None,
2641 None,
2642 Some(5),
2643 None,
2644 ]
2645 );
2646
2647 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2648 let excerpt_blocks_2 = writer.insert(vec![
2649 BlockProperties {
2650 style: BlockStyle::Fixed,
2651 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2652 height: 1,
2653 render: Arc::new(|_| div().into_any()),
2654 priority: 0,
2655 },
2656 BlockProperties {
2657 style: BlockStyle::Fixed,
2658 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 0))),
2659 height: 1,
2660 render: Arc::new(|_| div().into_any()),
2661 priority: 0,
2662 },
2663 BlockProperties {
2664 style: BlockStyle::Fixed,
2665 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 0))),
2666 height: 1,
2667 render: Arc::new(|_| div().into_any()),
2668 priority: 0,
2669 },
2670 ]);
2671 let excerpt_blocks_3 = writer.insert(vec![
2672 BlockProperties {
2673 style: BlockStyle::Fixed,
2674 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))),
2675 height: 1,
2676 render: Arc::new(|_| div().into_any()),
2677 priority: 0,
2678 },
2679 BlockProperties {
2680 style: BlockStyle::Fixed,
2681 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))),
2682 height: 1,
2683 render: Arc::new(|_| div().into_any()),
2684 priority: 0,
2685 },
2686 ]);
2687
2688 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2689 assert_eq!(
2690 blocks_snapshot.text(),
2691 "\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"
2692 );
2693 assert_eq!(
2694 blocks_snapshot
2695 .row_infos(BlockRow(0))
2696 .map(|i| i.buffer_row)
2697 .collect::<Vec<_>>(),
2698 vec![
2699 None,
2700 None,
2701 None,
2702 Some(0),
2703 None,
2704 None,
2705 None,
2706 None,
2707 None,
2708 Some(1),
2709 None,
2710 None,
2711 None,
2712 Some(2),
2713 None,
2714 None,
2715 Some(3),
2716 None,
2717 None,
2718 None,
2719 None,
2720 None,
2721 None,
2722 Some(4),
2723 None,
2724 None,
2725 Some(5),
2726 None,
2727 None,
2728 ]
2729 );
2730
2731 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2732 buffer.read_with(cx, |buffer, cx| {
2733 writer.fold_buffer(buffer_id_1, buffer, cx);
2734 });
2735 let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
2736 style: BlockStyle::Fixed,
2737 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))),
2738 height: 1,
2739 render: Arc::new(|_| div().into_any()),
2740 priority: 0,
2741 }]);
2742 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2743 let blocks = blocks_snapshot
2744 .blocks_in_range(0..u32::MAX)
2745 .collect::<Vec<_>>();
2746 for (_, block) in &blocks {
2747 if let BlockId::Custom(custom_block_id) = block.id() {
2748 assert!(
2749 !excerpt_blocks_1.contains(&custom_block_id),
2750 "Should have no blocks from the folded buffer"
2751 );
2752 assert!(
2753 excerpt_blocks_2.contains(&custom_block_id)
2754 || excerpt_blocks_3.contains(&custom_block_id),
2755 "Should have only blocks from unfolded buffers"
2756 );
2757 }
2758 }
2759 assert_eq!(
2760 1,
2761 blocks
2762 .iter()
2763 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2764 .count(),
2765 "Should have one folded block, producing a header of the second buffer"
2766 );
2767 assert_eq!(
2768 blocks_snapshot.text(),
2769 "\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"
2770 );
2771 assert_eq!(
2772 blocks_snapshot
2773 .row_infos(BlockRow(0))
2774 .map(|i| i.buffer_row)
2775 .collect::<Vec<_>>(),
2776 vec![
2777 None,
2778 None,
2779 None,
2780 None,
2781 None,
2782 None,
2783 Some(1),
2784 None,
2785 None,
2786 None,
2787 Some(2),
2788 None,
2789 None,
2790 Some(3),
2791 None,
2792 None,
2793 None,
2794 None,
2795 None,
2796 None,
2797 Some(4),
2798 None,
2799 None,
2800 Some(5),
2801 None,
2802 None,
2803 ]
2804 );
2805
2806 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2807 buffer.read_with(cx, |buffer, cx| {
2808 writer.fold_buffer(buffer_id_2, buffer, cx);
2809 });
2810 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2811 let blocks = blocks_snapshot
2812 .blocks_in_range(0..u32::MAX)
2813 .collect::<Vec<_>>();
2814 for (_, block) in &blocks {
2815 if let BlockId::Custom(custom_block_id) = block.id() {
2816 assert!(
2817 !excerpt_blocks_1.contains(&custom_block_id),
2818 "Should have no blocks from the folded buffer_1"
2819 );
2820 assert!(
2821 !excerpt_blocks_2.contains(&custom_block_id),
2822 "Should have no blocks from the folded buffer_2"
2823 );
2824 assert!(
2825 excerpt_blocks_3.contains(&custom_block_id),
2826 "Should have only blocks from unfolded buffers"
2827 );
2828 }
2829 }
2830 assert_eq!(
2831 2,
2832 blocks
2833 .iter()
2834 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2835 .count(),
2836 "Should have two folded blocks, producing headers"
2837 );
2838 assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n\n555\n\n\n666\n\n");
2839 assert_eq!(
2840 blocks_snapshot
2841 .row_infos(BlockRow(0))
2842 .map(|i| i.buffer_row)
2843 .collect::<Vec<_>>(),
2844 vec![
2845 None,
2846 None,
2847 None,
2848 None,
2849 None,
2850 None,
2851 None,
2852 None,
2853 Some(4),
2854 None,
2855 None,
2856 Some(5),
2857 None,
2858 None,
2859 ]
2860 );
2861
2862 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2863 buffer.read_with(cx, |buffer, cx| {
2864 writer.unfold_buffer(buffer_id_1, buffer, cx);
2865 });
2866 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2867 let blocks = blocks_snapshot
2868 .blocks_in_range(0..u32::MAX)
2869 .collect::<Vec<_>>();
2870 for (_, block) in &blocks {
2871 if let BlockId::Custom(custom_block_id) = block.id() {
2872 assert!(
2873 !excerpt_blocks_2.contains(&custom_block_id),
2874 "Should have no blocks from the folded buffer_2"
2875 );
2876 assert!(
2877 excerpt_blocks_1.contains(&custom_block_id)
2878 || excerpt_blocks_3.contains(&custom_block_id),
2879 "Should have only blocks from unfolded buffers"
2880 );
2881 }
2882 }
2883 assert_eq!(
2884 1,
2885 blocks
2886 .iter()
2887 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2888 .count(),
2889 "Should be back to a single folded buffer, producing a header for buffer_2"
2890 );
2891 assert_eq!(
2892 blocks_snapshot.text(),
2893 "\n\n\n\n111\n\n\n\n\n\n\n\n555\n\n\n666\n\n",
2894 "Should have extra newline for 111 buffer, due to a new block added when it was folded"
2895 );
2896 assert_eq!(
2897 blocks_snapshot
2898 .row_infos(BlockRow(0))
2899 .map(|i| i.buffer_row)
2900 .collect::<Vec<_>>(),
2901 vec![
2902 None,
2903 None,
2904 None,
2905 None,
2906 Some(0),
2907 None,
2908 None,
2909 None,
2910 None,
2911 None,
2912 None,
2913 None,
2914 Some(4),
2915 None,
2916 None,
2917 Some(5),
2918 None,
2919 None,
2920 ]
2921 );
2922
2923 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2924 buffer.read_with(cx, |buffer, cx| {
2925 writer.fold_buffer(buffer_id_3, buffer, cx);
2926 });
2927 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2928 let blocks = blocks_snapshot
2929 .blocks_in_range(0..u32::MAX)
2930 .collect::<Vec<_>>();
2931 for (_, block) in &blocks {
2932 if let BlockId::Custom(custom_block_id) = block.id() {
2933 assert!(
2934 excerpt_blocks_1.contains(&custom_block_id),
2935 "Should have no blocks from the folded buffer_1"
2936 );
2937 assert!(
2938 !excerpt_blocks_2.contains(&custom_block_id),
2939 "Should have only blocks from unfolded buffers"
2940 );
2941 assert!(
2942 !excerpt_blocks_3.contains(&custom_block_id),
2943 "Should have only blocks from unfolded buffers"
2944 );
2945 }
2946 }
2947
2948 assert_eq!(
2949 blocks_snapshot.text(),
2950 "\n\n\n\n111\n\n\n\n\n",
2951 "Should have a single, first buffer left after folding"
2952 );
2953 assert_eq!(
2954 blocks_snapshot
2955 .row_infos(BlockRow(0))
2956 .map(|i| i.buffer_row)
2957 .collect::<Vec<_>>(),
2958 vec![
2959 None,
2960 None,
2961 None,
2962 None,
2963 Some(0),
2964 None,
2965 None,
2966 None,
2967 None,
2968 None,
2969 ]
2970 );
2971 }
2972
2973 #[gpui::test]
2974 fn test_basic_buffer_fold(cx: &mut gpui::TestAppContext) {
2975 cx.update(init_test);
2976
2977 let text = "111";
2978
2979 let buffer = cx.update(|cx| {
2980 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(0, 3)])], cx)
2981 });
2982 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2983 let buffer_ids = buffer_snapshot
2984 .excerpts()
2985 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2986 .dedup()
2987 .collect::<Vec<_>>();
2988 assert_eq!(buffer_ids.len(), 1);
2989 let buffer_id = buffer_ids[0];
2990
2991 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2992 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2993 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2994 let (_, wrap_snapshot) =
2995 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2996 let mut block_map = BlockMap::new(wrap_snapshot.clone(), true, 2, 1, 1);
2997 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2998
2999 assert_eq!(blocks_snapshot.text(), "\n\n\n111\n");
3000
3001 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
3002 buffer.read_with(cx, |buffer, cx| {
3003 writer.fold_buffer(buffer_id, buffer, cx);
3004 });
3005 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
3006 let blocks = blocks_snapshot
3007 .blocks_in_range(0..u32::MAX)
3008 .collect::<Vec<_>>();
3009 assert_eq!(
3010 1,
3011 blocks
3012 .iter()
3013 .filter(|(_, block)| {
3014 match block {
3015 Block::FoldedBuffer { prev_excerpt, .. } => {
3016 assert!(prev_excerpt.is_none());
3017 true
3018 }
3019 _ => false,
3020 }
3021 })
3022 .count(),
3023 "Should have one folded block, producing a header of the second buffer"
3024 );
3025 assert_eq!(blocks_snapshot.text(), "\n");
3026 assert_eq!(
3027 blocks_snapshot
3028 .row_infos(BlockRow(0))
3029 .map(|i| i.buffer_row)
3030 .collect::<Vec<_>>(),
3031 vec![None, None],
3032 "When fully folded, should be no buffer rows"
3033 );
3034 }
3035
3036 #[gpui::test(iterations = 100)]
3037 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
3038 cx.update(init_test);
3039
3040 let operations = env::var("OPERATIONS")
3041 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3042 .unwrap_or(10);
3043
3044 let wrap_width = if rng.gen_bool(0.2) {
3045 None
3046 } else {
3047 Some(px(rng.gen_range(0.0..=100.0)))
3048 };
3049 let tab_size = 1.try_into().unwrap();
3050 let font_size = px(14.0);
3051 let buffer_start_header_height = rng.gen_range(1..=5);
3052 let excerpt_header_height = rng.gen_range(1..=5);
3053 let excerpt_footer_height = rng.gen_range(1..=5);
3054
3055 log::info!("Wrap width: {:?}", wrap_width);
3056 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
3057 log::info!("Excerpt Footer Height: {:?}", excerpt_footer_height);
3058 let is_singleton = rng.gen();
3059 let buffer = if is_singleton {
3060 let len = rng.gen_range(0..10);
3061 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
3062 log::info!("initial singleton buffer text: {:?}", text);
3063 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
3064 } else {
3065 cx.update(|cx| {
3066 let multibuffer = MultiBuffer::build_random(&mut rng, cx);
3067 log::info!(
3068 "initial multi-buffer text: {:?}",
3069 multibuffer.read(cx).read(cx).text()
3070 );
3071 multibuffer
3072 })
3073 };
3074
3075 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3076 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3077 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3078 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3079 let font = test_font();
3080 let (wrap_map, wraps_snapshot) =
3081 cx.update(|cx| WrapMap::new(tab_snapshot, font, font_size, wrap_width, cx));
3082 let mut block_map = BlockMap::new(
3083 wraps_snapshot,
3084 true,
3085 buffer_start_header_height,
3086 excerpt_header_height,
3087 excerpt_footer_height,
3088 );
3089
3090 for _ in 0..operations {
3091 let mut buffer_edits = Vec::new();
3092 match rng.gen_range(0..=100) {
3093 0..=19 => {
3094 let wrap_width = if rng.gen_bool(0.2) {
3095 None
3096 } else {
3097 Some(px(rng.gen_range(0.0..=100.0)))
3098 };
3099 log::info!("Setting wrap width to {:?}", wrap_width);
3100 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
3101 }
3102 20..=39 => {
3103 let block_count = rng.gen_range(1..=5);
3104 let block_properties = (0..block_count)
3105 .map(|_| {
3106 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
3107 let offset =
3108 buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left);
3109 let mut min_height = 0;
3110 let placement = match rng.gen_range(0..3) {
3111 0 => {
3112 min_height = 1;
3113 let start = buffer.anchor_after(offset);
3114 let end = buffer.anchor_after(buffer.clip_offset(
3115 rng.gen_range(offset..=buffer.len()),
3116 Bias::Left,
3117 ));
3118 BlockPlacement::Replace(start..=end)
3119 }
3120 1 => BlockPlacement::Above(buffer.anchor_after(offset)),
3121 _ => BlockPlacement::Below(buffer.anchor_after(offset)),
3122 };
3123
3124 let height = rng.gen_range(min_height..5);
3125 BlockProperties {
3126 style: BlockStyle::Fixed,
3127 placement,
3128 height,
3129 render: Arc::new(|_| div().into_any()),
3130 priority: 0,
3131 }
3132 })
3133 .collect::<Vec<_>>();
3134
3135 let (inlay_snapshot, inlay_edits) =
3136 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3137 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3138 let (tab_snapshot, tab_edits) =
3139 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3140 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3141 wrap_map.sync(tab_snapshot, tab_edits, cx)
3142 });
3143 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3144 let block_ids =
3145 block_map.insert(block_properties.iter().map(|props| BlockProperties {
3146 placement: props.placement.clone(),
3147 height: props.height,
3148 style: props.style,
3149 render: Arc::new(|_| div().into_any()),
3150 priority: 0,
3151 }));
3152
3153 for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3154 log::info!(
3155 "inserted block {:?} with height {} and id {:?}",
3156 block_properties
3157 .placement
3158 .as_ref()
3159 .map(|p| p.to_point(&buffer_snapshot)),
3160 block_properties.height,
3161 block_id
3162 );
3163 }
3164 }
3165 40..=59 if !block_map.custom_blocks.is_empty() => {
3166 let block_count = rng.gen_range(1..=4.min(block_map.custom_blocks.len()));
3167 let block_ids_to_remove = block_map
3168 .custom_blocks
3169 .choose_multiple(&mut rng, block_count)
3170 .map(|block| block.id)
3171 .collect::<HashSet<_>>();
3172
3173 let (inlay_snapshot, inlay_edits) =
3174 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3175 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3176 let (tab_snapshot, tab_edits) =
3177 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3178 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3179 wrap_map.sync(tab_snapshot, tab_edits, cx)
3180 });
3181 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3182 log::info!(
3183 "removing {} blocks: {:?}",
3184 block_ids_to_remove.len(),
3185 block_ids_to_remove
3186 );
3187 block_map.remove(block_ids_to_remove);
3188 }
3189 60..=79 => {
3190 if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
3191 log::info!("Noop fold/unfold operation on a singleton buffer");
3192 continue;
3193 }
3194 let (inlay_snapshot, inlay_edits) =
3195 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3196 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3197 let (tab_snapshot, tab_edits) =
3198 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3199 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3200 wrap_map.sync(tab_snapshot, tab_edits, cx)
3201 });
3202 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3203 let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
3204 let folded_buffers = block_map
3205 .0
3206 .folded_buffers
3207 .iter()
3208 .cloned()
3209 .collect::<Vec<_>>();
3210 let mut unfolded_buffers = buffer.excerpt_buffer_ids();
3211 unfolded_buffers.dedup();
3212 log::debug!("All buffers {unfolded_buffers:?}");
3213 log::debug!("Folded buffers {folded_buffers:?}");
3214 unfolded_buffers
3215 .retain(|buffer_id| !block_map.0.folded_buffers.contains(buffer_id));
3216 (unfolded_buffers, folded_buffers)
3217 });
3218 let mut folded_count = folded_buffers.len();
3219 let mut unfolded_count = unfolded_buffers.len();
3220
3221 let fold = !unfolded_buffers.is_empty() && rng.gen_bool(0.5);
3222 let unfold = !folded_buffers.is_empty() && rng.gen_bool(0.5);
3223 if !fold && !unfold {
3224 log::info!("Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}");
3225 continue;
3226 }
3227
3228 buffer.update(cx, |buffer, cx| {
3229 if fold {
3230 let buffer_to_fold =
3231 unfolded_buffers[rng.gen_range(0..unfolded_buffers.len())];
3232 log::info!("Folding {buffer_to_fold:?}");
3233 let related_excerpts = buffer_snapshot
3234 .excerpts()
3235 .filter_map(|(excerpt_id, buffer, range)| {
3236 if buffer.remote_id() == buffer_to_fold {
3237 Some((
3238 excerpt_id,
3239 buffer
3240 .text_for_range(range.context)
3241 .collect::<String>(),
3242 ))
3243 } else {
3244 None
3245 }
3246 })
3247 .collect::<Vec<_>>();
3248 log::info!(
3249 "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
3250 );
3251 folded_count += 1;
3252 unfolded_count -= 1;
3253 block_map.fold_buffer(buffer_to_fold, buffer, cx);
3254 }
3255 if unfold {
3256 let buffer_to_unfold =
3257 folded_buffers[rng.gen_range(0..folded_buffers.len())];
3258 log::info!("Unfolding {buffer_to_unfold:?}");
3259 unfolded_count += 1;
3260 folded_count -= 1;
3261 block_map.unfold_buffer(buffer_to_unfold, buffer, cx);
3262 }
3263 log::info!(
3264 "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3265 );
3266 });
3267 }
3268 _ => {
3269 buffer.update(cx, |buffer, cx| {
3270 let mutation_count = rng.gen_range(1..=5);
3271 let subscription = buffer.subscribe();
3272 buffer.randomly_mutate(&mut rng, mutation_count, cx);
3273 buffer_snapshot = buffer.snapshot(cx);
3274 buffer_edits.extend(subscription.consume());
3275 log::info!("buffer text: {:?}", buffer_snapshot.text());
3276 });
3277 }
3278 }
3279
3280 let (inlay_snapshot, inlay_edits) =
3281 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
3282 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3283 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3284 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3285 wrap_map.sync(tab_snapshot, tab_edits, cx)
3286 });
3287 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
3288 assert_eq!(
3289 blocks_snapshot.transforms.summary().input_rows,
3290 wraps_snapshot.max_point().row() + 1
3291 );
3292 log::info!("wrapped text: {:?}", wraps_snapshot.text());
3293 log::info!("blocks text: {:?}", blocks_snapshot.text());
3294
3295 let mut expected_blocks = Vec::new();
3296 expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
3297 Some((
3298 block.placement.to_wrap_row(&wraps_snapshot)?,
3299 Block::Custom(block.clone()),
3300 ))
3301 }));
3302
3303 // Note that this needs to be synced with the related section in BlockMap::sync
3304 expected_blocks.extend(BlockMap::header_and_footer_blocks(
3305 true,
3306 excerpt_footer_height,
3307 buffer_start_header_height,
3308 excerpt_header_height,
3309 &buffer_snapshot,
3310 &block_map.folded_buffers,
3311 0..,
3312 &wraps_snapshot,
3313 ));
3314
3315 BlockMap::sort_blocks(&mut expected_blocks);
3316
3317 for (placement, block) in &expected_blocks {
3318 log::info!(
3319 "Block {:?} placement: {:?} Height: {:?}",
3320 block.id(),
3321 placement,
3322 block.height()
3323 );
3324 }
3325
3326 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
3327
3328 let input_buffer_rows = buffer_snapshot
3329 .row_infos(MultiBufferRow(0))
3330 .map(|row| row.buffer_row)
3331 .collect::<Vec<_>>();
3332 let mut expected_buffer_rows = Vec::new();
3333 let mut expected_text = String::new();
3334 let mut expected_block_positions = Vec::new();
3335 let mut expected_replaced_buffer_rows = HashSet::default();
3336 let input_text = wraps_snapshot.text();
3337
3338 // Loop over the input lines, creating (N - 1) empty lines for
3339 // blocks of height N.
3340 //
3341 // It's important to note that output *starts* as one empty line,
3342 // so we special case row 0 to assume a leading '\n'.
3343 //
3344 // Linehood is the birthright of strings.
3345 let mut input_text_lines = input_text.split('\n').enumerate().peekable();
3346 let mut block_row = 0;
3347 while let Some((wrap_row, input_line)) = input_text_lines.next() {
3348 let wrap_row = wrap_row as u32;
3349 let multibuffer_row = wraps_snapshot
3350 .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
3351 .row;
3352
3353 // Create empty lines for the above block
3354 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3355 if placement.start().0 == wrap_row && block.place_above() {
3356 let (_, block) = sorted_blocks_iter.next().unwrap();
3357 expected_block_positions.push((block_row, block.id()));
3358 if block.height() > 0 {
3359 let text = "\n".repeat((block.height() - 1) as usize);
3360 if block_row > 0 {
3361 expected_text.push('\n')
3362 }
3363 expected_text.push_str(&text);
3364 for _ in 0..block.height() {
3365 expected_buffer_rows.push(None);
3366 }
3367 block_row += block.height();
3368 }
3369 } else {
3370 break;
3371 }
3372 }
3373
3374 // Skip lines within replace blocks, then create empty lines for the replace block's height
3375 let mut is_in_replace_block = false;
3376 if let Some((BlockPlacement::Replace(replace_range), block)) =
3377 sorted_blocks_iter.peek()
3378 {
3379 if wrap_row >= replace_range.start().0 {
3380 is_in_replace_block = true;
3381
3382 if wrap_row == replace_range.start().0 {
3383 if matches!(block, Block::FoldedBuffer { .. }) {
3384 expected_buffer_rows.push(None);
3385 } else {
3386 expected_buffer_rows
3387 .push(input_buffer_rows[multibuffer_row as usize]);
3388 }
3389 }
3390
3391 if wrap_row == replace_range.end().0 {
3392 expected_block_positions.push((block_row, block.id()));
3393 let text = "\n".repeat((block.height() - 1) as usize);
3394 if block_row > 0 {
3395 expected_text.push('\n');
3396 }
3397 expected_text.push_str(&text);
3398
3399 for _ in 1..block.height() {
3400 expected_buffer_rows.push(None);
3401 }
3402 block_row += block.height();
3403
3404 sorted_blocks_iter.next();
3405 }
3406 }
3407 }
3408
3409 if is_in_replace_block {
3410 expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
3411 } else {
3412 let buffer_row = input_buffer_rows[multibuffer_row as usize];
3413 let soft_wrapped = wraps_snapshot
3414 .to_tab_point(WrapPoint::new(wrap_row, 0))
3415 .column()
3416 > 0;
3417 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
3418 if block_row > 0 {
3419 expected_text.push('\n');
3420 }
3421 expected_text.push_str(input_line);
3422 block_row += 1;
3423 }
3424
3425 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3426 if placement.end().0 == wrap_row && block.place_below() {
3427 let (_, block) = sorted_blocks_iter.next().unwrap();
3428 expected_block_positions.push((block_row, block.id()));
3429 if block.height() > 0 {
3430 let text = "\n".repeat((block.height() - 1) as usize);
3431 if block_row > 0 {
3432 expected_text.push('\n')
3433 }
3434 expected_text.push_str(&text);
3435 for _ in 0..block.height() {
3436 expected_buffer_rows.push(None);
3437 }
3438 block_row += block.height();
3439 }
3440 } else {
3441 break;
3442 }
3443 }
3444 }
3445
3446 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
3447 let expected_row_count = expected_lines.len();
3448 log::info!("expected text: {expected_text:?}");
3449
3450 assert_eq!(
3451 blocks_snapshot.max_point().row + 1,
3452 expected_row_count as u32,
3453 "actual row count != expected row count",
3454 );
3455 assert_eq!(
3456 blocks_snapshot.text(),
3457 expected_text,
3458 "actual text != expected text",
3459 );
3460
3461 for start_row in 0..expected_row_count {
3462 let end_row = rng.gen_range(start_row + 1..=expected_row_count);
3463 let mut expected_text = expected_lines[start_row..end_row].join("\n");
3464 if end_row < expected_row_count {
3465 expected_text.push('\n');
3466 }
3467
3468 let actual_text = blocks_snapshot
3469 .chunks(
3470 start_row as u32..end_row as u32,
3471 false,
3472 false,
3473 Highlights::default(),
3474 )
3475 .map(|chunk| chunk.text)
3476 .collect::<String>();
3477 assert_eq!(
3478 actual_text,
3479 expected_text,
3480 "incorrect text starting row row range {:?}",
3481 start_row..end_row
3482 );
3483 assert_eq!(
3484 blocks_snapshot
3485 .row_infos(BlockRow(start_row as u32))
3486 .map(|row_info| row_info.buffer_row)
3487 .collect::<Vec<_>>(),
3488 &expected_buffer_rows[start_row..],
3489 "incorrect buffer_rows starting at row {:?}",
3490 start_row
3491 );
3492 }
3493
3494 assert_eq!(
3495 blocks_snapshot
3496 .blocks_in_range(0..(expected_row_count as u32))
3497 .map(|(row, block)| (row, block.id()))
3498 .collect::<Vec<_>>(),
3499 expected_block_positions,
3500 "invalid blocks_in_range({:?})",
3501 0..expected_row_count
3502 );
3503
3504 for (_, expected_block) in
3505 blocks_snapshot.blocks_in_range(0..(expected_row_count as u32))
3506 {
3507 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
3508 assert_eq!(
3509 actual_block.map(|block| block.id()),
3510 Some(expected_block.id())
3511 );
3512 }
3513
3514 for (block_row, block_id) in expected_block_positions {
3515 if let BlockId::Custom(block_id) = block_id {
3516 assert_eq!(
3517 blocks_snapshot.row_for_block(block_id),
3518 Some(BlockRow(block_row))
3519 );
3520 }
3521 }
3522
3523 let mut expected_longest_rows = Vec::new();
3524 let mut longest_line_len = -1_isize;
3525 for (row, line) in expected_lines.iter().enumerate() {
3526 let row = row as u32;
3527
3528 assert_eq!(
3529 blocks_snapshot.line_len(BlockRow(row)),
3530 line.len() as u32,
3531 "invalid line len for row {}",
3532 row
3533 );
3534
3535 let line_char_count = line.chars().count() as isize;
3536 match line_char_count.cmp(&longest_line_len) {
3537 Ordering::Less => {}
3538 Ordering::Equal => expected_longest_rows.push(row),
3539 Ordering::Greater => {
3540 longest_line_len = line_char_count;
3541 expected_longest_rows.clear();
3542 expected_longest_rows.push(row);
3543 }
3544 }
3545 }
3546
3547 let longest_row = blocks_snapshot.longest_row();
3548 assert!(
3549 expected_longest_rows.contains(&longest_row),
3550 "incorrect longest row {}. expected {:?} with length {}",
3551 longest_row,
3552 expected_longest_rows,
3553 longest_line_len,
3554 );
3555
3556 for _ in 0..10 {
3557 let end_row = rng.gen_range(1..=expected_lines.len());
3558 let start_row = rng.gen_range(0..end_row);
3559
3560 let mut expected_longest_rows_in_range = vec![];
3561 let mut longest_line_len_in_range = 0;
3562
3563 let mut row = start_row as u32;
3564 for line in &expected_lines[start_row..end_row] {
3565 let line_char_count = line.chars().count() as isize;
3566 match line_char_count.cmp(&longest_line_len_in_range) {
3567 Ordering::Less => {}
3568 Ordering::Equal => expected_longest_rows_in_range.push(row),
3569 Ordering::Greater => {
3570 longest_line_len_in_range = line_char_count;
3571 expected_longest_rows_in_range.clear();
3572 expected_longest_rows_in_range.push(row);
3573 }
3574 }
3575 row += 1;
3576 }
3577
3578 let longest_row_in_range = blocks_snapshot
3579 .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
3580 assert!(
3581 expected_longest_rows_in_range.contains(&longest_row_in_range.0),
3582 "incorrect longest row {} in range {:?}. expected {:?} with length {}",
3583 longest_row,
3584 start_row..end_row,
3585 expected_longest_rows_in_range,
3586 longest_line_len_in_range,
3587 );
3588 }
3589
3590 // Ensure that conversion between block points and wrap points is stable.
3591 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
3592 let wrap_point = WrapPoint::new(row, 0);
3593 let block_point = blocks_snapshot.to_block_point(wrap_point);
3594 let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
3595 let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
3596 assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
3597 assert_eq!(
3598 blocks_snapshot.to_block_point(right_wrap_point),
3599 block_point
3600 );
3601 }
3602
3603 let mut block_point = BlockPoint::new(0, 0);
3604 for c in expected_text.chars() {
3605 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
3606 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
3607 assert_eq!(
3608 blocks_snapshot
3609 .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
3610 left_point,
3611 "block point: {:?}, wrap point: {:?}",
3612 block_point,
3613 blocks_snapshot.to_wrap_point(left_point, Bias::Left)
3614 );
3615 assert_eq!(
3616 left_buffer_point,
3617 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
3618 "{:?} is not valid in buffer coordinates",
3619 left_point
3620 );
3621
3622 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
3623 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
3624 assert_eq!(
3625 blocks_snapshot
3626 .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
3627 right_point,
3628 "block point: {:?}, wrap point: {:?}",
3629 block_point,
3630 blocks_snapshot.to_wrap_point(right_point, Bias::Right)
3631 );
3632 assert_eq!(
3633 right_buffer_point,
3634 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
3635 "{:?} is not valid in buffer coordinates",
3636 right_point
3637 );
3638
3639 if c == '\n' {
3640 block_point.0 += Point::new(1, 0);
3641 } else {
3642 block_point.column += c.len_utf8() as u32;
3643 }
3644 }
3645
3646 for buffer_row in 0..=buffer_snapshot.max_point().row {
3647 let buffer_row = MultiBufferRow(buffer_row);
3648 assert_eq!(
3649 blocks_snapshot.is_line_replaced(buffer_row),
3650 expected_replaced_buffer_rows.contains(&buffer_row),
3651 "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
3652 );
3653 }
3654 }
3655 }
3656
3657 fn init_test(cx: &mut gpui::App) {
3658 let settings = SettingsStore::test(cx);
3659 cx.set_global(settings);
3660 theme::init(theme::LoadThemes::JustBase, cx);
3661 assets::Assets.load_test_fonts(cx);
3662 }
3663
3664 impl Block {
3665 fn as_custom(&self) -> Option<&CustomBlock> {
3666 match self {
3667 Block::Custom(block) => Some(block),
3668 _ => None,
3669 }
3670 }
3671 }
3672
3673 impl BlockSnapshot {
3674 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
3675 self.wrap_snapshot
3676 .to_point(self.to_wrap_point(point, bias), bias)
3677 }
3678 }
3679}