1use super::wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot};
2use crate::{Anchor, ToPoint as _};
3use collections::{Bound, HashMap, HashSet};
4use gpui::{AppContext, ElementBox};
5use language::{BufferSnapshot, Chunk};
6use parking_lot::Mutex;
7use std::{
8 cmp::{self, Ordering},
9 fmt::Debug,
10 ops::{Deref, Range},
11 sync::{
12 atomic::{AtomicUsize, Ordering::SeqCst},
13 Arc,
14 },
15};
16use sum_tree::{Bias, SumTree};
17use text::{Edit, Point};
18
19const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize];
20
21pub struct BlockMap {
22 next_block_id: AtomicUsize,
23 wrap_snapshot: Mutex<WrapSnapshot>,
24 blocks: Vec<Arc<Block>>,
25 transforms: Mutex<SumTree<Transform>>,
26 excerpt_header_height: u8,
27}
28
29pub struct BlockMapWriter<'a>(&'a mut BlockMap);
30
31pub struct BlockSnapshot {
32 wrap_snapshot: WrapSnapshot,
33 transforms: SumTree<Transform>,
34}
35
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
37pub struct BlockId(usize);
38
39#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
40pub struct BlockPoint(pub super::Point);
41
42#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
43struct BlockRow(u32);
44
45#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
46struct WrapRow(u32);
47
48pub type RenderBlock = Arc<dyn Fn(&BlockContext) -> ElementBox>;
49
50pub struct Block {
51 id: BlockId,
52 position: Anchor,
53 height: u8,
54 render: Mutex<RenderBlock>,
55 disposition: BlockDisposition,
56}
57
58#[derive(Clone)]
59pub struct BlockProperties<P>
60where
61 P: Clone,
62{
63 pub position: P,
64 pub height: u8,
65 pub render: Arc<dyn Fn(&BlockContext) -> ElementBox>,
66 pub disposition: BlockDisposition,
67}
68
69pub struct BlockContext<'a> {
70 pub cx: &'a AppContext,
71 pub anchor_x: f32,
72 pub scroll_x: f32,
73 pub gutter_width: f32,
74 pub gutter_padding: f32,
75 pub em_width: f32,
76 pub line_height: f32,
77}
78
79#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
80pub enum BlockDisposition {
81 Above,
82 Below,
83}
84
85#[derive(Clone, Debug)]
86struct Transform {
87 summary: TransformSummary,
88 block: Option<TransformBlock>,
89}
90
91#[derive(Clone)]
92pub enum TransformBlock {
93 Custom {
94 block: Arc<Block>,
95 column: u32,
96 },
97 ExcerptHeader {
98 buffer: BufferSnapshot,
99 range: Range<text::Anchor>,
100 height: u8,
101 },
102}
103
104impl TransformBlock {
105 fn disposition(&self) -> BlockDisposition {
106 match self {
107 TransformBlock::Custom { block, .. } => block.disposition,
108 TransformBlock::ExcerptHeader { .. } => BlockDisposition::Above,
109 }
110 }
111
112 fn height(&self) -> u8 {
113 match self {
114 TransformBlock::Custom { block, .. } => block.height,
115 TransformBlock::ExcerptHeader { height, .. } => *height,
116 }
117 }
118}
119
120impl Debug for TransformBlock {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 match self {
123 Self::Custom { block, column } => f
124 .debug_struct("Custom")
125 .field("block", block)
126 .field("column", column)
127 .finish(),
128 Self::ExcerptHeader { buffer, .. } => f
129 .debug_struct("ExcerptHeader")
130 .field("path", &buffer.path())
131 .finish(),
132 }
133 }
134}
135
136#[derive(Clone, Debug, Default)]
137struct TransformSummary {
138 input_rows: u32,
139 output_rows: u32,
140}
141
142pub struct BlockChunks<'a> {
143 transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
144 input_chunks: wrap_map::WrapChunks<'a>,
145 input_chunk: Chunk<'a>,
146 output_row: u32,
147 max_output_row: u32,
148}
149
150pub struct BlockBufferRows<'a> {
151 transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
152 input_buffer_rows: wrap_map::WrapBufferRows<'a>,
153 output_row: u32,
154 started: bool,
155}
156
157impl BlockMap {
158 pub fn new(wrap_snapshot: WrapSnapshot, excerpt_header_height: u8) -> Self {
159 let row_count = wrap_snapshot.max_point().row() + 1;
160 let map = Self {
161 next_block_id: AtomicUsize::new(0),
162 blocks: Vec::new(),
163 transforms: Mutex::new(SumTree::from_item(Transform::isomorphic(row_count), &())),
164 wrap_snapshot: Mutex::new(wrap_snapshot.clone()),
165 excerpt_header_height,
166 };
167 map.sync(
168 &wrap_snapshot,
169 vec![Edit {
170 old: 0..row_count,
171 new: 0..row_count,
172 }],
173 );
174 map
175 }
176
177 pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Vec<WrapEdit>) -> BlockSnapshot {
178 self.sync(&wrap_snapshot, edits);
179 *self.wrap_snapshot.lock() = wrap_snapshot.clone();
180 BlockSnapshot {
181 wrap_snapshot,
182 transforms: self.transforms.lock().clone(),
183 }
184 }
185
186 pub fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Vec<WrapEdit>) -> BlockMapWriter {
187 self.sync(&wrap_snapshot, edits);
188 *self.wrap_snapshot.lock() = wrap_snapshot;
189 BlockMapWriter(self)
190 }
191
192 fn sync(&self, wrap_snapshot: &WrapSnapshot, edits: Vec<WrapEdit>) {
193 if edits.is_empty() {
194 return;
195 }
196
197 let buffer = wrap_snapshot.buffer_snapshot();
198 let mut transforms = self.transforms.lock();
199 let mut new_transforms = SumTree::new();
200 let old_row_count = transforms.summary().input_rows;
201 let new_row_count = wrap_snapshot.max_point().row() + 1;
202 let mut cursor = transforms.cursor::<WrapRow>();
203 let mut last_block_ix = 0;
204 let mut blocks_in_edit = Vec::new();
205 let mut edits = edits.into_iter().peekable();
206
207 while let Some(edit) = edits.next() {
208 // Preserve any old transforms that precede this edit.
209 let old_start = WrapRow(edit.old.start);
210 let new_start = WrapRow(edit.new.start);
211 new_transforms.push_tree(cursor.slice(&old_start, Bias::Left, &()), &());
212 if let Some(transform) = cursor.item() {
213 if transform.is_isomorphic() && old_start == cursor.end(&()) {
214 new_transforms.push(transform.clone(), &());
215 cursor.next(&());
216 while let Some(transform) = cursor.item() {
217 if transform
218 .block
219 .as_ref()
220 .map_or(false, |b| b.disposition().is_below())
221 {
222 new_transforms.push(transform.clone(), &());
223 cursor.next(&());
224 } else {
225 break;
226 }
227 }
228 }
229 }
230
231 // Preserve any portion of an old transform that precedes this edit.
232 let extent_before_edit = old_start.0 - cursor.start().0;
233 push_isomorphic(&mut new_transforms, extent_before_edit);
234
235 // Skip over any old transforms that intersect this edit.
236 let mut old_end = WrapRow(edit.old.end);
237 let mut new_end = WrapRow(edit.new.end);
238 cursor.seek(&old_end, Bias::Left, &());
239 cursor.next(&());
240 if old_end == *cursor.start() {
241 while let Some(transform) = cursor.item() {
242 if transform
243 .block
244 .as_ref()
245 .map_or(false, |b| b.disposition().is_below())
246 {
247 cursor.next(&());
248 } else {
249 break;
250 }
251 }
252 }
253
254 // Combine this edit with any subsequent edits that intersect the same transform.
255 while let Some(next_edit) = edits.peek() {
256 if next_edit.old.start <= cursor.start().0 {
257 old_end = WrapRow(next_edit.old.end);
258 new_end = WrapRow(next_edit.new.end);
259 cursor.seek(&old_end, Bias::Left, &());
260 cursor.next(&());
261 if old_end == *cursor.start() {
262 while let Some(transform) = cursor.item() {
263 if transform
264 .block
265 .as_ref()
266 .map_or(false, |b| b.disposition().is_below())
267 {
268 cursor.next(&());
269 } else {
270 break;
271 }
272 }
273 }
274 edits.next();
275 } else {
276 break;
277 }
278 }
279
280 // Find the blocks within this edited region.
281 let new_buffer_start =
282 wrap_snapshot.to_point(WrapPoint::new(new_start.0, 0), Bias::Left);
283 let start_anchor = buffer.anchor_before(new_buffer_start);
284 let start_bound = Bound::Included(start_anchor.clone());
285 let start_block_ix = match self.blocks[last_block_ix..].binary_search_by(|probe| {
286 probe
287 .position
288 .cmp(&start_anchor, &buffer)
289 .unwrap()
290 .then(Ordering::Greater)
291 }) {
292 Ok(ix) | Err(ix) => last_block_ix + ix,
293 };
294
295 let end_bound;
296 let end_block_ix = if new_end.0 > wrap_snapshot.max_point().row() {
297 end_bound = Bound::Unbounded;
298 self.blocks.len()
299 } else {
300 let new_buffer_end =
301 wrap_snapshot.to_point(WrapPoint::new(new_end.0, 0), Bias::Left);
302 let end_anchor = buffer.anchor_before(new_buffer_end);
303 end_bound = Bound::Excluded(end_anchor.clone());
304 match self.blocks[start_block_ix..].binary_search_by(|probe| {
305 probe
306 .position
307 .cmp(&end_anchor, &buffer)
308 .unwrap()
309 .then(Ordering::Greater)
310 }) {
311 Ok(ix) | Err(ix) => start_block_ix + ix,
312 }
313 };
314 last_block_ix = end_block_ix;
315
316 debug_assert!(blocks_in_edit.is_empty());
317 blocks_in_edit.extend(
318 self.blocks[start_block_ix..end_block_ix]
319 .iter()
320 .map(|block| {
321 let mut position = block.position.to_point(&buffer);
322 let column = wrap_snapshot.from_point(position, Bias::Left).column();
323 match block.disposition {
324 BlockDisposition::Above => position.column = 0,
325 BlockDisposition::Below => {
326 position.column = buffer.line_len(position.row)
327 }
328 }
329 let position = wrap_snapshot.from_point(position, Bias::Left);
330 (
331 position.row(),
332 TransformBlock::Custom {
333 block: block.clone(),
334 column,
335 },
336 )
337 }),
338 );
339 blocks_in_edit.extend(
340 buffer
341 .excerpt_boundaries_in_range((start_bound, end_bound))
342 .map(|excerpt_boundary| {
343 (
344 wrap_snapshot
345 .from_point(Point::new(excerpt_boundary.row, 0), Bias::Left)
346 .row(),
347 TransformBlock::ExcerptHeader {
348 buffer: excerpt_boundary.buffer,
349 range: excerpt_boundary.range,
350 height: self.excerpt_header_height,
351 },
352 )
353 }),
354 );
355
356 // Place excerpt headers above custom blocks on the same row.
357 blocks_in_edit.sort_unstable_by(|(row_a, block_a), (row_b, block_b)| {
358 row_a.cmp(&row_b).then_with(|| match (block_a, block_b) {
359 (
360 TransformBlock::ExcerptHeader { .. },
361 TransformBlock::ExcerptHeader { .. },
362 ) => Ordering::Equal,
363 (TransformBlock::ExcerptHeader { .. }, _) => Ordering::Less,
364 (_, TransformBlock::ExcerptHeader { .. }) => Ordering::Greater,
365 (
366 TransformBlock::Custom { block: block_a, .. },
367 TransformBlock::Custom { block: block_b, .. },
368 ) => block_a
369 .disposition
370 .cmp(&block_b.disposition)
371 .then_with(|| block_a.id.cmp(&block_b.id)),
372 })
373 });
374
375 // For each of these blocks, insert a new isomorphic transform preceding the block,
376 // and then insert the block itself.
377 for (block_row, block) in blocks_in_edit.drain(..) {
378 let insertion_row = match block.disposition() {
379 BlockDisposition::Above => block_row,
380 BlockDisposition::Below => block_row + 1,
381 };
382 let extent_before_block = insertion_row - new_transforms.summary().input_rows;
383 push_isomorphic(&mut new_transforms, extent_before_block);
384 new_transforms.push(Transform::block(block), &());
385 }
386
387 old_end = WrapRow(old_end.0.min(old_row_count));
388 new_end = WrapRow(new_end.0.min(new_row_count));
389
390 // Insert an isomorphic transform after the final block.
391 let extent_after_last_block = new_end.0 - new_transforms.summary().input_rows;
392 push_isomorphic(&mut new_transforms, extent_after_last_block);
393
394 // Preserve any portion of the old transform after this edit.
395 let extent_after_edit = cursor.start().0 - old_end.0;
396 push_isomorphic(&mut new_transforms, extent_after_edit);
397 }
398
399 new_transforms.push_tree(cursor.suffix(&()), &());
400 debug_assert_eq!(
401 new_transforms.summary().input_rows,
402 wrap_snapshot.max_point().row() + 1
403 );
404
405 drop(cursor);
406 *transforms = new_transforms;
407 }
408
409 pub fn replace(&mut self, mut renderers: HashMap<BlockId, RenderBlock>) {
410 for block in &self.blocks {
411 if let Some(render) = renderers.remove(&block.id) {
412 *block.render.lock() = render;
413 }
414 }
415 }
416}
417
418fn push_isomorphic(tree: &mut SumTree<Transform>, rows: u32) {
419 if rows == 0 {
420 return;
421 }
422
423 let mut extent = Some(rows);
424 tree.update_last(
425 |last_transform| {
426 if last_transform.is_isomorphic() {
427 let extent = extent.take().unwrap();
428 last_transform.summary.input_rows += extent;
429 last_transform.summary.output_rows += extent;
430 }
431 },
432 &(),
433 );
434 if let Some(extent) = extent {
435 tree.push(Transform::isomorphic(extent), &());
436 }
437}
438
439impl BlockPoint {
440 pub fn new(row: u32, column: u32) -> Self {
441 Self(Point::new(row, column))
442 }
443}
444
445impl Deref for BlockPoint {
446 type Target = Point;
447
448 fn deref(&self) -> &Self::Target {
449 &self.0
450 }
451}
452
453impl std::ops::DerefMut for BlockPoint {
454 fn deref_mut(&mut self) -> &mut Self::Target {
455 &mut self.0
456 }
457}
458
459impl<'a> BlockMapWriter<'a> {
460 pub fn insert(
461 &mut self,
462 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
463 ) -> Vec<BlockId> {
464 let mut ids = Vec::new();
465 let mut edits = Vec::<Edit<u32>>::new();
466 let wrap_snapshot = &*self.0.wrap_snapshot.lock();
467 let buffer = wrap_snapshot.buffer_snapshot();
468
469 for block in blocks {
470 let id = BlockId(self.0.next_block_id.fetch_add(1, SeqCst));
471 ids.push(id);
472
473 let position = block.position;
474 let point = position.to_point(&buffer);
475 let wrap_row = wrap_snapshot
476 .from_point(Point::new(point.row, 0), Bias::Left)
477 .row();
478 let start_row = wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
479 let end_row = wrap_snapshot
480 .next_row_boundary(WrapPoint::new(wrap_row, 0))
481 .unwrap_or(wrap_snapshot.max_point().row() + 1);
482
483 let block_ix = match self
484 .0
485 .blocks
486 .binary_search_by(|probe| probe.position.cmp(&position, &buffer).unwrap())
487 {
488 Ok(ix) | Err(ix) => ix,
489 };
490 self.0.blocks.insert(
491 block_ix,
492 Arc::new(Block {
493 id,
494 position,
495 height: block.height,
496 render: Mutex::new(block.render),
497 disposition: block.disposition,
498 }),
499 );
500
501 if let Err(edit_ix) = edits.binary_search_by_key(&start_row, |edit| edit.old.start) {
502 edits.insert(
503 edit_ix,
504 Edit {
505 old: start_row..end_row,
506 new: start_row..end_row,
507 },
508 );
509 }
510 }
511
512 self.0.sync(wrap_snapshot, edits);
513 ids
514 }
515
516 pub fn remove(&mut self, block_ids: HashSet<BlockId>) {
517 let wrap_snapshot = &*self.0.wrap_snapshot.lock();
518 let buffer = wrap_snapshot.buffer_snapshot();
519 let mut edits = Vec::new();
520 let mut last_block_buffer_row = None;
521 self.0.blocks.retain(|block| {
522 if block_ids.contains(&block.id) {
523 let buffer_row = block.position.to_point(&buffer).row;
524 if last_block_buffer_row != Some(buffer_row) {
525 last_block_buffer_row = Some(buffer_row);
526 let wrap_row = wrap_snapshot
527 .from_point(Point::new(buffer_row, 0), Bias::Left)
528 .row();
529 let start_row = wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
530 let end_row = wrap_snapshot
531 .next_row_boundary(WrapPoint::new(wrap_row, 0))
532 .unwrap_or(wrap_snapshot.max_point().row() + 1);
533 edits.push(Edit {
534 old: start_row..end_row,
535 new: start_row..end_row,
536 })
537 }
538 false
539 } else {
540 true
541 }
542 });
543 self.0.sync(wrap_snapshot, edits);
544 }
545}
546
547impl BlockSnapshot {
548 #[cfg(test)]
549 pub fn text(&self) -> String {
550 self.chunks(0..self.transforms.summary().output_rows, false)
551 .map(|chunk| chunk.text)
552 .collect()
553 }
554
555 pub fn chunks<'a>(&'a self, rows: Range<u32>, language_aware: bool) -> BlockChunks<'a> {
556 let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
557 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
558 let input_end = {
559 cursor.seek(&BlockRow(rows.end), Bias::Right, &());
560 let overshoot = if cursor
561 .item()
562 .map_or(false, |transform| transform.is_isomorphic())
563 {
564 rows.end - cursor.start().0 .0
565 } else {
566 0
567 };
568 cursor.start().1 .0 + overshoot
569 };
570 let input_start = {
571 cursor.seek(&BlockRow(rows.start), Bias::Right, &());
572 let overshoot = if cursor
573 .item()
574 .map_or(false, |transform| transform.is_isomorphic())
575 {
576 rows.start - cursor.start().0 .0
577 } else {
578 0
579 };
580 cursor.start().1 .0 + overshoot
581 };
582 BlockChunks {
583 input_chunks: self
584 .wrap_snapshot
585 .chunks(input_start..input_end, language_aware),
586 input_chunk: Default::default(),
587 transforms: cursor,
588 output_row: rows.start,
589 max_output_row,
590 }
591 }
592
593 pub fn buffer_rows<'a>(&'a self, start_row: u32) -> BlockBufferRows<'a> {
594 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
595 cursor.seek(&BlockRow(start_row), Bias::Right, &());
596 let (output_start, input_start) = cursor.start();
597 let overshoot = if cursor.item().map_or(false, |t| t.is_isomorphic()) {
598 start_row - output_start.0
599 } else {
600 0
601 };
602 let input_start_row = input_start.0 + overshoot;
603 BlockBufferRows {
604 transforms: cursor,
605 input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row),
606 output_row: start_row,
607 started: false,
608 }
609 }
610
611 pub fn blocks_in_range<'a>(
612 &'a self,
613 rows: Range<u32>,
614 ) -> impl Iterator<Item = (u32, &'a TransformBlock)> {
615 let mut cursor = self.transforms.cursor::<BlockRow>();
616 cursor.seek(&BlockRow(rows.start), Bias::Right, &());
617 std::iter::from_fn(move || {
618 while let Some(transform) = cursor.item() {
619 let start_row = cursor.start().0;
620 if start_row >= rows.end {
621 break;
622 }
623 if let Some(block) = &transform.block {
624 cursor.next(&());
625 return Some((start_row, block));
626 } else {
627 cursor.next(&());
628 }
629 }
630 None
631 })
632 }
633
634 pub fn max_point(&self) -> BlockPoint {
635 let row = self.transforms.summary().output_rows - 1;
636 BlockPoint::new(row, self.line_len(row))
637 }
638
639 pub fn longest_row(&self) -> u32 {
640 let input_row = self.wrap_snapshot.longest_row();
641 self.to_block_point(WrapPoint::new(input_row, 0)).row
642 }
643
644 pub fn line_len(&self, row: u32) -> u32 {
645 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
646 cursor.seek(&BlockRow(row), Bias::Right, &());
647 if let Some(transform) = cursor.item() {
648 let (output_start, input_start) = cursor.start();
649 let overshoot = row - output_start.0;
650 if transform.block.is_some() {
651 0
652 } else {
653 self.wrap_snapshot.line_len(input_start.0 + overshoot)
654 }
655 } else {
656 panic!("row out of range");
657 }
658 }
659
660 pub fn is_block_line(&self, row: u32) -> bool {
661 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
662 cursor.seek(&BlockRow(row), Bias::Right, &());
663 cursor.item().map_or(false, |t| t.block.is_some())
664 }
665
666 pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint {
667 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
668 cursor.seek(&BlockRow(point.row), Bias::Right, &());
669
670 let max_input_row = WrapRow(self.transforms.summary().input_rows);
671 let mut search_left =
672 (bias == Bias::Left && cursor.start().1 .0 > 0) || cursor.end(&()).1 == max_input_row;
673 let mut reversed = false;
674
675 loop {
676 if let Some(transform) = cursor.item() {
677 if transform.is_isomorphic() {
678 let (output_start_row, input_start_row) = cursor.start();
679 let (output_end_row, input_end_row) = cursor.end(&());
680 let output_start = Point::new(output_start_row.0, 0);
681 let input_start = Point::new(input_start_row.0, 0);
682 let input_end = Point::new(input_end_row.0, 0);
683 let input_point = if point.row >= output_end_row.0 {
684 let line_len = self.wrap_snapshot.line_len(input_end_row.0 - 1);
685 self.wrap_snapshot
686 .clip_point(WrapPoint::new(input_end_row.0 - 1, line_len), bias)
687 } else {
688 let output_overshoot = point.0.saturating_sub(output_start);
689 self.wrap_snapshot
690 .clip_point(WrapPoint(input_start + output_overshoot), bias)
691 };
692
693 if (input_start..input_end).contains(&input_point.0) {
694 let input_overshoot = input_point.0.saturating_sub(input_start);
695 return BlockPoint(output_start + input_overshoot);
696 }
697 }
698
699 if search_left {
700 cursor.prev(&());
701 } else {
702 cursor.next(&());
703 }
704 } else if reversed {
705 return self.max_point();
706 } else {
707 reversed = true;
708 search_left = !search_left;
709 cursor.seek(&BlockRow(point.row), Bias::Right, &());
710 }
711 }
712 }
713
714 pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
715 let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>();
716 cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &());
717 if let Some(transform) = cursor.item() {
718 debug_assert!(transform.is_isomorphic());
719 } else {
720 return self.max_point();
721 }
722
723 let (input_start_row, output_start_row) = cursor.start();
724 let input_start = Point::new(input_start_row.0, 0);
725 let output_start = Point::new(output_start_row.0, 0);
726 let input_overshoot = wrap_point.0 - input_start;
727 BlockPoint(output_start + input_overshoot)
728 }
729
730 pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint {
731 let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
732 cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
733 if let Some(transform) = cursor.item() {
734 match transform.block.as_ref().map(|b| b.disposition()) {
735 Some(BlockDisposition::Above) => WrapPoint::new(cursor.start().1 .0, 0),
736 Some(BlockDisposition::Below) => {
737 let wrap_row = cursor.start().1 .0 - 1;
738 WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
739 }
740 None => {
741 let overshoot = block_point.row - cursor.start().0 .0;
742 let wrap_row = cursor.start().1 .0 + overshoot;
743 WrapPoint::new(wrap_row, block_point.column)
744 }
745 }
746 } else {
747 self.wrap_snapshot.max_point()
748 }
749 }
750}
751
752impl Transform {
753 fn isomorphic(rows: u32) -> Self {
754 Self {
755 summary: TransformSummary {
756 input_rows: rows,
757 output_rows: rows,
758 },
759 block: None,
760 }
761 }
762
763 fn block(block: TransformBlock) -> Self {
764 Self {
765 summary: TransformSummary {
766 input_rows: 0,
767 output_rows: block.height() as u32,
768 },
769 block: Some(block),
770 }
771 }
772
773 fn is_isomorphic(&self) -> bool {
774 self.block.is_none()
775 }
776}
777
778impl<'a> Iterator for BlockChunks<'a> {
779 type Item = Chunk<'a>;
780
781 fn next(&mut self) -> Option<Self::Item> {
782 if self.output_row >= self.max_output_row {
783 return None;
784 }
785
786 let transform = self.transforms.item()?;
787 if transform.block.is_some() {
788 let block_start = self.transforms.start().0 .0;
789 let mut block_end = self.transforms.end(&()).0 .0;
790 self.transforms.next(&());
791 if self.transforms.item().is_none() {
792 block_end -= 1;
793 }
794
795 let start_in_block = self.output_row - block_start;
796 let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
797 let line_count = end_in_block - start_in_block;
798 self.output_row += line_count;
799
800 return Some(Chunk {
801 text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) },
802 highlight_id: None,
803 diagnostic: None,
804 });
805 }
806
807 if self.input_chunk.text.is_empty() {
808 if let Some(input_chunk) = self.input_chunks.next() {
809 self.input_chunk = input_chunk;
810 } else {
811 self.output_row += 1;
812 if self.output_row < self.max_output_row {
813 self.transforms.next(&());
814 return Some(Chunk {
815 text: "\n",
816 ..Default::default()
817 });
818 } else {
819 return None;
820 }
821 }
822 }
823
824 let transform_end = self.transforms.end(&()).0 .0;
825 let (prefix_rows, prefix_bytes) =
826 offset_for_row(self.input_chunk.text, transform_end - self.output_row);
827 self.output_row += prefix_rows;
828 let (prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes);
829 self.input_chunk.text = suffix;
830 if self.output_row == transform_end {
831 self.transforms.next(&());
832 }
833
834 Some(Chunk {
835 text: prefix,
836 ..self.input_chunk
837 })
838 }
839}
840
841impl<'a> Iterator for BlockBufferRows<'a> {
842 type Item = Option<u32>;
843
844 fn next(&mut self) -> Option<Self::Item> {
845 if self.started {
846 self.output_row += 1;
847 } else {
848 self.started = true;
849 }
850
851 if self.output_row >= self.transforms.end(&()).0 .0 {
852 self.transforms.next(&());
853 }
854
855 let transform = self.transforms.item()?;
856 if transform.block.is_some() {
857 Some(None)
858 } else {
859 Some(self.input_buffer_rows.next().unwrap())
860 }
861 }
862}
863
864impl sum_tree::Item for Transform {
865 type Summary = TransformSummary;
866
867 fn summary(&self) -> Self::Summary {
868 self.summary.clone()
869 }
870}
871
872impl sum_tree::Summary for TransformSummary {
873 type Context = ();
874
875 fn add_summary(&mut self, summary: &Self, _: &()) {
876 self.input_rows += summary.input_rows;
877 self.output_rows += summary.output_rows;
878 }
879}
880
881impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow {
882 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
883 self.0 += summary.input_rows;
884 }
885}
886
887impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
888 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
889 self.0 += summary.output_rows;
890 }
891}
892
893impl BlockDisposition {
894 fn is_below(&self) -> bool {
895 matches!(self, BlockDisposition::Below)
896 }
897}
898
899impl<'a> Deref for BlockContext<'a> {
900 type Target = AppContext;
901
902 fn deref(&self) -> &Self::Target {
903 &self.cx
904 }
905}
906
907impl Block {
908 pub fn render(&self, cx: &BlockContext) -> ElementBox {
909 self.render.lock()(cx)
910 }
911}
912
913impl Debug for Block {
914 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
915 f.debug_struct("Block")
916 .field("id", &self.id)
917 .field("position", &self.position)
918 .field("disposition", &self.disposition)
919 .finish()
920 }
921}
922
923// Count the number of bytes prior to a target point. If the string doesn't contain the target
924// point, return its total extent. Otherwise return the target point itself.
925fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
926 let mut row = 0;
927 let mut offset = 0;
928 for (ix, line) in s.split('\n').enumerate() {
929 if ix > 0 {
930 row += 1;
931 offset += 1;
932 }
933 if row >= target {
934 break;
935 }
936 offset += line.len() as usize;
937 }
938 (row, offset)
939}
940
941#[cfg(test)]
942mod tests {
943 use super::*;
944 use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
945 use crate::multi_buffer::MultiBuffer;
946 use gpui::{elements::Empty, Element};
947 use rand::prelude::*;
948 use std::env;
949 use text::RandomCharIter;
950
951 #[gpui::test]
952 fn test_offset_for_row() {
953 assert_eq!(offset_for_row("", 0), (0, 0));
954 assert_eq!(offset_for_row("", 1), (0, 0));
955 assert_eq!(offset_for_row("abcd", 0), (0, 0));
956 assert_eq!(offset_for_row("abcd", 1), (0, 4));
957 assert_eq!(offset_for_row("\n", 0), (0, 0));
958 assert_eq!(offset_for_row("\n", 1), (1, 1));
959 assert_eq!(offset_for_row("abc\ndef\nghi", 0), (0, 0));
960 assert_eq!(offset_for_row("abc\ndef\nghi", 1), (1, 4));
961 assert_eq!(offset_for_row("abc\ndef\nghi", 2), (2, 8));
962 assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11));
963 }
964
965 #[gpui::test]
966 fn test_basic_blocks(cx: &mut gpui::MutableAppContext) {
967 let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
968 let font_id = cx
969 .font_cache()
970 .select_font(family_id, &Default::default())
971 .unwrap();
972
973 let text = "aaa\nbbb\nccc\nddd";
974
975 let buffer = MultiBuffer::build_simple(text, cx);
976 let buffer_snapshot = buffer.read(cx).snapshot(cx);
977 let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
978 let (fold_map, folds_snapshot) = FoldMap::new(buffer_snapshot.clone());
979 let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1);
980 let (wrap_map, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, None, cx);
981 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1);
982
983 let mut writer = block_map.write(wraps_snapshot.clone(), vec![]);
984 writer.insert(vec![
985 BlockProperties {
986 position: buffer_snapshot.anchor_after(Point::new(1, 0)),
987 height: 1,
988 disposition: BlockDisposition::Above,
989 render: Arc::new(|_| Empty::new().named("block 1")),
990 },
991 BlockProperties {
992 position: buffer_snapshot.anchor_after(Point::new(1, 2)),
993 height: 2,
994 disposition: BlockDisposition::Above,
995 render: Arc::new(|_| Empty::new().named("block 2")),
996 },
997 BlockProperties {
998 position: buffer_snapshot.anchor_after(Point::new(3, 3)),
999 height: 3,
1000 disposition: BlockDisposition::Below,
1001 render: Arc::new(|_| Empty::new().named("block 3")),
1002 },
1003 ]);
1004
1005 let snapshot = block_map.read(wraps_snapshot, vec![]);
1006 assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n");
1007
1008 let blocks = snapshot
1009 .blocks_in_range(0..8)
1010 .map(|(start_row, block)| {
1011 let (block, column) = block.as_custom().unwrap();
1012 (
1013 start_row..start_row + block.height as u32,
1014 column,
1015 block
1016 .render(&BlockContext {
1017 cx,
1018 anchor_x: 0.,
1019 gutter_padding: 0.,
1020 scroll_x: 0.,
1021 gutter_width: 0.,
1022 line_height: 0.,
1023 em_width: 0.,
1024 })
1025 .name()
1026 .unwrap()
1027 .to_string(),
1028 )
1029 })
1030 .collect::<Vec<_>>();
1031
1032 // When multiple blocks are on the same line, the newer blocks appear first.
1033 assert_eq!(
1034 blocks,
1035 &[
1036 (1..3, 2, "block 2".to_string()),
1037 (3..4, 0, "block 1".to_string()),
1038 (7..10, 3, "block 3".to_string()),
1039 ]
1040 );
1041
1042 assert_eq!(
1043 snapshot.to_block_point(WrapPoint::new(0, 3)),
1044 BlockPoint::new(0, 3)
1045 );
1046 assert_eq!(
1047 snapshot.to_block_point(WrapPoint::new(1, 0)),
1048 BlockPoint::new(4, 0)
1049 );
1050 assert_eq!(
1051 snapshot.to_block_point(WrapPoint::new(3, 3)),
1052 BlockPoint::new(6, 3)
1053 );
1054
1055 assert_eq!(
1056 snapshot.to_wrap_point(BlockPoint::new(0, 3)),
1057 WrapPoint::new(0, 3)
1058 );
1059 assert_eq!(
1060 snapshot.to_wrap_point(BlockPoint::new(1, 0)),
1061 WrapPoint::new(1, 0)
1062 );
1063 assert_eq!(
1064 snapshot.to_wrap_point(BlockPoint::new(3, 0)),
1065 WrapPoint::new(1, 0)
1066 );
1067 assert_eq!(
1068 snapshot.to_wrap_point(BlockPoint::new(7, 0)),
1069 WrapPoint::new(3, 3)
1070 );
1071
1072 assert_eq!(
1073 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left),
1074 BlockPoint::new(0, 3)
1075 );
1076 assert_eq!(
1077 snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right),
1078 BlockPoint::new(4, 0)
1079 );
1080 assert_eq!(
1081 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left),
1082 BlockPoint::new(0, 3)
1083 );
1084 assert_eq!(
1085 snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right),
1086 BlockPoint::new(4, 0)
1087 );
1088 assert_eq!(
1089 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Left),
1090 BlockPoint::new(4, 0)
1091 );
1092 assert_eq!(
1093 snapshot.clip_point(BlockPoint::new(4, 0), Bias::Right),
1094 BlockPoint::new(4, 0)
1095 );
1096 assert_eq!(
1097 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Left),
1098 BlockPoint::new(6, 3)
1099 );
1100 assert_eq!(
1101 snapshot.clip_point(BlockPoint::new(6, 3), Bias::Right),
1102 BlockPoint::new(6, 3)
1103 );
1104 assert_eq!(
1105 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Left),
1106 BlockPoint::new(6, 3)
1107 );
1108 assert_eq!(
1109 snapshot.clip_point(BlockPoint::new(7, 0), Bias::Right),
1110 BlockPoint::new(6, 3)
1111 );
1112
1113 assert_eq!(
1114 snapshot.buffer_rows(0).collect::<Vec<_>>(),
1115 &[
1116 Some(0),
1117 None,
1118 None,
1119 None,
1120 Some(1),
1121 Some(2),
1122 Some(3),
1123 None,
1124 None,
1125 None
1126 ]
1127 );
1128
1129 // Insert a line break, separating two block decorations into separate lines.
1130 let buffer_snapshot = buffer.update(cx, |buffer, cx| {
1131 buffer.edit([Point::new(1, 1)..Point::new(1, 1)], "!!!\n", cx);
1132 buffer.snapshot(cx)
1133 });
1134
1135 let (folds_snapshot, fold_edits) =
1136 fold_map.read(buffer_snapshot, subscription.consume().into_inner());
1137 let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
1138 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1139 wrap_map.sync(tabs_snapshot, tab_edits, cx)
1140 });
1141 let snapshot = block_map.read(wraps_snapshot, wrap_edits);
1142 assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
1143 }
1144
1145 #[gpui::test]
1146 fn test_blocks_on_wrapped_lines(cx: &mut gpui::MutableAppContext) {
1147 let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
1148 let font_id = cx
1149 .font_cache()
1150 .select_font(family_id, &Default::default())
1151 .unwrap();
1152
1153 let text = "one two three\nfour five six\nseven eight";
1154
1155 let buffer = MultiBuffer::build_simple(text, cx);
1156 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1157 let (_, folds_snapshot) = FoldMap::new(buffer_snapshot.clone());
1158 let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1);
1159 let (_, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, Some(60.), cx);
1160 let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1);
1161
1162 let mut writer = block_map.write(wraps_snapshot.clone(), vec![]);
1163 writer.insert(vec![
1164 BlockProperties {
1165 position: buffer_snapshot.anchor_after(Point::new(1, 12)),
1166 disposition: BlockDisposition::Above,
1167 render: Arc::new(|_| Empty::new().named("block 1")),
1168 height: 1,
1169 },
1170 BlockProperties {
1171 position: buffer_snapshot.anchor_after(Point::new(1, 1)),
1172 disposition: BlockDisposition::Below,
1173 render: Arc::new(|_| Empty::new().named("block 2")),
1174 height: 1,
1175 },
1176 ]);
1177
1178 // Blocks with an 'above' disposition go above their corresponding buffer line.
1179 // Blocks with a 'below' disposition go below their corresponding buffer line.
1180 let snapshot = block_map.read(wraps_snapshot, vec![]);
1181 assert_eq!(
1182 snapshot.text(),
1183 "one two \nthree\n\nfour five \nsix\n\nseven \neight"
1184 );
1185 }
1186
1187 #[gpui::test(iterations = 100)]
1188 fn test_random_blocks(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
1189 let operations = env::var("OPERATIONS")
1190 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1191 .unwrap_or(10);
1192
1193 let wrap_width = if rng.gen_bool(0.2) {
1194 None
1195 } else {
1196 Some(rng.gen_range(0.0..=100.0))
1197 };
1198 let tab_size = 1;
1199 let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
1200 let font_id = cx
1201 .font_cache()
1202 .select_font(family_id, &Default::default())
1203 .unwrap();
1204 let font_size = 14.0;
1205 let excerpt_header_height = rng.gen_range(1..=5);
1206
1207 log::info!("Wrap width: {:?}", wrap_width);
1208 log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
1209
1210 let buffer = if rng.gen() {
1211 let len = rng.gen_range(0..10);
1212 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
1213 log::info!("initial buffer text: {:?}", text);
1214 MultiBuffer::build_simple(&text, cx)
1215 } else {
1216 MultiBuffer::build_random(&mut rng, cx)
1217 };
1218
1219 let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
1220 let (fold_map, folds_snapshot) = FoldMap::new(buffer_snapshot.clone());
1221 let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size);
1222 let (wrap_map, wraps_snapshot) =
1223 WrapMap::new(tabs_snapshot, font_id, font_size, wrap_width, cx);
1224 let mut block_map = BlockMap::new(wraps_snapshot.clone(), excerpt_header_height);
1225 let mut custom_blocks = Vec::new();
1226
1227 for _ in 0..operations {
1228 let mut buffer_edits = Vec::new();
1229 match rng.gen_range(0..=100) {
1230 0..=19 => {
1231 let wrap_width = if rng.gen_bool(0.2) {
1232 None
1233 } else {
1234 Some(rng.gen_range(0.0..=100.0))
1235 };
1236 log::info!("Setting wrap width to {:?}", wrap_width);
1237 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
1238 }
1239 20..=39 => {
1240 let block_count = rng.gen_range(1..=5);
1241 let block_properties = (0..block_count)
1242 .map(|_| {
1243 let buffer = buffer.read(cx).read(cx);
1244 let position = buffer.anchor_after(
1245 buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left),
1246 );
1247
1248 let disposition = if rng.gen() {
1249 BlockDisposition::Above
1250 } else {
1251 BlockDisposition::Below
1252 };
1253 let height = rng.gen_range(1..5);
1254 log::info!(
1255 "inserting block {:?} {:?} with height {}",
1256 disposition,
1257 position.to_point(&buffer),
1258 height
1259 );
1260 BlockProperties {
1261 position,
1262 height,
1263 disposition,
1264 render: Arc::new(|_| Empty::new().boxed()),
1265 }
1266 })
1267 .collect::<Vec<_>>();
1268
1269 let (folds_snapshot, fold_edits) =
1270 fold_map.read(buffer_snapshot.clone(), vec![]);
1271 let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
1272 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1273 wrap_map.sync(tabs_snapshot, tab_edits, cx)
1274 });
1275 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
1276 let block_ids = block_map.insert(block_properties.clone());
1277 for (block_id, props) in block_ids.into_iter().zip(block_properties) {
1278 custom_blocks.push((block_id, props));
1279 }
1280 }
1281 40..=59 if !custom_blocks.is_empty() => {
1282 let block_count = rng.gen_range(1..=4.min(custom_blocks.len()));
1283 let block_ids_to_remove = (0..block_count)
1284 .map(|_| {
1285 custom_blocks
1286 .remove(rng.gen_range(0..custom_blocks.len()))
1287 .0
1288 })
1289 .collect();
1290
1291 let (folds_snapshot, fold_edits) =
1292 fold_map.read(buffer_snapshot.clone(), vec![]);
1293 let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
1294 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1295 wrap_map.sync(tabs_snapshot, tab_edits, cx)
1296 });
1297 let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
1298 block_map.remove(block_ids_to_remove);
1299 }
1300 _ => {
1301 buffer.update(cx, |buffer, cx| {
1302 let edit_count = rng.gen_range(1..=5);
1303 let subscription = buffer.subscribe();
1304 buffer.randomly_edit(&mut rng, edit_count, cx);
1305 buffer_snapshot = buffer.snapshot(cx);
1306 buffer_edits.extend(subscription.consume());
1307 log::info!("buffer text: {:?}", buffer_snapshot.text());
1308 });
1309 }
1310 }
1311
1312 let (folds_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits);
1313 let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
1314 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
1315 wrap_map.sync(tabs_snapshot, tab_edits, cx)
1316 });
1317 let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
1318 assert_eq!(
1319 blocks_snapshot.transforms.summary().input_rows,
1320 wraps_snapshot.max_point().row() + 1
1321 );
1322 log::info!("blocks text: {:?}", blocks_snapshot.text());
1323
1324 let mut expected_blocks = Vec::new();
1325 expected_blocks.extend(custom_blocks.iter().map(|(id, block)| {
1326 let mut position = block.position.to_point(&buffer_snapshot);
1327 let column = wraps_snapshot.from_point(position, Bias::Left).column();
1328 match block.disposition {
1329 BlockDisposition::Above => {
1330 position.column = 0;
1331 }
1332 BlockDisposition::Below => {
1333 position.column = buffer_snapshot.line_len(position.row);
1334 }
1335 };
1336 let row = wraps_snapshot.from_point(position, Bias::Left).row();
1337 (row, block.disposition, Some(*id), block.height)
1338 }));
1339 expected_blocks.extend(buffer_snapshot.excerpt_boundaries_in_range(0..).map(
1340 |boundary| {
1341 let position =
1342 wraps_snapshot.from_point(Point::new(boundary.row, 0), Bias::Left);
1343 (
1344 position.row(),
1345 BlockDisposition::Above,
1346 None,
1347 excerpt_header_height,
1348 )
1349 },
1350 ));
1351 expected_blocks
1352 .sort_unstable_by_key(|(row, disposition, id, _)| (*row, *disposition, *id));
1353 let mut sorted_blocks_iter = expected_blocks.iter().peekable();
1354
1355 let input_buffer_rows = buffer_snapshot.buffer_rows(0).collect::<Vec<_>>();
1356 let mut expected_buffer_rows = Vec::new();
1357 let mut expected_text = String::new();
1358 let mut expected_block_positions = Vec::new();
1359 let input_text = wraps_snapshot.text();
1360 for (row, input_line) in input_text.split('\n').enumerate() {
1361 let row = row as u32;
1362 if row > 0 {
1363 expected_text.push('\n');
1364 }
1365
1366 let buffer_row = input_buffer_rows[wraps_snapshot
1367 .to_point(WrapPoint::new(row, 0), Bias::Left)
1368 .row as usize];
1369
1370 while let Some((block_row, disposition, id, height)) = sorted_blocks_iter.peek() {
1371 if *block_row == row && *disposition == BlockDisposition::Above {
1372 expected_block_positions
1373 .push((expected_text.matches('\n').count() as u32, *id));
1374 let text = "\n".repeat(*height as usize);
1375 expected_text.push_str(&text);
1376 for _ in 0..*height {
1377 expected_buffer_rows.push(None);
1378 }
1379 sorted_blocks_iter.next();
1380 } else {
1381 break;
1382 }
1383 }
1384
1385 let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0;
1386 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
1387 expected_text.push_str(input_line);
1388
1389 while let Some((block_row, disposition, id, height)) = sorted_blocks_iter.peek() {
1390 if *block_row == row && *disposition == BlockDisposition::Below {
1391 expected_block_positions
1392 .push((expected_text.matches('\n').count() as u32 + 1, *id));
1393 let text = "\n".repeat(*height as usize);
1394 expected_text.push_str(&text);
1395 for _ in 0..*height {
1396 expected_buffer_rows.push(None);
1397 }
1398 sorted_blocks_iter.next();
1399 } else {
1400 break;
1401 }
1402 }
1403 }
1404
1405 let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
1406 let expected_row_count = expected_lines.len();
1407 for start_row in 0..expected_row_count {
1408 let expected_text = expected_lines[start_row..].join("\n");
1409 let actual_text = blocks_snapshot
1410 .chunks(start_row as u32..expected_row_count as u32, false)
1411 .map(|chunk| chunk.text)
1412 .collect::<String>();
1413 assert_eq!(
1414 actual_text, expected_text,
1415 "incorrect text starting from row {}",
1416 start_row
1417 );
1418 assert_eq!(
1419 blocks_snapshot
1420 .buffer_rows(start_row as u32)
1421 .collect::<Vec<_>>(),
1422 &expected_buffer_rows[start_row..]
1423 );
1424 }
1425
1426 assert_eq!(
1427 blocks_snapshot
1428 .blocks_in_range(0..(expected_row_count as u32))
1429 .map(|(row, block)| (row, block.as_custom().map(|(b, _)| b.id)))
1430 .collect::<Vec<_>>(),
1431 expected_block_positions
1432 );
1433
1434 let mut expected_longest_rows = Vec::new();
1435 let mut longest_line_len = -1_isize;
1436 for (row, line) in expected_lines.iter().enumerate() {
1437 let row = row as u32;
1438
1439 assert_eq!(
1440 blocks_snapshot.line_len(row),
1441 line.len() as u32,
1442 "invalid line len for row {}",
1443 row
1444 );
1445
1446 let line_char_count = line.chars().count() as isize;
1447 match line_char_count.cmp(&longest_line_len) {
1448 Ordering::Less => {}
1449 Ordering::Equal => expected_longest_rows.push(row),
1450 Ordering::Greater => {
1451 longest_line_len = line_char_count;
1452 expected_longest_rows.clear();
1453 expected_longest_rows.push(row);
1454 }
1455 }
1456 }
1457
1458 let longest_row = blocks_snapshot.longest_row();
1459 assert!(
1460 expected_longest_rows.contains(&longest_row),
1461 "incorrect longest row {}. expected {:?} with length {}",
1462 longest_row,
1463 expected_longest_rows,
1464 longest_line_len,
1465 );
1466
1467 for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
1468 let wrap_point = WrapPoint::new(row, 0);
1469 let block_point = blocks_snapshot.to_block_point(wrap_point);
1470 assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point);
1471 }
1472
1473 let mut block_point = BlockPoint::new(0, 0);
1474 for c in expected_text.chars() {
1475 let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
1476 let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left);
1477 assert_eq!(
1478 blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(left_point)),
1479 left_point
1480 );
1481 assert_eq!(
1482 left_buffer_point,
1483 buffer_snapshot.clip_point(left_buffer_point, Bias::Right),
1484 "{:?} is not valid in buffer coordinates",
1485 left_point
1486 );
1487
1488 let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
1489 let right_buffer_point = blocks_snapshot.to_point(right_point, Bias::Right);
1490 assert_eq!(
1491 blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(right_point)),
1492 right_point
1493 );
1494 assert_eq!(
1495 right_buffer_point,
1496 buffer_snapshot.clip_point(right_buffer_point, Bias::Left),
1497 "{:?} is not valid in buffer coordinates",
1498 right_point
1499 );
1500
1501 if c == '\n' {
1502 block_point.0 += Point::new(1, 0);
1503 } else {
1504 block_point.column += c.len_utf8() as u32;
1505 }
1506 }
1507 }
1508 }
1509
1510 impl TransformBlock {
1511 fn as_custom(&self) -> Option<(&Block, u32)> {
1512 match self {
1513 TransformBlock::Custom { block, column } => Some((block, *column)),
1514 TransformBlock::ExcerptHeader { .. } => None,
1515 }
1516 }
1517 }
1518
1519 impl BlockSnapshot {
1520 fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
1521 self.wrap_snapshot.to_point(self.to_wrap_point(point), bias)
1522 }
1523 }
1524}