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