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