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