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