excerpt_list.rs

  1use crate::{buffer, Buffer, Chunk};
  2use collections::HashMap;
  3use gpui::{AppContext, Entity, ModelContext, ModelHandle};
  4use parking_lot::Mutex;
  5use smallvec::{smallvec, SmallVec};
  6use std::{cmp, iter, ops::Range};
  7use sum_tree::{Bias, Cursor, SumTree};
  8use text::{
  9    subscription::{Subscription, Topic},
 10    Anchor, AnchorRangeExt, Edit, Point, TextSummary,
 11};
 12use theme::SyntaxTheme;
 13
 14const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize];
 15
 16pub trait ToOffset {
 17    fn to_offset<'a>(&self, content: &Snapshot) -> usize;
 18}
 19
 20pub type ExcerptId = Location;
 21
 22#[derive(Default)]
 23pub struct ExcerptList {
 24    snapshot: Mutex<Snapshot>,
 25    buffers: HashMap<usize, BufferState>,
 26    subscriptions: Topic,
 27}
 28
 29#[derive(Debug)]
 30struct BufferState {
 31    buffer: ModelHandle<Buffer>,
 32    last_sync: clock::Global,
 33    excerpts: Vec<ExcerptId>,
 34}
 35
 36#[derive(Clone, Default)]
 37pub struct Snapshot {
 38    excerpts: SumTree<Excerpt>,
 39}
 40
 41pub struct ExcerptProperties<'a, T> {
 42    buffer: &'a ModelHandle<Buffer>,
 43    range: Range<T>,
 44    header_height: u8,
 45}
 46
 47#[derive(Clone)]
 48struct Excerpt {
 49    id: ExcerptId,
 50    buffer: buffer::BufferSnapshot,
 51    range: Range<Anchor>,
 52    text_summary: TextSummary,
 53    header_height: u8,
 54}
 55
 56#[derive(Clone, Debug, Default)]
 57struct EntrySummary {
 58    excerpt_id: ExcerptId,
 59    text: TextSummary,
 60}
 61
 62#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
 63pub struct Location(SmallVec<[u8; 4]>);
 64
 65pub struct Chunks<'a> {
 66    range: Range<usize>,
 67    cursor: Cursor<'a, Excerpt, usize>,
 68    header_height: u8,
 69    entry_chunks: Option<buffer::BufferChunks<'a>>,
 70    theme: Option<&'a SyntaxTheme>,
 71}
 72
 73impl ExcerptList {
 74    pub fn new() -> Self {
 75        Self::default()
 76    }
 77
 78    pub fn snapshot(&self, cx: &AppContext) -> Snapshot {
 79        self.sync(cx);
 80        self.snapshot.lock().clone()
 81    }
 82
 83    pub fn subscribe(&mut self) -> Subscription {
 84        self.subscriptions.subscribe()
 85    }
 86
 87    pub fn push<O>(&mut self, props: ExcerptProperties<O>, cx: &mut ModelContext<Self>) -> ExcerptId
 88    where
 89        O: text::ToOffset,
 90    {
 91        self.sync(cx);
 92
 93        let buffer = props.buffer.read(cx);
 94        let range = buffer.anchor_before(props.range.start)..buffer.anchor_after(props.range.end);
 95        let mut snapshot = self.snapshot.lock();
 96        let prev_id = snapshot.excerpts.last().map(|e| &e.id);
 97        let id = ExcerptId::between(prev_id.unwrap_or(&ExcerptId::min()), &ExcerptId::max());
 98
 99        let edit_start = snapshot.excerpts.summary().text.bytes;
100        let excerpt = Excerpt::new(id.clone(), buffer.snapshot(), range, props.header_height);
101        let edit = Edit {
102            old: edit_start..edit_start,
103            new: edit_start..edit_start + excerpt.text_summary.bytes,
104        };
105        snapshot.excerpts.push(excerpt, &());
106        self.buffers
107            .entry(props.buffer.id())
108            .or_insert_with(|| BufferState {
109                buffer: props.buffer.clone(),
110                last_sync: buffer.version(),
111                excerpts: Default::default(),
112            })
113            .excerpts
114            .push(id.clone());
115
116        self.subscriptions.publish_mut([edit]);
117
118        id
119    }
120
121    fn sync(&self, cx: &AppContext) {
122        let mut snapshot = self.snapshot.lock();
123        let mut excerpts_to_edit = Vec::new();
124        for buffer_state in self.buffers.values() {
125            if buffer_state
126                .buffer
127                .read(cx)
128                .version()
129                .gt(&buffer_state.last_sync)
130            {
131                excerpts_to_edit.extend(
132                    buffer_state
133                        .excerpts
134                        .iter()
135                        .map(|excerpt_id| (excerpt_id, buffer_state)),
136                );
137            }
138        }
139        excerpts_to_edit.sort_unstable_by_key(|(excerpt_id, _)| *excerpt_id);
140
141        let mut edits = Vec::new();
142        let mut new_excerpts = SumTree::new();
143        let mut cursor = snapshot.excerpts.cursor::<(ExcerptId, usize)>();
144
145        for (id, buffer_state) in excerpts_to_edit {
146            new_excerpts.push_tree(cursor.slice(id, Bias::Left, &()), &());
147            let old_excerpt = cursor.item().unwrap();
148            let buffer = buffer_state.buffer.read(cx);
149
150            edits.extend(
151                buffer
152                    .edits_since_in_range::<usize>(
153                        old_excerpt.buffer.version(),
154                        old_excerpt.range.clone(),
155                    )
156                    .map(|mut edit| {
157                        let excerpt_old_start =
158                            cursor.start().1 + old_excerpt.header_height as usize;
159                        let excerpt_new_start =
160                            new_excerpts.summary().text.bytes + old_excerpt.header_height as usize;
161                        edit.old.start += excerpt_old_start;
162                        edit.old.end += excerpt_old_start;
163                        edit.new.start += excerpt_new_start;
164                        edit.new.end += excerpt_new_start;
165                        edit
166                    }),
167            );
168
169            new_excerpts.push(
170                Excerpt::new(
171                    id.clone(),
172                    buffer.snapshot(),
173                    old_excerpt.range.clone(),
174                    old_excerpt.header_height,
175                ),
176                &(),
177            );
178
179            cursor.next(&());
180        }
181        new_excerpts.push_tree(cursor.suffix(&()), &());
182
183        drop(cursor);
184        snapshot.excerpts = new_excerpts;
185
186        self.subscriptions.publish(edits);
187    }
188}
189
190impl Entity for ExcerptList {
191    type Event = ();
192}
193
194impl Snapshot {
195    pub fn text(&self) -> String {
196        self.chunks(0..self.len(), None)
197            .map(|chunk| chunk.text)
198            .collect()
199    }
200
201    pub fn text_for_range<'a, T: ToOffset>(
202        &'a self,
203        range: Range<T>,
204    ) -> impl Iterator<Item = &'a str> {
205        self.chunks(range, None).map(|chunk| chunk.text)
206    }
207
208    pub fn len(&self) -> usize {
209        self.excerpts.summary().text.bytes
210    }
211
212    pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize {
213        let mut cursor = self.excerpts.cursor::<usize>();
214        cursor.seek(&offset, Bias::Right, &());
215        if let Some(excerpt) = cursor.item() {
216            let overshoot = offset - cursor.start();
217            let header_height = excerpt.header_height as usize;
218            if overshoot < header_height {
219                *cursor.start()
220            } else {
221                let excerpt_start =
222                    text::ToOffset::to_offset(&excerpt.range.start, &excerpt.buffer);
223                let buffer_offset = excerpt.buffer.clip_offset(
224                    excerpt_start + (offset - header_height - cursor.start()),
225                    bias,
226                );
227                let offset_in_excerpt = if buffer_offset > excerpt_start {
228                    buffer_offset - excerpt_start
229                } else {
230                    0
231                };
232                cursor.start() + header_height + offset_in_excerpt
233            }
234        } else {
235            self.excerpts.summary().text.bytes
236        }
237    }
238
239    pub fn to_point(&self, offset: usize) -> Point {
240        let mut cursor = self.excerpts.cursor::<(usize, Point)>();
241        cursor.seek(&offset, Bias::Right, &());
242        if let Some(excerpt) = cursor.item() {
243            let overshoot = offset - cursor.start().0;
244            let header_height = excerpt.header_height as usize;
245            if overshoot < header_height {
246                cursor.start().1
247            } else {
248                let excerpt_start_offset =
249                    text::ToOffset::to_offset(&excerpt.range.start, &excerpt.buffer);
250                let excerpt_start_point =
251                    text::ToPoint::to_point(&excerpt.range.start, &excerpt.buffer);
252                let buffer_point = excerpt
253                    .buffer
254                    .to_point(excerpt_start_offset + (offset - header_height - cursor.start().0));
255                cursor.start().1
256                    + Point::new(header_height as u32, 0)
257                    + (buffer_point - excerpt_start_point)
258            }
259        } else {
260            self.excerpts.summary().text.lines
261        }
262    }
263
264    pub fn to_offset(&self, point: Point) -> usize {
265        let mut cursor = self.excerpts.cursor::<(Point, usize)>();
266        cursor.seek(&point, Bias::Right, &());
267        if let Some(excerpt) = cursor.item() {
268            let overshoot = point - cursor.start().0;
269            let header_height = Point::new(excerpt.header_height as u32, 0);
270            if overshoot < header_height {
271                cursor.start().1
272            } else {
273                let excerpt_start_offset =
274                    text::ToOffset::to_offset(&excerpt.range.start, &excerpt.buffer);
275                let excerpt_start_point =
276                    text::ToPoint::to_point(&excerpt.range.start, &excerpt.buffer);
277                let buffer_offset = excerpt
278                    .buffer
279                    .to_offset(excerpt_start_point + (point - header_height - cursor.start().0));
280                cursor.start().1
281                    + excerpt.header_height as usize
282                    + (buffer_offset - excerpt_start_offset)
283            }
284        } else {
285            self.excerpts.summary().text.bytes
286        }
287    }
288
289    pub fn chunks<'a, T: ToOffset>(
290        &'a self,
291        range: Range<T>,
292        theme: Option<&'a SyntaxTheme>,
293    ) -> Chunks<'a> {
294        let range = range.start.to_offset(self)..range.end.to_offset(self);
295        let mut cursor = self.excerpts.cursor::<usize>();
296        cursor.seek(&range.start, Bias::Right, &());
297
298        let mut header_height: u8 = 0;
299        let entry_chunks = cursor.item().map(|excerpt| {
300            let buffer_range = excerpt.range.to_offset(&excerpt.buffer);
301            header_height = excerpt.header_height;
302
303            let buffer_start;
304            let start_overshoot = range.start - cursor.start();
305            if start_overshoot < excerpt.header_height as usize {
306                header_height -= start_overshoot as u8;
307                buffer_start = buffer_range.start;
308            } else {
309                buffer_start =
310                    buffer_range.start + start_overshoot - excerpt.header_height as usize;
311                header_height = 0;
312            }
313
314            let buffer_end;
315            let end_overshoot = range.end - cursor.start();
316            if end_overshoot < excerpt.header_height as usize {
317                header_height -= excerpt.header_height - end_overshoot as u8;
318                buffer_end = buffer_start;
319            } else {
320                buffer_end = cmp::min(
321                    buffer_range.end,
322                    buffer_range.start + end_overshoot - excerpt.header_height as usize,
323                );
324            }
325
326            excerpt.buffer.chunks(buffer_start..buffer_end, theme)
327        });
328
329        Chunks {
330            range,
331            cursor,
332            header_height,
333            entry_chunks,
334            theme,
335        }
336    }
337}
338
339impl Excerpt {
340    fn new(
341        id: ExcerptId,
342        buffer: buffer::BufferSnapshot,
343        range: Range<Anchor>,
344        header_height: u8,
345    ) -> Self {
346        let mut text_summary = buffer.text_summary_for_range::<TextSummary, _>(range.clone());
347        if header_height > 0 {
348            text_summary.first_line_chars = 0;
349            text_summary.lines.row += header_height as u32;
350            text_summary.lines_utf16.row += header_height as u32;
351            text_summary.bytes += header_height as usize;
352            text_summary.longest_row += header_height as u32;
353        }
354        text_summary.last_line_chars = 0;
355        text_summary.lines.row += 1;
356        text_summary.lines.column = 0;
357        text_summary.lines_utf16.row += 1;
358        text_summary.lines_utf16.column = 0;
359        text_summary.bytes += 1;
360
361        Excerpt {
362            id,
363            buffer,
364            range,
365            text_summary,
366            header_height,
367        }
368    }
369}
370
371impl sum_tree::Item for Excerpt {
372    type Summary = EntrySummary;
373
374    fn summary(&self) -> Self::Summary {
375        EntrySummary {
376            excerpt_id: self.id.clone(),
377            text: self.text_summary.clone(),
378        }
379    }
380}
381
382impl sum_tree::Summary for EntrySummary {
383    type Context = ();
384
385    fn add_summary(&mut self, summary: &Self, _: &()) {
386        debug_assert!(summary.excerpt_id > self.excerpt_id);
387        self.excerpt_id = summary.excerpt_id.clone();
388        self.text.add_summary(&summary.text, &());
389    }
390}
391
392impl<'a> sum_tree::Dimension<'a, EntrySummary> for usize {
393    fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
394        *self += summary.text.bytes;
395    }
396}
397
398impl<'a> sum_tree::Dimension<'a, EntrySummary> for Point {
399    fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
400        *self += summary.text.lines;
401    }
402}
403
404impl<'a> sum_tree::Dimension<'a, EntrySummary> for Location {
405    fn add_summary(&mut self, summary: &'a EntrySummary, _: &()) {
406        debug_assert!(summary.excerpt_id > *self);
407        *self = summary.excerpt_id.clone();
408    }
409}
410
411impl<'a> Iterator for Chunks<'a> {
412    type Item = Chunk<'a>;
413
414    fn next(&mut self) -> Option<Self::Item> {
415        loop {
416            if self.header_height > 0 {
417                let chunk = Chunk {
418                    text: unsafe {
419                        std::str::from_utf8_unchecked(&NEWLINES[..self.header_height as usize])
420                    },
421                    ..Default::default()
422                };
423                self.header_height = 0;
424                return Some(chunk);
425            }
426
427            if let Some(entry_chunks) = self.entry_chunks.as_mut() {
428                if let Some(chunk) = entry_chunks.next() {
429                    return Some(chunk);
430                }
431                self.entry_chunks.take();
432                if self.cursor.end(&()) <= self.range.end {
433                    return Some(Chunk {
434                        text: "\n",
435                        ..Default::default()
436                    });
437                }
438            }
439
440            self.cursor.next(&());
441            if *self.cursor.start() >= self.range.end {
442                return None;
443            }
444
445            let excerpt = self.cursor.item()?;
446            let buffer_range = excerpt.range.to_offset(&excerpt.buffer);
447
448            let buffer_end = cmp::min(
449                buffer_range.end,
450                buffer_range.start + self.range.end
451                    - excerpt.header_height as usize
452                    - self.cursor.start(),
453            );
454
455            self.header_height = excerpt.header_height;
456            self.entry_chunks = Some(
457                excerpt
458                    .buffer
459                    .chunks(buffer_range.start..buffer_end, self.theme),
460            );
461        }
462    }
463}
464
465impl ToOffset for usize {
466    fn to_offset<'a>(&self, _: &Snapshot) -> usize {
467        *self
468    }
469}
470
471impl ToOffset for Point {
472    fn to_offset<'a>(&self, snapshot: &Snapshot) -> usize {
473        snapshot.to_offset(*self)
474    }
475}
476
477impl Default for Location {
478    fn default() -> Self {
479        Self::min()
480    }
481}
482
483impl Location {
484    pub fn min() -> Self {
485        Self(smallvec![u8::MIN])
486    }
487
488    pub fn max() -> Self {
489        Self(smallvec![u8::MAX])
490    }
491
492    pub fn between(lhs: &Self, rhs: &Self) -> Self {
493        let lhs = lhs.0.iter().copied().chain(iter::repeat(u8::MIN));
494        let rhs = rhs.0.iter().copied().chain(iter::repeat(u8::MAX));
495        let mut location = SmallVec::new();
496        for (lhs, rhs) in lhs.zip(rhs) {
497            let mid = lhs + (rhs.saturating_sub(lhs)) / 2;
498            location.push(mid);
499            if mid > lhs {
500                break;
501            }
502        }
503        Self(location)
504    }
505}
506
507#[cfg(test)]
508mod tests {
509    use super::*;
510    use crate::Buffer;
511    use gpui::MutableAppContext;
512    use rand::prelude::*;
513    use std::{env, mem};
514    use text::{Point, RandomCharIter};
515    use util::test::sample_text;
516
517    #[gpui::test]
518    fn test_excerpt_buffer(cx: &mut MutableAppContext) {
519        let buffer_1 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'a'), cx));
520        let buffer_2 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'g'), cx));
521
522        let list = cx.add_model(|_| ExcerptList::new());
523
524        let subscription = list.update(cx, |list, cx| {
525            let subscription = list.subscribe();
526            list.push(
527                ExcerptProperties {
528                    buffer: &buffer_1,
529                    range: Point::new(1, 2)..Point::new(2, 5),
530                    header_height: 2,
531                },
532                cx,
533            );
534            assert_eq!(
535                subscription.consume().into_inner(),
536                [Edit {
537                    old: 0..0,
538                    new: 0..13
539                }]
540            );
541
542            list.push(
543                ExcerptProperties {
544                    buffer: &buffer_1,
545                    range: Point::new(3, 3)..Point::new(4, 4),
546                    header_height: 1,
547                },
548                cx,
549            );
550            list.push(
551                ExcerptProperties {
552                    buffer: &buffer_2,
553                    range: Point::new(3, 1)..Point::new(3, 3),
554                    header_height: 3,
555                },
556                cx,
557            );
558            assert_eq!(
559                subscription.consume().into_inner(),
560                [Edit {
561                    old: 13..13,
562                    new: 13..29
563                }]
564            );
565
566            subscription
567        });
568
569        assert_eq!(
570            list.read(cx).snapshot(cx).text(),
571            concat!(
572                "\n",      // Preserve newlines
573                "\n",      //
574                "bbbb\n",  //
575                "ccccc\n", //
576                "\n",      //
577                "ddd\n",   //
578                "eeee\n",  //
579                "\n",      //
580                "\n",      //
581                "\n",      //
582                "jj\n"     //
583            )
584        );
585
586        buffer_1.update(cx, |buffer, cx| {
587            buffer.edit(
588                [
589                    Point::new(0, 0)..Point::new(0, 0),
590                    Point::new(2, 1)..Point::new(2, 3),
591                ],
592                "\n",
593                cx,
594            );
595        });
596
597        assert_eq!(
598            list.read(cx).snapshot(cx).text(),
599            concat!(
600                "\n",     // Preserve newlines
601                "\n",     //
602                "bbbb\n", //
603                "c\n",    //
604                "cc\n",   //
605                "\n",     //
606                "ddd\n",  //
607                "eeee\n", //
608                "\n",     //
609                "\n",     //
610                "\n",     //
611                "jj\n"    //
612            )
613        );
614
615        assert_eq!(
616            subscription.consume().into_inner(),
617            [Edit {
618                old: 8..10,
619                new: 8..9
620            }]
621        );
622    }
623
624    #[gpui::test(iterations = 100)]
625    fn test_random_excerpts(cx: &mut MutableAppContext, mut rng: StdRng) {
626        let operations = env::var("OPERATIONS")
627            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
628            .unwrap_or(10);
629
630        let mut buffers: Vec<ModelHandle<Buffer>> = Vec::new();
631        let list = cx.add_model(|_| ExcerptList::new());
632        let mut excerpt_ids = Vec::new();
633        let mut expected_excerpts = Vec::new();
634        let mut old_versions = Vec::new();
635
636        for _ in 0..operations {
637            match rng.gen_range(0..100) {
638                0..=19 if !buffers.is_empty() => {
639                    let buffer = buffers.choose(&mut rng).unwrap();
640                    buffer.update(cx, |buf, cx| buf.randomly_edit(&mut rng, 1, cx));
641                }
642                _ => {
643                    let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
644                        let base_text = RandomCharIter::new(&mut rng).take(10).collect::<String>();
645                        buffers.push(cx.add_model(|cx| Buffer::new(0, base_text, cx)));
646                        buffers.last().unwrap()
647                    } else {
648                        buffers.choose(&mut rng).unwrap()
649                    };
650
651                    let buffer = buffer_handle.read(cx);
652                    let end_ix = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Right);
653                    let start_ix = buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
654                    let header_height = rng.gen_range(0..=5);
655                    let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix);
656                    log::info!(
657                        "Pushing excerpt wih header {}, buffer {}: {:?}[{:?}] = {:?}",
658                        header_height,
659                        buffer_handle.id(),
660                        buffer.text(),
661                        start_ix..end_ix,
662                        &buffer.text()[start_ix..end_ix]
663                    );
664
665                    let excerpt_id = list.update(cx, |list, cx| {
666                        list.push(
667                            ExcerptProperties {
668                                buffer: &buffer_handle,
669                                range: start_ix..end_ix,
670                                header_height,
671                            },
672                            cx,
673                        )
674                    });
675                    excerpt_ids.push(excerpt_id);
676                    expected_excerpts.push((buffer_handle.clone(), anchor_range, header_height));
677                }
678            }
679
680            if rng.gen_bool(0.3) {
681                list.update(cx, |list, cx| {
682                    old_versions.push((list.snapshot(cx), list.subscribe()));
683                })
684            }
685
686            let snapshot = list.read(cx).snapshot(cx);
687
688            let mut expected_text = String::new();
689            for (buffer, range, header_height) in &expected_excerpts {
690                let buffer_id = buffer.id();
691                let buffer = buffer.read(cx);
692                let buffer_range = range.to_offset(buffer);
693                let buffer_start_point = buffer.to_point(buffer_range.start);
694
695                for _ in 0..*header_height {
696                    expected_text.push('\n');
697                }
698
699                let excerpt_start = TextSummary::from(expected_text.as_str());
700                expected_text.extend(buffer.text_for_range(buffer_range.clone()));
701                expected_text.push('\n');
702
703                for buffer_offset in buffer_range.clone() {
704                    let offset = excerpt_start.bytes + (buffer_offset - buffer_range.start);
705                    let left_offset = snapshot.clip_offset(offset, Bias::Left);
706                    let right_offset = snapshot.clip_offset(offset, Bias::Right);
707                    let buffer_left_offset = buffer.clip_offset(buffer_offset, Bias::Left);
708                    let buffer_right_offset = buffer.clip_offset(buffer_offset, Bias::Right);
709                    let left_point = snapshot.to_point(left_offset);
710
711                    assert_eq!(
712                        left_offset,
713                        excerpt_start.bytes + (buffer_left_offset - buffer_range.start),
714                        "clip_offset({}, Left). buffer: {}, buffer offset: {}",
715                        offset,
716                        buffer_id,
717                        buffer_offset,
718                    );
719                    assert_eq!(
720                        right_offset,
721                        excerpt_start.bytes + (buffer_right_offset - buffer_range.start),
722                        "clip_offset({}, Right). buffer: {}, buffer offset: {}",
723                        offset,
724                        buffer_id,
725                        buffer_offset,
726                    );
727                    assert_eq!(
728                        left_point,
729                        excerpt_start.lines
730                            + (buffer.to_point(buffer_left_offset) - buffer_start_point),
731                        "to_point({}). buffer: {}, buffer offset: {}",
732                        offset,
733                        buffer_id,
734                        buffer_offset,
735                    );
736                    assert_eq!(
737                        snapshot.to_offset(left_point),
738                        left_offset,
739                        "to_offset({:?})",
740                        left_point,
741                    )
742                }
743            }
744
745            assert_eq!(snapshot.text(), expected_text);
746
747            for _ in 0..10 {
748                let end_ix = snapshot.clip_offset(rng.gen_range(0..=snapshot.len()), Bias::Right);
749                let start_ix = snapshot.clip_offset(rng.gen_range(0..=end_ix), Bias::Left);
750
751                assert_eq!(
752                    snapshot
753                        .text_for_range(start_ix..end_ix)
754                        .collect::<String>(),
755                    &expected_text[start_ix..end_ix],
756                    "incorrect text for range {:?}",
757                    start_ix..end_ix
758                );
759            }
760        }
761
762        let snapshot = list.read(cx).snapshot(cx);
763        for (old_snapshot, subscription) in old_versions {
764            let edits = subscription.consume().into_inner();
765
766            log::info!(
767                "applying edits since old text: {:?}: {:?}",
768                old_snapshot.text(),
769                edits,
770            );
771
772            let mut text = old_snapshot.text();
773            for edit in edits {
774                let new_text: String = snapshot.text_for_range(edit.new.clone()).collect();
775                text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text);
776            }
777            assert_eq!(text.to_string(), snapshot.text());
778        }
779    }
780
781    #[gpui::test(iterations = 100)]
782    fn test_location(mut rng: StdRng) {
783        let mut lhs = Default::default();
784        let mut rhs = Default::default();
785        while lhs == rhs {
786            lhs = Location(
787                (0..rng.gen_range(1..=5))
788                    .map(|_| rng.gen_range(0..=100))
789                    .collect(),
790            );
791            rhs = Location(
792                (0..rng.gen_range(1..=5))
793                    .map(|_| rng.gen_range(0..=100))
794                    .collect(),
795            );
796        }
797
798        if lhs > rhs {
799            mem::swap(&mut lhs, &mut rhs);
800        }
801
802        let middle = Location::between(&lhs, &rhs);
803        assert!(middle > lhs);
804        assert!(middle < rhs);
805        for ix in 0..middle.0.len() - 1 {
806            assert!(
807                middle.0[ix] == *lhs.0.get(ix).unwrap_or(&0)
808                    || middle.0[ix] == *rhs.0.get(ix).unwrap_or(&0)
809            );
810        }
811    }
812}