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