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