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