1use super::{
2 wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
3 Highlights,
4};
5use crate::{EditorStyle, GutterDimensions};
6use collections::{Bound, HashMap, HashSet};
7use gpui::{AnyElement, EntityId, Pixels, WindowContext};
8use language::{BufferSnapshot, Chunk, Patch, Point};
9use multi_buffer::{Anchor, ExcerptId, ExcerptRange, MultiBufferRow, ToPoint as _};
10use parking_lot::Mutex;
11use std::{
12 cell::RefCell,
13 cmp::{self, Ordering},
14 fmt::Debug,
15 ops::{Deref, DerefMut, Range, RangeBounds},
16 sync::{
17 atomic::{AtomicUsize, Ordering::SeqCst},
18 Arc,
19 },
20};
21use sum_tree::{Bias, SumTree, TreeMap};
22use text::Edit;
23use ui::ElementId;
24
25const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
26
27/// Tracks custom blocks such as diagnostics that should be displayed within buffer.
28///
29/// See the [`display_map` module documentation](crate::display_map) for more information.
30pub struct BlockMap {
31 next_block_id: AtomicUsize,
32 wrap_snapshot: RefCell<WrapSnapshot>,
33 custom_blocks: Vec<Arc<CustomBlock>>,
34 custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
35 transforms: RefCell<SumTree<Transform>>,
36 show_excerpt_controls: bool,
37 buffer_header_height: u8,
38 excerpt_header_height: u8,
39 excerpt_footer_height: u8,
40}
41
42pub struct BlockMapReader<'a> {
43 blocks: &'a Vec<Arc<CustomBlock>>,
44 pub snapshot: BlockSnapshot,
45}
46
47pub struct BlockMapWriter<'a>(&'a mut BlockMap);
48
49#[derive(Clone)]
50pub struct BlockSnapshot {
51 wrap_snapshot: WrapSnapshot,
52 transforms: SumTree<Transform>,
53 custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
54}
55
56#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
57pub struct CustomBlockId(usize);
58
59impl Into<ElementId> for CustomBlockId {
60 fn into(self) -> ElementId {
61 ElementId::Integer(self.0)
62 }
63}
64
65#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
66pub struct BlockPoint(pub Point);
67
68#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
69pub struct BlockRow(pub(super) u32);
70
71#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
72struct WrapRow(u32);
73
74pub type RenderBlock = Box<dyn Send + FnMut(&mut BlockContext) -> AnyElement>;
75
76pub struct CustomBlock {
77 id: CustomBlockId,
78 position: Anchor,
79 height: u8,
80 style: BlockStyle,
81 render: Mutex<RenderBlock>,
82 disposition: BlockDisposition,
83}
84
85pub struct BlockProperties<P> {
86 pub position: P,
87 pub height: u8,
88 pub style: BlockStyle,
89 pub render: RenderBlock,
90 pub disposition: BlockDisposition,
91}
92
93impl<P: Debug> Debug for BlockProperties<P> {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 f.debug_struct("BlockProperties")
96 .field("position", &self.position)
97 .field("height", &self.height)
98 .field("style", &self.style)
99 .field("disposition", &self.disposition)
100 .finish()
101 }
102}
103
104#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
105pub enum BlockStyle {
106 Fixed,
107 Flex,
108 Sticky,
109}
110
111pub struct BlockContext<'a, 'b> {
112 pub context: &'b mut WindowContext<'a>,
113 pub anchor_x: Pixels,
114 pub max_width: Pixels,
115 pub gutter_dimensions: &'b GutterDimensions,
116 pub em_width: Pixels,
117 pub line_height: Pixels,
118 pub block_id: BlockId,
119 pub editor_style: &'b EditorStyle,
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
123pub enum BlockId {
124 Custom(CustomBlockId),
125 ExcerptHeader(ExcerptId),
126 ExcerptFooter(ExcerptId),
127}
128
129impl From<BlockId> for EntityId {
130 fn from(value: BlockId) -> Self {
131 match value {
132 BlockId::Custom(CustomBlockId(id)) => EntityId::from(id as u64),
133 BlockId::ExcerptHeader(id) => id.into(),
134 BlockId::ExcerptFooter(id) => id.into(),
135 }
136 }
137}
138
139impl From<BlockId> for ElementId {
140 fn from(value: BlockId) -> Self {
141 match value {
142 BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(),
143 BlockId::ExcerptHeader(id) => ("ExcerptHeader", EntityId::from(id)).into(),
144 BlockId::ExcerptFooter(id) => ("ExcerptFooter", EntityId::from(id)).into(),
145 }
146 }
147}
148
149impl std::fmt::Display for BlockId {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 match self {
152 Self::Custom(id) => write!(f, "Block({id:?})"),
153 Self::ExcerptHeader(id) => write!(f, "ExcerptHeader({id:?})"),
154 Self::ExcerptFooter(id) => write!(f, "ExcerptFooter({id:?})"),
155 }
156 }
157}
158
159/// Whether the block should be considered above or below the anchor line
160#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
161pub enum BlockDisposition {
162 Above,
163 Below,
164}
165
166#[derive(Clone, Debug)]
167struct Transform {
168 summary: TransformSummary,
169 block: Option<Block>,
170}
171
172pub(crate) enum BlockType {
173 Custom(CustomBlockId),
174 Header,
175 Footer,
176}
177
178pub(crate) trait BlockLike {
179 fn block_type(&self) -> BlockType;
180 fn disposition(&self) -> BlockDisposition;
181}
182
183#[allow(clippy::large_enum_variant)]
184#[derive(Clone)]
185pub enum Block {
186 Custom(Arc<CustomBlock>),
187 ExcerptHeader {
188 id: ExcerptId,
189 buffer: BufferSnapshot,
190 range: ExcerptRange<text::Anchor>,
191 height: u8,
192 starts_new_buffer: bool,
193 show_excerpt_controls: bool,
194 },
195 ExcerptFooter {
196 id: ExcerptId,
197 disposition: BlockDisposition,
198 height: u8,
199 },
200}
201
202impl BlockLike for Block {
203 fn block_type(&self) -> BlockType {
204 match self {
205 Block::Custom(block) => BlockType::Custom(block.id),
206 Block::ExcerptHeader { .. } => BlockType::Header,
207 Block::ExcerptFooter { .. } => BlockType::Footer,
208 }
209 }
210
211 fn disposition(&self) -> BlockDisposition {
212 self.disposition()
213 }
214}
215
216impl Block {
217 pub fn id(&self) -> BlockId {
218 match self {
219 Block::Custom(block) => BlockId::Custom(block.id),
220 Block::ExcerptHeader { id, .. } => BlockId::ExcerptHeader(*id),
221 Block::ExcerptFooter { id, .. } => BlockId::ExcerptFooter(*id),
222 }
223 }
224
225 fn disposition(&self) -> BlockDisposition {
226 match self {
227 Block::Custom(block) => block.disposition,
228 Block::ExcerptHeader { .. } => BlockDisposition::Above,
229 Block::ExcerptFooter { disposition, .. } => *disposition,
230 }
231 }
232
233 pub fn height(&self) -> u8 {
234 match self {
235 Block::Custom(block) => block.height,
236 Block::ExcerptHeader { height, .. } => *height,
237 Block::ExcerptFooter { height, .. } => *height,
238 }
239 }
240
241 pub fn style(&self) -> BlockStyle {
242 match self {
243 Block::Custom(block) => block.style,
244 Block::ExcerptHeader { .. } => BlockStyle::Sticky,
245 Block::ExcerptFooter { .. } => BlockStyle::Sticky,
246 }
247 }
248}
249
250impl Debug for Block {
251 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252 match self {
253 Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(),
254 Self::ExcerptHeader {
255 buffer,
256 starts_new_buffer,
257 id,
258 ..
259 } => f
260 .debug_struct("ExcerptHeader")
261 .field("id", &id)
262 .field("path", &buffer.file().map(|f| f.path()))
263 .field("starts_new_buffer", &starts_new_buffer)
264 .finish(),
265 Block::ExcerptFooter {
266 id, disposition, ..
267 } => f
268 .debug_struct("ExcerptFooter")
269 .field("id", &id)
270 .field("disposition", &disposition)
271 .finish(),
272 }
273 }
274}
275
276#[derive(Clone, Debug, Default)]
277struct TransformSummary {
278 input_rows: u32,
279 output_rows: u32,
280}
281
282pub struct BlockChunks<'a> {
283 transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
284 input_chunks: wrap_map::WrapChunks<'a>,
285 input_chunk: Chunk<'a>,
286 output_row: u32,
287 max_output_row: u32,
288}
289
290#[derive(Clone)]
291pub struct BlockBufferRows<'a> {
292 transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
293 input_buffer_rows: wrap_map::WrapBufferRows<'a>,
294 output_row: BlockRow,
295 started: bool,
296}
297
298impl BlockMap {
299 pub fn new(
300 wrap_snapshot: WrapSnapshot,
301 show_excerpt_controls: bool,
302 buffer_header_height: u8,
303 excerpt_header_height: u8,
304 excerpt_footer_height: u8,
305 ) -> Self {
306 let row_count = wrap_snapshot.max_point().row() + 1;
307 let map = Self {
308 next_block_id: AtomicUsize::new(0),
309 custom_blocks: Vec::new(),
310 custom_blocks_by_id: TreeMap::default(),
311 transforms: RefCell::new(SumTree::from_item(Transform::isomorphic(row_count), &())),
312 wrap_snapshot: RefCell::new(wrap_snapshot.clone()),
313 show_excerpt_controls,
314 buffer_header_height,
315 excerpt_header_height,
316 excerpt_footer_height,
317 };
318 map.sync(
319 &wrap_snapshot,
320 Patch::new(vec![Edit {
321 old: 0..row_count,
322 new: 0..row_count,
323 }]),
324 );
325 map
326 }
327
328 pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapReader {
329 self.sync(&wrap_snapshot, edits);
330 *self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone();
331 BlockMapReader {
332 blocks: &self.custom_blocks,
333 snapshot: BlockSnapshot {
334 wrap_snapshot,
335 transforms: self.transforms.borrow().clone(),
336 custom_blocks_by_id: self.custom_blocks_by_id.clone(),
337 },
338 }
339 }
340
341 pub fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Patch<u32>) -> BlockMapWriter {
342 self.sync(&wrap_snapshot, edits);
343 *self.wrap_snapshot.borrow_mut() = wrap_snapshot;
344 BlockMapWriter(self)
345 }
346
347 fn sync(&self, wrap_snapshot: &WrapSnapshot, mut edits: Patch<u32>) {
348 let buffer = wrap_snapshot.buffer_snapshot();
349
350 // Handle changing the last excerpt if it is empty.
351 if buffer.trailing_excerpt_update_count()
352 != self
353 .wrap_snapshot
354 .borrow()
355 .buffer_snapshot()
356 .trailing_excerpt_update_count()
357 {
358 let max_point = wrap_snapshot.max_point();
359 let edit_start = wrap_snapshot.prev_row_boundary(max_point);
360 let edit_end = max_point.row() + 1;
361 edits = edits.compose([WrapEdit {
362 old: edit_start..edit_end,
363 new: edit_start..edit_end,
364 }]);
365 }
366
367 let edits = edits.into_inner();
368 if edits.is_empty() {
369 return;
370 }
371
372 let mut transforms = self.transforms.borrow_mut();
373 let mut new_transforms = SumTree::new();
374 let old_row_count = transforms.summary().input_rows;
375 let new_row_count = wrap_snapshot.max_point().row() + 1;
376 let mut cursor = transforms.cursor::<WrapRow>();
377 let mut last_block_ix = 0;
378 let mut blocks_in_edit = Vec::new();
379 let mut edits = edits.into_iter().peekable();
380
381 while let Some(edit) = edits.next() {
382 // Preserve any old transforms that precede this edit.
383 let old_start = WrapRow(edit.old.start);
384 let new_start = WrapRow(edit.new.start);
385 new_transforms.append(cursor.slice(&old_start, Bias::Left, &()), &());
386 if let Some(transform) = cursor.item() {
387 if transform.is_isomorphic() && old_start == cursor.end(&()) {
388 new_transforms.push(transform.clone(), &());
389 cursor.next(&());
390 while let Some(transform) = cursor.item() {
391 if transform
392 .block
393 .as_ref()
394 .map_or(false, |b| b.disposition().is_below())
395 {
396 new_transforms.push(transform.clone(), &());
397 cursor.next(&());
398 } else {
399 break;
400 }
401 }
402 }
403 }
404
405 // Preserve any portion of an old transform that precedes this edit.
406 let extent_before_edit = old_start.0 - cursor.start().0;
407 push_isomorphic(&mut new_transforms, extent_before_edit);
408
409 // Skip over any old transforms that intersect this edit.
410 let mut old_end = WrapRow(edit.old.end);
411 let mut new_end = WrapRow(edit.new.end);
412 cursor.seek(&old_end, Bias::Left, &());
413 cursor.next(&());
414 if old_end == *cursor.start() {
415 while let Some(transform) = cursor.item() {
416 if transform
417 .block
418 .as_ref()
419 .map_or(false, |b| b.disposition().is_below())
420 {
421 cursor.next(&());
422 } else {
423 break;
424 }
425 }
426 }
427
428 // Combine this edit with any subsequent edits that intersect the same transform.
429 while let Some(next_edit) = edits.peek() {
430 if next_edit.old.start <= cursor.start().0 {
431 old_end = WrapRow(next_edit.old.end);
432 new_end = WrapRow(next_edit.new.end);
433 cursor.seek(&old_end, Bias::Left, &());
434 cursor.next(&());
435 if old_end == *cursor.start() {
436 while let Some(transform) = cursor.item() {
437 if transform
438 .block
439 .as_ref()
440 .map_or(false, |b| b.disposition().is_below())
441 {
442 cursor.next(&());
443 } else {
444 break;
445 }
446 }
447 }
448 edits.next();
449 } else {
450 break;
451 }
452 }
453
454 // Find the blocks within this edited region.
455 let new_buffer_start =
456 wrap_snapshot.to_point(WrapPoint::new(new_start.0, 0), Bias::Left);
457 let start_bound = Bound::Included(new_buffer_start);
458 let start_block_ix =
459 match self.custom_blocks[last_block_ix..].binary_search_by(|probe| {
460 probe
461 .position
462 .to_point(buffer)
463 .cmp(&new_buffer_start)
464 .then(Ordering::Greater)
465 }) {
466 Ok(ix) | Err(ix) => last_block_ix + ix,
467 };
468
469 let end_bound;
470 let end_block_ix = if new_end.0 > wrap_snapshot.max_point().row() {
471 end_bound = Bound::Unbounded;
472 self.custom_blocks.len()
473 } else {
474 let new_buffer_end =
475 wrap_snapshot.to_point(WrapPoint::new(new_end.0, 0), Bias::Left);
476 end_bound = Bound::Excluded(new_buffer_end);
477 match self.custom_blocks[start_block_ix..].binary_search_by(|probe| {
478 probe
479 .position
480 .to_point(buffer)
481 .cmp(&new_buffer_end)
482 .then(Ordering::Greater)
483 }) {
484 Ok(ix) | Err(ix) => start_block_ix + ix,
485 }
486 };
487 last_block_ix = end_block_ix;
488
489 debug_assert!(blocks_in_edit.is_empty());
490 blocks_in_edit.extend(self.custom_blocks[start_block_ix..end_block_ix].iter().map(
491 |block| {
492 let mut position = block.position.to_point(buffer);
493 match block.disposition {
494 BlockDisposition::Above => position.column = 0,
495 BlockDisposition::Below => {
496 position.column = buffer.line_len(MultiBufferRow(position.row))
497 }
498 }
499 let position = wrap_snapshot.make_wrap_point(position, Bias::Left);
500 (position.row(), Block::Custom(block.clone()))
501 },
502 ));
503
504 if buffer.show_headers() {
505 blocks_in_edit.extend(BlockMap::header_and_footer_blocks(
506 self.show_excerpt_controls,
507 self.excerpt_footer_height,
508 self.buffer_header_height,
509 self.excerpt_header_height,
510 buffer,
511 (start_bound, end_bound),
512 wrap_snapshot,
513 ));
514 }
515
516 BlockMap::sort_blocks(&mut blocks_in_edit);
517
518 // For each of these blocks, insert a new isomorphic transform preceding the block,
519 // and then insert the block itself.
520 for (block_row, block) in blocks_in_edit.drain(..) {
521 let insertion_row = match block.disposition() {
522 BlockDisposition::Above => block_row,
523 BlockDisposition::Below => block_row + 1,
524 };
525 let extent_before_block = insertion_row - new_transforms.summary().input_rows;
526 push_isomorphic(&mut new_transforms, extent_before_block);
527 new_transforms.push(Transform::block(block), &());
528 }
529
530 old_end = WrapRow(old_end.0.min(old_row_count));
531 new_end = WrapRow(new_end.0.min(new_row_count));
532
533 // Insert an isomorphic transform after the final block.
534 let extent_after_last_block = new_end.0 - new_transforms.summary().input_rows;
535 push_isomorphic(&mut new_transforms, extent_after_last_block);
536
537 // Preserve any portion of the old transform after this edit.
538 let extent_after_edit = cursor.start().0 - old_end.0;
539 push_isomorphic(&mut new_transforms, extent_after_edit);
540 }
541
542 new_transforms.append(cursor.suffix(&()), &());
543 debug_assert_eq!(
544 new_transforms.summary().input_rows,
545 wrap_snapshot.max_point().row() + 1
546 );
547
548 drop(cursor);
549 *transforms = new_transforms;
550 }
551
552 pub fn replace_renderers(&mut self, mut renderers: HashMap<CustomBlockId, RenderBlock>) {
553 for block in &mut self.custom_blocks {
554 if let Some(render) = renderers.remove(&block.id) {
555 *block.render.lock() = render;
556 }
557 }
558 }
559
560 pub fn show_excerpt_controls(&self) -> bool {
561 self.show_excerpt_controls
562 }
563
564 pub fn header_and_footer_blocks<'a, 'b: 'a, 'c: 'a + 'b, R, T>(
565 show_excerpt_controls: bool,
566 excerpt_footer_height: u8,
567 buffer_header_height: u8,
568 excerpt_header_height: u8,
569 buffer: &'b multi_buffer::MultiBufferSnapshot,
570 range: R,
571 wrap_snapshot: &'c WrapSnapshot,
572 ) -> impl Iterator<Item = (u32, Block)> + 'b
573 where
574 R: RangeBounds<T>,
575 T: multi_buffer::ToOffset,
576 {
577 buffer
578 .excerpt_boundaries_in_range(range)
579 .flat_map(move |excerpt_boundary| {
580 let mut wrap_row = wrap_snapshot
581 .make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
582 .row();
583
584 [
585 show_excerpt_controls
586 .then(|| {
587 let disposition;
588 if excerpt_boundary.next.is_some() {
589 disposition = BlockDisposition::Above;
590 } else {
591 wrap_row = wrap_snapshot
592 .make_wrap_point(
593 Point::new(
594 excerpt_boundary.row.0,
595 buffer.line_len(excerpt_boundary.row),
596 ),
597 Bias::Left,
598 )
599 .row();
600 disposition = BlockDisposition::Below;
601 }
602
603 excerpt_boundary.prev.as_ref().map(|prev| {
604 (
605 wrap_row,
606 Block::ExcerptFooter {
607 id: prev.id,
608 height: excerpt_footer_height,
609 disposition,
610 },
611 )
612 })
613 })
614 .flatten(),
615 excerpt_boundary.next.map(|next| {
616 let starts_new_buffer = excerpt_boundary
617 .prev
618 .map_or(true, |prev| prev.buffer_id != next.buffer_id);
619
620 (
621 wrap_row,
622 Block::ExcerptHeader {
623 id: next.id,
624 buffer: next.buffer,
625 range: next.range,
626 height: if starts_new_buffer {
627 buffer_header_height
628 } else {
629 excerpt_header_height
630 },
631 starts_new_buffer,
632 show_excerpt_controls,
633 },
634 )
635 }),
636 ]
637 })
638 .flatten()
639 }
640
641 pub(crate) fn sort_blocks<B: BlockLike>(blocks: &mut Vec<(u32, B)>) {
642 // Place excerpt headers and footers above custom blocks on the same row
643 blocks.sort_unstable_by(|(row_a, block_a), (row_b, block_b)| {
644 row_a.cmp(row_b).then_with(|| {
645 block_a
646 .disposition()
647 .cmp(&block_b.disposition())
648 .then_with(|| match ((block_a.block_type()), (block_b.block_type())) {
649 (BlockType::Footer, BlockType::Footer) => Ordering::Equal,
650 (BlockType::Footer, _) => Ordering::Less,
651 (_, BlockType::Footer) => Ordering::Greater,
652 (BlockType::Header, BlockType::Header) => Ordering::Equal,
653 (BlockType::Header, _) => Ordering::Less,
654 (_, BlockType::Header) => Ordering::Greater,
655 (BlockType::Custom(a_id), BlockType::Custom(b_id)) => a_id.cmp(&b_id),
656 })
657 })
658 });
659 }
660}
661
662fn push_isomorphic(tree: &mut SumTree<Transform>, rows: u32) {
663 if rows == 0 {
664 return;
665 }
666
667 let mut extent = Some(rows);
668 tree.update_last(
669 |last_transform| {
670 if last_transform.is_isomorphic() {
671 let extent = extent.take().unwrap();
672 last_transform.summary.input_rows += extent;
673 last_transform.summary.output_rows += extent;
674 }
675 },
676 &(),
677 );
678 if let Some(extent) = extent {
679 tree.push(Transform::isomorphic(extent), &());
680 }
681}
682
683impl BlockPoint {
684 pub fn new(row: u32, column: u32) -> Self {
685 Self(Point::new(row, column))
686 }
687}
688
689impl Deref for BlockPoint {
690 type Target = Point;
691
692 fn deref(&self) -> &Self::Target {
693 &self.0
694 }
695}
696
697impl std::ops::DerefMut for BlockPoint {
698 fn deref_mut(&mut self) -> &mut Self::Target {
699 &mut self.0
700 }
701}
702
703impl<'a> Deref for BlockMapReader<'a> {
704 type Target = BlockSnapshot;
705
706 fn deref(&self) -> &Self::Target {
707 &self.snapshot
708 }
709}
710
711impl<'a> DerefMut for BlockMapReader<'a> {
712 fn deref_mut(&mut self) -> &mut Self::Target {
713 &mut self.snapshot
714 }
715}
716
717impl<'a> BlockMapReader<'a> {
718 pub fn row_for_block(&self, block_id: CustomBlockId) -> Option<BlockRow> {
719 let block = self.blocks.iter().find(|block| block.id == block_id)?;
720 let buffer_row = block
721 .position
722 .to_point(self.wrap_snapshot.buffer_snapshot())
723 .row;
724 let wrap_row = self
725 .wrap_snapshot
726 .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
727 .row();
728 let start_wrap_row = WrapRow(
729 self.wrap_snapshot
730 .prev_row_boundary(WrapPoint::new(wrap_row, 0)),
731 );
732 let end_wrap_row = WrapRow(
733 self.wrap_snapshot
734 .next_row_boundary(WrapPoint::new(wrap_row, 0))
735 .unwrap_or(self.wrap_snapshot.max_point().row() + 1),
736 );
737
738 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
739 cursor.seek(&start_wrap_row, Bias::Left, &());
740 while let Some(transform) = cursor.item() {
741 if cursor.start().0 > end_wrap_row {
742 break;
743 }
744
745 if let Some(BlockType::Custom(id)) =
746 transform.block.as_ref().map(|block| block.block_type())
747 {
748 if id == block_id {
749 return Some(cursor.start().1);
750 }
751 }
752 cursor.next(&());
753 }
754
755 None
756 }
757}
758
759impl<'a> BlockMapWriter<'a> {
760 pub fn insert(
761 &mut self,
762 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
763 ) -> Vec<CustomBlockId> {
764 let mut ids = Vec::new();
765 let mut edits = Patch::default();
766 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
767 let buffer = wrap_snapshot.buffer_snapshot();
768
769 for block in blocks {
770 let id = CustomBlockId(self.0.next_block_id.fetch_add(1, SeqCst));
771 ids.push(id);
772
773 let position = block.position;
774 let point = position.to_point(buffer);
775 let wrap_row = wrap_snapshot
776 .make_wrap_point(Point::new(point.row, 0), Bias::Left)
777 .row();
778 let start_row = wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
779 let end_row = wrap_snapshot
780 .next_row_boundary(WrapPoint::new(wrap_row, 0))
781 .unwrap_or(wrap_snapshot.max_point().row() + 1);
782
783 let block_ix = match self
784 .0
785 .custom_blocks
786 .binary_search_by(|probe| probe.position.cmp(&position, buffer))
787 {
788 Ok(ix) | Err(ix) => ix,
789 };
790 let new_block = Arc::new(CustomBlock {
791 id,
792 position,
793 height: block.height,
794 render: Mutex::new(block.render),
795 disposition: block.disposition,
796 style: block.style,
797 });
798 self.0.custom_blocks.insert(block_ix, new_block.clone());
799 self.0.custom_blocks_by_id.insert(id, new_block);
800
801 edits = edits.compose([Edit {
802 old: start_row..end_row,
803 new: start_row..end_row,
804 }]);
805 }
806
807 self.0.sync(wrap_snapshot, edits);
808 ids
809 }
810
811 pub fn replace(
812 &mut self,
813 mut heights_and_renderers: HashMap<CustomBlockId, (u8, RenderBlock)>,
814 ) {
815 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
816 let buffer = wrap_snapshot.buffer_snapshot();
817 let mut edits = Patch::default();
818 let mut last_block_buffer_row = None;
819
820 for block in &mut self.0.custom_blocks {
821 if let Some((new_height, render)) = heights_and_renderers.remove(&block.id) {
822 if block.height != new_height {
823 let new_block = CustomBlock {
824 id: block.id,
825 position: block.position,
826 height: new_height,
827 style: block.style,
828 render: Mutex::new(render),
829 disposition: block.disposition,
830 };
831 let new_block = Arc::new(new_block);
832 *block = new_block.clone();
833 self.0.custom_blocks_by_id.insert(block.id, new_block);
834
835 let buffer_row = block.position.to_point(buffer).row;
836 if last_block_buffer_row != Some(buffer_row) {
837 last_block_buffer_row = Some(buffer_row);
838 let wrap_row = wrap_snapshot
839 .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
840 .row();
841 let start_row =
842 wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
843 let end_row = wrap_snapshot
844 .next_row_boundary(WrapPoint::new(wrap_row, 0))
845 .unwrap_or(wrap_snapshot.max_point().row() + 1);
846 edits.push(Edit {
847 old: start_row..end_row,
848 new: start_row..end_row,
849 })
850 }
851 }
852 }
853 }
854
855 self.0.sync(wrap_snapshot, edits);
856 }
857
858 pub fn remove(&mut self, block_ids: HashSet<CustomBlockId>) {
859 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
860 let buffer = wrap_snapshot.buffer_snapshot();
861 let mut edits = Patch::default();
862 let mut last_block_buffer_row = None;
863 self.0.custom_blocks.retain(|block| {
864 if block_ids.contains(&block.id) {
865 let buffer_row = block.position.to_point(buffer).row;
866 if last_block_buffer_row != Some(buffer_row) {
867 last_block_buffer_row = Some(buffer_row);
868 let wrap_row = wrap_snapshot
869 .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
870 .row();
871 let start_row = wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
872 let end_row = wrap_snapshot
873 .next_row_boundary(WrapPoint::new(wrap_row, 0))
874 .unwrap_or(wrap_snapshot.max_point().row() + 1);
875 edits.push(Edit {
876 old: start_row..end_row,
877 new: start_row..end_row,
878 })
879 }
880 self.0.custom_blocks_by_id.remove(&block.id);
881 false
882 } else {
883 true
884 }
885 });
886 self.0.sync(wrap_snapshot, edits);
887 }
888}
889
890impl BlockSnapshot {
891 #[cfg(test)]
892 pub fn text(&self) -> String {
893 self.chunks(
894 0..self.transforms.summary().output_rows,
895 false,
896 Highlights::default(),
897 )
898 .map(|chunk| chunk.text)
899 .collect()
900 }
901
902 pub(crate) fn chunks<'a>(
903 &'a self,
904 rows: Range<u32>,
905 language_aware: bool,
906 highlights: Highlights<'a>,
907 ) -> BlockChunks<'a> {
908 let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
909 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
910 let input_end = {
911 cursor.seek(&BlockRow(rows.end), Bias::Right, &());
912 let overshoot = if cursor
913 .item()
914 .map_or(false, |transform| transform.is_isomorphic())
915 {
916 rows.end - cursor.start().0 .0
917 } else {
918 0
919 };
920 cursor.start().1 .0 + overshoot
921 };
922 let input_start = {
923 cursor.seek(&BlockRow(rows.start), Bias::Right, &());
924 let overshoot = if cursor
925 .item()
926 .map_or(false, |transform| transform.is_isomorphic())
927 {
928 rows.start - cursor.start().0 .0
929 } else {
930 0
931 };
932 cursor.start().1 .0 + overshoot
933 };
934 BlockChunks {
935 input_chunks: self.wrap_snapshot.chunks(
936 input_start..input_end,
937 language_aware,
938 highlights,
939 ),
940 input_chunk: Default::default(),
941 transforms: cursor,
942 output_row: rows.start,
943 max_output_row,
944 }
945 }
946
947 pub(super) fn buffer_rows(&self, start_row: BlockRow) -> BlockBufferRows {
948 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
949 cursor.seek(&start_row, Bias::Right, &());
950 let (output_start, input_start) = cursor.start();
951 let overshoot = if cursor.item().map_or(false, |t| t.is_isomorphic()) {
952 start_row.0 - output_start.0
953 } else {
954 0
955 };
956 let input_start_row = input_start.0 + overshoot;
957 BlockBufferRows {
958 transforms: cursor,
959 input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row),
960 output_row: start_row,
961 started: false,
962 }
963 }
964
965 pub fn blocks_in_range(&self, rows: Range<u32>) -> impl Iterator<Item = (u32, &Block)> {
966 let mut cursor = self.transforms.cursor::<BlockRow>();
967 cursor.seek(&BlockRow(rows.start), Bias::Right, &());
968 std::iter::from_fn(move || {
969 while let Some(transform) = cursor.item() {
970 let start_row = cursor.start().0;
971 if start_row >= rows.end {
972 break;
973 }
974 if let Some(block) = &transform.block {
975 cursor.next(&());
976 return Some((start_row, block));
977 } else {
978 cursor.next(&());
979 }
980 }
981 None
982 })
983 }
984
985 pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
986 let buffer = self.wrap_snapshot.buffer_snapshot();
987
988 match block_id {
989 BlockId::Custom(custom_block_id) => {
990 let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
991 Some(Block::Custom(custom_block.clone()))
992 }
993 BlockId::ExcerptHeader(excerpt_id) => {
994 let excerpt_range = buffer.range_for_excerpt::<Point>(excerpt_id)?;
995 let wrap_point = self
996 .wrap_snapshot
997 .make_wrap_point(excerpt_range.start, Bias::Left);
998 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
999 cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
1000 while let Some(transform) = cursor.item() {
1001 if let Some(block) = transform.block.as_ref() {
1002 if block.id() == block_id {
1003 return Some(block.clone());
1004 }
1005 } else if cursor.start().0 > WrapRow(wrap_point.row()) {
1006 break;
1007 }
1008
1009 cursor.next(&());
1010 }
1011
1012 None
1013 }
1014 BlockId::ExcerptFooter(excerpt_id) => {
1015 let excerpt_range = buffer.range_for_excerpt::<Point>(excerpt_id)?;
1016 let wrap_point = self
1017 .wrap_snapshot
1018 .make_wrap_point(excerpt_range.end, Bias::Left);
1019
1020 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
1021 cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
1022 while let Some(transform) = cursor.item() {
1023 if let Some(block) = transform.block.as_ref() {
1024 if block.id() == block_id {
1025 return Some(block.clone());
1026 }
1027 } else if cursor.start().0 > WrapRow(wrap_point.row()) {
1028 break;
1029 }
1030
1031 cursor.next(&());
1032 }
1033
1034 None
1035 }
1036 }
1037 }
1038
1039 pub fn max_point(&self) -> BlockPoint {
1040 let row = self.transforms.summary().output_rows - 1;
1041 BlockPoint::new(row, self.line_len(BlockRow(row)))
1042 }
1043
1044 pub fn longest_row(&self) -> u32 {
1045 let input_row = self.wrap_snapshot.longest_row();
1046 self.to_block_point(WrapPoint::new(input_row, 0)).row
1047 }
1048
1049 pub(super) fn line_len(&self, row: BlockRow) -> u32 {
1050 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
1051 cursor.seek(&BlockRow(row.0), Bias::Right, &());
1052 if let Some(transform) = cursor.item() {
1053 let (output_start, input_start) = cursor.start();
1054 let overshoot = row.0 - output_start.0;
1055 if transform.block.is_some() {
1056 0
1057 } else {
1058 self.wrap_snapshot.line_len(input_start.0 + overshoot)
1059 }
1060 } else {
1061 panic!("row out of range");
1062 }
1063 }
1064
1065 pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
1066 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
1067 cursor.seek(&row, Bias::Right, &());
1068 cursor.item().map_or(false, |t| t.block.is_some())
1069 }
1070
1071 pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
1072 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
1073 cursor.seek(&BlockRow(point.row), Bias::Right, &());
1074
1075 let max_input_row = WrapRow(self.transforms.summary().input_rows);
1076 let mut search_left =
1077 (bias == Bias::Left && cursor.start().1 .0 > 0) || cursor.end(&()).1 == max_input_row;
1078 let mut reversed = false;
1079
1080 loop {
1081 if let Some(transform) = cursor.item() {
1082 if transform.is_isomorphic() {
1083 let (output_start_row, input_start_row) = cursor.start();
1084 let (output_end_row, input_end_row) = cursor.end(&());
1085 let output_start = Point::new(output_start_row.0, 0);
1086 let input_start = Point::new(input_start_row.0, 0);
1087 let input_end = Point::new(input_end_row.0, 0);
1088 let input_point = if point.row >= output_end_row.0 {
1089 let line_len = self.wrap_snapshot.line_len(input_end_row.0 - 1);
1090 self.wrap_snapshot
1091 .clip_point(WrapPoint::new(input_end_row.0 - 1, line_len), bias)
1092 } else {
1093 let output_overshoot = point.0.saturating_sub(output_start);
1094 self.wrap_snapshot
1095 .clip_point(WrapPoint(input_start + output_overshoot), bias)
1096 };
1097
1098 if (input_start..input_end).contains(&input_point.0) {
1099 let input_overshoot = input_point.0.saturating_sub(input_start);
1100 return BlockPoint(output_start + input_overshoot);
1101 }
1102 }
1103
1104 if search_left {
1105 cursor.prev(&());
1106 } else {
1107 cursor.next(&());
1108 }
1109 } else if reversed {
1110 return self.max_point();
1111 } else {
1112 reversed = true;
1113 search_left = !search_left;
1114 cursor.seek(&BlockRow(point.row), Bias::Right, &());
1115 }
1116 }
1117 }
1118
1119 pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
1120 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
1121 cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
1122 if let Some(transform) = cursor.item() {
1123 debug_assert!(transform.is_isomorphic());
1124 } else {
1125 return self.max_point();
1126 }
1127
1128 let (input_start_row, output_start_row) = cursor.start();
1129 let input_start = Point::new(input_start_row.0, 0);
1130 let output_start = Point::new(output_start_row.0, 0);
1131 let input_overshoot = wrap_point.0 - input_start;
1132 BlockPoint(output_start + input_overshoot)
1133 }
1134
1135 pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint {
1136 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
1137 cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
1138 if let Some(transform) = cursor.item() {
1139 match transform.block.as_ref().map(|b| b.disposition()) {
1140 Some(BlockDisposition::Above) => WrapPoint::new(cursor.start().1 .0, 0),
1141 Some(BlockDisposition::Below) => {
1142 let wrap_row = cursor.start().1 .0 - 1;
1143 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1144 }
1145 None => {
1146 let overshoot = block_point.row - cursor.start().0 .0;
1147 let wrap_row = cursor.start().1 .0 + overshoot;
1148 WrapPoint::new(wrap_row, block_point.column)
1149 }
1150 }
1151 } else {
1152 self.wrap_snapshot.max_point()
1153 }
1154 }
1155}
1156
1157impl Transform {
1158 fn isomorphic(rows: u32) -> Self {
1159 Self {
1160 summary: TransformSummary {
1161 input_rows: rows,
1162 output_rows: rows,
1163 },
1164 block: None,
1165 }
1166 }
1167
1168 fn block(block: Block) -> Self {
1169 Self {
1170 summary: TransformSummary {
1171 input_rows: 0,
1172 output_rows: block.height() as u32,
1173 },
1174 block: Some(block),
1175 }
1176 }
1177
1178 fn is_isomorphic(&self) -> bool {
1179 self.block.is_none()
1180 }
1181}
1182
1183impl<'a> Iterator for BlockChunks<'a> {
1184 type Item = Chunk<'a>;
1185
1186 fn next(&mut self) -> Option<Self::Item> {
1187 if self.output_row >= self.max_output_row {
1188 return None;
1189 }
1190
1191 let transform = self.transforms.item()?;
1192 if transform.block.is_some() {
1193 let block_start = self.transforms.start().0 .0;
1194 let mut block_end = self.transforms.end(&()).0 .0;
1195 self.transforms.next(&());
1196 if self.transforms.item().is_none() {
1197 block_end -= 1;
1198 }
1199
1200 let start_in_block = self.output_row - block_start;
1201 let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
1202 let line_count = end_in_block - start_in_block;
1203 self.output_row += line_count;
1204
1205 return Some(Chunk {
1206 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) },
1207 ..Default::default()
1208 });
1209 }
1210
1211 if self.input_chunk.text.is_empty() {
1212 if let Some(input_chunk) = self.input_chunks.next() {
1213 self.input_chunk = input_chunk;
1214 } else {
1215 self.output_row += 1;
1216 if self.output_row < self.max_output_row {
1217 self.transforms.next(&());
1218 return Some(Chunk {
1219 text: "\n",
1220 ..Default::default()
1221 });
1222 } else {
1223 return None;
1224 }
1225 }
1226 }
1227
1228 let transform_end = self.transforms.end(&()).0 .0;
1229 let (prefix_rows, prefix_bytes) =
1230 offset_for_row(self.input_chunk.text, transform_end - self.output_row);
1231 self.output_row += prefix_rows;
1232 let (prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
1233 self.input_chunk.text = suffix;
1234 if self.output_row == transform_end {
1235 self.transforms.next(&());
1236 }
1237
1238 Some(Chunk {
1239 text: prefix,
1240 ..self.input_chunk.clone()
1241 })
1242 }
1243}
1244
1245impl<'a> Iterator for BlockBufferRows<'a> {
1246 type Item = Option<BlockRow>;
1247
1248 fn next(&mut self) -> Option<Self::Item> {
1249 if self.started {
1250 self.output_row.0 += 1;
1251 } else {
1252 self.started = true;
1253 }
1254
1255 if self.output_row.0 >= self.transforms.end(&()).0 .0 {
1256 self.transforms.next(&());
1257 }
1258
1259 let transform = self.transforms.item()?;
1260 if transform.block.is_some() {
1261 Some(None)
1262 } else {
1263 Some(self.input_buffer_rows.next().unwrap().map(BlockRow))
1264 }
1265 }
1266}
1267
1268impl sum_tree::Item for Transform {
1269 type Summary = TransformSummary;
1270
1271 fn summary(&self) -> Self::Summary {
1272 self.summary.clone()
1273 }
1274}
1275
1276impl sum_tree::Summary for TransformSummary {
1277 type Context = ();
1278
1279 fn add_summary(&mut self, summary: &Self, _: &()) {
1280 self.input_rows += summary.input_rows;
1281 self.output_rows += summary.output_rows;
1282 }
1283}
1284
1285impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
1286 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1287 self.0 += summary.input_rows;
1288 }
1289}
1290
1291impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
1292 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1293 self.0 += summary.output_rows;
1294 }
1295}
1296
1297impl BlockDisposition {
1298 fn is_below(&self) -> bool {
1299 matches!(self, BlockDisposition::Below)
1300 }
1301}
1302
1303impl<'a> Deref for BlockContext<'a, '_> {
1304 type Target = WindowContext<'a>;
1305
1306 fn deref(&self) -> &Self::Target {
1307 self.context
1308 }
1309}
1310
1311impl DerefMut for BlockContext<'_, '_> {
1312 fn deref_mut(&mut self) -> &mut Self::Target {
1313 self.context
1314 }
1315}
1316
1317impl CustomBlock {
1318 pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
1319 self.render.lock()(cx)
1320 }
1321
1322 pub fn position(&self) -> &Anchor {
1323 &self.position
1324 }
1325
1326 pub fn style(&self) -> BlockStyle {
1327 self.style
1328 }
1329}
1330
1331impl Debug for CustomBlock {
1332 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1333 f.debug_struct("Block")
1334 .field("id", &self.id)
1335 .field("position", &self.position)
1336 .field("disposition", &self.disposition)
1337 .finish()
1338 }
1339}
1340
1341// Count the number of bytes prior to a target point. If the string doesn't contain the target
1342// point, return its total extent. Otherwise return the target point itself.
1343fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
1344 let mut row = 0;
1345 let mut offset = 0;
1346 for (ix, line) in s.split('\n').enumerate() {
1347 if ix > 0 {
1348 row += 1;
1349 offset += 1;
1350 }
1351 if row >= target {
1352 break;
1353 }
1354 offset += line.len();
1355 }
1356 (row, offset)
1357}
1358
1359#[cfg(test)]
1360mod tests {
1361 use super::*;
1362 use crate::display_map::{
1363 fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap,
1364 };
1365 use gpui::{div, font, px, AppContext, Context as _, Element};
1366 use language::{Buffer, Capability};
1367 use multi_buffer::MultiBuffer;
1368 use rand::prelude::*;
1369 use settings::SettingsStore;
1370 use std::env;
1371 use util::RandomCharIter;
1372
1373 #[gpui::test]
1374 fn test_offset_for_row() {
1375 assert_eq!(offset_for_row("", 0), (0, 0));
1376 assert_eq!(offset_for_row("", 1), (0, 0));
1377 assert_eq!(offset_for_row("abcd", 0), (0, 0));
1378 assert_eq!(offset_for_row("abcd", 1), (0, 4));
1379 assert_eq!(offset_for_row("\n", 0), (0, 0));
1380 assert_eq!(offset_for_row("\n", 1), (1, 1));
1381 assert_eq!(offset_for_row("abc\ndef\nghi", 0), (0, 0));
1382 assert_eq!(offset_for_row("abc\ndef\nghi", 1), (1, 4));
1383 assert_eq!(offset_for_row("abc\ndef\nghi", 2), (2, 8));
1384 assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11));
1385 }
1386
1387 #[gpui::test]
1388 fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
1389 cx.update(|cx| init_test(cx));
1390
1391 let text = "aaa\nbbb\nccc\nddd";
1392
1393 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
1394 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1395 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1396 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1397 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
1398 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
1399 let (wrap_map, wraps_snapshot) =
1400 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
1401 let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
1402
1403 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
1404 let block_ids = writer.insert(vec![
1405 BlockProperties {
1406 style: BlockStyle::Fixed,
1407 position: buffer_snapshot.anchor_after(Point::new(1, 0)),
1408 height: 1,
1409 disposition: BlockDisposition::Above,
1410 render: Box::new(|_| div().into_any()),
1411 },
1412 BlockProperties {
1413 style: BlockStyle::Fixed,
1414 position: buffer_snapshot.anchor_after(Point::new(1, 2)),
1415 height: 2,
1416 disposition: BlockDisposition::Above,
1417 render: Box::new(|_| div().into_any()),
1418 },
1419 BlockProperties {
1420 style: BlockStyle::Fixed,
1421 position: buffer_snapshot.anchor_after(Point::new(3, 3)),
1422 height: 3,
1423 disposition: BlockDisposition::Below,
1424 render: Box::new(|_| div().into_any()),
1425 },
1426 ]);
1427
1428 let snapshot = block_map.read(wraps_snapshot, Default::default());
1429 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
1430
1431 let blocks = snapshot
1432 .blocks_in_range(0..8)
1433 .map(|(start_row, block)| {
1434 let block = block.as_custom().unwrap();
1435 (start_row..start_row + block.height as u32, block.id)
1436 })
1437 .collect::<Vec<_>>();
1438
1439 // When multiple blocks are on the same line, the newer blocks appear first.
1440 assert_eq!(
1441 blocks,
1442 &[
1443 (1..2, block_ids[0]),
1444 (2..4, block_ids[1]),
1445 (7..10, block_ids[2]),
1446 ]
1447 );
1448
1449 assert_eq!(
1450 snapshot.to_block_point(WrapPoint::new(0, 3)),
1451 BlockPoint::new(0, 3)
1452 );
1453 assert_eq!(
1454 snapshot.to_block_point(WrapPoint::new(1, 0)),
1455 BlockPoint::new(4, 0)
1456 );
1457 assert_eq!(
1458 snapshot.to_block_point(WrapPoint::new(3, 3)),
1459 BlockPoint::new(6, 3)
1460 );
1461
1462 assert_eq!(
1463 snapshot.to_wrap_point(BlockPoint::new(0, 3)),
1464 WrapPoint::new(0, 3)
1465 );
1466 assert_eq!(
1467 snapshot.to_wrap_point(BlockPoint::new(1, 0)),
1468 WrapPoint::new(1, 0)
1469 );
1470 assert_eq!(
1471 snapshot.to_wrap_point(BlockPoint::new(3, 0)),
1472 WrapPoint::new(1, 0)
1473 );
1474 assert_eq!(
1475 snapshot.to_wrap_point(BlockPoint::new(7, 0)),
1476 WrapPoint::new(3, 3)
1477 );
1478
1479 assert_eq!(
1480 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left),
1481 BlockPoint::new(0, 3)
1482 );
1483 assert_eq!(
1484 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right),
1485 BlockPoint::new(4, 0)
1486 );
1487 assert_eq!(
1488 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left),
1489 BlockPoint::new(0, 3)
1490 );
1491 assert_eq!(
1492 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right),
1493 BlockPoint::new(4, 0)
1494 );
1495 assert_eq!(
1496 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Left),
1497 BlockPoint::new(4, 0)
1498 );
1499 assert_eq!(
1500 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Right),
1501 BlockPoint::new(4, 0)
1502 );
1503 assert_eq!(
1504 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Left),
1505 BlockPoint::new(6, 3)
1506 );
1507 assert_eq!(
1508 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Right),
1509 BlockPoint::new(6, 3)
1510 );
1511 assert_eq!(
1512 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Left),
1513 BlockPoint::new(6, 3)
1514 );
1515 assert_eq!(
1516 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Right),
1517 BlockPoint::new(6, 3)
1518 );
1519
1520 assert_eq!(
1521 snapshot
1522 .buffer_rows(BlockRow(0))
1523 .map(|row| row.map(|r| r.0))
1524 .collect::<Vec<_>>(),
1525 &[
1526 Some(0),
1527 None,
1528 None,
1529 None,
1530 Some(1),
1531 Some(2),
1532 Some(3),
1533 None,
1534 None,
1535 None
1536 ]
1537 );
1538
1539 // Insert a line break, separating two block decorations into separate lines.
1540 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1541 buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
1542 buffer.snapshot(cx)
1543 });
1544
1545 let (inlay_snapshot, inlay_edits) =
1546 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1547 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
1548 let (tab_snapshot, tab_edits) =
1549 tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
1550 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1551 wrap_map.sync(tab_snapshot, tab_edits, cx)
1552 });
1553 let snapshot = block_map.read(wraps_snapshot, wrap_edits);
1554 assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
1555 }
1556
1557 #[gpui::test]
1558 fn test_multibuffer_headers_and_footers(cx: &mut AppContext) {
1559 init_test(cx);
1560
1561 let buffer1 = cx.new_model(|cx| Buffer::local("Buffer 1", cx));
1562 let buffer2 = cx.new_model(|cx| Buffer::local("Buffer 2", cx));
1563 let buffer3 = cx.new_model(|cx| Buffer::local("Buffer 3", cx));
1564
1565 let mut excerpt_ids = Vec::new();
1566 let multi_buffer = cx.new_model(|cx| {
1567 let mut multi_buffer = MultiBuffer::new(0, Capability::ReadWrite);
1568 excerpt_ids.extend(multi_buffer.push_excerpts(
1569 buffer1.clone(),
1570 [ExcerptRange {
1571 context: 0..buffer1.read(cx).len(),
1572 primary: None,
1573 }],
1574 cx,
1575 ));
1576 excerpt_ids.extend(multi_buffer.push_excerpts(
1577 buffer2.clone(),
1578 [ExcerptRange {
1579 context: 0..buffer2.read(cx).len(),
1580 primary: None,
1581 }],
1582 cx,
1583 ));
1584 excerpt_ids.extend(multi_buffer.push_excerpts(
1585 buffer3.clone(),
1586 [ExcerptRange {
1587 context: 0..buffer3.read(cx).len(),
1588 primary: None,
1589 }],
1590 cx,
1591 ));
1592
1593 multi_buffer
1594 });
1595
1596 let font = font("Helvetica");
1597 let font_size = px(14.);
1598 let font_id = cx.text_system().resolve_font(&font);
1599 let mut wrap_width = px(0.);
1600 for c in "Buff".chars() {
1601 wrap_width += cx
1602 .text_system()
1603 .advance(font_id, font_size, c)
1604 .unwrap()
1605 .width;
1606 }
1607
1608 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
1609 let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot.clone());
1610 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
1611 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
1612 let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
1613
1614 let block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
1615 let snapshot = block_map.read(wraps_snapshot, Default::default());
1616
1617 // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
1618 assert_eq!(
1619 snapshot.text(),
1620 "\nBuff\ner 1\n\n\nBuff\ner 2\n\n\nBuff\ner 3\n"
1621 );
1622
1623 let blocks: Vec<_> = snapshot
1624 .blocks_in_range(0..u32::MAX)
1625 .map(|(row, block)| (row, block.id()))
1626 .collect();
1627 assert_eq!(
1628 blocks,
1629 vec![
1630 (0, BlockId::ExcerptHeader(excerpt_ids[0])),
1631 (3, BlockId::ExcerptFooter(excerpt_ids[0])),
1632 (4, BlockId::ExcerptHeader(excerpt_ids[1])),
1633 (7, BlockId::ExcerptFooter(excerpt_ids[1])),
1634 (8, BlockId::ExcerptHeader(excerpt_ids[2])),
1635 (11, BlockId::ExcerptFooter(excerpt_ids[2]))
1636 ]
1637 );
1638 }
1639
1640 #[gpui::test]
1641 fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
1642 let _update = cx.update(|cx| init_test(cx));
1643
1644 let text = "aaa\nbbb\nccc\nddd";
1645
1646 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
1647 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1648 let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1649 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1650 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
1651 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
1652 let (_wrap_map, wraps_snapshot) =
1653 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
1654 let mut block_map = BlockMap::new(wraps_snapshot.clone(), false, 1, 1, 0);
1655
1656 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
1657 let block_ids = writer.insert(vec![
1658 BlockProperties {
1659 style: BlockStyle::Fixed,
1660 position: buffer_snapshot.anchor_after(Point::new(1, 0)),
1661 height: 1,
1662 disposition: BlockDisposition::Above,
1663 render: Box::new(|_| div().into_any()),
1664 },
1665 BlockProperties {
1666 style: BlockStyle::Fixed,
1667 position: buffer_snapshot.anchor_after(Point::new(1, 2)),
1668 height: 2,
1669 disposition: BlockDisposition::Above,
1670 render: Box::new(|_| div().into_any()),
1671 },
1672 BlockProperties {
1673 style: BlockStyle::Fixed,
1674 position: buffer_snapshot.anchor_after(Point::new(3, 3)),
1675 height: 3,
1676 disposition: BlockDisposition::Below,
1677 render: Box::new(|_| div().into_any()),
1678 },
1679 ]);
1680
1681 {
1682 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1683 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
1684
1685 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1686
1687 let mut hash_map = HashMap::default();
1688 let render: RenderBlock = Box::new(|_| div().into_any());
1689 hash_map.insert(block_ids[0], (2_u8, render));
1690 block_map_writer.replace(hash_map);
1691 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1692 assert_eq!(snapshot.text(), "aaa\n\n\n\n\nbbb\nccc\nddd\n\n\n");
1693 }
1694
1695 {
1696 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1697
1698 let mut hash_map = HashMap::default();
1699 let render: RenderBlock = Box::new(|_| div().into_any());
1700 hash_map.insert(block_ids[0], (1_u8, render));
1701 block_map_writer.replace(hash_map);
1702
1703 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1704 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
1705 }
1706
1707 {
1708 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1709
1710 let mut hash_map = HashMap::default();
1711 let render: RenderBlock = Box::new(|_| div().into_any());
1712 hash_map.insert(block_ids[0], (0_u8, render));
1713 block_map_writer.replace(hash_map);
1714
1715 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1716 assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
1717 }
1718
1719 {
1720 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1721
1722 let mut hash_map = HashMap::default();
1723 let render: RenderBlock = Box::new(|_| div().into_any());
1724 hash_map.insert(block_ids[0], (3_u8, render));
1725 block_map_writer.replace(hash_map);
1726
1727 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1728 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
1729 }
1730
1731 {
1732 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1733
1734 let mut hash_map = HashMap::default();
1735 let render: RenderBlock = Box::new(|_| div().into_any());
1736 hash_map.insert(block_ids[0], (3_u8, render));
1737 block_map_writer.replace(hash_map);
1738
1739 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1740 // Same height as before, should remain the same
1741 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
1742 }
1743 }
1744
1745 #[cfg(target_os = "macos")]
1746 #[gpui::test]
1747 fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
1748 cx.update(|cx| init_test(cx));
1749
1750 let _font_id = cx.text_system().font_id(&font("Helvetica")).unwrap();
1751
1752 let text = "one two three\nfour five six\nseven eight";
1753
1754 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
1755 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1756 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1757 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
1758 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
1759 let (_, wraps_snapshot) = cx.update(|cx| {
1760 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(60.)), cx)
1761 });
1762 let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 0);
1763
1764 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
1765 writer.insert(vec![
1766 BlockProperties {
1767 style: BlockStyle::Fixed,
1768 position: buffer_snapshot.anchor_after(Point::new(1, 12)),
1769 disposition: BlockDisposition::Above,
1770 render: Box::new(|_| div().into_any()),
1771 height: 1,
1772 },
1773 BlockProperties {
1774 style: BlockStyle::Fixed,
1775 position: buffer_snapshot.anchor_after(Point::new(1, 1)),
1776 disposition: BlockDisposition::Below,
1777 render: Box::new(|_| div().into_any()),
1778 height: 1,
1779 },
1780 ]);
1781
1782 // Blocks with an 'above' disposition go above their corresponding buffer line.
1783 // Blocks with a 'below' disposition go below their corresponding buffer line.
1784 let snapshot = block_map.read(wraps_snapshot, Default::default());
1785 assert_eq!(
1786 snapshot.text(),
1787 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
1788 );
1789 }
1790
1791 #[gpui::test(iterations = 100)]
1792 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
1793 cx.update(|cx| init_test(cx));
1794
1795 let operations = env::var("OPERATIONS")
1796 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1797 .unwrap_or(10);
1798
1799 let wrap_width = if rng.gen_bool(0.2) {
1800 None
1801 } else {
1802 Some(px(rng.gen_range(0.0..=100.0)))
1803 };
1804 let tab_size = 1.try_into().unwrap();
1805 let font_size = px(14.0);
1806 let buffer_start_header_height = rng.gen_range(1..=5);
1807 let excerpt_header_height = rng.gen_range(1..=5);
1808 let excerpt_footer_height = rng.gen_range(1..=5);
1809
1810 log::info!("Wrap width: {:?}", wrap_width);
1811 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
1812 log::info!("Excerpt Footer Height: {:?}", excerpt_footer_height);
1813
1814 let buffer = if rng.gen() {
1815 let len = rng.gen_range(0..10);
1816 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
1817 log::info!("initial buffer text: {:?}", text);
1818 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
1819 } else {
1820 cx.update(|cx| MultiBuffer::build_random(&mut rng, cx))
1821 };
1822
1823 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1824 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1825 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
1826 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
1827 let (wrap_map, wraps_snapshot) = cx
1828 .update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), font_size, wrap_width, cx));
1829 let mut block_map = BlockMap::new(
1830 wraps_snapshot,
1831 true,
1832 buffer_start_header_height,
1833 excerpt_header_height,
1834 excerpt_footer_height,
1835 );
1836 let mut custom_blocks = Vec::new();
1837
1838 for _ in 0..operations {
1839 let mut buffer_edits = Vec::new();
1840 match rng.gen_range(0..=100) {
1841 0..=19 => {
1842 let wrap_width = if rng.gen_bool(0.2) {
1843 None
1844 } else {
1845 Some(px(rng.gen_range(0.0..=100.0)))
1846 };
1847 log::info!("Setting wrap width to {:?}", wrap_width);
1848 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
1849 }
1850 20..=39 => {
1851 let block_count = rng.gen_range(1..=5);
1852 let block_properties = (0..block_count)
1853 .map(|_| {
1854 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
1855 let position = buffer.anchor_after(
1856 buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left),
1857 );
1858
1859 let disposition = if rng.gen() {
1860 BlockDisposition::Above
1861 } else {
1862 BlockDisposition::Below
1863 };
1864 let height = rng.gen_range(1..5);
1865 log::info!(
1866 "inserting block {:?} {:?} with height {}",
1867 disposition,
1868 position.to_point(&buffer),
1869 height
1870 );
1871 BlockProperties {
1872 style: BlockStyle::Fixed,
1873 position,
1874 height,
1875 disposition,
1876 render: Box::new(|_| div().into_any()),
1877 }
1878 })
1879 .collect::<Vec<_>>();
1880
1881 let (inlay_snapshot, inlay_edits) =
1882 inlay_map.sync(buffer_snapshot.clone(), vec![]);
1883 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
1884 let (tab_snapshot, tab_edits) =
1885 tab_map.sync(fold_snapshot, fold_edits, tab_size);
1886 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1887 wrap_map.sync(tab_snapshot, tab_edits, cx)
1888 });
1889 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
1890 let block_ids =
1891 block_map.insert(block_properties.iter().map(|props| BlockProperties {
1892 position: props.position,
1893 height: props.height,
1894 style: props.style,
1895 render: Box::new(|_| div().into_any()),
1896 disposition: props.disposition,
1897 }));
1898 for (block_id, props) in block_ids.into_iter().zip(block_properties) {
1899 custom_blocks.push((block_id, props));
1900 }
1901 }
1902 40..=59 if !custom_blocks.is_empty() => {
1903 let block_count = rng.gen_range(1..=4.min(custom_blocks.len()));
1904 let block_ids_to_remove = (0..block_count)
1905 .map(|_| {
1906 custom_blocks
1907 .remove(rng.gen_range(0..custom_blocks.len()))
1908 .0
1909 })
1910 .collect();
1911
1912 let (inlay_snapshot, inlay_edits) =
1913 inlay_map.sync(buffer_snapshot.clone(), vec![]);
1914 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
1915 let (tab_snapshot, tab_edits) =
1916 tab_map.sync(fold_snapshot, fold_edits, tab_size);
1917 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1918 wrap_map.sync(tab_snapshot, tab_edits, cx)
1919 });
1920 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
1921 block_map.remove(block_ids_to_remove);
1922 }
1923 _ => {
1924 buffer.update(cx, |buffer, cx| {
1925 let mutation_count = rng.gen_range(1..=5);
1926 let subscription = buffer.subscribe();
1927 buffer.randomly_mutate(&mut rng, mutation_count, cx);
1928 buffer_snapshot = buffer.snapshot(cx);
1929 buffer_edits.extend(subscription.consume());
1930 log::info!("buffer text: {:?}", buffer_snapshot.text());
1931 });
1932 }
1933 }
1934
1935 let (inlay_snapshot, inlay_edits) =
1936 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1937 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
1938 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
1939 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1940 wrap_map.sync(tab_snapshot, tab_edits, cx)
1941 });
1942 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
1943 assert_eq!(
1944 blocks_snapshot.transforms.summary().input_rows,
1945 wraps_snapshot.max_point().row() + 1
1946 );
1947 log::info!("blocks text: {:?}", blocks_snapshot.text());
1948
1949 let mut expected_blocks = Vec::new();
1950 expected_blocks.extend(custom_blocks.iter().map(|(id, block)| {
1951 let mut position = block.position.to_point(&buffer_snapshot);
1952 match block.disposition {
1953 BlockDisposition::Above => {
1954 position.column = 0;
1955 }
1956 BlockDisposition::Below => {
1957 position.column = buffer_snapshot.line_len(MultiBufferRow(position.row));
1958 }
1959 };
1960 let row = wraps_snapshot.make_wrap_point(position, Bias::Left).row();
1961 (
1962 row,
1963 ExpectedBlock::Custom {
1964 disposition: block.disposition,
1965 id: *id,
1966 height: block.height,
1967 },
1968 )
1969 }));
1970
1971 // Note that this needs to be synced with the related section in BlockMap::sync
1972 expected_blocks.extend(
1973 BlockMap::header_and_footer_blocks(
1974 true,
1975 excerpt_footer_height,
1976 buffer_start_header_height,
1977 excerpt_header_height,
1978 &buffer_snapshot,
1979 0..,
1980 &wraps_snapshot,
1981 )
1982 .map(|(row, block)| (row, block.into())),
1983 );
1984
1985 BlockMap::sort_blocks(&mut expected_blocks);
1986
1987 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
1988
1989 let input_buffer_rows = buffer_snapshot
1990 .buffer_rows(MultiBufferRow(0))
1991 .collect::<Vec<_>>();
1992 let mut expected_buffer_rows = Vec::new();
1993 let mut expected_text = String::new();
1994 let mut expected_block_positions = Vec::new();
1995 let input_text = wraps_snapshot.text();
1996 for (row, input_line) in input_text.split('\n').enumerate() {
1997 let row = row as u32;
1998 if row > 0 {
1999 expected_text.push('\n');
2000 }
2001
2002 let buffer_row = input_buffer_rows[wraps_snapshot
2003 .to_point(WrapPoint::new(row, 0), Bias::Left)
2004 .row as usize];
2005
2006 while let Some((block_row, block)) = sorted_blocks_iter.peek() {
2007 if *block_row == row && block.disposition() == BlockDisposition::Above {
2008 let (_, block) = sorted_blocks_iter.next().unwrap();
2009 let height = block.height() as usize;
2010 expected_block_positions
2011 .push((expected_text.matches('\n').count() as u32, block));
2012 let text = "\n".repeat(height);
2013 expected_text.push_str(&text);
2014 for _ in 0..height {
2015 expected_buffer_rows.push(None);
2016 }
2017 } else {
2018 break;
2019 }
2020 }
2021
2022 let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0;
2023 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
2024 expected_text.push_str(input_line);
2025
2026 while let Some((block_row, block)) = sorted_blocks_iter.peek() {
2027 if *block_row == row && block.disposition() == BlockDisposition::Below {
2028 let (_, block) = sorted_blocks_iter.next().unwrap();
2029 let height = block.height() as usize;
2030 expected_block_positions
2031 .push((expected_text.matches('\n').count() as u32 + 1, block));
2032 let text = "\n".repeat(height);
2033 expected_text.push_str(&text);
2034 for _ in 0..height {
2035 expected_buffer_rows.push(None);
2036 }
2037 } else {
2038 break;
2039 }
2040 }
2041 }
2042
2043 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
2044 let expected_row_count = expected_lines.len();
2045 for start_row in 0..expected_row_count {
2046 let expected_text = expected_lines[start_row..].join("\n");
2047 let actual_text = blocks_snapshot
2048 .chunks(
2049 start_row as u32..blocks_snapshot.max_point().row + 1,
2050 false,
2051 Highlights::default(),
2052 )
2053 .map(|chunk| chunk.text)
2054 .collect::<String>();
2055 assert_eq!(
2056 actual_text, expected_text,
2057 "incorrect text starting from row {}",
2058 start_row
2059 );
2060 assert_eq!(
2061 blocks_snapshot
2062 .buffer_rows(BlockRow(start_row as u32))
2063 .map(|row| row.map(|r| r.0))
2064 .collect::<Vec<_>>(),
2065 &expected_buffer_rows[start_row..]
2066 );
2067 }
2068
2069 assert_eq!(
2070 blocks_snapshot
2071 .blocks_in_range(0..(expected_row_count as u32))
2072 .map(|(row, block)| (row, block.clone().into()))
2073 .collect::<Vec<_>>(),
2074 expected_block_positions
2075 );
2076
2077 for (_, expected_block) in
2078 blocks_snapshot.blocks_in_range(0..(expected_row_count as u32))
2079 {
2080 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
2081 assert_eq!(
2082 actual_block.map(|block| block.id()),
2083 Some(expected_block.id())
2084 );
2085 }
2086
2087 for (block_row, block) in expected_block_positions {
2088 if let BlockType::Custom(block_id) = block.block_type() {
2089 assert_eq!(
2090 blocks_snapshot.row_for_block(block_id),
2091 Some(BlockRow(block_row))
2092 );
2093 }
2094 }
2095
2096 let mut expected_longest_rows = Vec::new();
2097 let mut longest_line_len = -1_isize;
2098 for (row, line) in expected_lines.iter().enumerate() {
2099 let row = row as u32;
2100
2101 assert_eq!(
2102 blocks_snapshot.line_len(BlockRow(row)),
2103 line.len() as u32,
2104 "invalid line len for row {}",
2105 row
2106 );
2107
2108 let line_char_count = line.chars().count() as isize;
2109 match line_char_count.cmp(&longest_line_len) {
2110 Ordering::Less => {}
2111 Ordering::Equal => expected_longest_rows.push(row),
2112 Ordering::Greater => {
2113 longest_line_len = line_char_count;
2114 expected_longest_rows.clear();
2115 expected_longest_rows.push(row);
2116 }
2117 }
2118 }
2119
2120 let longest_row = blocks_snapshot.longest_row();
2121 assert!(
2122 expected_longest_rows.contains(&longest_row),
2123 "incorrect longest row {}. expected {:?} with length {}",
2124 longest_row,
2125 expected_longest_rows,
2126 longest_line_len,
2127 );
2128
2129 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
2130 let wrap_point = WrapPoint::new(row, 0);
2131 let block_point = blocks_snapshot.to_block_point(wrap_point);
2132 assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point);
2133 }
2134
2135 let mut block_point = BlockPoint::new(0, 0);
2136 for c in expected_text.chars() {
2137 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
2138 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
2139 assert_eq!(
2140 blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(left_point)),
2141 left_point
2142 );
2143 assert_eq!(
2144 left_buffer_point,
2145 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
2146 "{:?} is not valid in buffer coordinates",
2147 left_point
2148 );
2149
2150 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
2151 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
2152 assert_eq!(
2153 blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(right_point)),
2154 right_point
2155 );
2156 assert_eq!(
2157 right_buffer_point,
2158 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
2159 "{:?} is not valid in buffer coordinates",
2160 right_point
2161 );
2162
2163 if c == '\n' {
2164 block_point.0 += Point::new(1, 0);
2165 } else {
2166 block_point.column += c.len_utf8() as u32;
2167 }
2168 }
2169 }
2170
2171 #[derive(Debug, Eq, PartialEq)]
2172 enum ExpectedBlock {
2173 ExcerptHeader {
2174 height: u8,
2175 starts_new_buffer: bool,
2176 },
2177 ExcerptFooter {
2178 height: u8,
2179 disposition: BlockDisposition,
2180 },
2181 Custom {
2182 disposition: BlockDisposition,
2183 id: CustomBlockId,
2184 height: u8,
2185 },
2186 }
2187
2188 impl BlockLike for ExpectedBlock {
2189 fn block_type(&self) -> BlockType {
2190 match self {
2191 ExpectedBlock::Custom { id, .. } => BlockType::Custom(*id),
2192 ExpectedBlock::ExcerptHeader { .. } => BlockType::Header,
2193 ExpectedBlock::ExcerptFooter { .. } => BlockType::Footer,
2194 }
2195 }
2196
2197 fn disposition(&self) -> BlockDisposition {
2198 self.disposition()
2199 }
2200 }
2201
2202 impl ExpectedBlock {
2203 fn height(&self) -> u8 {
2204 match self {
2205 ExpectedBlock::ExcerptHeader { height, .. } => *height,
2206 ExpectedBlock::Custom { height, .. } => *height,
2207 ExpectedBlock::ExcerptFooter { height, .. } => *height,
2208 }
2209 }
2210
2211 fn disposition(&self) -> BlockDisposition {
2212 match self {
2213 ExpectedBlock::ExcerptHeader { .. } => BlockDisposition::Above,
2214 ExpectedBlock::Custom { disposition, .. } => *disposition,
2215 ExpectedBlock::ExcerptFooter { disposition, .. } => *disposition,
2216 }
2217 }
2218 }
2219
2220 impl From<Block> for ExpectedBlock {
2221 fn from(block: Block) -> Self {
2222 match block {
2223 Block::Custom(block) => ExpectedBlock::Custom {
2224 id: block.id,
2225 disposition: block.disposition,
2226 height: block.height,
2227 },
2228 Block::ExcerptHeader {
2229 height,
2230 starts_new_buffer,
2231 ..
2232 } => ExpectedBlock::ExcerptHeader {
2233 height,
2234 starts_new_buffer,
2235 },
2236 Block::ExcerptFooter {
2237 height,
2238 disposition,
2239 ..
2240 } => ExpectedBlock::ExcerptFooter {
2241 height,
2242 disposition,
2243 },
2244 }
2245 }
2246 }
2247 }
2248
2249 fn init_test(cx: &mut gpui::AppContext) {
2250 let settings = SettingsStore::test(cx);
2251 cx.set_global(settings);
2252 theme::init(theme::LoadThemes::JustBase, cx);
2253 assets::Assets.load_test_fonts(cx);
2254 }
2255
2256 impl Block {
2257 fn as_custom(&self) -> Option<&CustomBlock> {
2258 match self {
2259 Block::Custom(block) => Some(block),
2260 Block::ExcerptHeader { .. } => None,
2261 Block::ExcerptFooter { .. } => None,
2262 }
2263 }
2264 }
2265
2266 impl BlockSnapshot {
2267 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
2268 self.wrap_snapshot.to_point(self.to_wrap_point(point), bias)
2269 }
2270 }
2271}