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