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 // TODO az remove option
1761 pub next_buffer_row: Option<u32>,
1762}
1763
1764impl<'a> Iterator for BlockChunks<'a> {
1765 type Item = Chunk<'a>;
1766
1767 fn next(&mut self) -> Option<Self::Item> {
1768 if self.output_row >= self.max_output_row {
1769 return None;
1770 }
1771
1772 let transform = self.transforms.item()?;
1773 if transform.block.is_some() {
1774 let block_start = self.transforms.start().0 .0;
1775 let mut block_end = self.transforms.end(&()).0 .0;
1776 self.advance();
1777 if self.transforms.item().is_none() {
1778 block_end -= 1;
1779 }
1780
1781 let start_in_block = self.output_row - block_start;
1782 let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
1783 let line_count = end_in_block - start_in_block;
1784 self.output_row += line_count;
1785
1786 return Some(Chunk {
1787 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) },
1788 ..Default::default()
1789 });
1790 }
1791
1792 if self.input_chunk.text.is_empty() {
1793 if let Some(input_chunk) = self.input_chunks.next() {
1794 self.input_chunk = input_chunk;
1795 } else {
1796 if self.output_row < self.max_output_row {
1797 self.output_row += 1;
1798 self.advance();
1799 if self.transforms.item().is_some() {
1800 return Some(Chunk {
1801 text: "\n",
1802 ..Default::default()
1803 });
1804 }
1805 }
1806 return None;
1807 }
1808 }
1809
1810 let transform_end = self.transforms.end(&()).0 .0;
1811 let (prefix_rows, prefix_bytes) =
1812 offset_for_row(self.input_chunk.text, transform_end - self.output_row);
1813 self.output_row += prefix_rows;
1814
1815 let (mut prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
1816 self.input_chunk.text = suffix;
1817 if self.output_row == transform_end {
1818 self.advance();
1819 }
1820
1821 if self.masked {
1822 // Not great for multibyte text because to keep cursor math correct we
1823 // need to have the same number of bytes in the input as output.
1824 let chars = prefix.chars().count();
1825 let bullet_len = chars;
1826 prefix = &BULLETS[..bullet_len];
1827 }
1828
1829 Some(Chunk {
1830 text: prefix,
1831 ..self.input_chunk.clone()
1832 })
1833 }
1834}
1835
1836impl<'a> Iterator for BlockBufferRows<'a> {
1837 type Item = Option<u32>;
1838
1839 fn next(&mut self) -> Option<Self::Item> {
1840 if self.started {
1841 self.output_row.0 += 1;
1842 } else {
1843 self.started = true;
1844 }
1845
1846 if self.output_row.0 >= self.transforms.end(&()).0 .0 {
1847 self.transforms.next(&());
1848 while let Some(transform) = self.transforms.item() {
1849 if transform
1850 .block
1851 .as_ref()
1852 .map_or(false, |block| block.height() == 0)
1853 {
1854 self.transforms.next(&());
1855 } else {
1856 break;
1857 }
1858 }
1859
1860 let transform = self.transforms.item()?;
1861 if transform
1862 .block
1863 .as_ref()
1864 .map_or(true, |block| block.is_replacement())
1865 {
1866 self.input_buffer_rows.seek(self.transforms.start().1 .0);
1867 }
1868 }
1869
1870 let transform = self.transforms.item()?;
1871 if let Some(block) = transform.block.as_ref() {
1872 if block.is_replacement() && self.transforms.start().0 == self.output_row {
1873 if matches!(block, Block::FoldedBuffer { .. }) {
1874 Some(None)
1875 } else {
1876 Some(self.input_buffer_rows.next().unwrap())
1877 }
1878 } else {
1879 Some(None)
1880 }
1881 } else {
1882 Some(self.input_buffer_rows.next().unwrap())
1883 }
1884 }
1885}
1886
1887impl sum_tree::Item for Transform {
1888 type Summary = TransformSummary;
1889
1890 fn summary(&self, _cx: &()) -> Self::Summary {
1891 self.summary.clone()
1892 }
1893}
1894
1895impl sum_tree::Summary for TransformSummary {
1896 type Context = ();
1897
1898 fn zero(_cx: &()) -> Self {
1899 Default::default()
1900 }
1901
1902 fn add_summary(&mut self, summary: &Self, _: &()) {
1903 if summary.longest_row_chars > self.longest_row_chars {
1904 self.longest_row = self.output_rows + summary.longest_row;
1905 self.longest_row_chars = summary.longest_row_chars;
1906 }
1907 self.input_rows += summary.input_rows;
1908 self.output_rows += summary.output_rows;
1909 }
1910}
1911
1912impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
1913 fn zero(_cx: &()) -> Self {
1914 Default::default()
1915 }
1916
1917 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1918 self.0 += summary.input_rows;
1919 }
1920}
1921
1922impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
1923 fn zero(_cx: &()) -> Self {
1924 Default::default()
1925 }
1926
1927 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1928 self.0 += summary.output_rows;
1929 }
1930}
1931
1932impl<'a> Deref for BlockContext<'a, '_> {
1933 type Target = WindowContext<'a>;
1934
1935 fn deref(&self) -> &Self::Target {
1936 self.context
1937 }
1938}
1939
1940impl DerefMut for BlockContext<'_, '_> {
1941 fn deref_mut(&mut self) -> &mut Self::Target {
1942 self.context
1943 }
1944}
1945
1946impl CustomBlock {
1947 pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
1948 self.render.lock()(cx)
1949 }
1950
1951 pub fn start(&self) -> Anchor {
1952 *self.placement.start()
1953 }
1954
1955 pub fn end(&self) -> Anchor {
1956 *self.placement.end()
1957 }
1958
1959 pub fn style(&self) -> BlockStyle {
1960 self.style
1961 }
1962}
1963
1964impl Debug for CustomBlock {
1965 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1966 f.debug_struct("Block")
1967 .field("id", &self.id)
1968 .field("placement", &self.placement)
1969 .field("height", &self.height)
1970 .field("style", &self.style)
1971 .field("priority", &self.priority)
1972 .finish_non_exhaustive()
1973 }
1974}
1975
1976// Count the number of bytes prior to a target point. If the string doesn't contain the target
1977// point, return its total extent. Otherwise return the target point itself.
1978fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
1979 let mut row = 0;
1980 let mut offset = 0;
1981 for (ix, line) in s.split('\n').enumerate() {
1982 if ix > 0 {
1983 row += 1;
1984 offset += 1;
1985 }
1986 if row >= target {
1987 break;
1988 }
1989 offset += line.len();
1990 }
1991 (row, offset)
1992}
1993
1994#[cfg(test)]
1995mod tests {
1996 use super::*;
1997 use crate::display_map::{
1998 fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap,
1999 };
2000 use gpui::{div, font, px, AppContext, Context as _, Element};
2001 use itertools::Itertools;
2002 use language::{Buffer, Capability};
2003 use multi_buffer::{ExcerptRange, MultiBuffer};
2004 use rand::prelude::*;
2005 use settings::SettingsStore;
2006 use std::env;
2007 use util::RandomCharIter;
2008
2009 #[gpui::test]
2010 fn test_offset_for_row() {
2011 assert_eq!(offset_for_row("", 0), (0, 0));
2012 assert_eq!(offset_for_row("", 1), (0, 0));
2013 assert_eq!(offset_for_row("abcd", 0), (0, 0));
2014 assert_eq!(offset_for_row("abcd", 1), (0, 4));
2015 assert_eq!(offset_for_row("\n", 0), (0, 0));
2016 assert_eq!(offset_for_row("\n", 1), (1, 1));
2017 assert_eq!(offset_for_row("abc\ndef\nghi", 0), (0, 0));
2018 assert_eq!(offset_for_row("abc\ndef\nghi", 1), (1, 4));
2019 assert_eq!(offset_for_row("abc\ndef\nghi", 2), (2, 8));
2020 assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11));
2021 }
2022
2023 #[gpui::test]
2024 fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
2025 cx.update(init_test);
2026
2027 let text = "aaa\nbbb\nccc\nddd";
2028
2029 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2030 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2031 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2032 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2033 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2034 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2035 let (wrap_map, wraps_snapshot) =
2036 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2037 let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
2038
2039 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2040 let block_ids = writer.insert(vec![
2041 BlockProperties {
2042 style: BlockStyle::Fixed,
2043 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2044 height: 1,
2045 render: Arc::new(|_| div().into_any()),
2046 priority: 0,
2047 },
2048 BlockProperties {
2049 style: BlockStyle::Fixed,
2050 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2051 height: 2,
2052 render: Arc::new(|_| div().into_any()),
2053 priority: 0,
2054 },
2055 BlockProperties {
2056 style: BlockStyle::Fixed,
2057 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2058 height: 3,
2059 render: Arc::new(|_| div().into_any()),
2060 priority: 0,
2061 },
2062 ]);
2063
2064 let snapshot = block_map.read(wraps_snapshot, Default::default());
2065 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2066
2067 let blocks = snapshot
2068 .blocks_in_range(0..8)
2069 .map(|(start_row, block)| {
2070 let block = block.as_custom().unwrap();
2071 (start_row..start_row + block.height, block.id)
2072 })
2073 .collect::<Vec<_>>();
2074
2075 // When multiple blocks are on the same line, the newer blocks appear first.
2076 assert_eq!(
2077 blocks,
2078 &[
2079 (1..2, block_ids[0]),
2080 (2..4, block_ids[1]),
2081 (7..10, block_ids[2]),
2082 ]
2083 );
2084
2085 assert_eq!(
2086 snapshot.to_block_point(WrapPoint::new(0, 3)),
2087 BlockPoint::new(0, 3)
2088 );
2089 assert_eq!(
2090 snapshot.to_block_point(WrapPoint::new(1, 0)),
2091 BlockPoint::new(4, 0)
2092 );
2093 assert_eq!(
2094 snapshot.to_block_point(WrapPoint::new(3, 3)),
2095 BlockPoint::new(6, 3)
2096 );
2097
2098 assert_eq!(
2099 snapshot.to_wrap_point(BlockPoint::new(0, 3), Bias::Left),
2100 WrapPoint::new(0, 3)
2101 );
2102 assert_eq!(
2103 snapshot.to_wrap_point(BlockPoint::new(1, 0), Bias::Left),
2104 WrapPoint::new(1, 0)
2105 );
2106 assert_eq!(
2107 snapshot.to_wrap_point(BlockPoint::new(3, 0), Bias::Left),
2108 WrapPoint::new(1, 0)
2109 );
2110 assert_eq!(
2111 snapshot.to_wrap_point(BlockPoint::new(7, 0), Bias::Left),
2112 WrapPoint::new(3, 3)
2113 );
2114
2115 assert_eq!(
2116 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left),
2117 BlockPoint::new(0, 3)
2118 );
2119 assert_eq!(
2120 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right),
2121 BlockPoint::new(4, 0)
2122 );
2123 assert_eq!(
2124 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left),
2125 BlockPoint::new(0, 3)
2126 );
2127 assert_eq!(
2128 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right),
2129 BlockPoint::new(4, 0)
2130 );
2131 assert_eq!(
2132 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Left),
2133 BlockPoint::new(4, 0)
2134 );
2135 assert_eq!(
2136 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Right),
2137 BlockPoint::new(4, 0)
2138 );
2139 assert_eq!(
2140 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Left),
2141 BlockPoint::new(6, 3)
2142 );
2143 assert_eq!(
2144 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Right),
2145 BlockPoint::new(6, 3)
2146 );
2147 assert_eq!(
2148 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Left),
2149 BlockPoint::new(6, 3)
2150 );
2151 assert_eq!(
2152 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Right),
2153 BlockPoint::new(6, 3)
2154 );
2155
2156 assert_eq!(
2157 snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
2158 &[
2159 Some(0),
2160 None,
2161 None,
2162 None,
2163 Some(1),
2164 Some(2),
2165 Some(3),
2166 None,
2167 None,
2168 None
2169 ]
2170 );
2171
2172 // Insert a line break, separating two block decorations into separate lines.
2173 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2174 buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
2175 buffer.snapshot(cx)
2176 });
2177
2178 let (inlay_snapshot, inlay_edits) =
2179 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
2180 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2181 let (tab_snapshot, tab_edits) =
2182 tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
2183 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2184 wrap_map.sync(tab_snapshot, tab_edits, cx)
2185 });
2186 let snapshot = block_map.read(wraps_snapshot, wrap_edits);
2187 assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
2188 }
2189
2190 #[gpui::test]
2191 fn test_multibuffer_headers_and_footers(cx: &mut AppContext) {
2192 init_test(cx);
2193
2194 let buffer1 = cx.new_model(|cx| Buffer::local("Buffer 1", cx));
2195 let buffer2 = cx.new_model(|cx| Buffer::local("Buffer 2", cx));
2196 let buffer3 = cx.new_model(|cx| Buffer::local("Buffer 3", cx));
2197
2198 let mut excerpt_ids = Vec::new();
2199 let multi_buffer = cx.new_model(|cx| {
2200 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
2201 excerpt_ids.extend(multi_buffer.push_excerpts(
2202 buffer1.clone(),
2203 [ExcerptRange {
2204 context: 0..buffer1.read(cx).len(),
2205 primary: None,
2206 }],
2207 cx,
2208 ));
2209 excerpt_ids.extend(multi_buffer.push_excerpts(
2210 buffer2.clone(),
2211 [ExcerptRange {
2212 context: 0..buffer2.read(cx).len(),
2213 primary: None,
2214 }],
2215 cx,
2216 ));
2217 excerpt_ids.extend(multi_buffer.push_excerpts(
2218 buffer3.clone(),
2219 [ExcerptRange {
2220 context: 0..buffer3.read(cx).len(),
2221 primary: None,
2222 }],
2223 cx,
2224 ));
2225
2226 multi_buffer
2227 });
2228
2229 let font = font("Helvetica");
2230 let font_size = px(14.);
2231 let font_id = cx.text_system().resolve_font(&font);
2232 let mut wrap_width = px(0.);
2233 for c in "Buff".chars() {
2234 wrap_width += cx
2235 .text_system()
2236 .advance(font_id, font_size, c)
2237 .unwrap()
2238 .width;
2239 }
2240
2241 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2242 let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot.clone());
2243 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2244 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2245 let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
2246
2247 let block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
2248 let snapshot = block_map.read(wraps_snapshot, Default::default());
2249
2250 // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
2251 assert_eq!(
2252 snapshot.text(),
2253 "\n\nBuff\ner 1\n\n\n\nBuff\ner 2\n\n\n\nBuff\ner 3\n"
2254 );
2255
2256 let blocks: Vec<_> = snapshot
2257 .blocks_in_range(0..u32::MAX)
2258 .map(|(row, block)| (row..row + block.height(), block.id()))
2259 .collect();
2260 assert_eq!(
2261 blocks,
2262 vec![
2263 (0..2, BlockId::ExcerptBoundary(Some(excerpt_ids[0]))), // path, header
2264 (4..7, BlockId::ExcerptBoundary(Some(excerpt_ids[1]))), // footer, path, header
2265 (9..12, BlockId::ExcerptBoundary(Some(excerpt_ids[2]))), // footer, path, header
2266 (14..15, BlockId::ExcerptBoundary(None)), // footer
2267 ]
2268 );
2269 }
2270
2271 #[gpui::test]
2272 fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
2273 cx.update(init_test);
2274
2275 let text = "aaa\nbbb\nccc\nddd";
2276
2277 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2278 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2279 let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
2280 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2281 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2282 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
2283 let (_wrap_map, wraps_snapshot) =
2284 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2285 let mut block_map = BlockMap::new(wraps_snapshot.clone(), false, 1, 1, 0);
2286
2287 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2288 let block_ids = writer.insert(vec![
2289 BlockProperties {
2290 style: BlockStyle::Fixed,
2291 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2292 height: 1,
2293 render: Arc::new(|_| div().into_any()),
2294 priority: 0,
2295 },
2296 BlockProperties {
2297 style: BlockStyle::Fixed,
2298 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 2))),
2299 height: 2,
2300 render: Arc::new(|_| div().into_any()),
2301 priority: 0,
2302 },
2303 BlockProperties {
2304 style: BlockStyle::Fixed,
2305 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 3))),
2306 height: 3,
2307 render: Arc::new(|_| div().into_any()),
2308 priority: 0,
2309 },
2310 ]);
2311
2312 {
2313 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2314 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2315
2316 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2317
2318 let mut new_heights = HashMap::default();
2319 new_heights.insert(block_ids[0], 2);
2320 block_map_writer.resize(new_heights);
2321 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2322 assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2323 }
2324
2325 {
2326 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2327
2328 let mut new_heights = HashMap::default();
2329 new_heights.insert(block_ids[0], 1);
2330 block_map_writer.resize(new_heights);
2331
2332 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2333 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
2334 }
2335
2336 {
2337 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2338
2339 let mut new_heights = HashMap::default();
2340 new_heights.insert(block_ids[0], 0);
2341 block_map_writer.resize(new_heights);
2342
2343 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2344 assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
2345 }
2346
2347 {
2348 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2349
2350 let mut new_heights = HashMap::default();
2351 new_heights.insert(block_ids[0], 3);
2352 block_map_writer.resize(new_heights);
2353
2354 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2355 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2356 }
2357
2358 {
2359 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
2360
2361 let mut new_heights = HashMap::default();
2362 new_heights.insert(block_ids[0], 3);
2363 block_map_writer.resize(new_heights);
2364
2365 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2366 // Same height as before, should remain the same
2367 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
2368 }
2369 }
2370
2371 #[cfg(target_os = "macos")]
2372 #[gpui::test]
2373 fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
2374 cx.update(init_test);
2375
2376 let _font_id = cx.text_system().font_id(&font("Helvetica")).unwrap();
2377
2378 let text = "one two three\nfour five six\nseven eight";
2379
2380 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2381 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2382 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2383 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2384 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2385 let (_, wraps_snapshot) = cx.update(|cx| {
2386 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(60.)), cx)
2387 });
2388 let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 0);
2389
2390 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2391 writer.insert(vec![
2392 BlockProperties {
2393 style: BlockStyle::Fixed,
2394 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 12))),
2395 render: Arc::new(|_| div().into_any()),
2396 height: 1,
2397 priority: 0,
2398 },
2399 BlockProperties {
2400 style: BlockStyle::Fixed,
2401 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 1))),
2402 render: Arc::new(|_| div().into_any()),
2403 height: 1,
2404 priority: 0,
2405 },
2406 ]);
2407
2408 // Blocks with an 'above' disposition go above their corresponding buffer line.
2409 // Blocks with a 'below' disposition go below their corresponding buffer line.
2410 let snapshot = block_map.read(wraps_snapshot, Default::default());
2411 assert_eq!(
2412 snapshot.text(),
2413 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
2414 );
2415 }
2416
2417 #[gpui::test]
2418 fn test_replace_lines(cx: &mut gpui::TestAppContext) {
2419 cx.update(init_test);
2420
2421 let text = "line1\nline2\nline3\nline4\nline5";
2422
2423 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
2424 let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe());
2425 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2426 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2427 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
2428 let tab_size = 1.try_into().unwrap();
2429 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size);
2430 let (wrap_map, wraps_snapshot) =
2431 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2432 let mut block_map = BlockMap::new(wraps_snapshot.clone(), false, 1, 1, 0);
2433
2434 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2435 let replace_block_id = writer.insert(vec![BlockProperties {
2436 style: BlockStyle::Fixed,
2437 placement: BlockPlacement::Replace(
2438 buffer_snapshot.anchor_after(Point::new(1, 3))
2439 ..=buffer_snapshot.anchor_before(Point::new(3, 1)),
2440 ),
2441 height: 4,
2442 render: Arc::new(|_| div().into_any()),
2443 priority: 0,
2444 }])[0];
2445
2446 let blocks_snapshot = block_map.read(wraps_snapshot, Default::default());
2447 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2448
2449 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2450 buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx);
2451 buffer.snapshot(cx)
2452 });
2453 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
2454 buffer_snapshot.clone(),
2455 buffer_subscription.consume().into_inner(),
2456 );
2457 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2458 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2459 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2460 wrap_map.sync(tab_snapshot, tab_edits, cx)
2461 });
2462 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
2463 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2464
2465 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
2466 buffer.edit(
2467 [(
2468 Point::new(1, 5)..Point::new(1, 5),
2469 "\nline 2.1\nline2.2\nline 2.3\nline 2.4",
2470 )],
2471 None,
2472 cx,
2473 );
2474 buffer.snapshot(cx)
2475 });
2476 let (inlay_snapshot, inlay_edits) = inlay_map.sync(
2477 buffer_snapshot.clone(),
2478 buffer_subscription.consume().into_inner(),
2479 );
2480 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2481 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2482 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2483 wrap_map.sync(tab_snapshot, tab_edits, cx)
2484 });
2485 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
2486 assert_eq!(blocks_snapshot.text(), "line1\n\n\n\n\nline5");
2487
2488 // Blocks inserted right above the start or right below the end of the replaced region are hidden.
2489 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2490 writer.insert(vec![
2491 BlockProperties {
2492 style: BlockStyle::Fixed,
2493 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 3))),
2494 height: 1,
2495 render: Arc::new(|_| div().into_any()),
2496 priority: 0,
2497 },
2498 BlockProperties {
2499 style: BlockStyle::Fixed,
2500 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 3))),
2501 height: 1,
2502 render: Arc::new(|_| div().into_any()),
2503 priority: 0,
2504 },
2505 BlockProperties {
2506 style: BlockStyle::Fixed,
2507 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(6, 2))),
2508 height: 1,
2509 render: Arc::new(|_| div().into_any()),
2510 priority: 0,
2511 },
2512 ]);
2513 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2514 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2515
2516 // Ensure blocks inserted *inside* replaced region are hidden.
2517 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2518 writer.insert(vec![
2519 BlockProperties {
2520 style: BlockStyle::Fixed,
2521 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(1, 3))),
2522 height: 1,
2523 render: Arc::new(|_| div().into_any()),
2524 priority: 0,
2525 },
2526 BlockProperties {
2527 style: BlockStyle::Fixed,
2528 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 1))),
2529 height: 1,
2530 render: Arc::new(|_| div().into_any()),
2531 priority: 0,
2532 },
2533 BlockProperties {
2534 style: BlockStyle::Fixed,
2535 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(6, 1))),
2536 height: 1,
2537 render: Arc::new(|_| div().into_any()),
2538 priority: 0,
2539 },
2540 ]);
2541 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2542 assert_eq!(blocks_snapshot.text(), "\nline1\n\n\n\n\nline5");
2543
2544 // Removing the replace block shows all the hidden blocks again.
2545 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
2546 writer.remove(HashSet::from_iter([replace_block_id]));
2547 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
2548 assert_eq!(
2549 blocks_snapshot.text(),
2550 "\nline1\n\nline2\n\n\nline 2.1\nline2.2\nline 2.3\nline 2.4\n\nline4\n\nline5"
2551 );
2552 }
2553
2554 #[gpui::test]
2555 fn test_custom_blocks_inside_buffer_folds(cx: &mut gpui::TestAppContext) {
2556 cx.update(init_test);
2557
2558 let text = "111\n222\n333\n444\n555\n666";
2559
2560 let buffer = cx.update(|cx| {
2561 MultiBuffer::build_multi(
2562 [
2563 (text, vec![Point::new(0, 0)..Point::new(0, 3)]),
2564 (
2565 text,
2566 vec![
2567 Point::new(1, 0)..Point::new(1, 3),
2568 Point::new(2, 0)..Point::new(2, 3),
2569 Point::new(3, 0)..Point::new(3, 3),
2570 ],
2571 ),
2572 (
2573 text,
2574 vec![
2575 Point::new(4, 0)..Point::new(4, 3),
2576 Point::new(5, 0)..Point::new(5, 3),
2577 ],
2578 ),
2579 ],
2580 cx,
2581 )
2582 });
2583 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2584 let buffer_ids = buffer_snapshot
2585 .excerpts()
2586 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2587 .dedup()
2588 .collect::<Vec<_>>();
2589 assert_eq!(buffer_ids.len(), 3);
2590 let buffer_id_1 = buffer_ids[0];
2591 let buffer_id_2 = buffer_ids[1];
2592 let buffer_id_3 = buffer_ids[2];
2593
2594 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2595 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2596 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2597 let (_, wrap_snapshot) =
2598 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2599 let mut block_map = BlockMap::new(wrap_snapshot.clone(), true, 2, 1, 1);
2600 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2601
2602 assert_eq!(
2603 blocks_snapshot.text(),
2604 "\n\n\n111\n\n\n\n\n222\n\n\n333\n\n\n444\n\n\n\n\n555\n\n\n666\n"
2605 );
2606 assert_eq!(
2607 blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
2608 vec![
2609 None,
2610 None,
2611 None,
2612 Some(0),
2613 None,
2614 None,
2615 None,
2616 None,
2617 Some(1),
2618 None,
2619 None,
2620 Some(2),
2621 None,
2622 None,
2623 Some(3),
2624 None,
2625 None,
2626 None,
2627 None,
2628 Some(4),
2629 None,
2630 None,
2631 Some(5),
2632 None,
2633 ]
2634 );
2635
2636 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2637 let excerpt_blocks_2 = writer.insert(vec![
2638 BlockProperties {
2639 style: BlockStyle::Fixed,
2640 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(1, 0))),
2641 height: 1,
2642 render: Arc::new(|_| div().into_any()),
2643 priority: 0,
2644 },
2645 BlockProperties {
2646 style: BlockStyle::Fixed,
2647 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(2, 0))),
2648 height: 1,
2649 render: Arc::new(|_| div().into_any()),
2650 priority: 0,
2651 },
2652 BlockProperties {
2653 style: BlockStyle::Fixed,
2654 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(3, 0))),
2655 height: 1,
2656 render: Arc::new(|_| div().into_any()),
2657 priority: 0,
2658 },
2659 ]);
2660 let excerpt_blocks_3 = writer.insert(vec![
2661 BlockProperties {
2662 style: BlockStyle::Fixed,
2663 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(4, 0))),
2664 height: 1,
2665 render: Arc::new(|_| div().into_any()),
2666 priority: 0,
2667 },
2668 BlockProperties {
2669 style: BlockStyle::Fixed,
2670 placement: BlockPlacement::Below(buffer_snapshot.anchor_after(Point::new(5, 0))),
2671 height: 1,
2672 render: Arc::new(|_| div().into_any()),
2673 priority: 0,
2674 },
2675 ]);
2676
2677 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2678 assert_eq!(
2679 blocks_snapshot.text(),
2680 "\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"
2681 );
2682 assert_eq!(
2683 blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
2684 vec![
2685 None,
2686 None,
2687 None,
2688 Some(0),
2689 None,
2690 None,
2691 None,
2692 None,
2693 None,
2694 Some(1),
2695 None,
2696 None,
2697 None,
2698 Some(2),
2699 None,
2700 None,
2701 Some(3),
2702 None,
2703 None,
2704 None,
2705 None,
2706 None,
2707 None,
2708 Some(4),
2709 None,
2710 None,
2711 Some(5),
2712 None,
2713 None,
2714 ]
2715 );
2716
2717 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2718 buffer.read_with(cx, |buffer, cx| {
2719 writer.fold_buffer(buffer_id_1, buffer, cx);
2720 });
2721 let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
2722 style: BlockStyle::Fixed,
2723 placement: BlockPlacement::Above(buffer_snapshot.anchor_after(Point::new(0, 0))),
2724 height: 1,
2725 render: Arc::new(|_| div().into_any()),
2726 priority: 0,
2727 }]);
2728 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2729 let blocks = blocks_snapshot
2730 .blocks_in_range(0..u32::MAX)
2731 .collect::<Vec<_>>();
2732 for (_, block) in &blocks {
2733 if let BlockId::Custom(custom_block_id) = block.id() {
2734 assert!(
2735 !excerpt_blocks_1.contains(&custom_block_id),
2736 "Should have no blocks from the folded buffer"
2737 );
2738 assert!(
2739 excerpt_blocks_2.contains(&custom_block_id)
2740 || excerpt_blocks_3.contains(&custom_block_id),
2741 "Should have only blocks from unfolded buffers"
2742 );
2743 }
2744 }
2745 assert_eq!(
2746 1,
2747 blocks
2748 .iter()
2749 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2750 .count(),
2751 "Should have one folded block, producing a header of the second buffer"
2752 );
2753 assert_eq!(
2754 blocks_snapshot.text(),
2755 "\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"
2756 );
2757 assert_eq!(
2758 blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
2759 vec![
2760 None,
2761 None,
2762 None,
2763 None,
2764 None,
2765 None,
2766 Some(1),
2767 None,
2768 None,
2769 None,
2770 Some(2),
2771 None,
2772 None,
2773 Some(3),
2774 None,
2775 None,
2776 None,
2777 None,
2778 None,
2779 None,
2780 Some(4),
2781 None,
2782 None,
2783 Some(5),
2784 None,
2785 None,
2786 ]
2787 );
2788
2789 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2790 buffer.read_with(cx, |buffer, cx| {
2791 writer.fold_buffer(buffer_id_2, buffer, cx);
2792 });
2793 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2794 let blocks = blocks_snapshot
2795 .blocks_in_range(0..u32::MAX)
2796 .collect::<Vec<_>>();
2797 for (_, block) in &blocks {
2798 if let BlockId::Custom(custom_block_id) = block.id() {
2799 assert!(
2800 !excerpt_blocks_1.contains(&custom_block_id),
2801 "Should have no blocks from the folded buffer_1"
2802 );
2803 assert!(
2804 !excerpt_blocks_2.contains(&custom_block_id),
2805 "Should have no blocks from the folded buffer_2"
2806 );
2807 assert!(
2808 excerpt_blocks_3.contains(&custom_block_id),
2809 "Should have only blocks from unfolded buffers"
2810 );
2811 }
2812 }
2813 assert_eq!(
2814 2,
2815 blocks
2816 .iter()
2817 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2818 .count(),
2819 "Should have two folded blocks, producing headers"
2820 );
2821 assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n\n555\n\n\n666\n\n");
2822 assert_eq!(
2823 blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
2824 vec![
2825 None,
2826 None,
2827 None,
2828 None,
2829 None,
2830 None,
2831 None,
2832 None,
2833 Some(4),
2834 None,
2835 None,
2836 Some(5),
2837 None,
2838 None,
2839 ]
2840 );
2841
2842 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2843 buffer.read_with(cx, |buffer, cx| {
2844 writer.unfold_buffer(buffer_id_1, buffer, cx);
2845 });
2846 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2847 let blocks = blocks_snapshot
2848 .blocks_in_range(0..u32::MAX)
2849 .collect::<Vec<_>>();
2850 for (_, block) in &blocks {
2851 if let BlockId::Custom(custom_block_id) = block.id() {
2852 assert!(
2853 !excerpt_blocks_2.contains(&custom_block_id),
2854 "Should have no blocks from the folded buffer_2"
2855 );
2856 assert!(
2857 excerpt_blocks_1.contains(&custom_block_id)
2858 || excerpt_blocks_3.contains(&custom_block_id),
2859 "Should have only blocks from unfolded buffers"
2860 );
2861 }
2862 }
2863 assert_eq!(
2864 1,
2865 blocks
2866 .iter()
2867 .filter(|(_, block)| matches!(block, Block::FoldedBuffer { .. }))
2868 .count(),
2869 "Should be back to a single folded buffer, producing a header for buffer_2"
2870 );
2871 assert_eq!(
2872 blocks_snapshot.text(),
2873 "\n\n\n\n111\n\n\n\n\n\n\n\n555\n\n\n666\n\n",
2874 "Should have extra newline for 111 buffer, due to a new block added when it was folded"
2875 );
2876 assert_eq!(
2877 blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
2878 vec![
2879 None,
2880 None,
2881 None,
2882 None,
2883 Some(0),
2884 None,
2885 None,
2886 None,
2887 None,
2888 None,
2889 None,
2890 None,
2891 Some(4),
2892 None,
2893 None,
2894 Some(5),
2895 None,
2896 None,
2897 ]
2898 );
2899
2900 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2901 buffer.read_with(cx, |buffer, cx| {
2902 writer.fold_buffer(buffer_id_3, buffer, cx);
2903 });
2904 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2905 let blocks = blocks_snapshot
2906 .blocks_in_range(0..u32::MAX)
2907 .collect::<Vec<_>>();
2908 for (_, block) in &blocks {
2909 if let BlockId::Custom(custom_block_id) = block.id() {
2910 assert!(
2911 excerpt_blocks_1.contains(&custom_block_id),
2912 "Should have no blocks from the folded buffer_1"
2913 );
2914 assert!(
2915 !excerpt_blocks_2.contains(&custom_block_id),
2916 "Should have only blocks from unfolded buffers"
2917 );
2918 assert!(
2919 !excerpt_blocks_3.contains(&custom_block_id),
2920 "Should have only blocks from unfolded buffers"
2921 );
2922 }
2923 }
2924
2925 assert_eq!(
2926 blocks_snapshot.text(),
2927 "\n\n\n\n111\n\n\n\n\n",
2928 "Should have a single, first buffer left after folding"
2929 );
2930 assert_eq!(
2931 blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
2932 vec![
2933 None,
2934 None,
2935 None,
2936 None,
2937 Some(0),
2938 None,
2939 None,
2940 None,
2941 None,
2942 None,
2943 ]
2944 );
2945 }
2946
2947 #[gpui::test]
2948 fn test_basic_buffer_fold(cx: &mut gpui::TestAppContext) {
2949 cx.update(init_test);
2950
2951 let text = "111";
2952
2953 let buffer = cx.update(|cx| {
2954 MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(0, 3)])], cx)
2955 });
2956 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
2957 let buffer_ids = buffer_snapshot
2958 .excerpts()
2959 .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
2960 .dedup()
2961 .collect::<Vec<_>>();
2962 assert_eq!(buffer_ids.len(), 1);
2963 let buffer_id = buffer_ids[0];
2964
2965 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
2966 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
2967 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
2968 let (_, wrap_snapshot) =
2969 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
2970 let mut block_map = BlockMap::new(wrap_snapshot.clone(), true, 2, 1, 1);
2971 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2972
2973 assert_eq!(blocks_snapshot.text(), "\n\n\n111\n");
2974
2975 let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
2976 buffer.read_with(cx, |buffer, cx| {
2977 writer.fold_buffer(buffer_id, buffer, cx);
2978 });
2979 let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
2980 let blocks = blocks_snapshot
2981 .blocks_in_range(0..u32::MAX)
2982 .collect::<Vec<_>>();
2983 assert_eq!(
2984 1,
2985 blocks
2986 .iter()
2987 .filter(|(_, block)| {
2988 match block {
2989 Block::FoldedBuffer { prev_excerpt, .. } => {
2990 assert!(prev_excerpt.is_none());
2991 true
2992 }
2993 _ => false,
2994 }
2995 })
2996 .count(),
2997 "Should have one folded block, producing a header of the second buffer"
2998 );
2999 assert_eq!(blocks_snapshot.text(), "\n");
3000 assert_eq!(
3001 blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
3002 vec![None, None],
3003 "When fully folded, should be no buffer rows"
3004 );
3005 }
3006
3007 #[gpui::test(iterations = 100)]
3008 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
3009 cx.update(init_test);
3010
3011 let operations = env::var("OPERATIONS")
3012 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3013 .unwrap_or(10);
3014
3015 let wrap_width = if rng.gen_bool(0.2) {
3016 None
3017 } else {
3018 Some(px(rng.gen_range(0.0..=100.0)))
3019 };
3020 let tab_size = 1.try_into().unwrap();
3021 let font_size = px(14.0);
3022 let buffer_start_header_height = rng.gen_range(1..=5);
3023 let excerpt_header_height = rng.gen_range(1..=5);
3024 let excerpt_footer_height = rng.gen_range(1..=5);
3025
3026 log::info!("Wrap width: {:?}", wrap_width);
3027 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
3028 log::info!("Excerpt Footer Height: {:?}", excerpt_footer_height);
3029 let is_singleton = rng.gen();
3030 let buffer = if is_singleton {
3031 let len = rng.gen_range(0..10);
3032 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
3033 log::info!("initial singleton buffer text: {:?}", text);
3034 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
3035 } else {
3036 cx.update(|cx| {
3037 let multibuffer = MultiBuffer::build_random(&mut rng, cx);
3038 log::info!(
3039 "initial multi-buffer text: {:?}",
3040 multibuffer.read(cx).read(cx).text()
3041 );
3042 multibuffer
3043 })
3044 };
3045
3046 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
3047 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
3048 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
3049 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
3050 let (wrap_map, wraps_snapshot) = cx
3051 .update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), font_size, wrap_width, cx));
3052 let mut block_map = BlockMap::new(
3053 wraps_snapshot,
3054 true,
3055 buffer_start_header_height,
3056 excerpt_header_height,
3057 excerpt_footer_height,
3058 );
3059
3060 for _ in 0..operations {
3061 let mut buffer_edits = Vec::new();
3062 match rng.gen_range(0..=100) {
3063 0..=19 => {
3064 let wrap_width = if rng.gen_bool(0.2) {
3065 None
3066 } else {
3067 Some(px(rng.gen_range(0.0..=100.0)))
3068 };
3069 log::info!("Setting wrap width to {:?}", wrap_width);
3070 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
3071 }
3072 20..=39 => {
3073 let block_count = rng.gen_range(1..=5);
3074 let block_properties = (0..block_count)
3075 .map(|_| {
3076 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
3077 let offset =
3078 buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left);
3079 let mut min_height = 0;
3080 let placement = match rng.gen_range(0..3) {
3081 0 => {
3082 min_height = 1;
3083 let start = buffer.anchor_after(offset);
3084 let end = buffer.anchor_after(buffer.clip_offset(
3085 rng.gen_range(offset..=buffer.len()),
3086 Bias::Left,
3087 ));
3088 BlockPlacement::Replace(start..=end)
3089 }
3090 1 => BlockPlacement::Above(buffer.anchor_after(offset)),
3091 _ => BlockPlacement::Below(buffer.anchor_after(offset)),
3092 };
3093
3094 let height = rng.gen_range(min_height..5);
3095 BlockProperties {
3096 style: BlockStyle::Fixed,
3097 placement,
3098 height,
3099 render: Arc::new(|_| div().into_any()),
3100 priority: 0,
3101 }
3102 })
3103 .collect::<Vec<_>>();
3104
3105 let (inlay_snapshot, inlay_edits) =
3106 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3107 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3108 let (tab_snapshot, tab_edits) =
3109 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3110 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3111 wrap_map.sync(tab_snapshot, tab_edits, cx)
3112 });
3113 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3114 let block_ids =
3115 block_map.insert(block_properties.iter().map(|props| BlockProperties {
3116 placement: props.placement.clone(),
3117 height: props.height,
3118 style: props.style,
3119 render: Arc::new(|_| div().into_any()),
3120 priority: 0,
3121 }));
3122
3123 for (block_properties, block_id) in block_properties.iter().zip(block_ids) {
3124 log::info!(
3125 "inserted block {:?} with height {} and id {:?}",
3126 block_properties
3127 .placement
3128 .as_ref()
3129 .map(|p| p.to_point(&buffer_snapshot)),
3130 block_properties.height,
3131 block_id
3132 );
3133 }
3134 }
3135 40..=59 if !block_map.custom_blocks.is_empty() => {
3136 let block_count = rng.gen_range(1..=4.min(block_map.custom_blocks.len()));
3137 let block_ids_to_remove = block_map
3138 .custom_blocks
3139 .choose_multiple(&mut rng, block_count)
3140 .map(|block| block.id)
3141 .collect::<HashSet<_>>();
3142
3143 let (inlay_snapshot, inlay_edits) =
3144 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3145 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3146 let (tab_snapshot, tab_edits) =
3147 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3148 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3149 wrap_map.sync(tab_snapshot, tab_edits, cx)
3150 });
3151 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3152 log::info!(
3153 "removing {} blocks: {:?}",
3154 block_ids_to_remove.len(),
3155 block_ids_to_remove
3156 );
3157 block_map.remove(block_ids_to_remove);
3158 }
3159 60..=79 => {
3160 if buffer.read_with(cx, |buffer, _| buffer.is_singleton()) {
3161 log::info!("Noop fold/unfold operation on a singleton buffer");
3162 continue;
3163 }
3164 let (inlay_snapshot, inlay_edits) =
3165 inlay_map.sync(buffer_snapshot.clone(), vec![]);
3166 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3167 let (tab_snapshot, tab_edits) =
3168 tab_map.sync(fold_snapshot, fold_edits, tab_size);
3169 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3170 wrap_map.sync(tab_snapshot, tab_edits, cx)
3171 });
3172 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
3173 let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
3174 let folded_buffers = block_map
3175 .0
3176 .folded_buffers
3177 .iter()
3178 .cloned()
3179 .collect::<Vec<_>>();
3180 let mut unfolded_buffers = buffer.excerpt_buffer_ids();
3181 unfolded_buffers.dedup();
3182 log::debug!("All buffers {unfolded_buffers:?}");
3183 log::debug!("Folded buffers {folded_buffers:?}");
3184 unfolded_buffers
3185 .retain(|buffer_id| !block_map.0.folded_buffers.contains(buffer_id));
3186 (unfolded_buffers, folded_buffers)
3187 });
3188 let mut folded_count = folded_buffers.len();
3189 let mut unfolded_count = unfolded_buffers.len();
3190
3191 let fold = !unfolded_buffers.is_empty() && rng.gen_bool(0.5);
3192 let unfold = !folded_buffers.is_empty() && rng.gen_bool(0.5);
3193 if !fold && !unfold {
3194 log::info!("Noop fold/unfold operation. Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}");
3195 continue;
3196 }
3197
3198 buffer.update(cx, |buffer, cx| {
3199 if fold {
3200 let buffer_to_fold =
3201 unfolded_buffers[rng.gen_range(0..unfolded_buffers.len())];
3202 log::info!("Folding {buffer_to_fold:?}");
3203 let related_excerpts = buffer_snapshot
3204 .excerpts()
3205 .filter_map(|(excerpt_id, buffer, range)| {
3206 if buffer.remote_id() == buffer_to_fold {
3207 Some((
3208 excerpt_id,
3209 buffer
3210 .text_for_range(range.context)
3211 .collect::<String>(),
3212 ))
3213 } else {
3214 None
3215 }
3216 })
3217 .collect::<Vec<_>>();
3218 log::info!(
3219 "Folding {buffer_to_fold:?}, related excerpts: {related_excerpts:?}"
3220 );
3221 folded_count += 1;
3222 unfolded_count -= 1;
3223 block_map.fold_buffer(buffer_to_fold, buffer, cx);
3224 }
3225 if unfold {
3226 let buffer_to_unfold =
3227 folded_buffers[rng.gen_range(0..folded_buffers.len())];
3228 log::info!("Unfolding {buffer_to_unfold:?}");
3229 unfolded_count += 1;
3230 folded_count -= 1;
3231 block_map.unfold_buffer(buffer_to_unfold, buffer, cx);
3232 }
3233 log::info!(
3234 "Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
3235 );
3236 });
3237 }
3238 _ => {
3239 buffer.update(cx, |buffer, cx| {
3240 let mutation_count = rng.gen_range(1..=5);
3241 let subscription = buffer.subscribe();
3242 buffer.randomly_mutate(&mut rng, mutation_count, cx);
3243 buffer_snapshot = buffer.snapshot(cx);
3244 buffer_edits.extend(subscription.consume());
3245 log::info!("buffer text: {:?}", buffer_snapshot.text());
3246 });
3247 }
3248 }
3249
3250 let (inlay_snapshot, inlay_edits) =
3251 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
3252 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
3253 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
3254 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
3255 wrap_map.sync(tab_snapshot, tab_edits, cx)
3256 });
3257 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
3258 assert_eq!(
3259 blocks_snapshot.transforms.summary().input_rows,
3260 wraps_snapshot.max_point().row() + 1
3261 );
3262 log::info!("wrapped text: {:?}", wraps_snapshot.text());
3263 log::info!("blocks text: {:?}", blocks_snapshot.text());
3264
3265 let mut expected_blocks = Vec::new();
3266 expected_blocks.extend(block_map.custom_blocks.iter().filter_map(|block| {
3267 Some((
3268 block.placement.to_wrap_row(&wraps_snapshot)?,
3269 Block::Custom(block.clone()),
3270 ))
3271 }));
3272
3273 // Note that this needs to be synced with the related section in BlockMap::sync
3274 expected_blocks.extend(BlockMap::header_and_footer_blocks(
3275 true,
3276 excerpt_footer_height,
3277 buffer_start_header_height,
3278 excerpt_header_height,
3279 &buffer_snapshot,
3280 &block_map.folded_buffers,
3281 0..,
3282 &wraps_snapshot,
3283 ));
3284
3285 BlockMap::sort_blocks(&mut expected_blocks);
3286
3287 for (placement, block) in &expected_blocks {
3288 log::info!(
3289 "Block {:?} placement: {:?} Height: {:?}",
3290 block.id(),
3291 placement,
3292 block.height()
3293 );
3294 }
3295
3296 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
3297
3298 let input_buffer_rows = buffer_snapshot
3299 .buffer_rows(MultiBufferRow(0))
3300 .collect::<Vec<_>>();
3301 let mut expected_buffer_rows = Vec::new();
3302 let mut expected_text = String::new();
3303 let mut expected_block_positions = Vec::new();
3304 let mut expected_replaced_buffer_rows = HashSet::default();
3305 let input_text = wraps_snapshot.text();
3306
3307 // Loop over the input lines, creating (N - 1) empty lines for
3308 // blocks of height N.
3309 //
3310 // It's important to note that output *starts* as one empty line,
3311 // so we special case row 0 to assume a leading '\n'.
3312 //
3313 // Linehood is the birthright of strings.
3314 let mut input_text_lines = input_text.split('\n').enumerate().peekable();
3315 let mut block_row = 0;
3316 while let Some((wrap_row, input_line)) = input_text_lines.next() {
3317 let wrap_row = wrap_row as u32;
3318 let multibuffer_row = wraps_snapshot
3319 .to_point(WrapPoint::new(wrap_row, 0), Bias::Left)
3320 .row;
3321
3322 // Create empty lines for the above block
3323 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3324 if placement.start().0 == wrap_row && block.place_above() {
3325 let (_, block) = sorted_blocks_iter.next().unwrap();
3326 expected_block_positions.push((block_row, block.id()));
3327 if block.height() > 0 {
3328 let text = "\n".repeat((block.height() - 1) as usize);
3329 if block_row > 0 {
3330 expected_text.push('\n')
3331 }
3332 expected_text.push_str(&text);
3333 for _ in 0..block.height() {
3334 expected_buffer_rows.push(None);
3335 }
3336 block_row += block.height();
3337 }
3338 } else {
3339 break;
3340 }
3341 }
3342
3343 // Skip lines within replace blocks, then create empty lines for the replace block's height
3344 let mut is_in_replace_block = false;
3345 if let Some((BlockPlacement::Replace(replace_range), block)) =
3346 sorted_blocks_iter.peek()
3347 {
3348 if wrap_row >= replace_range.start().0 {
3349 is_in_replace_block = true;
3350
3351 if wrap_row == replace_range.start().0 {
3352 if matches!(block, Block::FoldedBuffer { .. }) {
3353 expected_buffer_rows.push(None);
3354 } else {
3355 expected_buffer_rows
3356 .push(input_buffer_rows[multibuffer_row as usize]);
3357 }
3358 }
3359
3360 if wrap_row == replace_range.end().0 {
3361 expected_block_positions.push((block_row, block.id()));
3362 let text = "\n".repeat((block.height() - 1) as usize);
3363 if block_row > 0 {
3364 expected_text.push('\n');
3365 }
3366 expected_text.push_str(&text);
3367
3368 for _ in 1..block.height() {
3369 expected_buffer_rows.push(None);
3370 }
3371 block_row += block.height();
3372
3373 sorted_blocks_iter.next();
3374 }
3375 }
3376 }
3377
3378 if is_in_replace_block {
3379 expected_replaced_buffer_rows.insert(MultiBufferRow(multibuffer_row));
3380 } else {
3381 let buffer_row = input_buffer_rows[multibuffer_row as usize];
3382 let soft_wrapped = wraps_snapshot
3383 .to_tab_point(WrapPoint::new(wrap_row, 0))
3384 .column()
3385 > 0;
3386 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
3387 if block_row > 0 {
3388 expected_text.push('\n');
3389 }
3390 expected_text.push_str(input_line);
3391 block_row += 1;
3392 }
3393
3394 while let Some((placement, block)) = sorted_blocks_iter.peek() {
3395 if placement.end().0 == wrap_row && block.place_below() {
3396 let (_, block) = sorted_blocks_iter.next().unwrap();
3397 expected_block_positions.push((block_row, block.id()));
3398 if block.height() > 0 {
3399 let text = "\n".repeat((block.height() - 1) as usize);
3400 if block_row > 0 {
3401 expected_text.push('\n')
3402 }
3403 expected_text.push_str(&text);
3404 for _ in 0..block.height() {
3405 expected_buffer_rows.push(None);
3406 }
3407 block_row += block.height();
3408 }
3409 } else {
3410 break;
3411 }
3412 }
3413 }
3414
3415 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
3416 let expected_row_count = expected_lines.len();
3417 log::info!("expected text: {expected_text:?}");
3418
3419 assert_eq!(
3420 blocks_snapshot.max_point().row + 1,
3421 expected_row_count as u32,
3422 "actual row count != expected row count",
3423 );
3424 assert_eq!(
3425 blocks_snapshot.text(),
3426 expected_text,
3427 "actual text != expected text",
3428 );
3429
3430 for start_row in 0..expected_row_count {
3431 let end_row = rng.gen_range(start_row + 1..=expected_row_count);
3432 let mut expected_text = expected_lines[start_row..end_row].join("\n");
3433 if end_row < expected_row_count {
3434 expected_text.push('\n');
3435 }
3436
3437 let actual_text = blocks_snapshot
3438 .chunks(
3439 start_row as u32..end_row as u32,
3440 false,
3441 false,
3442 Highlights::default(),
3443 )
3444 .map(|chunk| chunk.text)
3445 .collect::<String>();
3446 assert_eq!(
3447 actual_text,
3448 expected_text,
3449 "incorrect text starting row row range {:?}",
3450 start_row..end_row
3451 );
3452 assert_eq!(
3453 blocks_snapshot
3454 .buffer_rows(BlockRow(start_row as u32))
3455 .collect::<Vec<_>>(),
3456 &expected_buffer_rows[start_row..],
3457 "incorrect buffer_rows starting at row {:?}",
3458 start_row
3459 );
3460 }
3461
3462 assert_eq!(
3463 blocks_snapshot
3464 .blocks_in_range(0..(expected_row_count as u32))
3465 .map(|(row, block)| (row, block.id()))
3466 .collect::<Vec<_>>(),
3467 expected_block_positions,
3468 "invalid blocks_in_range({:?})",
3469 0..expected_row_count
3470 );
3471
3472 for (_, expected_block) in
3473 blocks_snapshot.blocks_in_range(0..(expected_row_count as u32))
3474 {
3475 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
3476 assert_eq!(
3477 actual_block.map(|block| block.id()),
3478 Some(expected_block.id())
3479 );
3480 }
3481
3482 for (block_row, block_id) in expected_block_positions {
3483 if let BlockId::Custom(block_id) = block_id {
3484 assert_eq!(
3485 blocks_snapshot.row_for_block(block_id),
3486 Some(BlockRow(block_row))
3487 );
3488 }
3489 }
3490
3491 let mut expected_longest_rows = Vec::new();
3492 let mut longest_line_len = -1_isize;
3493 for (row, line) in expected_lines.iter().enumerate() {
3494 let row = row as u32;
3495
3496 assert_eq!(
3497 blocks_snapshot.line_len(BlockRow(row)),
3498 line.len() as u32,
3499 "invalid line len for row {}",
3500 row
3501 );
3502
3503 let line_char_count = line.chars().count() as isize;
3504 match line_char_count.cmp(&longest_line_len) {
3505 Ordering::Less => {}
3506 Ordering::Equal => expected_longest_rows.push(row),
3507 Ordering::Greater => {
3508 longest_line_len = line_char_count;
3509 expected_longest_rows.clear();
3510 expected_longest_rows.push(row);
3511 }
3512 }
3513 }
3514
3515 let longest_row = blocks_snapshot.longest_row();
3516 assert!(
3517 expected_longest_rows.contains(&longest_row),
3518 "incorrect longest row {}. expected {:?} with length {}",
3519 longest_row,
3520 expected_longest_rows,
3521 longest_line_len,
3522 );
3523
3524 for _ in 0..10 {
3525 let end_row = rng.gen_range(1..=expected_lines.len());
3526 let start_row = rng.gen_range(0..end_row);
3527
3528 let mut expected_longest_rows_in_range = vec![];
3529 let mut longest_line_len_in_range = 0;
3530
3531 let mut row = start_row as u32;
3532 for line in &expected_lines[start_row..end_row] {
3533 let line_char_count = line.chars().count() as isize;
3534 match line_char_count.cmp(&longest_line_len_in_range) {
3535 Ordering::Less => {}
3536 Ordering::Equal => expected_longest_rows_in_range.push(row),
3537 Ordering::Greater => {
3538 longest_line_len_in_range = line_char_count;
3539 expected_longest_rows_in_range.clear();
3540 expected_longest_rows_in_range.push(row);
3541 }
3542 }
3543 row += 1;
3544 }
3545
3546 let longest_row_in_range = blocks_snapshot
3547 .longest_row_in_range(BlockRow(start_row as u32)..BlockRow(end_row as u32));
3548 assert!(
3549 expected_longest_rows_in_range.contains(&longest_row_in_range.0),
3550 "incorrect longest row {} in range {:?}. expected {:?} with length {}",
3551 longest_row,
3552 start_row..end_row,
3553 expected_longest_rows_in_range,
3554 longest_line_len_in_range,
3555 );
3556 }
3557
3558 // Ensure that conversion between block points and wrap points is stable.
3559 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
3560 let wrap_point = WrapPoint::new(row, 0);
3561 let block_point = blocks_snapshot.to_block_point(wrap_point);
3562 let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left);
3563 let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right);
3564 assert_eq!(blocks_snapshot.to_block_point(left_wrap_point), block_point);
3565 assert_eq!(
3566 blocks_snapshot.to_block_point(right_wrap_point),
3567 block_point
3568 );
3569 }
3570
3571 let mut block_point = BlockPoint::new(0, 0);
3572 for c in expected_text.chars() {
3573 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
3574 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
3575 assert_eq!(
3576 blocks_snapshot
3577 .to_block_point(blocks_snapshot.to_wrap_point(left_point, Bias::Left)),
3578 left_point,
3579 "block point: {:?}, wrap point: {:?}",
3580 block_point,
3581 blocks_snapshot.to_wrap_point(left_point, Bias::Left)
3582 );
3583 assert_eq!(
3584 left_buffer_point,
3585 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
3586 "{:?} is not valid in buffer coordinates",
3587 left_point
3588 );
3589
3590 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
3591 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
3592 assert_eq!(
3593 blocks_snapshot
3594 .to_block_point(blocks_snapshot.to_wrap_point(right_point, Bias::Right)),
3595 right_point,
3596 "block point: {:?}, wrap point: {:?}",
3597 block_point,
3598 blocks_snapshot.to_wrap_point(right_point, Bias::Right)
3599 );
3600 assert_eq!(
3601 right_buffer_point,
3602 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
3603 "{:?} is not valid in buffer coordinates",
3604 right_point
3605 );
3606
3607 if c == '\n' {
3608 block_point.0 += Point::new(1, 0);
3609 } else {
3610 block_point.column += c.len_utf8() as u32;
3611 }
3612 }
3613
3614 for buffer_row in 0..=buffer_snapshot.max_point().row {
3615 let buffer_row = MultiBufferRow(buffer_row);
3616 assert_eq!(
3617 blocks_snapshot.is_line_replaced(buffer_row),
3618 expected_replaced_buffer_rows.contains(&buffer_row),
3619 "incorrect is_line_replaced({buffer_row:?}), expected replaced rows: {expected_replaced_buffer_rows:?}",
3620 );
3621 }
3622 }
3623 }
3624
3625 fn init_test(cx: &mut gpui::AppContext) {
3626 let settings = SettingsStore::test(cx);
3627 cx.set_global(settings);
3628 theme::init(theme::LoadThemes::JustBase, cx);
3629 assets::Assets.load_test_fonts(cx);
3630 }
3631
3632 impl Block {
3633 fn as_custom(&self) -> Option<&CustomBlock> {
3634 match self {
3635 Block::Custom(block) => Some(block),
3636 _ => None,
3637 }
3638 }
3639 }
3640
3641 impl BlockSnapshot {
3642 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
3643 self.wrap_snapshot
3644 .to_point(self.to_wrap_point(point, bias), bias)
3645 }
3646 }
3647}