1mod anchor;
2mod location;
3mod selection;
4
5use self::location::*;
6use crate::{
7 buffer::{self, Buffer, Chunk, ToOffset as _, ToPoint as _},
8 BufferSnapshot, Diagnostic, File, Language,
9};
10use anyhow::Result;
11use clock::ReplicaId;
12use collections::HashMap;
13use gpui::{AppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task};
14use parking_lot::{Mutex, MutexGuard};
15use std::{cmp, io, ops::Range, sync::Arc, time::SystemTime};
16use sum_tree::{Bias, Cursor, SumTree};
17use text::{
18 rope::TextDimension,
19 subscription::{Subscription, Topic},
20 AnchorRangeExt as _, Edit, Point, PointUtf16, Selection, SelectionSetId, TextSummary,
21};
22use theme::SyntaxTheme;
23
24pub use anchor::{Anchor, AnchorRangeExt, AnchorRangeMap, AnchorRangeSet};
25pub use selection::SelectionSet;
26
27const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize];
28
29#[derive(Default)]
30pub struct MultiBuffer {
31 snapshot: Mutex<MultiBufferSnapshot>,
32 buffers: HashMap<usize, BufferState>,
33 subscriptions: Topic,
34}
35
36pub trait ToOffset: 'static {
37 fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize;
38}
39
40pub trait ToPoint: 'static {
41 fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point;
42}
43
44#[derive(Debug)]
45struct BufferState {
46 buffer: ModelHandle<Buffer>,
47 last_sync: clock::Global,
48 excerpts: Vec<ExcerptId>,
49}
50
51#[derive(Clone, Default)]
52pub struct MultiBufferSnapshot {
53 excerpts: SumTree<Excerpt>,
54}
55
56pub struct ExcerptProperties<'a, T> {
57 buffer: &'a ModelHandle<Buffer>,
58 range: Range<T>,
59 header_height: u8,
60}
61
62#[derive(Clone)]
63struct Excerpt {
64 id: ExcerptId,
65 buffer: buffer::BufferSnapshot,
66 range: Range<text::Anchor>,
67 text_summary: TextSummary,
68 header_height: u8,
69}
70
71#[derive(Clone, Debug, Default)]
72struct ExcerptSummary {
73 excerpt_id: ExcerptId,
74 text: TextSummary,
75}
76
77pub struct MultiBufferChunks<'a> {
78 range: Range<usize>,
79 cursor: Cursor<'a, Excerpt, usize>,
80 header_height: u8,
81 excerpt_chunks: Option<buffer::BufferChunks<'a>>,
82 theme: Option<&'a SyntaxTheme>,
83}
84
85pub struct MultiBufferBytes<'a> {
86 chunks: MultiBufferChunks<'a>,
87}
88
89impl MultiBuffer {
90 pub fn new() -> Self {
91 Self::default()
92 }
93
94 pub fn singleton(buffer: ModelHandle<Buffer>, cx: &mut ModelContext<Self>) -> Self {
95 let mut this = Self::new();
96 this.push(
97 ExcerptProperties {
98 buffer: &buffer,
99 range: text::Anchor::min()..text::Anchor::max(),
100 header_height: 0,
101 },
102 cx,
103 );
104 this
105 }
106
107 pub fn build_simple(text: &str, cx: &mut MutableAppContext) -> ModelHandle<Self> {
108 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
109 cx.add_model(|cx| Self::singleton(buffer, cx))
110 }
111
112 pub fn snapshot(&self, cx: &AppContext) -> MultiBufferSnapshot {
113 self.sync(cx);
114 self.snapshot.lock().clone()
115 }
116
117 pub fn as_snapshot(&self) -> MutexGuard<MultiBufferSnapshot> {
118 self.snapshot.lock()
119 }
120
121 pub fn as_singleton(&self) -> Option<&ModelHandle<Buffer>> {
122 if self.buffers.len() == 1 {
123 return Some(&self.buffers.values().next().unwrap().buffer);
124 } else {
125 None
126 }
127 }
128
129 pub fn subscribe(&mut self) -> Subscription {
130 self.subscriptions.subscribe()
131 }
132
133 pub fn edit<I, S, T>(&mut self, ranges_iter: I, new_text: T, cx: &mut ModelContext<Self>)
134 where
135 I: IntoIterator<Item = Range<S>>,
136 S: ToOffset,
137 T: Into<String>,
138 {
139 self.edit_internal(ranges_iter, new_text, false, cx)
140 }
141
142 pub fn edit_with_autoindent<I, S, T>(
143 &mut self,
144 ranges_iter: I,
145 new_text: T,
146 cx: &mut ModelContext<Self>,
147 ) where
148 I: IntoIterator<Item = Range<S>>,
149 S: ToOffset,
150 T: Into<String>,
151 {
152 self.edit_internal(ranges_iter, new_text, true, cx)
153 }
154
155 pub fn edit_internal<I, S, T>(
156 &mut self,
157 ranges_iter: I,
158 new_text: T,
159 autoindent: bool,
160 cx: &mut ModelContext<Self>,
161 ) where
162 I: IntoIterator<Item = Range<S>>,
163 S: ToOffset,
164 T: Into<String>,
165 {
166 todo!()
167 }
168
169 pub fn start_transaction(
170 &mut self,
171 selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
172 ) -> Result<()> {
173 todo!()
174 }
175
176 pub fn end_transaction(
177 &mut self,
178 selection_set_ids: impl IntoIterator<Item = SelectionSetId>,
179 cx: &mut ModelContext<Self>,
180 ) -> Result<()> {
181 todo!()
182 }
183
184 pub fn undo(&mut self, cx: &mut ModelContext<Self>) {
185 todo!()
186 }
187
188 pub fn redo(&mut self, cx: &mut ModelContext<Self>) {
189 todo!()
190 }
191
192 pub fn selection_set(&self, set_id: SelectionSetId) -> Result<&SelectionSet> {
193 todo!()
194 }
195
196 pub fn add_selection_set<T: ToOffset>(
197 &mut self,
198 selections: &[Selection<T>],
199 cx: &mut ModelContext<Self>,
200 ) -> SelectionSetId {
201 todo!()
202 }
203
204 pub fn remove_selection_set(
205 &mut self,
206 set_id: SelectionSetId,
207 cx: &mut ModelContext<Self>,
208 ) -> Result<()> {
209 todo!()
210 }
211
212 pub fn update_selection_set<T: ToOffset>(
213 &mut self,
214 set_id: SelectionSetId,
215 selections: &[Selection<T>],
216 cx: &mut ModelContext<Self>,
217 ) -> Result<()> {
218 todo!()
219 }
220
221 pub fn set_active_selection_set(
222 &mut self,
223 set_id: Option<SelectionSetId>,
224 cx: &mut ModelContext<Self>,
225 ) -> Result<()> {
226 todo!()
227 }
228
229 pub fn selection_sets(&self) -> impl Iterator<Item = (&SelectionSetId, &SelectionSet)> {
230 todo!();
231 None.into_iter()
232 }
233
234 pub fn push<O>(&mut self, props: ExcerptProperties<O>, cx: &mut ModelContext<Self>) -> ExcerptId
235 where
236 O: text::ToOffset,
237 {
238 self.sync(cx);
239
240 let buffer = props.buffer.read(cx);
241 let range = buffer.anchor_before(props.range.start)..buffer.anchor_after(props.range.end);
242 let mut snapshot = self.snapshot.lock();
243 let prev_id = snapshot.excerpts.last().map(|e| &e.id);
244 let id = ExcerptId::between(prev_id.unwrap_or(&ExcerptId::min()), &ExcerptId::max());
245
246 let edit_start = snapshot.excerpts.summary().text.bytes;
247 let excerpt = Excerpt::new(id.clone(), buffer.snapshot(), range, props.header_height);
248 let edit = Edit {
249 old: edit_start..edit_start,
250 new: edit_start..edit_start + excerpt.text_summary.bytes,
251 };
252 snapshot.excerpts.push(excerpt, &());
253 self.buffers
254 .entry(props.buffer.id())
255 .or_insert_with(|| BufferState {
256 buffer: props.buffer.clone(),
257 last_sync: buffer.version(),
258 excerpts: Default::default(),
259 })
260 .excerpts
261 .push(id.clone());
262
263 self.subscriptions.publish_mut([edit]);
264
265 id
266 }
267
268 pub fn save(
269 &mut self,
270 cx: &mut ModelContext<Self>,
271 ) -> Result<Task<Result<(clock::Global, SystemTime)>>> {
272 todo!()
273 }
274
275 pub fn file<'a>(&self, cx: &'a AppContext) -> Option<&'a dyn File> {
276 self.as_singleton()
277 .and_then(|buffer| buffer.read(cx).file())
278 }
279
280 pub fn is_dirty(&self) -> bool {
281 todo!()
282 }
283
284 pub fn has_conflict(&self) -> bool {
285 todo!()
286 }
287
288 pub fn is_parsing(&self, _: &AppContext) -> bool {
289 todo!()
290 }
291
292 fn sync(&self, cx: &AppContext) {
293 let mut snapshot = self.snapshot.lock();
294 let mut excerpts_to_edit = Vec::new();
295 for buffer_state in self.buffers.values() {
296 if buffer_state
297 .buffer
298 .read(cx)
299 .version()
300 .gt(&buffer_state.last_sync)
301 {
302 excerpts_to_edit.extend(
303 buffer_state
304 .excerpts
305 .iter()
306 .map(|excerpt_id| (excerpt_id, buffer_state)),
307 );
308 }
309 }
310 excerpts_to_edit.sort_unstable_by_key(|(excerpt_id, _)| *excerpt_id);
311
312 let mut edits = Vec::new();
313 let mut new_excerpts = SumTree::new();
314 let mut cursor = snapshot.excerpts.cursor::<(ExcerptId, usize)>();
315
316 for (id, buffer_state) in excerpts_to_edit {
317 new_excerpts.push_tree(cursor.slice(id, Bias::Left, &()), &());
318 let old_excerpt = cursor.item().unwrap();
319 let buffer = buffer_state.buffer.read(cx);
320
321 edits.extend(
322 buffer
323 .edits_since_in_range::<usize>(
324 old_excerpt.buffer.version(),
325 old_excerpt.range.clone(),
326 )
327 .map(|mut edit| {
328 let excerpt_old_start =
329 cursor.start().1 + old_excerpt.header_height as usize;
330 let excerpt_new_start =
331 new_excerpts.summary().text.bytes + old_excerpt.header_height as usize;
332 edit.old.start += excerpt_old_start;
333 edit.old.end += excerpt_old_start;
334 edit.new.start += excerpt_new_start;
335 edit.new.end += excerpt_new_start;
336 edit
337 }),
338 );
339
340 new_excerpts.push(
341 Excerpt::new(
342 id.clone(),
343 buffer.snapshot(),
344 old_excerpt.range.clone(),
345 old_excerpt.header_height,
346 ),
347 &(),
348 );
349
350 cursor.next(&());
351 }
352 new_excerpts.push_tree(cursor.suffix(&()), &());
353
354 drop(cursor);
355 snapshot.excerpts = new_excerpts;
356
357 self.subscriptions.publish(edits);
358 }
359}
360
361// Methods delegating to the snapshot
362impl MultiBuffer {
363 pub fn replica_id(&self) -> ReplicaId {
364 self.snapshot.lock().replica_id()
365 }
366
367 pub fn text(&self) -> String {
368 self.snapshot.lock().text()
369 }
370
371 pub fn text_for_range<'a, T: ToOffset>(
372 &'a self,
373 range: Range<T>,
374 ) -> impl Iterator<Item = &'a str> {
375 todo!();
376 [].into_iter()
377 }
378
379 pub fn max_point(&self) -> Point {
380 self.snapshot.lock().max_point()
381 }
382
383 pub fn len(&self) -> usize {
384 self.snapshot.lock().len()
385 }
386
387 pub fn line_len(&self, row: u32) -> u32 {
388 self.snapshot.lock().line_len(row)
389 }
390
391 pub fn is_line_blank(&self, row: u32) -> bool {
392 self.snapshot.lock().is_line_blank(row)
393 }
394
395 pub fn indent_column_for_line(&self, row: u32) -> u32 {
396 self.snapshot.lock().indent_column_for_line(row)
397 }
398
399 pub fn anchor_before<T: ToOffset>(&self, position: T) -> Anchor {
400 self.snapshot.lock().anchor_before(position)
401 }
402
403 pub fn anchor_after<T: ToOffset>(&self, position: T) -> Anchor {
404 self.snapshot.lock().anchor_after(position)
405 }
406
407 pub fn anchor_at<T: ToOffset>(&self, position: T, bias: Bias) -> Anchor {
408 self.snapshot.lock().anchor_at(position, bias)
409 }
410
411 pub fn anchor_range_set<E>(
412 &self,
413 start_bias: Bias,
414 end_bias: Bias,
415 entries: E,
416 ) -> AnchorRangeSet
417 where
418 E: IntoIterator<Item = Range<usize>>,
419 {
420 todo!()
421 }
422
423 pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize {
424 self.snapshot.lock().clip_offset(offset, bias)
425 }
426
427 pub fn clip_point(&self, point: Point, bias: Bias) -> Point {
428 self.snapshot.lock().clip_point(point, bias)
429 }
430
431 pub fn language<'a>(&self) -> Option<&'a Arc<Language>> {
432 todo!()
433 }
434
435 pub fn parse_count(&self) -> usize {
436 self.snapshot.lock().parse_count()
437 }
438
439 pub fn diagnostics_update_count(&self) -> usize {
440 self.snapshot.lock().diagnostics_update_count()
441 }
442
443 pub fn diagnostics_in_range<'a, T, O>(
444 &'a self,
445 search_range: Range<T>,
446 ) -> impl Iterator<Item = (Range<O>, &Diagnostic)> + 'a
447 where
448 T: 'a + ToOffset,
449 O: 'a,
450 {
451 todo!();
452 None.into_iter()
453 }
454}
455
456#[cfg(any(test, feature = "test-support"))]
457impl MultiBuffer {
458 pub fn randomly_edit<R: rand::Rng>(&mut self, _: &mut R, _: usize, _: &mut ModelContext<Self>) {
459 todo!()
460 }
461
462 pub fn randomly_mutate<R: rand::Rng>(&mut self, rng: &mut R, cx: &mut ModelContext<Self>) {
463 todo!()
464 }
465}
466
467impl Entity for MultiBuffer {
468 type Event = super::Event;
469}
470
471impl MultiBufferSnapshot {
472 pub fn replica_id(&self) -> ReplicaId {
473 todo!()
474 }
475
476 pub fn text(&self) -> String {
477 self.chunks(0..self.len(), None)
478 .map(|chunk| chunk.text)
479 .collect()
480 }
481
482 pub fn reversed_chars_at<'a, T: ToOffset>(
483 &'a self,
484 position: T,
485 ) -> impl Iterator<Item = char> + 'a {
486 todo!();
487 None.into_iter()
488 }
489
490 pub fn chars_at<'a, T: ToOffset>(&'a self, position: T) -> impl Iterator<Item = char> + 'a {
491 let offset = position.to_offset(self);
492 self.text_for_range(offset..self.len())
493 .flat_map(|chunk| chunk.chars())
494 }
495
496 pub fn text_for_range<'a, T: ToOffset>(
497 &'a self,
498 range: Range<T>,
499 ) -> impl Iterator<Item = &'a str> {
500 self.chunks(range, None).map(|chunk| chunk.text)
501 }
502
503 pub fn is_line_blank(&self, row: u32) -> bool {
504 self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row)))
505 .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none())
506 }
507
508 pub fn contains_str_at<T>(&self, _: T, _: &str) -> bool
509 where
510 T: ToOffset,
511 {
512 todo!()
513 }
514
515 pub fn len(&self) -> usize {
516 self.excerpts.summary().text.bytes
517 }
518
519 pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize {
520 let mut cursor = self.excerpts.cursor::<usize>();
521 cursor.seek(&offset, Bias::Right, &());
522 if let Some(excerpt) = cursor.item() {
523 let start_after_header = *cursor.start() + excerpt.header_height as usize;
524 if offset < start_after_header {
525 *cursor.start()
526 } else {
527 let excerpt_start = excerpt.range.start.to_offset(&excerpt.buffer);
528 let buffer_offset = excerpt
529 .buffer
530 .clip_offset(excerpt_start + (offset - start_after_header), bias);
531 let offset_in_excerpt = if buffer_offset > excerpt_start {
532 buffer_offset - excerpt_start
533 } else {
534 0
535 };
536 start_after_header + offset_in_excerpt
537 }
538 } else {
539 self.excerpts.summary().text.bytes
540 }
541 }
542
543 pub fn clip_point(&self, point: Point, bias: Bias) -> Point {
544 let mut cursor = self.excerpts.cursor::<Point>();
545 cursor.seek(&point, Bias::Right, &());
546 if let Some(excerpt) = cursor.item() {
547 let start_after_header = *cursor.start() + Point::new(excerpt.header_height as u32, 0);
548 if point < start_after_header {
549 *cursor.start()
550 } else {
551 let excerpt_start = excerpt.range.start.to_point(&excerpt.buffer);
552 let buffer_point = excerpt
553 .buffer
554 .clip_point(excerpt_start + (point - start_after_header), bias);
555 let point_in_excerpt = if buffer_point > excerpt_start {
556 buffer_point - excerpt_start
557 } else {
558 Point::zero()
559 };
560 start_after_header + point_in_excerpt
561 }
562 } else {
563 self.excerpts.summary().text.lines
564 }
565 }
566
567 pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 {
568 let mut cursor = self.excerpts.cursor::<PointUtf16>();
569 cursor.seek(&point, Bias::Right, &());
570 if let Some(excerpt) = cursor.item() {
571 let start_after_header =
572 *cursor.start() + PointUtf16::new(excerpt.header_height as u32, 0);
573 if point < start_after_header {
574 *cursor.start()
575 } else {
576 let excerpt_start = excerpt
577 .buffer
578 .offset_to_point_utf16(excerpt.range.start.to_offset(&excerpt.buffer));
579 let buffer_point = excerpt
580 .buffer
581 .clip_point_utf16(excerpt_start + (point - start_after_header), bias);
582 let point_in_excerpt = if buffer_point > excerpt_start {
583 buffer_point - excerpt_start
584 } else {
585 PointUtf16::new(0, 0)
586 };
587 start_after_header + point_in_excerpt
588 }
589 } else {
590 self.excerpts.summary().text.lines_utf16
591 }
592 }
593
594 pub fn bytes_in_range<'a, T: ToOffset>(&'a self, range: Range<T>) -> MultiBufferBytes<'a> {
595 todo!()
596 }
597
598 pub fn chunks<'a, T: ToOffset>(
599 &'a self,
600 range: Range<T>,
601 theme: Option<&'a SyntaxTheme>,
602 ) -> MultiBufferChunks<'a> {
603 let range = range.start.to_offset(self)..range.end.to_offset(self);
604 let mut cursor = self.excerpts.cursor::<usize>();
605 cursor.seek(&range.start, Bias::Right, &());
606
607 let mut header_height: u8 = 0;
608 let excerpt_chunks = cursor.item().map(|excerpt| {
609 let buffer_range = excerpt.range.to_offset(&excerpt.buffer);
610 header_height = excerpt.header_height;
611
612 let buffer_start;
613 let start_overshoot = range.start - cursor.start();
614 if start_overshoot < excerpt.header_height as usize {
615 header_height -= start_overshoot as u8;
616 buffer_start = buffer_range.start;
617 } else {
618 buffer_start =
619 buffer_range.start + start_overshoot - excerpt.header_height as usize;
620 header_height = 0;
621 }
622
623 let buffer_end;
624 let end_overshoot = range.end - cursor.start();
625 if end_overshoot < excerpt.header_height as usize {
626 header_height -= excerpt.header_height - end_overshoot as u8;
627 buffer_end = buffer_start;
628 } else {
629 buffer_end = cmp::min(
630 buffer_range.end,
631 buffer_range.start + end_overshoot - excerpt.header_height as usize,
632 );
633 }
634
635 excerpt.buffer.chunks(buffer_start..buffer_end, theme)
636 });
637
638 MultiBufferChunks {
639 range,
640 cursor,
641 header_height,
642 excerpt_chunks,
643 theme,
644 }
645 }
646
647 pub fn offset_to_point(&self, offset: usize) -> Point {
648 let mut cursor = self.excerpts.cursor::<(usize, Point)>();
649 cursor.seek(&offset, Bias::Right, &());
650 if let Some(excerpt) = cursor.item() {
651 let (start_offset, start_point) = cursor.start();
652 let overshoot = offset - start_offset;
653 let header_height = excerpt.header_height as usize;
654 if overshoot < header_height {
655 *start_point
656 } else {
657 let excerpt_start_offset = excerpt.range.start.to_offset(&excerpt.buffer);
658 let excerpt_start_point = excerpt.range.start.to_point(&excerpt.buffer);
659 let buffer_point = excerpt
660 .buffer
661 .offset_to_point(excerpt_start_offset + (overshoot - header_height));
662 *start_point
663 + Point::new(header_height as u32, 0)
664 + (buffer_point - excerpt_start_point)
665 }
666 } else {
667 self.excerpts.summary().text.lines
668 }
669 }
670
671 pub fn point_to_offset(&self, point: Point) -> usize {
672 let mut cursor = self.excerpts.cursor::<(Point, usize)>();
673 cursor.seek(&point, Bias::Right, &());
674 if let Some(excerpt) = cursor.item() {
675 let (start_point, start_offset) = cursor.start();
676 let overshoot = point - start_point;
677 let header_height = Point::new(excerpt.header_height as u32, 0);
678 if overshoot < header_height {
679 *start_offset
680 } else {
681 let excerpt_start_offset = excerpt.range.start.to_offset(&excerpt.buffer);
682 let excerpt_start_point = excerpt.range.start.to_point(&excerpt.buffer);
683 let buffer_offset = excerpt
684 .buffer
685 .point_to_offset(excerpt_start_point + (overshoot - header_height));
686 *start_offset + excerpt.header_height as usize + buffer_offset
687 - excerpt_start_offset
688 }
689 } else {
690 self.excerpts.summary().text.bytes
691 }
692 }
693
694 pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
695 let mut cursor = self.excerpts.cursor::<(PointUtf16, usize)>();
696 cursor.seek(&point, Bias::Right, &());
697 if let Some(excerpt) = cursor.item() {
698 let (start_point, start_offset) = cursor.start();
699 let overshoot = point - start_point;
700 let header_height = PointUtf16::new(excerpt.header_height as u32, 0);
701 if overshoot < header_height {
702 *start_offset
703 } else {
704 let excerpt_start_offset = excerpt.range.start.to_offset(&excerpt.buffer);
705 let excerpt_start_point = excerpt
706 .buffer
707 .offset_to_point_utf16(excerpt.range.start.to_offset(&excerpt.buffer));
708 let buffer_offset = excerpt
709 .buffer
710 .point_utf16_to_offset(excerpt_start_point + (overshoot - header_height));
711 *start_offset
712 + excerpt.header_height as usize
713 + (buffer_offset - excerpt_start_offset)
714 }
715 } else {
716 self.excerpts.summary().text.bytes
717 }
718 }
719
720 pub fn indent_column_for_line(&self, row: u32) -> u32 {
721 todo!()
722 }
723
724 pub fn line_len(&self, row: u32) -> u32 {
725 let mut cursor = self.excerpts.cursor::<Point>();
726 cursor.seek(&Point::new(row, 0), Bias::Right, &());
727 if let Some(excerpt) = cursor.item() {
728 let overshoot = row - cursor.start().row;
729 let header_height = excerpt.header_height as u32;
730 if overshoot < header_height {
731 0
732 } else {
733 let excerpt_start = excerpt.range.start.to_point(&excerpt.buffer);
734 let excerpt_end = excerpt.range.end.to_point(&excerpt.buffer);
735 let buffer_row = excerpt_start.row + overshoot - header_height;
736 let mut len = excerpt.buffer.line_len(buffer_row);
737 if buffer_row == excerpt_end.row {
738 len = excerpt_end.column;
739 }
740 if buffer_row == excerpt_start.row {
741 len -= excerpt_start.column
742 }
743 len
744 }
745 } else {
746 0
747 }
748 }
749
750 pub fn max_point(&self) -> Point {
751 self.text_summary().lines
752 }
753
754 pub fn text_summary(&self) -> TextSummary {
755 self.excerpts.summary().text
756 }
757
758 pub fn text_summary_for_range<'a, D, O>(&'a self, range: Range<O>) -> D
759 where
760 D: TextDimension,
761 O: ToOffset,
762 {
763 let mut summary = D::default();
764 let mut range = range.start.to_offset(self)..range.end.to_offset(self);
765 let mut cursor = self.excerpts.cursor::<usize>();
766 cursor.seek(&range.start, Bias::Right, &());
767 if let Some(excerpt) = cursor.item() {
768 let start_after_header = cursor.start() + excerpt.header_height as usize;
769 if range.start < start_after_header {
770 let header_len = cmp::min(range.end, start_after_header) - range.start;
771 summary.add_assign(&D::from_text_summary(&TextSummary {
772 bytes: header_len,
773 lines: Point::new(header_len as u32, 0),
774 lines_utf16: PointUtf16::new(header_len as u32, 0),
775 first_line_chars: 0,
776 last_line_chars: 0,
777 longest_row: 0,
778 longest_row_chars: 0,
779 }));
780 range.start = start_after_header;
781 range.end = cmp::max(range.start, range.end);
782 }
783
784 let end_before_newline = cursor.end(&()) - 1;
785 let excerpt_start = excerpt.range.start.to_offset(&excerpt.buffer);
786 let start_in_excerpt = excerpt_start + (range.start - start_after_header);
787 let end_in_excerpt =
788 excerpt_start + (cmp::min(end_before_newline, range.end) - start_after_header);
789 summary.add_assign(
790 &excerpt
791 .buffer
792 .text_summary_for_range(start_in_excerpt..end_in_excerpt),
793 );
794
795 if range.end > end_before_newline {
796 summary.add_assign(&D::from_text_summary(&TextSummary {
797 bytes: 1,
798 lines: Point::new(1 as u32, 0),
799 lines_utf16: PointUtf16::new(1 as u32, 0),
800 first_line_chars: 0,
801 last_line_chars: 0,
802 longest_row: 0,
803 longest_row_chars: 0,
804 }));
805 }
806
807 cursor.next(&());
808 }
809
810 if range.end > *cursor.start() {
811 summary.add_assign(&D::from_text_summary(&cursor.summary::<_, TextSummary>(
812 &range.end,
813 Bias::Right,
814 &(),
815 )));
816 if let Some(excerpt) = cursor.item() {
817 let start_after_header = cursor.start() + excerpt.header_height as usize;
818 let header_len =
819 cmp::min(range.end - cursor.start(), excerpt.header_height as usize);
820 summary.add_assign(&D::from_text_summary(&TextSummary {
821 bytes: header_len,
822 lines: Point::new(header_len as u32, 0),
823 lines_utf16: PointUtf16::new(header_len as u32, 0),
824 first_line_chars: 0,
825 last_line_chars: 0,
826 longest_row: 0,
827 longest_row_chars: 0,
828 }));
829 range.end = cmp::max(start_after_header, range.end);
830
831 let excerpt_start = excerpt.range.start.to_offset(&excerpt.buffer);
832 let end_in_excerpt = excerpt_start + (range.end - start_after_header);
833 summary.add_assign(
834 &excerpt
835 .buffer
836 .text_summary_for_range(excerpt_start..end_in_excerpt),
837 );
838 cursor.next(&());
839 }
840 }
841
842 summary
843 }
844
845 pub fn anchor_before<T: ToOffset>(&self, position: T) -> Anchor {
846 self.anchor_at(position, Bias::Left)
847 }
848
849 pub fn anchor_after<T: ToOffset>(&self, position: T) -> Anchor {
850 self.anchor_at(position, Bias::Right)
851 }
852
853 pub fn anchor_at<T: ToOffset>(&self, position: T, bias: Bias) -> Anchor {
854 todo!()
855 }
856
857 pub fn parse_count(&self) -> usize {
858 todo!()
859 }
860
861 pub fn enclosing_bracket_ranges<T: ToOffset>(
862 &self,
863 range: Range<T>,
864 ) -> Option<(Range<usize>, Range<usize>)> {
865 todo!()
866 }
867
868 pub fn diagnostics_update_count(&self) -> usize {
869 todo!()
870 }
871
872 pub fn language<'a>(&self) -> Option<&'a Arc<Language>> {
873 todo!()
874 }
875
876 pub fn diagnostic_group<'a, O>(
877 &'a self,
878 group_id: usize,
879 ) -> impl Iterator<Item = (Range<O>, &Diagnostic)> + 'a
880 where
881 O: 'a,
882 {
883 todo!();
884 None.into_iter()
885 }
886
887 pub fn diagnostics_in_range<'a, T, O>(
888 &'a self,
889 search_range: Range<T>,
890 ) -> impl Iterator<Item = (Range<O>, &Diagnostic)> + 'a
891 where
892 T: 'a + ToOffset,
893 O: 'a,
894 {
895 todo!();
896 None.into_iter()
897 }
898
899 pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
900 todo!()
901 }
902
903 fn buffer_snapshot_for_excerpt<'a>(
904 &'a self,
905 excerpt_id: &ExcerptId,
906 ) -> Option<&'a BufferSnapshot> {
907 let mut cursor = self.excerpts.cursor::<ExcerptId>();
908 cursor.seek(excerpt_id, Bias::Left, &());
909 if let Some(excerpt) = cursor.item() {
910 if cursor.start() == excerpt_id {
911 return Some(&excerpt.buffer);
912 }
913 }
914 None
915 }
916}
917
918impl Excerpt {
919 fn new(
920 id: ExcerptId,
921 buffer: buffer::BufferSnapshot,
922 range: Range<text::Anchor>,
923 header_height: u8,
924 ) -> Self {
925 let mut text_summary =
926 buffer.text_summary_for_range::<TextSummary, _>(range.to_offset(&buffer));
927 if header_height > 0 {
928 text_summary.first_line_chars = 0;
929 text_summary.lines.row += header_height as u32;
930 text_summary.lines_utf16.row += header_height as u32;
931 text_summary.bytes += header_height as usize;
932 text_summary.longest_row += header_height as u32;
933 }
934 text_summary.last_line_chars = 0;
935 text_summary.lines.row += 1;
936 text_summary.lines.column = 0;
937 text_summary.lines_utf16.row += 1;
938 text_summary.lines_utf16.column = 0;
939 text_summary.bytes += 1;
940
941 Excerpt {
942 id,
943 buffer,
944 range,
945 text_summary,
946 header_height,
947 }
948 }
949
950 fn header_summary(&self) -> TextSummary {
951 TextSummary {
952 bytes: self.header_height as usize,
953 lines: Point::new(self.header_height as u32, 0),
954 lines_utf16: PointUtf16::new(self.header_height as u32, 0),
955 first_line_chars: 0,
956 last_line_chars: 0,
957 longest_row: 0,
958 longest_row_chars: 0,
959 }
960 }
961}
962
963impl sum_tree::Item for Excerpt {
964 type Summary = ExcerptSummary;
965
966 fn summary(&self) -> Self::Summary {
967 ExcerptSummary {
968 excerpt_id: self.id.clone(),
969 text: self.text_summary.clone(),
970 }
971 }
972}
973
974impl sum_tree::Summary for ExcerptSummary {
975 type Context = ();
976
977 fn add_summary(&mut self, summary: &Self, _: &()) {
978 debug_assert!(summary.excerpt_id > self.excerpt_id);
979 self.excerpt_id = summary.excerpt_id.clone();
980 self.text.add_summary(&summary.text, &());
981 }
982}
983
984impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for TextSummary {
985 fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
986 *self += &summary.text;
987 }
988}
989
990impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for usize {
991 fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
992 *self += summary.text.bytes;
993 }
994}
995
996impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for usize {
997 fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering {
998 Ord::cmp(self, &cursor_location.text.bytes)
999 }
1000}
1001
1002impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Location {
1003 fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering {
1004 Ord::cmp(self, &cursor_location.excerpt_id)
1005 }
1006}
1007
1008impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Point {
1009 fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
1010 *self += summary.text.lines;
1011 }
1012}
1013
1014impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for PointUtf16 {
1015 fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
1016 *self += summary.text.lines_utf16
1017 }
1018}
1019
1020impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Location {
1021 fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
1022 debug_assert!(summary.excerpt_id > *self);
1023 *self = summary.excerpt_id.clone();
1024 }
1025}
1026
1027impl<'a> MultiBufferChunks<'a> {
1028 pub fn offset(&self) -> usize {
1029 todo!()
1030 }
1031
1032 pub fn seek(&mut self, offset: usize) {
1033 todo!()
1034 }
1035}
1036
1037impl<'a> Iterator for MultiBufferChunks<'a> {
1038 type Item = Chunk<'a>;
1039
1040 fn next(&mut self) -> Option<Self::Item> {
1041 loop {
1042 if self.header_height > 0 {
1043 let chunk = Chunk {
1044 text: unsafe {
1045 std::str::from_utf8_unchecked(&NEWLINES[..self.header_height as usize])
1046 },
1047 ..Default::default()
1048 };
1049 self.header_height = 0;
1050 return Some(chunk);
1051 }
1052
1053 if let Some(excerpt_chunks) = self.excerpt_chunks.as_mut() {
1054 if let Some(chunk) = excerpt_chunks.next() {
1055 return Some(chunk);
1056 }
1057 self.excerpt_chunks.take();
1058 if self.cursor.end(&()) <= self.range.end {
1059 return Some(Chunk {
1060 text: "\n",
1061 ..Default::default()
1062 });
1063 }
1064 }
1065
1066 self.cursor.next(&());
1067 if *self.cursor.start() >= self.range.end {
1068 return None;
1069 }
1070
1071 let excerpt = self.cursor.item()?;
1072 let buffer_range = excerpt.range.to_offset(&excerpt.buffer);
1073
1074 let buffer_end = cmp::min(
1075 buffer_range.end,
1076 buffer_range.start + self.range.end
1077 - excerpt.header_height as usize
1078 - self.cursor.start(),
1079 );
1080
1081 self.header_height = excerpt.header_height;
1082 self.excerpt_chunks = Some(
1083 excerpt
1084 .buffer
1085 .chunks(buffer_range.start..buffer_end, self.theme),
1086 );
1087 }
1088 }
1089}
1090
1091impl<'a> Iterator for MultiBufferBytes<'a> {
1092 type Item = &'a [u8];
1093
1094 fn next(&mut self) -> Option<Self::Item> {
1095 todo!()
1096 }
1097}
1098
1099impl<'a> io::Read for MultiBufferBytes<'a> {
1100 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
1101 todo!()
1102 }
1103}
1104
1105impl ToOffset for Point {
1106 fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
1107 snapshot.point_to_offset(*self)
1108 }
1109}
1110
1111impl ToOffset for PointUtf16 {
1112 fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
1113 snapshot.point_utf16_to_offset(*self)
1114 }
1115}
1116
1117impl ToOffset for usize {
1118 fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
1119 assert!(*self <= snapshot.len(), "offset is out of range");
1120 *self
1121 }
1122}
1123
1124impl ToPoint for usize {
1125 fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
1126 snapshot.offset_to_point(*self)
1127 }
1128}
1129
1130impl ToPoint for Point {
1131 fn to_point<'a>(&self, _: &MultiBufferSnapshot) -> Point {
1132 *self
1133 }
1134}
1135
1136#[cfg(test)]
1137mod tests {
1138 use super::*;
1139 use crate::buffer::Buffer;
1140 use gpui::MutableAppContext;
1141 use rand::prelude::*;
1142 use std::env;
1143 use text::{Point, RandomCharIter};
1144 use util::test::sample_text;
1145
1146 #[gpui::test]
1147 fn test_excerpt_buffer(cx: &mut MutableAppContext) {
1148 let buffer_1 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'a'), cx));
1149 let buffer_2 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'g'), cx));
1150
1151 let list = cx.add_model(|_| MultiBuffer::new());
1152
1153 let subscription = list.update(cx, |list, cx| {
1154 let subscription = list.subscribe();
1155 list.push(
1156 ExcerptProperties {
1157 buffer: &buffer_1,
1158 range: Point::new(1, 2)..Point::new(2, 5),
1159 header_height: 2,
1160 },
1161 cx,
1162 );
1163 assert_eq!(
1164 subscription.consume().into_inner(),
1165 [Edit {
1166 old: 0..0,
1167 new: 0..13
1168 }]
1169 );
1170
1171 list.push(
1172 ExcerptProperties {
1173 buffer: &buffer_1,
1174 range: Point::new(3, 3)..Point::new(4, 4),
1175 header_height: 1,
1176 },
1177 cx,
1178 );
1179 list.push(
1180 ExcerptProperties {
1181 buffer: &buffer_2,
1182 range: Point::new(3, 1)..Point::new(3, 3),
1183 header_height: 3,
1184 },
1185 cx,
1186 );
1187 assert_eq!(
1188 subscription.consume().into_inner(),
1189 [Edit {
1190 old: 13..13,
1191 new: 13..29
1192 }]
1193 );
1194
1195 subscription
1196 });
1197
1198 assert_eq!(
1199 list.read(cx).snapshot(cx).text(),
1200 concat!(
1201 "\n", // Preserve newlines
1202 "\n", //
1203 "bbbb\n", //
1204 "ccccc\n", //
1205 "\n", //
1206 "ddd\n", //
1207 "eeee\n", //
1208 "\n", //
1209 "\n", //
1210 "\n", //
1211 "jj\n" //
1212 )
1213 );
1214
1215 buffer_1.update(cx, |buffer, cx| {
1216 buffer.edit(
1217 [
1218 Point::new(0, 0)..Point::new(0, 0),
1219 Point::new(2, 1)..Point::new(2, 3),
1220 ],
1221 "\n",
1222 cx,
1223 );
1224 });
1225
1226 assert_eq!(
1227 list.read(cx).snapshot(cx).text(),
1228 concat!(
1229 "\n", // Preserve newlines
1230 "\n", //
1231 "bbbb\n", //
1232 "c\n", //
1233 "cc\n", //
1234 "\n", //
1235 "ddd\n", //
1236 "eeee\n", //
1237 "\n", //
1238 "\n", //
1239 "\n", //
1240 "jj\n" //
1241 )
1242 );
1243
1244 assert_eq!(
1245 subscription.consume().into_inner(),
1246 [Edit {
1247 old: 8..10,
1248 new: 8..9
1249 }]
1250 );
1251 }
1252
1253 #[gpui::test(iterations = 100)]
1254 fn test_random_excerpts(cx: &mut MutableAppContext, mut rng: StdRng) {
1255 let operations = env::var("OPERATIONS")
1256 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1257 .unwrap_or(10);
1258
1259 let mut buffers: Vec<ModelHandle<Buffer>> = Vec::new();
1260 let list = cx.add_model(|_| MultiBuffer::new());
1261 let mut excerpt_ids = Vec::new();
1262 let mut expected_excerpts = Vec::new();
1263 let mut old_versions = Vec::new();
1264
1265 for _ in 0..operations {
1266 match rng.gen_range(0..100) {
1267 0..=19 if !buffers.is_empty() => {
1268 let buffer = buffers.choose(&mut rng).unwrap();
1269 buffer.update(cx, |buf, cx| buf.randomly_edit(&mut rng, 1, cx));
1270 }
1271 _ => {
1272 let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
1273 let base_text = RandomCharIter::new(&mut rng).take(10).collect::<String>();
1274 buffers.push(cx.add_model(|cx| Buffer::new(0, base_text, cx)));
1275 buffers.last().unwrap()
1276 } else {
1277 buffers.choose(&mut rng).unwrap()
1278 };
1279
1280 let buffer = buffer_handle.read(cx);
1281 let end_ix = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Right);
1282 let start_ix = buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
1283 let header_height = rng.gen_range(0..=5);
1284 let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
1285 log::info!(
1286 "Pushing excerpt wih header {}, buffer {}: {:?}[{:?}] = {:?}",
1287 header_height,
1288 buffer_handle.id(),
1289 buffer.text(),
1290 start_ix..end_ix,
1291 &buffer.text()[start_ix..end_ix]
1292 );
1293
1294 let excerpt_id = list.update(cx, |list, cx| {
1295 list.push(
1296 ExcerptProperties {
1297 buffer: &buffer_handle,
1298 range: start_ix..end_ix,
1299 header_height,
1300 },
1301 cx,
1302 )
1303 });
1304 excerpt_ids.push(excerpt_id);
1305 expected_excerpts.push((buffer_handle.clone(), anchor_range, header_height));
1306 }
1307 }
1308
1309 if rng.gen_bool(0.3) {
1310 list.update(cx, |list, cx| {
1311 old_versions.push((list.snapshot(cx), list.subscribe()));
1312 })
1313 }
1314
1315 let snapshot = list.read(cx).snapshot(cx);
1316
1317 let mut excerpt_starts = Vec::new();
1318 let mut expected_text = String::new();
1319 for (buffer, range, header_height) in &expected_excerpts {
1320 let buffer = buffer.read(cx);
1321 let buffer_range = range.to_offset(buffer);
1322
1323 for _ in 0..*header_height {
1324 expected_text.push('\n');
1325 }
1326
1327 excerpt_starts.push(TextSummary::from(expected_text.as_str()));
1328 expected_text.extend(buffer.text_for_range(buffer_range.clone()));
1329 expected_text.push('\n');
1330 }
1331
1332 assert_eq!(snapshot.text(), expected_text);
1333
1334 let mut excerpt_starts = excerpt_starts.into_iter();
1335 for (buffer, range, _) in &expected_excerpts {
1336 let buffer_id = buffer.id();
1337 let buffer = buffer.read(cx);
1338 let buffer_range = range.to_offset(buffer);
1339 let buffer_start_point = buffer.offset_to_point(buffer_range.start);
1340 let buffer_start_point_utf16 =
1341 buffer.text_summary_for_range::<PointUtf16, _>(0..buffer_range.start);
1342
1343 let excerpt_start = excerpt_starts.next().unwrap();
1344 let mut offset = excerpt_start.bytes;
1345 let mut buffer_offset = buffer_range.start;
1346 let mut point = excerpt_start.lines;
1347 let mut buffer_point = buffer_start_point;
1348 let mut point_utf16 = excerpt_start.lines_utf16;
1349 let mut buffer_point_utf16 = buffer_start_point_utf16;
1350 for byte in buffer.bytes_in_range(buffer_range.clone()).flatten() {
1351 let left_offset = snapshot.clip_offset(offset, Bias::Left);
1352 let right_offset = snapshot.clip_offset(offset, Bias::Right);
1353 let buffer_left_offset = buffer.clip_offset(buffer_offset, Bias::Left);
1354 let buffer_right_offset = buffer.clip_offset(buffer_offset, Bias::Right);
1355 assert_eq!(
1356 left_offset,
1357 excerpt_start.bytes + (buffer_left_offset - buffer_range.start),
1358 "clip_offset({:?}, Left). buffer: {:?}, buffer offset: {:?}",
1359 offset,
1360 buffer_id,
1361 buffer_offset,
1362 );
1363 assert_eq!(
1364 right_offset,
1365 excerpt_start.bytes + (buffer_right_offset - buffer_range.start),
1366 "clip_offset({:?}, Right). buffer: {:?}, buffer offset: {:?}",
1367 offset,
1368 buffer_id,
1369 buffer_offset,
1370 );
1371
1372 let left_point = snapshot.clip_point(point, Bias::Left);
1373 let right_point = snapshot.clip_point(point, Bias::Right);
1374 let buffer_left_point = buffer.clip_point(buffer_point, Bias::Left);
1375 let buffer_right_point = buffer.clip_point(buffer_point, Bias::Right);
1376 assert_eq!(
1377 left_point,
1378 excerpt_start.lines + (buffer_left_point - buffer_start_point),
1379 "clip_point({:?}, Left). buffer: {:?}, buffer point: {:?}",
1380 point,
1381 buffer_id,
1382 buffer_point,
1383 );
1384 assert_eq!(
1385 right_point,
1386 excerpt_start.lines + (buffer_right_point - buffer_start_point),
1387 "clip_point({:?}, Right). buffer: {:?}, buffer point: {:?}",
1388 point,
1389 buffer_id,
1390 buffer_point,
1391 );
1392
1393 let left_point_utf16 = snapshot.clip_point_utf16(point_utf16, Bias::Left);
1394 let right_point_utf16 = snapshot.clip_point_utf16(point_utf16, Bias::Right);
1395 let buffer_left_point_utf16 =
1396 buffer.clip_point_utf16(buffer_point_utf16, Bias::Left);
1397 let buffer_right_point_utf16 =
1398 buffer.clip_point_utf16(buffer_point_utf16, Bias::Right);
1399 assert_eq!(
1400 left_point_utf16,
1401 excerpt_start.lines_utf16
1402 + (buffer_left_point_utf16 - buffer_start_point_utf16),
1403 "clip_point_utf16({:?}, Left). buffer: {:?}, buffer point_utf16: {:?}",
1404 point_utf16,
1405 buffer_id,
1406 buffer_point_utf16,
1407 );
1408 assert_eq!(
1409 right_point_utf16,
1410 excerpt_start.lines_utf16
1411 + (buffer_right_point_utf16 - buffer_start_point_utf16),
1412 "clip_point_utf16({:?}, Right). buffer: {:?}, buffer point_utf16: {:?}",
1413 point_utf16,
1414 buffer_id,
1415 buffer_point_utf16,
1416 );
1417
1418 assert_eq!(
1419 snapshot.point_to_offset(left_point),
1420 left_offset,
1421 "point_to_offset({:?})",
1422 left_point,
1423 );
1424 assert_eq!(
1425 snapshot.offset_to_point(left_offset),
1426 left_point,
1427 "offset_to_point({:?})",
1428 left_offset,
1429 );
1430
1431 offset += 1;
1432 buffer_offset += 1;
1433 if *byte == b'\n' {
1434 point += Point::new(1, 0);
1435 point_utf16 += PointUtf16::new(1, 0);
1436 buffer_point += Point::new(1, 0);
1437 buffer_point_utf16 += PointUtf16::new(1, 0);
1438 } else {
1439 point += Point::new(0, 1);
1440 point_utf16 += PointUtf16::new(0, 1);
1441 buffer_point += Point::new(0, 1);
1442 buffer_point_utf16 += PointUtf16::new(0, 1);
1443 }
1444 }
1445 }
1446
1447 for (row, line) in expected_text.split('\n').enumerate() {
1448 assert_eq!(
1449 snapshot.line_len(row as u32),
1450 line.len() as u32,
1451 "line_len({}).",
1452 row
1453 );
1454 }
1455
1456 for _ in 0..10 {
1457 let end_ix = snapshot.clip_offset(rng.gen_range(0..=snapshot.len()), Bias::Right);
1458 let start_ix = snapshot.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
1459
1460 assert_eq!(
1461 snapshot
1462 .text_for_range(start_ix..end_ix)
1463 .collect::<String>(),
1464 &expected_text[start_ix..end_ix],
1465 "incorrect text for range {:?}",
1466 start_ix..end_ix
1467 );
1468
1469 let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]);
1470 assert_eq!(
1471 snapshot.text_summary_for_range::<TextSummary, _>(start_ix..end_ix),
1472 expected_summary,
1473 "incorrect summary for range {:?}",
1474 start_ix..end_ix
1475 );
1476 }
1477 }
1478
1479 let snapshot = list.read(cx).snapshot(cx);
1480 for (old_snapshot, subscription) in old_versions {
1481 let edits = subscription.consume().into_inner();
1482
1483 log::info!(
1484 "applying edits since old text: {:?}: {:?}",
1485 old_snapshot.text(),
1486 edits,
1487 );
1488
1489 let mut text = old_snapshot.text();
1490 for edit in edits {
1491 let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
1492 text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
1493 }
1494 assert_eq!(text.to_string(), snapshot.text());
1495 }
1496 }
1497}