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