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