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 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 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::default();
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 [(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 blocks = blocks.into_iter();
787 let mut ids = Vec::with_capacity(blocks.size_hint().1.unwrap_or(0));
788 let mut edits = Patch::default();
789 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
790 let buffer = wrap_snapshot.buffer_snapshot();
791
792 let mut previous_wrap_row_range: Option<Range<u32>> = None;
793 for block in blocks {
794 let id = CustomBlockId(self.0.next_block_id.fetch_add(1, SeqCst));
795 ids.push(id);
796
797 let position = block.position;
798 let point = position.to_point(buffer);
799 let wrap_row = wrap_snapshot
800 .make_wrap_point(Point::new(point.row, 0), Bias::Left)
801 .row();
802
803 let (start_row, end_row) = {
804 previous_wrap_row_range.take_if(|range| !range.contains(&wrap_row));
805 let range = previous_wrap_row_range.get_or_insert_with(|| {
806 let start_row = wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
807 let end_row = wrap_snapshot
808 .next_row_boundary(WrapPoint::new(wrap_row, 0))
809 .unwrap_or(wrap_snapshot.max_point().row() + 1);
810 start_row..end_row
811 });
812 (range.start, range.end)
813 };
814 let block_ix = match self
815 .0
816 .custom_blocks
817 .binary_search_by(|probe| probe.position.cmp(&position, buffer))
818 {
819 Ok(ix) | Err(ix) => ix,
820 };
821 let new_block = Arc::new(CustomBlock {
822 id,
823 position,
824 height: block.height,
825 render: Arc::new(Mutex::new(block.render)),
826 disposition: block.disposition,
827 style: block.style,
828 priority: block.priority,
829 });
830 self.0.custom_blocks.insert(block_ix, new_block.clone());
831 self.0.custom_blocks_by_id.insert(id, new_block);
832
833 edits = edits.compose([Edit {
834 old: start_row..end_row,
835 new: start_row..end_row,
836 }]);
837 }
838
839 self.0.sync(wrap_snapshot, edits);
840 ids
841 }
842
843 pub fn resize(&mut self, mut heights: HashMap<CustomBlockId, u32>) {
844 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
845 let buffer = wrap_snapshot.buffer_snapshot();
846 let mut edits = Patch::default();
847 let mut last_block_buffer_row = None;
848
849 for block in &mut self.0.custom_blocks {
850 if let Some(new_height) = heights.remove(&block.id) {
851 if block.height != new_height {
852 let new_block = CustomBlock {
853 id: block.id,
854 position: block.position,
855 height: new_height,
856 style: block.style,
857 render: block.render.clone(),
858 disposition: block.disposition,
859 priority: block.priority,
860 };
861 let new_block = Arc::new(new_block);
862 *block = new_block.clone();
863 self.0.custom_blocks_by_id.insert(block.id, new_block);
864
865 let buffer_row = block.position.to_point(buffer).row;
866 if last_block_buffer_row != Some(buffer_row) {
867 last_block_buffer_row = Some(buffer_row);
868 let wrap_row = wrap_snapshot
869 .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
870 .row();
871 let start_row =
872 wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
873 let end_row = wrap_snapshot
874 .next_row_boundary(WrapPoint::new(wrap_row, 0))
875 .unwrap_or(wrap_snapshot.max_point().row() + 1);
876 edits.push(Edit {
877 old: start_row..end_row,
878 new: start_row..end_row,
879 })
880 }
881 }
882 }
883 }
884
885 self.0.sync(wrap_snapshot, edits);
886 }
887
888 pub fn remove(&mut self, block_ids: HashSet<CustomBlockId>) {
889 let wrap_snapshot = &*self.0.wrap_snapshot.borrow();
890 let buffer = wrap_snapshot.buffer_snapshot();
891 let mut edits = Patch::default();
892 let mut last_block_buffer_row = None;
893 let mut previous_wrap_row_range: Option<Range<u32>> = None;
894 self.0.custom_blocks.retain(|block| {
895 if block_ids.contains(&block.id) {
896 let buffer_row = block.position.to_point(buffer).row;
897 if last_block_buffer_row != Some(buffer_row) {
898 last_block_buffer_row = Some(buffer_row);
899 let wrap_row = wrap_snapshot
900 .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
901 .row();
902 let (start_row, end_row) = {
903 previous_wrap_row_range.take_if(|range| !range.contains(&wrap_row));
904 let range = previous_wrap_row_range.get_or_insert_with(|| {
905 let start_row =
906 wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
907 let end_row = wrap_snapshot
908 .next_row_boundary(WrapPoint::new(wrap_row, 0))
909 .unwrap_or(wrap_snapshot.max_point().row() + 1);
910 start_row..end_row
911 });
912 (range.start, range.end)
913 };
914
915 edits.push(Edit {
916 old: start_row..end_row,
917 new: start_row..end_row,
918 })
919 }
920 false
921 } else {
922 true
923 }
924 });
925 self.0
926 .custom_blocks_by_id
927 .retain(|id, _| !block_ids.contains(id));
928 self.0.sync(wrap_snapshot, edits);
929 }
930}
931
932impl BlockSnapshot {
933 #[cfg(test)]
934 pub fn text(&self) -> String {
935 self.chunks(
936 0..self.transforms.summary().output_rows,
937 false,
938 false,
939 Highlights::default(),
940 )
941 .map(|chunk| chunk.text)
942 .collect()
943 }
944
945 pub(crate) fn chunks<'a>(
946 &'a self,
947 rows: Range<u32>,
948 language_aware: bool,
949 masked: bool,
950 highlights: Highlights<'a>,
951 ) -> BlockChunks<'a> {
952 let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
953 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
954 let input_end = {
955 cursor.seek(&BlockRow(rows.end), Bias::Right, &());
956 let overshoot = if cursor
957 .item()
958 .map_or(false, |transform| transform.is_isomorphic())
959 {
960 rows.end - cursor.start().0 .0
961 } else {
962 0
963 };
964 cursor.start().1 .0 + overshoot
965 };
966 let input_start = {
967 cursor.seek(&BlockRow(rows.start), Bias::Right, &());
968 let overshoot = if cursor
969 .item()
970 .map_or(false, |transform| transform.is_isomorphic())
971 {
972 rows.start - cursor.start().0 .0
973 } else {
974 0
975 };
976 cursor.start().1 .0 + overshoot
977 };
978 BlockChunks {
979 input_chunks: self.wrap_snapshot.chunks(
980 input_start..input_end,
981 language_aware,
982 highlights,
983 ),
984 input_chunk: Default::default(),
985 transforms: cursor,
986 output_row: rows.start,
987 max_output_row,
988 masked,
989 }
990 }
991
992 pub(super) fn buffer_rows(&self, start_row: BlockRow) -> BlockBufferRows {
993 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
994 cursor.seek(&start_row, Bias::Right, &());
995 let (output_start, input_start) = cursor.start();
996 let overshoot = if cursor.item().map_or(false, |t| t.is_isomorphic()) {
997 start_row.0 - output_start.0
998 } else {
999 0
1000 };
1001 let input_start_row = input_start.0 + overshoot;
1002 BlockBufferRows {
1003 transforms: cursor,
1004 input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row),
1005 output_row: start_row,
1006 started: false,
1007 }
1008 }
1009
1010 pub fn blocks_in_range(&self, rows: Range<u32>) -> impl Iterator<Item = (u32, &Block)> {
1011 let mut cursor = self.transforms.cursor::<BlockRow>(&());
1012 cursor.seek(&BlockRow(rows.start), Bias::Left, &());
1013 while cursor.start().0 < rows.start && cursor.end(&()).0 <= rows.start {
1014 cursor.next(&());
1015 }
1016
1017 std::iter::from_fn(move || {
1018 while let Some(transform) = cursor.item() {
1019 let start_row = cursor.start().0;
1020 if start_row > rows.end
1021 || (start_row == rows.end
1022 && transform
1023 .block
1024 .as_ref()
1025 .map_or(false, |block| block.height() > 0))
1026 {
1027 break;
1028 }
1029 if let Some(block) = &transform.block {
1030 cursor.next(&());
1031 return Some((start_row, block));
1032 } else {
1033 cursor.next(&());
1034 }
1035 }
1036 None
1037 })
1038 }
1039
1040 pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
1041 let buffer = self.wrap_snapshot.buffer_snapshot();
1042
1043 match block_id {
1044 BlockId::Custom(custom_block_id) => {
1045 let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
1046 Some(Block::Custom(custom_block.clone()))
1047 }
1048 BlockId::ExcerptHeader(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.start, Bias::Left);
1053 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
1054 cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
1055 while let Some(transform) = cursor.item() {
1056 if let Some(block) = transform.block.as_ref() {
1057 if block.id() == block_id {
1058 return Some(block.clone());
1059 }
1060 } else if cursor.start().0 > WrapRow(wrap_point.row()) {
1061 break;
1062 }
1063
1064 cursor.next(&());
1065 }
1066
1067 None
1068 }
1069 BlockId::ExcerptFooter(excerpt_id) => {
1070 let excerpt_range = buffer.range_for_excerpt::<Point>(excerpt_id)?;
1071 let wrap_point = self
1072 .wrap_snapshot
1073 .make_wrap_point(excerpt_range.end, Bias::Left);
1074
1075 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
1076 cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
1077 while let Some(transform) = cursor.item() {
1078 if let Some(block) = transform.block.as_ref() {
1079 if block.id() == block_id {
1080 return Some(block.clone());
1081 }
1082 } else if cursor.start().0 > WrapRow(wrap_point.row()) {
1083 break;
1084 }
1085
1086 cursor.next(&());
1087 }
1088
1089 None
1090 }
1091 }
1092 }
1093
1094 pub fn max_point(&self) -> BlockPoint {
1095 let row = self.transforms.summary().output_rows - 1;
1096 BlockPoint::new(row, self.line_len(BlockRow(row)))
1097 }
1098
1099 pub fn longest_row(&self) -> u32 {
1100 let input_row = self.wrap_snapshot.longest_row();
1101 self.to_block_point(WrapPoint::new(input_row, 0)).row
1102 }
1103
1104 pub(super) fn line_len(&self, row: BlockRow) -> u32 {
1105 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1106 cursor.seek(&BlockRow(row.0), Bias::Right, &());
1107 if let Some(transform) = cursor.item() {
1108 let (output_start, input_start) = cursor.start();
1109 let overshoot = row.0 - output_start.0;
1110 if transform.block.is_some() {
1111 0
1112 } else {
1113 self.wrap_snapshot.line_len(input_start.0 + overshoot)
1114 }
1115 } else {
1116 panic!("row out of range");
1117 }
1118 }
1119
1120 pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
1121 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1122 cursor.seek(&row, Bias::Right, &());
1123 cursor.item().map_or(false, |t| t.block.is_some())
1124 }
1125
1126 pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
1127 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1128 cursor.seek(&BlockRow(point.row), Bias::Right, &());
1129
1130 let max_input_row = WrapRow(self.transforms.summary().input_rows);
1131 let mut search_left =
1132 (bias == Bias::Left && cursor.start().1 .0 > 0) || cursor.end(&()).1 == max_input_row;
1133 let mut reversed = false;
1134
1135 loop {
1136 if let Some(transform) = cursor.item() {
1137 if transform.is_isomorphic() {
1138 let (output_start_row, input_start_row) = cursor.start();
1139 let (output_end_row, input_end_row) = cursor.end(&());
1140 let output_start = Point::new(output_start_row.0, 0);
1141 let input_start = Point::new(input_start_row.0, 0);
1142 let input_end = Point::new(input_end_row.0, 0);
1143 let input_point = if point.row >= output_end_row.0 {
1144 let line_len = self.wrap_snapshot.line_len(input_end_row.0 - 1);
1145 self.wrap_snapshot
1146 .clip_point(WrapPoint::new(input_end_row.0 - 1, line_len), bias)
1147 } else {
1148 let output_overshoot = point.0.saturating_sub(output_start);
1149 self.wrap_snapshot
1150 .clip_point(WrapPoint(input_start + output_overshoot), bias)
1151 };
1152
1153 if (input_start..input_end).contains(&input_point.0) {
1154 let input_overshoot = input_point.0.saturating_sub(input_start);
1155 return BlockPoint(output_start + input_overshoot);
1156 }
1157 }
1158
1159 if search_left {
1160 cursor.prev(&());
1161 } else {
1162 cursor.next(&());
1163 }
1164 } else if reversed {
1165 return self.max_point();
1166 } else {
1167 reversed = true;
1168 search_left = !search_left;
1169 cursor.seek(&BlockRow(point.row), Bias::Right, &());
1170 }
1171 }
1172 }
1173
1174 pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
1175 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
1176 cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
1177 if let Some(transform) = cursor.item() {
1178 debug_assert!(transform.is_isomorphic());
1179 } else {
1180 return self.max_point();
1181 }
1182
1183 let (input_start_row, output_start_row) = cursor.start();
1184 let input_start = Point::new(input_start_row.0, 0);
1185 let output_start = Point::new(output_start_row.0, 0);
1186 let input_overshoot = wrap_point.0 - input_start;
1187 BlockPoint(output_start + input_overshoot)
1188 }
1189
1190 pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint {
1191 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
1192 cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
1193 if let Some(transform) = cursor.item() {
1194 match transform.block.as_ref().map(|b| b.disposition()) {
1195 Some(BlockDisposition::Above) => WrapPoint::new(cursor.start().1 .0, 0),
1196 Some(BlockDisposition::Below) => {
1197 let wrap_row = cursor.start().1 .0 - 1;
1198 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
1199 }
1200 None => {
1201 let overshoot = block_point.row - cursor.start().0 .0;
1202 let wrap_row = cursor.start().1 .0 + overshoot;
1203 WrapPoint::new(wrap_row, block_point.column)
1204 }
1205 }
1206 } else {
1207 self.wrap_snapshot.max_point()
1208 }
1209 }
1210}
1211
1212impl Transform {
1213 fn isomorphic(rows: u32) -> Self {
1214 Self {
1215 summary: TransformSummary {
1216 input_rows: rows,
1217 output_rows: rows,
1218 },
1219 block: None,
1220 }
1221 }
1222
1223 fn block(block: Block) -> Self {
1224 Self {
1225 summary: TransformSummary {
1226 input_rows: 0,
1227 output_rows: block.height(),
1228 },
1229 block: Some(block),
1230 }
1231 }
1232
1233 fn is_isomorphic(&self) -> bool {
1234 self.block.is_none()
1235 }
1236}
1237
1238impl<'a> BlockChunks<'a> {
1239 fn advance(&mut self) {
1240 self.transforms.next(&());
1241 while let Some(transform) = self.transforms.item() {
1242 if transform
1243 .block
1244 .as_ref()
1245 .map_or(false, |block| block.height() == 0)
1246 {
1247 self.transforms.next(&());
1248 } else {
1249 break;
1250 }
1251 }
1252 }
1253}
1254
1255impl<'a> Iterator for BlockChunks<'a> {
1256 type Item = Chunk<'a>;
1257
1258 fn next(&mut self) -> Option<Self::Item> {
1259 if self.output_row >= self.max_output_row {
1260 return None;
1261 }
1262
1263 let transform = self.transforms.item()?;
1264 if transform.block.is_some() {
1265 let block_start = self.transforms.start().0 .0;
1266 let mut block_end = self.transforms.end(&()).0 .0;
1267 self.advance();
1268 if self.transforms.item().is_none() {
1269 block_end -= 1;
1270 }
1271
1272 let start_in_block = self.output_row - block_start;
1273 let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
1274 let line_count = end_in_block - start_in_block;
1275 self.output_row += line_count;
1276
1277 return Some(Chunk {
1278 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) },
1279 ..Default::default()
1280 });
1281 }
1282
1283 if self.input_chunk.text.is_empty() {
1284 if let Some(input_chunk) = self.input_chunks.next() {
1285 self.input_chunk = input_chunk;
1286 } else {
1287 self.output_row += 1;
1288 if self.output_row < self.max_output_row {
1289 self.advance();
1290 return Some(Chunk {
1291 text: "\n",
1292 ..Default::default()
1293 });
1294 } else {
1295 return None;
1296 }
1297 }
1298 }
1299
1300 let transform_end = self.transforms.end(&()).0 .0;
1301 let (prefix_rows, prefix_bytes) =
1302 offset_for_row(self.input_chunk.text, transform_end - self.output_row);
1303 self.output_row += prefix_rows;
1304 let (mut prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
1305 self.input_chunk.text = suffix;
1306 if self.output_row == transform_end {
1307 self.advance();
1308 }
1309
1310 if self.masked {
1311 // Not great for multibyte text because to keep cursor math correct we
1312 // need to have the same number of bytes in the input as output.
1313 let chars = prefix.chars().count();
1314 let bullet_len = chars;
1315 prefix = &BULLETS[..bullet_len];
1316 }
1317
1318 Some(Chunk {
1319 text: prefix,
1320 ..self.input_chunk.clone()
1321 })
1322 }
1323}
1324
1325impl<'a> Iterator for BlockBufferRows<'a> {
1326 type Item = Option<BlockRow>;
1327
1328 fn next(&mut self) -> Option<Self::Item> {
1329 if self.started {
1330 self.output_row.0 += 1;
1331 } else {
1332 self.started = true;
1333 }
1334
1335 if self.output_row.0 >= self.transforms.end(&()).0 .0 {
1336 self.transforms.next(&());
1337 }
1338
1339 while let Some(transform) = self.transforms.item() {
1340 if transform
1341 .block
1342 .as_ref()
1343 .map_or(false, |block| block.height() == 0)
1344 {
1345 self.transforms.next(&());
1346 } else {
1347 break;
1348 }
1349 }
1350
1351 let transform = self.transforms.item()?;
1352 if transform.block.is_some() {
1353 Some(None)
1354 } else {
1355 Some(self.input_buffer_rows.next().unwrap().map(BlockRow))
1356 }
1357 }
1358}
1359
1360impl sum_tree::Item for Transform {
1361 type Summary = TransformSummary;
1362
1363 fn summary(&self) -> Self::Summary {
1364 self.summary.clone()
1365 }
1366}
1367
1368impl sum_tree::Summary for TransformSummary {
1369 type Context = ();
1370
1371 fn zero(_cx: &()) -> Self {
1372 Default::default()
1373 }
1374
1375 fn add_summary(&mut self, summary: &Self, _: &()) {
1376 self.input_rows += summary.input_rows;
1377 self.output_rows += summary.output_rows;
1378 }
1379}
1380
1381impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
1382 fn zero(_cx: &()) -> Self {
1383 Default::default()
1384 }
1385
1386 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1387 self.0 += summary.input_rows;
1388 }
1389}
1390
1391impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
1392 fn zero(_cx: &()) -> Self {
1393 Default::default()
1394 }
1395
1396 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1397 self.0 += summary.output_rows;
1398 }
1399}
1400
1401impl BlockDisposition {
1402 fn is_below(&self) -> bool {
1403 matches!(self, BlockDisposition::Below)
1404 }
1405}
1406
1407impl<'a> Deref for BlockContext<'a, '_> {
1408 type Target = WindowContext<'a>;
1409
1410 fn deref(&self) -> &Self::Target {
1411 self.context
1412 }
1413}
1414
1415impl DerefMut for BlockContext<'_, '_> {
1416 fn deref_mut(&mut self) -> &mut Self::Target {
1417 self.context
1418 }
1419}
1420
1421impl CustomBlock {
1422 pub fn render(&self, cx: &mut BlockContext) -> AnyElement {
1423 self.render.lock()(cx)
1424 }
1425
1426 pub fn position(&self) -> &Anchor {
1427 &self.position
1428 }
1429
1430 pub fn style(&self) -> BlockStyle {
1431 self.style
1432 }
1433}
1434
1435impl Debug for CustomBlock {
1436 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1437 f.debug_struct("Block")
1438 .field("id", &self.id)
1439 .field("position", &self.position)
1440 .field("disposition", &self.disposition)
1441 .finish()
1442 }
1443}
1444
1445// Count the number of bytes prior to a target point. If the string doesn't contain the target
1446// point, return its total extent. Otherwise return the target point itself.
1447fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
1448 let mut row = 0;
1449 let mut offset = 0;
1450 for (ix, line) in s.split('\n').enumerate() {
1451 if ix > 0 {
1452 row += 1;
1453 offset += 1;
1454 }
1455 if row >= target {
1456 break;
1457 }
1458 offset += line.len();
1459 }
1460 (row, offset)
1461}
1462
1463#[cfg(test)]
1464mod tests {
1465 use super::*;
1466 use crate::display_map::{
1467 fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap,
1468 };
1469 use gpui::{div, font, px, AppContext, Context as _, Element};
1470 use language::{Buffer, Capability};
1471 use multi_buffer::MultiBuffer;
1472 use rand::prelude::*;
1473 use settings::SettingsStore;
1474 use std::env;
1475 use util::RandomCharIter;
1476
1477 #[gpui::test]
1478 fn test_offset_for_row() {
1479 assert_eq!(offset_for_row("", 0), (0, 0));
1480 assert_eq!(offset_for_row("", 1), (0, 0));
1481 assert_eq!(offset_for_row("abcd", 0), (0, 0));
1482 assert_eq!(offset_for_row("abcd", 1), (0, 4));
1483 assert_eq!(offset_for_row("\n", 0), (0, 0));
1484 assert_eq!(offset_for_row("\n", 1), (1, 1));
1485 assert_eq!(offset_for_row("abc\ndef\nghi", 0), (0, 0));
1486 assert_eq!(offset_for_row("abc\ndef\nghi", 1), (1, 4));
1487 assert_eq!(offset_for_row("abc\ndef\nghi", 2), (2, 8));
1488 assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11));
1489 }
1490
1491 #[gpui::test]
1492 fn test_basic_blocks(cx: &mut gpui::TestAppContext) {
1493 cx.update(init_test);
1494
1495 let text = "aaa\nbbb\nccc\nddd";
1496
1497 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
1498 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1499 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1500 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1501 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
1502 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
1503 let (wrap_map, wraps_snapshot) =
1504 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
1505 let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
1506
1507 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
1508 let block_ids = writer.insert(vec![
1509 BlockProperties {
1510 style: BlockStyle::Fixed,
1511 position: buffer_snapshot.anchor_after(Point::new(1, 0)),
1512 height: 1,
1513 disposition: BlockDisposition::Above,
1514 render: Box::new(|_| div().into_any()),
1515 priority: 0,
1516 },
1517 BlockProperties {
1518 style: BlockStyle::Fixed,
1519 position: buffer_snapshot.anchor_after(Point::new(1, 2)),
1520 height: 2,
1521 disposition: BlockDisposition::Above,
1522 render: Box::new(|_| div().into_any()),
1523 priority: 0,
1524 },
1525 BlockProperties {
1526 style: BlockStyle::Fixed,
1527 position: buffer_snapshot.anchor_after(Point::new(3, 3)),
1528 height: 3,
1529 disposition: BlockDisposition::Below,
1530 render: Box::new(|_| div().into_any()),
1531 priority: 0,
1532 },
1533 ]);
1534
1535 let snapshot = block_map.read(wraps_snapshot, Default::default());
1536 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
1537
1538 let blocks = snapshot
1539 .blocks_in_range(0..8)
1540 .map(|(start_row, block)| {
1541 let block = block.as_custom().unwrap();
1542 (start_row..start_row + block.height, block.id)
1543 })
1544 .collect::<Vec<_>>();
1545
1546 // When multiple blocks are on the same line, the newer blocks appear first.
1547 assert_eq!(
1548 blocks,
1549 &[
1550 (1..2, block_ids[0]),
1551 (2..4, block_ids[1]),
1552 (7..10, block_ids[2]),
1553 ]
1554 );
1555
1556 assert_eq!(
1557 snapshot.to_block_point(WrapPoint::new(0, 3)),
1558 BlockPoint::new(0, 3)
1559 );
1560 assert_eq!(
1561 snapshot.to_block_point(WrapPoint::new(1, 0)),
1562 BlockPoint::new(4, 0)
1563 );
1564 assert_eq!(
1565 snapshot.to_block_point(WrapPoint::new(3, 3)),
1566 BlockPoint::new(6, 3)
1567 );
1568
1569 assert_eq!(
1570 snapshot.to_wrap_point(BlockPoint::new(0, 3)),
1571 WrapPoint::new(0, 3)
1572 );
1573 assert_eq!(
1574 snapshot.to_wrap_point(BlockPoint::new(1, 0)),
1575 WrapPoint::new(1, 0)
1576 );
1577 assert_eq!(
1578 snapshot.to_wrap_point(BlockPoint::new(3, 0)),
1579 WrapPoint::new(1, 0)
1580 );
1581 assert_eq!(
1582 snapshot.to_wrap_point(BlockPoint::new(7, 0)),
1583 WrapPoint::new(3, 3)
1584 );
1585
1586 assert_eq!(
1587 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left),
1588 BlockPoint::new(0, 3)
1589 );
1590 assert_eq!(
1591 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right),
1592 BlockPoint::new(4, 0)
1593 );
1594 assert_eq!(
1595 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left),
1596 BlockPoint::new(0, 3)
1597 );
1598 assert_eq!(
1599 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right),
1600 BlockPoint::new(4, 0)
1601 );
1602 assert_eq!(
1603 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Left),
1604 BlockPoint::new(4, 0)
1605 );
1606 assert_eq!(
1607 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Right),
1608 BlockPoint::new(4, 0)
1609 );
1610 assert_eq!(
1611 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Left),
1612 BlockPoint::new(6, 3)
1613 );
1614 assert_eq!(
1615 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Right),
1616 BlockPoint::new(6, 3)
1617 );
1618 assert_eq!(
1619 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Left),
1620 BlockPoint::new(6, 3)
1621 );
1622 assert_eq!(
1623 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Right),
1624 BlockPoint::new(6, 3)
1625 );
1626
1627 assert_eq!(
1628 snapshot
1629 .buffer_rows(BlockRow(0))
1630 .map(|row| row.map(|r| r.0))
1631 .collect::<Vec<_>>(),
1632 &[
1633 Some(0),
1634 None,
1635 None,
1636 None,
1637 Some(1),
1638 Some(2),
1639 Some(3),
1640 None,
1641 None,
1642 None
1643 ]
1644 );
1645
1646 // Insert a line break, separating two block decorations into separate lines.
1647 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1648 buffer.edit([(Point::new(1, 1)..Point::new(1, 1), "!!!\n")], None, cx);
1649 buffer.snapshot(cx)
1650 });
1651
1652 let (inlay_snapshot, inlay_edits) =
1653 inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
1654 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
1655 let (tab_snapshot, tab_edits) =
1656 tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
1657 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1658 wrap_map.sync(tab_snapshot, tab_edits, cx)
1659 });
1660 let snapshot = block_map.read(wraps_snapshot, wrap_edits);
1661 assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
1662 }
1663
1664 #[gpui::test]
1665 fn test_multibuffer_headers_and_footers(cx: &mut AppContext) {
1666 init_test(cx);
1667
1668 let buffer1 = cx.new_model(|cx| Buffer::local("Buffer 1", cx));
1669 let buffer2 = cx.new_model(|cx| Buffer::local("Buffer 2", cx));
1670 let buffer3 = cx.new_model(|cx| Buffer::local("Buffer 3", cx));
1671
1672 let mut excerpt_ids = Vec::new();
1673 let multi_buffer = cx.new_model(|cx| {
1674 let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
1675 excerpt_ids.extend(multi_buffer.push_excerpts(
1676 buffer1.clone(),
1677 [ExcerptRange {
1678 context: 0..buffer1.read(cx).len(),
1679 primary: None,
1680 }],
1681 cx,
1682 ));
1683 excerpt_ids.extend(multi_buffer.push_excerpts(
1684 buffer2.clone(),
1685 [ExcerptRange {
1686 context: 0..buffer2.read(cx).len(),
1687 primary: None,
1688 }],
1689 cx,
1690 ));
1691 excerpt_ids.extend(multi_buffer.push_excerpts(
1692 buffer3.clone(),
1693 [ExcerptRange {
1694 context: 0..buffer3.read(cx).len(),
1695 primary: None,
1696 }],
1697 cx,
1698 ));
1699
1700 multi_buffer
1701 });
1702
1703 let font = font("Helvetica");
1704 let font_size = px(14.);
1705 let font_id = cx.text_system().resolve_font(&font);
1706 let mut wrap_width = px(0.);
1707 for c in "Buff".chars() {
1708 wrap_width += cx
1709 .text_system()
1710 .advance(font_id, font_size, c)
1711 .unwrap()
1712 .width;
1713 }
1714
1715 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
1716 let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot.clone());
1717 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
1718 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
1719 let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
1720
1721 let block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
1722 let snapshot = block_map.read(wraps_snapshot, Default::default());
1723
1724 // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
1725 assert_eq!(
1726 snapshot.text(),
1727 "\nBuff\ner 1\n\n\nBuff\ner 2\n\n\nBuff\ner 3\n"
1728 );
1729
1730 let blocks: Vec<_> = snapshot
1731 .blocks_in_range(0..u32::MAX)
1732 .map(|(row, block)| (row, block.id()))
1733 .collect();
1734 assert_eq!(
1735 blocks,
1736 vec![
1737 (0, BlockId::ExcerptHeader(excerpt_ids[0])),
1738 (3, BlockId::ExcerptFooter(excerpt_ids[0])),
1739 (4, BlockId::ExcerptHeader(excerpt_ids[1])),
1740 (7, BlockId::ExcerptFooter(excerpt_ids[1])),
1741 (8, BlockId::ExcerptHeader(excerpt_ids[2])),
1742 (11, BlockId::ExcerptFooter(excerpt_ids[2]))
1743 ]
1744 );
1745 }
1746
1747 #[gpui::test]
1748 fn test_replace_with_heights(cx: &mut gpui::TestAppContext) {
1749 cx.update(init_test);
1750
1751 let text = "aaa\nbbb\nccc\nddd";
1752
1753 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
1754 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1755 let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
1756 let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1757 let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
1758 let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
1759 let (_wrap_map, wraps_snapshot) =
1760 cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
1761 let mut block_map = BlockMap::new(wraps_snapshot.clone(), false, 1, 1, 0);
1762
1763 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
1764 let block_ids = writer.insert(vec![
1765 BlockProperties {
1766 style: BlockStyle::Fixed,
1767 position: buffer_snapshot.anchor_after(Point::new(1, 0)),
1768 height: 1,
1769 disposition: BlockDisposition::Above,
1770 render: Box::new(|_| div().into_any()),
1771 priority: 0,
1772 },
1773 BlockProperties {
1774 style: BlockStyle::Fixed,
1775 position: buffer_snapshot.anchor_after(Point::new(1, 2)),
1776 height: 2,
1777 disposition: BlockDisposition::Above,
1778 render: Box::new(|_| div().into_any()),
1779 priority: 0,
1780 },
1781 BlockProperties {
1782 style: BlockStyle::Fixed,
1783 position: buffer_snapshot.anchor_after(Point::new(3, 3)),
1784 height: 3,
1785 disposition: BlockDisposition::Below,
1786 render: Box::new(|_| div().into_any()),
1787 priority: 0,
1788 },
1789 ]);
1790
1791 {
1792 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1793 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
1794
1795 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1796
1797 let mut new_heights = HashMap::default();
1798 new_heights.insert(block_ids[0], 2);
1799 block_map_writer.resize(new_heights);
1800 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1801 assert_eq!(snapshot.text(), "aaa\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], 1);
1809 block_map_writer.resize(new_heights);
1810
1811 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1812 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
1813 }
1814
1815 {
1816 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1817
1818 let mut new_heights = HashMap::default();
1819 new_heights.insert(block_ids[0], 0);
1820 block_map_writer.resize(new_heights);
1821
1822 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1823 assert_eq!(snapshot.text(), "aaa\n\n\nbbb\nccc\nddd\n\n\n");
1824 }
1825
1826 {
1827 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1828
1829 let mut new_heights = HashMap::default();
1830 new_heights.insert(block_ids[0], 3);
1831 block_map_writer.resize(new_heights);
1832
1833 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1834 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
1835 }
1836
1837 {
1838 let mut block_map_writer = block_map.write(wraps_snapshot.clone(), Default::default());
1839
1840 let mut new_heights = HashMap::default();
1841 new_heights.insert(block_ids[0], 3);
1842 block_map_writer.resize(new_heights);
1843
1844 let snapshot = block_map.read(wraps_snapshot.clone(), Default::default());
1845 // Same height as before, should remain the same
1846 assert_eq!(snapshot.text(), "aaa\n\n\n\n\n\nbbb\nccc\nddd\n\n\n");
1847 }
1848 }
1849
1850 #[cfg(target_os = "macos")]
1851 #[gpui::test]
1852 fn test_blocks_on_wrapped_lines(cx: &mut gpui::TestAppContext) {
1853 cx.update(init_test);
1854
1855 let _font_id = cx.text_system().font_id(&font("Helvetica")).unwrap();
1856
1857 let text = "one two three\nfour five six\nseven eight";
1858
1859 let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx));
1860 let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1861 let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1862 let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
1863 let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
1864 let (_, wraps_snapshot) = cx.update(|cx| {
1865 WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(60.)), cx)
1866 });
1867 let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 0);
1868
1869 let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
1870 writer.insert(vec![
1871 BlockProperties {
1872 style: BlockStyle::Fixed,
1873 position: buffer_snapshot.anchor_after(Point::new(1, 12)),
1874 disposition: BlockDisposition::Above,
1875 render: Box::new(|_| div().into_any()),
1876 height: 1,
1877 priority: 0,
1878 },
1879 BlockProperties {
1880 style: BlockStyle::Fixed,
1881 position: buffer_snapshot.anchor_after(Point::new(1, 1)),
1882 disposition: BlockDisposition::Below,
1883 render: Box::new(|_| div().into_any()),
1884 height: 1,
1885 priority: 0,
1886 },
1887 ]);
1888
1889 // Blocks with an 'above' disposition go above their corresponding buffer line.
1890 // Blocks with a 'below' disposition go below their corresponding buffer line.
1891 let snapshot = block_map.read(wraps_snapshot, Default::default());
1892 assert_eq!(
1893 snapshot.text(),
1894 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
1895 );
1896 }
1897
1898 #[gpui::test(iterations = 100)]
1899 fn test_random_blocks(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
1900 cx.update(init_test);
1901
1902 let operations = env::var("OPERATIONS")
1903 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1904 .unwrap_or(10);
1905
1906 let wrap_width = if rng.gen_bool(0.2) {
1907 None
1908 } else {
1909 Some(px(rng.gen_range(0.0..=100.0)))
1910 };
1911 let tab_size = 1.try_into().unwrap();
1912 let font_size = px(14.0);
1913 let buffer_start_header_height = rng.gen_range(1..=5);
1914 let excerpt_header_height = rng.gen_range(1..=5);
1915 let excerpt_footer_height = rng.gen_range(1..=5);
1916
1917 log::info!("Wrap width: {:?}", wrap_width);
1918 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
1919 log::info!("Excerpt Footer Height: {:?}", excerpt_footer_height);
1920
1921 let buffer = if rng.gen() {
1922 let len = rng.gen_range(0..10);
1923 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
1924 log::info!("initial buffer text: {:?}", text);
1925 cx.update(|cx| MultiBuffer::build_simple(&text, cx))
1926 } else {
1927 cx.update(|cx| MultiBuffer::build_random(&mut rng, cx))
1928 };
1929
1930 let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
1931 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1932 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
1933 let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
1934 let (wrap_map, wraps_snapshot) = cx
1935 .update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), font_size, wrap_width, cx));
1936 let mut block_map = BlockMap::new(
1937 wraps_snapshot,
1938 true,
1939 buffer_start_header_height,
1940 excerpt_header_height,
1941 excerpt_footer_height,
1942 );
1943 let mut custom_blocks = Vec::new();
1944
1945 for _ in 0..operations {
1946 let mut buffer_edits = Vec::new();
1947 match rng.gen_range(0..=100) {
1948 0..=19 => {
1949 let wrap_width = if rng.gen_bool(0.2) {
1950 None
1951 } else {
1952 Some(px(rng.gen_range(0.0..=100.0)))
1953 };
1954 log::info!("Setting wrap width to {:?}", wrap_width);
1955 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
1956 }
1957 20..=39 => {
1958 let block_count = rng.gen_range(1..=5);
1959 let block_properties = (0..block_count)
1960 .map(|_| {
1961 let buffer = cx.update(|cx| buffer.read(cx).read(cx).clone());
1962 let position = buffer.anchor_after(
1963 buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left),
1964 );
1965
1966 let disposition = if rng.gen() {
1967 BlockDisposition::Above
1968 } else {
1969 BlockDisposition::Below
1970 };
1971 let height = rng.gen_range(0..5);
1972 log::info!(
1973 "inserting block {:?} {:?} with height {}",
1974 disposition,
1975 position.to_point(&buffer),
1976 height
1977 );
1978 BlockProperties {
1979 style: BlockStyle::Fixed,
1980 position,
1981 height,
1982 disposition,
1983 render: Box::new(|_| div().into_any()),
1984 priority: 0,
1985 }
1986 })
1987 .collect::<Vec<_>>();
1988
1989 let (inlay_snapshot, inlay_edits) =
1990 inlay_map.sync(buffer_snapshot.clone(), vec![]);
1991 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
1992 let (tab_snapshot, tab_edits) =
1993 tab_map.sync(fold_snapshot, fold_edits, tab_size);
1994 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1995 wrap_map.sync(tab_snapshot, tab_edits, cx)
1996 });
1997 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
1998 let block_ids =
1999 block_map.insert(block_properties.iter().map(|props| BlockProperties {
2000 position: props.position,
2001 height: props.height,
2002 style: props.style,
2003 render: Box::new(|_| div().into_any()),
2004 disposition: props.disposition,
2005 priority: 0,
2006 }));
2007 for (block_id, props) in block_ids.into_iter().zip(block_properties) {
2008 custom_blocks.push((block_id, props));
2009 }
2010 }
2011 40..=59 if !custom_blocks.is_empty() => {
2012 let block_count = rng.gen_range(1..=4.min(custom_blocks.len()));
2013 let block_ids_to_remove = (0..block_count)
2014 .map(|_| {
2015 custom_blocks
2016 .remove(rng.gen_range(0..custom_blocks.len()))
2017 .0
2018 })
2019 .collect();
2020
2021 let (inlay_snapshot, inlay_edits) =
2022 inlay_map.sync(buffer_snapshot.clone(), vec![]);
2023 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2024 let (tab_snapshot, tab_edits) =
2025 tab_map.sync(fold_snapshot, fold_edits, tab_size);
2026 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2027 wrap_map.sync(tab_snapshot, tab_edits, cx)
2028 });
2029 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
2030 block_map.remove(block_ids_to_remove);
2031 }
2032 _ => {
2033 buffer.update(cx, |buffer, cx| {
2034 let mutation_count = rng.gen_range(1..=5);
2035 let subscription = buffer.subscribe();
2036 buffer.randomly_mutate(&mut rng, mutation_count, cx);
2037 buffer_snapshot = buffer.snapshot(cx);
2038 buffer_edits.extend(subscription.consume());
2039 log::info!("buffer text: {:?}", buffer_snapshot.text());
2040 });
2041 }
2042 }
2043
2044 let (inlay_snapshot, inlay_edits) =
2045 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
2046 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
2047 let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
2048 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
2049 wrap_map.sync(tab_snapshot, tab_edits, cx)
2050 });
2051 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
2052 assert_eq!(
2053 blocks_snapshot.transforms.summary().input_rows,
2054 wraps_snapshot.max_point().row() + 1
2055 );
2056 log::info!("blocks text: {:?}", blocks_snapshot.text());
2057
2058 let mut expected_blocks = Vec::new();
2059 expected_blocks.extend(custom_blocks.iter().map(|(id, block)| {
2060 let mut position = block.position.to_point(&buffer_snapshot);
2061 match block.disposition {
2062 BlockDisposition::Above => {
2063 position.column = 0;
2064 }
2065 BlockDisposition::Below => {
2066 position.column = buffer_snapshot.line_len(MultiBufferRow(position.row));
2067 }
2068 };
2069 let row = wraps_snapshot.make_wrap_point(position, Bias::Left).row();
2070 (
2071 row,
2072 ExpectedBlock::Custom {
2073 disposition: block.disposition,
2074 id: *id,
2075 height: block.height,
2076 priority: block.priority,
2077 },
2078 )
2079 }));
2080
2081 // Note that this needs to be synced with the related section in BlockMap::sync
2082 expected_blocks.extend(
2083 BlockMap::header_and_footer_blocks(
2084 true,
2085 excerpt_footer_height,
2086 buffer_start_header_height,
2087 excerpt_header_height,
2088 &buffer_snapshot,
2089 0..,
2090 &wraps_snapshot,
2091 )
2092 .map(|(row, block)| (row, block.into())),
2093 );
2094
2095 BlockMap::sort_blocks(&mut expected_blocks);
2096
2097 let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
2098
2099 let input_buffer_rows = buffer_snapshot
2100 .buffer_rows(MultiBufferRow(0))
2101 .collect::<Vec<_>>();
2102 let mut expected_buffer_rows = Vec::new();
2103 let mut expected_text = String::new();
2104 let mut expected_block_positions = Vec::new();
2105 let input_text = wraps_snapshot.text();
2106 for (row, input_line) in input_text.split('\n').enumerate() {
2107 let row = row as u32;
2108 if row > 0 {
2109 expected_text.push('\n');
2110 }
2111
2112 let buffer_row = input_buffer_rows[wraps_snapshot
2113 .to_point(WrapPoint::new(row, 0), Bias::Left)
2114 .row as usize];
2115
2116 while let Some((block_row, block)) = sorted_blocks_iter.peek() {
2117 if *block_row == row && block.disposition() == BlockDisposition::Above {
2118 let (_, block) = sorted_blocks_iter.next().unwrap();
2119 let height = block.height() as usize;
2120 expected_block_positions
2121 .push((expected_text.matches('\n').count() as u32, block));
2122 let text = "\n".repeat(height);
2123 expected_text.push_str(&text);
2124 for _ in 0..height {
2125 expected_buffer_rows.push(None);
2126 }
2127 } else {
2128 break;
2129 }
2130 }
2131
2132 let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0;
2133 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
2134 expected_text.push_str(input_line);
2135
2136 while let Some((block_row, block)) = sorted_blocks_iter.peek() {
2137 if *block_row == row && block.disposition() == BlockDisposition::Below {
2138 let (_, block) = sorted_blocks_iter.next().unwrap();
2139 let height = block.height() as usize;
2140 expected_block_positions
2141 .push((expected_text.matches('\n').count() as u32 + 1, block));
2142 let text = "\n".repeat(height);
2143 expected_text.push_str(&text);
2144 for _ in 0..height {
2145 expected_buffer_rows.push(None);
2146 }
2147 } else {
2148 break;
2149 }
2150 }
2151 }
2152
2153 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
2154 let expected_row_count = expected_lines.len();
2155 for start_row in 0..expected_row_count {
2156 let expected_text = expected_lines[start_row..].join("\n");
2157 let actual_text = blocks_snapshot
2158 .chunks(
2159 start_row as u32..blocks_snapshot.max_point().row + 1,
2160 false,
2161 false,
2162 Highlights::default(),
2163 )
2164 .map(|chunk| chunk.text)
2165 .collect::<String>();
2166 assert_eq!(
2167 actual_text, expected_text,
2168 "incorrect text starting from row {}",
2169 start_row
2170 );
2171 assert_eq!(
2172 blocks_snapshot
2173 .buffer_rows(BlockRow(start_row as u32))
2174 .map(|row| row.map(|r| r.0))
2175 .collect::<Vec<_>>(),
2176 &expected_buffer_rows[start_row..]
2177 );
2178 }
2179
2180 assert_eq!(
2181 blocks_snapshot
2182 .blocks_in_range(0..(expected_row_count as u32))
2183 .map(|(row, block)| (row, block.clone().into()))
2184 .collect::<Vec<_>>(),
2185 expected_block_positions,
2186 "invalid blocks_in_range({:?})",
2187 0..expected_row_count
2188 );
2189
2190 for (_, expected_block) in
2191 blocks_snapshot.blocks_in_range(0..(expected_row_count as u32))
2192 {
2193 let actual_block = blocks_snapshot.block_for_id(expected_block.id());
2194 assert_eq!(
2195 actual_block.map(|block| block.id()),
2196 Some(expected_block.id())
2197 );
2198 }
2199
2200 for (block_row, block) in expected_block_positions {
2201 if let BlockType::Custom(block_id) = block.block_type() {
2202 assert_eq!(
2203 blocks_snapshot.row_for_block(block_id),
2204 Some(BlockRow(block_row))
2205 );
2206 }
2207 }
2208
2209 let mut expected_longest_rows = Vec::new();
2210 let mut longest_line_len = -1_isize;
2211 for (row, line) in expected_lines.iter().enumerate() {
2212 let row = row as u32;
2213
2214 assert_eq!(
2215 blocks_snapshot.line_len(BlockRow(row)),
2216 line.len() as u32,
2217 "invalid line len for row {}",
2218 row
2219 );
2220
2221 let line_char_count = line.chars().count() as isize;
2222 match line_char_count.cmp(&longest_line_len) {
2223 Ordering::Less => {}
2224 Ordering::Equal => expected_longest_rows.push(row),
2225 Ordering::Greater => {
2226 longest_line_len = line_char_count;
2227 expected_longest_rows.clear();
2228 expected_longest_rows.push(row);
2229 }
2230 }
2231 }
2232
2233 let longest_row = blocks_snapshot.longest_row();
2234 assert!(
2235 expected_longest_rows.contains(&longest_row),
2236 "incorrect longest row {}. expected {:?} with length {}",
2237 longest_row,
2238 expected_longest_rows,
2239 longest_line_len,
2240 );
2241
2242 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
2243 let wrap_point = WrapPoint::new(row, 0);
2244 let block_point = blocks_snapshot.to_block_point(wrap_point);
2245 assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point);
2246 }
2247
2248 let mut block_point = BlockPoint::new(0, 0);
2249 for c in expected_text.chars() {
2250 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
2251 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
2252 assert_eq!(
2253 blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(left_point)),
2254 left_point
2255 );
2256 assert_eq!(
2257 left_buffer_point,
2258 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
2259 "{:?} is not valid in buffer coordinates",
2260 left_point
2261 );
2262
2263 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
2264 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
2265 assert_eq!(
2266 blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(right_point)),
2267 right_point
2268 );
2269 assert_eq!(
2270 right_buffer_point,
2271 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
2272 "{:?} is not valid in buffer coordinates",
2273 right_point
2274 );
2275
2276 if c == '\n' {
2277 block_point.0 += Point::new(1, 0);
2278 } else {
2279 block_point.column += c.len_utf8() as u32;
2280 }
2281 }
2282 }
2283
2284 #[derive(Debug, Eq, PartialEq)]
2285 enum ExpectedBlock {
2286 ExcerptHeader {
2287 height: u32,
2288 starts_new_buffer: bool,
2289 },
2290 ExcerptFooter {
2291 height: u32,
2292 disposition: BlockDisposition,
2293 },
2294 Custom {
2295 disposition: BlockDisposition,
2296 id: CustomBlockId,
2297 height: u32,
2298 priority: usize,
2299 },
2300 }
2301
2302 impl BlockLike for ExpectedBlock {
2303 fn block_type(&self) -> BlockType {
2304 match self {
2305 ExpectedBlock::Custom { id, .. } => BlockType::Custom(*id),
2306 ExpectedBlock::ExcerptHeader { .. } => BlockType::Header,
2307 ExpectedBlock::ExcerptFooter { .. } => BlockType::Footer,
2308 }
2309 }
2310
2311 fn disposition(&self) -> BlockDisposition {
2312 self.disposition()
2313 }
2314
2315 fn priority(&self) -> usize {
2316 match self {
2317 ExpectedBlock::Custom { priority, .. } => *priority,
2318 ExpectedBlock::ExcerptHeader { .. } => usize::MAX,
2319 ExpectedBlock::ExcerptFooter { .. } => 0,
2320 }
2321 }
2322 }
2323
2324 impl ExpectedBlock {
2325 fn height(&self) -> u32 {
2326 match self {
2327 ExpectedBlock::ExcerptHeader { height, .. } => *height,
2328 ExpectedBlock::Custom { height, .. } => *height,
2329 ExpectedBlock::ExcerptFooter { height, .. } => *height,
2330 }
2331 }
2332
2333 fn disposition(&self) -> BlockDisposition {
2334 match self {
2335 ExpectedBlock::ExcerptHeader { .. } => BlockDisposition::Above,
2336 ExpectedBlock::Custom { disposition, .. } => *disposition,
2337 ExpectedBlock::ExcerptFooter { disposition, .. } => *disposition,
2338 }
2339 }
2340 }
2341
2342 impl From<Block> for ExpectedBlock {
2343 fn from(block: Block) -> Self {
2344 match block {
2345 Block::Custom(block) => ExpectedBlock::Custom {
2346 id: block.id,
2347 disposition: block.disposition,
2348 height: block.height,
2349 priority: block.priority,
2350 },
2351 Block::ExcerptHeader {
2352 height,
2353 starts_new_buffer,
2354 ..
2355 } => ExpectedBlock::ExcerptHeader {
2356 height,
2357 starts_new_buffer,
2358 },
2359 Block::ExcerptFooter {
2360 height,
2361 disposition,
2362 ..
2363 } => ExpectedBlock::ExcerptFooter {
2364 height,
2365 disposition,
2366 },
2367 }
2368 }
2369 }
2370 }
2371
2372 fn init_test(cx: &mut gpui::AppContext) {
2373 let settings = SettingsStore::test(cx);
2374 cx.set_global(settings);
2375 theme::init(theme::LoadThemes::JustBase, cx);
2376 assets::Assets.load_test_fonts(cx);
2377 }
2378
2379 impl Block {
2380 fn as_custom(&self) -> Option<&CustomBlock> {
2381 match self {
2382 Block::Custom(block) => Some(block),
2383 Block::ExcerptHeader { .. } => None,
2384 Block::ExcerptFooter { .. } => None,
2385 }
2386 }
2387 }
2388
2389 impl BlockSnapshot {
2390 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
2391 self.wrap_snapshot.to_point(self.to_wrap_point(point), bias)
2392 }
2393 }
2394}