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