suggestion_map.rs

  1use super::{
  2    fold_map::{FoldBufferRows, FoldChunks, FoldEdit, FoldOffset, FoldPoint, FoldSnapshot},
  3    TextHighlights,
  4};
  5use crate::ToPoint;
  6use gpui::fonts::HighlightStyle;
  7use language::{Bias, Chunk, Edit, Patch, Point, Rope};
  8use parking_lot::Mutex;
  9use std::{
 10    cmp,
 11    ops::{Add, AddAssign, Range, Sub},
 12};
 13use util::post_inc;
 14
 15pub type SuggestionEdit = Edit<SuggestionOffset>;
 16
 17#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 18pub struct SuggestionOffset(pub usize);
 19
 20impl Add for SuggestionOffset {
 21    type Output = Self;
 22
 23    fn add(self, rhs: Self) -> Self::Output {
 24        Self(self.0 + rhs.0)
 25    }
 26}
 27
 28impl Sub for SuggestionOffset {
 29    type Output = Self;
 30
 31    fn sub(self, rhs: Self) -> Self::Output {
 32        Self(self.0 - rhs.0)
 33    }
 34}
 35
 36impl AddAssign for SuggestionOffset {
 37    fn add_assign(&mut self, rhs: Self) {
 38        self.0 += rhs.0;
 39    }
 40}
 41
 42#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 43pub struct SuggestionPoint(pub Point);
 44
 45impl SuggestionPoint {
 46    pub fn row(self) -> u32 {
 47        self.0.row
 48    }
 49
 50    pub fn column(self) -> u32 {
 51        self.0.column
 52    }
 53}
 54
 55#[derive(Clone, Debug)]
 56pub struct Suggestion<T> {
 57    position: T,
 58    text: Rope,
 59    highlight_style: HighlightStyle,
 60}
 61
 62pub struct SuggestionMap(Mutex<SuggestionSnapshot>);
 63
 64impl SuggestionMap {
 65    pub fn new(fold_snapshot: FoldSnapshot) -> (Self, SuggestionSnapshot) {
 66        let snapshot = SuggestionSnapshot {
 67            fold_snapshot,
 68            suggestion: None,
 69        };
 70        (Self(Mutex::new(snapshot.clone())), snapshot)
 71    }
 72
 73    pub fn replace<T>(
 74        &self,
 75        new_suggestion: Option<Suggestion<T>>,
 76        fold_snapshot: FoldSnapshot,
 77        fold_edits: Vec<FoldEdit>,
 78    ) -> (SuggestionSnapshot, Vec<SuggestionEdit>)
 79    where
 80        T: ToPoint,
 81    {
 82        let new_suggestion = new_suggestion.map(|new_suggestion| {
 83            let buffer_point = new_suggestion
 84                .position
 85                .to_point(fold_snapshot.buffer_snapshot());
 86            let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left);
 87            let fold_offset = fold_point.to_offset(&fold_snapshot);
 88            Suggestion {
 89                position: fold_offset,
 90                text: new_suggestion.text,
 91                highlight_style: new_suggestion.highlight_style,
 92            }
 93        });
 94
 95        let (_, edits) = self.sync(fold_snapshot, fold_edits);
 96        let mut snapshot = self.0.lock();
 97
 98        let mut patch = Patch::new(edits);
 99        if let Some(suggestion) = snapshot.suggestion.take() {
100            patch = patch.compose([SuggestionEdit {
101                old: SuggestionOffset(suggestion.position.0)
102                    ..SuggestionOffset(suggestion.position.0 + suggestion.text.len()),
103                new: SuggestionOffset(suggestion.position.0)
104                    ..SuggestionOffset(suggestion.position.0),
105            }]);
106        }
107
108        if let Some(suggestion) = new_suggestion.as_ref() {
109            patch = patch.compose([SuggestionEdit {
110                old: SuggestionOffset(suggestion.position.0)
111                    ..SuggestionOffset(suggestion.position.0),
112                new: SuggestionOffset(suggestion.position.0)
113                    ..SuggestionOffset(suggestion.position.0 + suggestion.text.len()),
114            }]);
115        }
116
117        snapshot.suggestion = new_suggestion;
118        (snapshot.clone(), patch.into_inner())
119    }
120
121    pub fn sync(
122        &self,
123        fold_snapshot: FoldSnapshot,
124        fold_edits: Vec<FoldEdit>,
125    ) -> (SuggestionSnapshot, Vec<SuggestionEdit>) {
126        let mut snapshot = self.0.lock();
127        let mut suggestion_edits = Vec::new();
128
129        let mut suggestion_old_len = 0;
130        let mut suggestion_new_len = 0;
131        for fold_edit in fold_edits {
132            let start = fold_edit.new.start;
133            let end = FoldOffset(start.0 + fold_edit.old_len().0);
134            if let Some(suggestion) = snapshot.suggestion.as_mut() {
135                if end <= suggestion.position {
136                    suggestion.position.0 += fold_edit.new_len().0;
137                    suggestion.position.0 -= fold_edit.old_len().0;
138                } else if start > suggestion.position {
139                    suggestion_old_len = suggestion.text.len();
140                    suggestion_new_len = suggestion_old_len;
141                } else {
142                    suggestion_old_len = suggestion.text.len();
143                    snapshot.suggestion.take();
144                    suggestion_edits.push(SuggestionEdit {
145                        old: SuggestionOffset(fold_edit.old.start.0)
146                            ..SuggestionOffset(fold_edit.old.end.0 + suggestion_old_len),
147                        new: SuggestionOffset(fold_edit.new.start.0)
148                            ..SuggestionOffset(fold_edit.new.end.0),
149                    });
150                    continue;
151                }
152            }
153
154            suggestion_edits.push(SuggestionEdit {
155                old: SuggestionOffset(fold_edit.old.start.0 + suggestion_old_len)
156                    ..SuggestionOffset(fold_edit.old.end.0 + suggestion_old_len),
157                new: SuggestionOffset(fold_edit.new.start.0 + suggestion_new_len)
158                    ..SuggestionOffset(fold_edit.new.end.0 + suggestion_new_len),
159            });
160        }
161        snapshot.fold_snapshot = fold_snapshot;
162
163        (snapshot.clone(), suggestion_edits)
164    }
165}
166
167#[derive(Clone)]
168pub struct SuggestionSnapshot {
169    fold_snapshot: FoldSnapshot,
170    suggestion: Option<Suggestion<FoldOffset>>,
171}
172
173impl SuggestionSnapshot {
174    pub fn max_point(&self) -> SuggestionPoint {
175        if let Some(suggestion) = self.suggestion.as_ref() {
176            let suggestion_point = suggestion.position.to_point(&self.fold_snapshot);
177            let mut max_point = suggestion_point.0;
178            max_point += suggestion.text.max_point();
179            max_point += self.fold_snapshot.max_point().0 - suggestion_point.0;
180            SuggestionPoint(max_point)
181        } else {
182            SuggestionPoint(self.fold_snapshot.max_point().0)
183        }
184    }
185
186    pub fn len(&self) -> SuggestionOffset {
187        if let Some(suggestion) = self.suggestion.as_ref() {
188            let mut len = suggestion.position.0;
189            len += suggestion.text.len();
190            len += self.fold_snapshot.len().0 - suggestion.position.0;
191            SuggestionOffset(len)
192        } else {
193            SuggestionOffset(self.fold_snapshot.len().0)
194        }
195    }
196
197    pub fn clip_point(&self, point: SuggestionPoint, bias: Bias) -> SuggestionPoint {
198        if let Some(suggestion) = self.suggestion.as_ref() {
199            let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0;
200            let suggestion_end = suggestion_start + suggestion.text.max_point();
201            if point.0 <= suggestion_start {
202                SuggestionPoint(self.fold_snapshot.clip_point(FoldPoint(point.0), bias).0)
203            } else if point.0 > suggestion_end {
204                let fold_point = self.fold_snapshot.clip_point(
205                    FoldPoint(suggestion_start + (point.0 - suggestion_end)),
206                    bias,
207                );
208                let suggestion_point = suggestion_end + (fold_point.0 - suggestion_start);
209                if bias == Bias::Left && suggestion_point == suggestion_end {
210                    SuggestionPoint(suggestion_start)
211                } else {
212                    SuggestionPoint(suggestion_point)
213                }
214            } else if bias == Bias::Left || suggestion_start == self.fold_snapshot.max_point().0 {
215                SuggestionPoint(suggestion_start)
216            } else {
217                let fold_point = if self.fold_snapshot.line_len(suggestion_start.row)
218                    > suggestion_start.column
219                {
220                    FoldPoint(suggestion_start + Point::new(0, 1))
221                } else {
222                    FoldPoint(suggestion_start + Point::new(1, 0))
223                };
224                let clipped_fold_point = self.fold_snapshot.clip_point(fold_point, bias);
225                SuggestionPoint(suggestion_end + (clipped_fold_point.0 - suggestion_start))
226            }
227        } else {
228            SuggestionPoint(self.fold_snapshot.clip_point(FoldPoint(point.0), bias).0)
229        }
230    }
231
232    pub fn line_len(&self, row: u32) -> u32 {
233        if let Some(suggestion) = self.suggestion.as_ref() {
234            let suggestion_lines = suggestion.text.max_point();
235            let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0;
236            let suggestion_end = suggestion_start + suggestion_lines;
237
238            if row < suggestion_start.row {
239                self.fold_snapshot.line_len(row)
240            } else if row > suggestion_end.row {
241                self.fold_snapshot.line_len(row - suggestion_lines.row)
242            } else {
243                let mut len = suggestion.text.line_len(row - suggestion_start.row);
244                if row == suggestion_start.row {
245                    len += suggestion_start.column;
246                }
247                if row == suggestion_end.row {
248                    len +=
249                        self.fold_snapshot.line_len(suggestion_start.row) - suggestion_start.column;
250                }
251                len
252            }
253        } else {
254            self.fold_snapshot.line_len(row)
255        }
256    }
257
258    pub fn chunks<'a>(
259        &'a self,
260        range: Range<SuggestionOffset>,
261        language_aware: bool,
262        text_highlights: Option<&'a TextHighlights>,
263    ) -> Chunks<'a> {
264        if let Some(suggestion) = self.suggestion.as_ref() {
265            let suggestion_range =
266                suggestion.position.0..suggestion.position.0 + suggestion.text.len();
267
268            let prefix_chunks = if range.start.0 < suggestion_range.start {
269                Some(self.fold_snapshot.chunks(
270                    FoldOffset(range.start.0)
271                        ..cmp::min(FoldOffset(suggestion_range.start), FoldOffset(range.end.0)),
272                    language_aware,
273                    text_highlights,
274                ))
275            } else {
276                None
277            };
278
279            let clipped_suggestion_range = cmp::max(range.start.0, suggestion_range.start)
280                ..cmp::min(range.end.0, suggestion_range.end);
281            let suggestion_chunks = if clipped_suggestion_range.start < clipped_suggestion_range.end
282            {
283                let start = clipped_suggestion_range.start - suggestion_range.start;
284                let end = clipped_suggestion_range.end - suggestion_range.start;
285                Some(suggestion.text.chunks_in_range(start..end))
286            } else {
287                None
288            };
289
290            let suffix_chunks = if range.end.0 > suggestion_range.end {
291                let start = cmp::max(suggestion_range.end, range.start.0) - suggestion_range.len();
292                let end = range.end.0 - suggestion_range.len();
293                Some(self.fold_snapshot.chunks(
294                    FoldOffset(start)..FoldOffset(end),
295                    language_aware,
296                    text_highlights,
297                ))
298            } else {
299                None
300            };
301
302            Chunks {
303                prefix_chunks,
304                suggestion_chunks,
305                suffix_chunks,
306                highlight_style: suggestion.highlight_style,
307            }
308        } else {
309            Chunks {
310                prefix_chunks: Some(self.fold_snapshot.chunks(
311                    FoldOffset(range.start.0)..FoldOffset(range.end.0),
312                    language_aware,
313                    text_highlights,
314                )),
315                suggestion_chunks: None,
316                suffix_chunks: None,
317                highlight_style: Default::default(),
318            }
319        }
320    }
321
322    pub fn buffer_rows<'a>(&'a self, row: u32) -> SuggestionBufferRows<'a> {
323        let suggestion_range = if let Some(suggestion) = self.suggestion.as_ref() {
324            let start = suggestion.position.to_point(&self.fold_snapshot).0;
325            let end = start + suggestion.text.max_point();
326            start.row..end.row
327        } else {
328            u32::MAX..u32::MAX
329        };
330
331        let fold_buffer_rows = if row <= suggestion_range.start {
332            self.fold_snapshot.buffer_rows(row)
333        } else if row > suggestion_range.end {
334            self.fold_snapshot
335                .buffer_rows(row - (suggestion_range.end - suggestion_range.start))
336        } else {
337            let mut rows = self.fold_snapshot.buffer_rows(suggestion_range.start);
338            rows.next();
339            rows
340        };
341
342        SuggestionBufferRows {
343            current_row: row,
344            suggestion_row_start: suggestion_range.start,
345            suggestion_row_end: suggestion_range.end,
346            fold_buffer_rows,
347        }
348    }
349
350    #[cfg(test)]
351    pub fn text(&self) -> String {
352        self.chunks(Default::default()..self.len(), false, None)
353            .map(|chunk| chunk.text)
354            .collect()
355    }
356}
357
358pub struct Chunks<'a> {
359    prefix_chunks: Option<FoldChunks<'a>>,
360    suggestion_chunks: Option<text::Chunks<'a>>,
361    suffix_chunks: Option<FoldChunks<'a>>,
362    highlight_style: HighlightStyle,
363}
364
365impl<'a> Iterator for Chunks<'a> {
366    type Item = Chunk<'a>;
367
368    fn next(&mut self) -> Option<Self::Item> {
369        if let Some(chunks) = self.prefix_chunks.as_mut() {
370            if let Some(chunk) = chunks.next() {
371                return Some(chunk);
372            } else {
373                self.prefix_chunks = None;
374            }
375        }
376
377        if let Some(chunks) = self.suggestion_chunks.as_mut() {
378            if let Some(chunk) = chunks.next() {
379                return Some(Chunk {
380                    text: chunk,
381                    syntax_highlight_id: None,
382                    highlight_style: Some(self.highlight_style),
383                    diagnostic_severity: None,
384                    is_unnecessary: false,
385                });
386            } else {
387                self.suggestion_chunks = None;
388            }
389        }
390
391        if let Some(chunks) = self.suffix_chunks.as_mut() {
392            if let Some(chunk) = chunks.next() {
393                return Some(chunk);
394            } else {
395                self.suffix_chunks = None;
396            }
397        }
398
399        None
400    }
401}
402
403pub struct SuggestionBufferRows<'a> {
404    current_row: u32,
405    suggestion_row_start: u32,
406    suggestion_row_end: u32,
407    fold_buffer_rows: FoldBufferRows<'a>,
408}
409
410impl<'a> Iterator for SuggestionBufferRows<'a> {
411    type Item = Option<u32>;
412
413    fn next(&mut self) -> Option<Self::Item> {
414        let row = post_inc(&mut self.current_row);
415        if row <= self.suggestion_row_start || row > self.suggestion_row_end {
416            self.fold_buffer_rows.next()
417        } else {
418            Some(None)
419        }
420    }
421}
422
423#[cfg(test)]
424mod tests {
425    use super::*;
426    use crate::{display_map::fold_map::FoldMap, MultiBuffer};
427    use gpui::MutableAppContext;
428    use rand::{prelude::StdRng, Rng};
429    use settings::Settings;
430    use std::{
431        env,
432        ops::{Bound, RangeBounds},
433    };
434
435    #[gpui::test]
436    fn test_basic(cx: &mut MutableAppContext) {
437        let buffer = MultiBuffer::build_simple("abcdefghi", cx);
438        let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
439        let (mut fold_map, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
440        let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone());
441        assert_eq!(suggestion_snapshot.text(), "abcdefghi");
442
443        let (suggestion_snapshot, _) = suggestion_map.replace(
444            Some(Suggestion {
445                position: 3,
446                text: "123\n456".into(),
447                highlight_style: Default::default(),
448            }),
449            fold_snapshot,
450            Default::default(),
451        );
452        assert_eq!(suggestion_snapshot.text(), "abc123\n456defghi");
453
454        buffer.update(cx, |buffer, cx| {
455            buffer.edit(
456                [(0..0, "ABC"), (3..3, "DEF"), (4..4, "GHI"), (9..9, "JKL")],
457                None,
458                cx,
459            )
460        });
461        let (fold_snapshot, fold_edits) = fold_map.read(
462            buffer.read(cx).snapshot(cx),
463            buffer_edits.consume().into_inner(),
464        );
465        let (suggestion_snapshot, _) = suggestion_map.sync(fold_snapshot.clone(), fold_edits);
466        assert_eq!(suggestion_snapshot.text(), "ABCabcDEF123\n456dGHIefghiJKL");
467
468        let (mut fold_map_writer, _, _) =
469            fold_map.write(buffer.read(cx).snapshot(cx), Default::default());
470        let (fold_snapshot, fold_edits) = fold_map_writer.fold([0..3]);
471        let (suggestion_snapshot, _) = suggestion_map.sync(fold_snapshot, fold_edits);
472        assert_eq!(suggestion_snapshot.text(), "⋯abcDEF123\n456dGHIefghiJKL");
473
474        let (mut fold_map_writer, _, _) =
475            fold_map.write(buffer.read(cx).snapshot(cx), Default::default());
476        let (fold_snapshot, fold_edits) = fold_map_writer.fold([6..10]);
477        let (suggestion_snapshot, _) = suggestion_map.sync(fold_snapshot, fold_edits);
478        assert_eq!(suggestion_snapshot.text(), "⋯abc⋯GHIefghiJKL");
479    }
480
481    #[gpui::test(iterations = 100)]
482    fn test_random_suggestions(cx: &mut MutableAppContext, mut rng: StdRng) {
483        cx.set_global(Settings::test(cx));
484        let operations = env::var("OPERATIONS")
485            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
486            .unwrap_or(10);
487
488        let len = rng.gen_range(0..30);
489        let buffer = if rng.gen() {
490            let text = util::RandomCharIter::new(&mut rng)
491                .take(len)
492                .collect::<String>();
493            MultiBuffer::build_simple(&text, cx)
494        } else {
495            MultiBuffer::build_random(&mut rng, cx)
496        };
497        let mut buffer_snapshot = buffer.read(cx).snapshot(cx);
498        log::info!("buffer text: {:?}", buffer_snapshot.text());
499
500        let (mut fold_map, mut fold_snapshot) = FoldMap::new(buffer_snapshot.clone());
501        let (suggestion_map, mut suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone());
502
503        for _ in 0..operations {
504            let mut suggestion_edits = Patch::default();
505
506            let mut prev_suggestion_text = suggestion_snapshot.text();
507            let mut buffer_edits = Vec::new();
508            match rng.gen_range(0..=100) {
509                0..=29 => {
510                    let new_suggestion = if rng.gen_bool(0.3) {
511                        None
512                    } else {
513                        let index = rng.gen_range(0..=buffer_snapshot.len());
514                        let len = rng.gen_range(0..30);
515                        Some(Suggestion {
516                            position: index,
517                            text: util::RandomCharIter::new(&mut rng)
518                                .take(len)
519                                .collect::<String>()
520                                .as_str()
521                                .into(),
522                            highlight_style: Default::default(),
523                        })
524                    };
525
526                    log::info!("replacing suggestion with {:?}", new_suggestion);
527                    let (_, edits) =
528                        suggestion_map.replace(new_suggestion, fold_snapshot, Default::default());
529                    suggestion_edits = suggestion_edits.compose(edits);
530                }
531                30..=59 => {
532                    for (new_fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) {
533                        fold_snapshot = new_fold_snapshot;
534                        let (_, edits) = suggestion_map.sync(fold_snapshot.clone(), fold_edits);
535                        suggestion_edits = suggestion_edits.compose(edits);
536                    }
537                }
538                _ => buffer.update(cx, |buffer, cx| {
539                    let subscription = buffer.subscribe();
540                    let edit_count = rng.gen_range(1..=5);
541                    buffer.randomly_mutate(&mut rng, edit_count, cx);
542                    buffer_snapshot = buffer.snapshot(cx);
543                    let edits = subscription.consume().into_inner();
544                    log::info!("editing {:?}", edits);
545                    buffer_edits.extend(edits);
546                }),
547            };
548
549            let (new_fold_snapshot, fold_edits) =
550                fold_map.read(buffer_snapshot.clone(), buffer_edits);
551            fold_snapshot = new_fold_snapshot;
552            let (new_suggestion_snapshot, edits) =
553                suggestion_map.sync(fold_snapshot.clone(), fold_edits);
554            suggestion_snapshot = new_suggestion_snapshot;
555            suggestion_edits = suggestion_edits.compose(edits);
556
557            log::info!("buffer text: {:?}", buffer_snapshot.text());
558            log::info!("folds text: {:?}", fold_snapshot.text());
559            log::info!("suggestions text: {:?}", suggestion_snapshot.text());
560
561            let mut expected_text = Rope::from(fold_snapshot.text().as_str());
562            let mut expected_buffer_rows = fold_snapshot.buffer_rows(0).collect::<Vec<_>>();
563            if let Some(suggestion) = suggestion_snapshot.suggestion.as_ref() {
564                expected_text.replace(
565                    suggestion.position.0..suggestion.position.0,
566                    &suggestion.text.to_string(),
567                );
568                let suggestion_start = suggestion.position.to_point(&fold_snapshot).0;
569                let suggestion_end = suggestion_start + suggestion.text.max_point();
570                expected_buffer_rows.splice(
571                    (suggestion_start.row + 1) as usize..(suggestion_start.row + 1) as usize,
572                    (0..suggestion_end.row - suggestion_start.row).map(|_| None),
573                );
574            }
575            assert_eq!(suggestion_snapshot.text(), expected_text.to_string());
576            for row_start in 0..expected_buffer_rows.len() {
577                assert_eq!(
578                    suggestion_snapshot
579                        .buffer_rows(row_start as u32)
580                        .collect::<Vec<_>>(),
581                    &expected_buffer_rows[row_start..],
582                    "incorrect buffer rows starting at {}",
583                    row_start
584                );
585            }
586
587            for _ in 0..5 {
588                let mut end = rng.gen_range(0..=suggestion_snapshot.len().0);
589                end = expected_text.clip_offset(end, Bias::Right);
590                let mut start = rng.gen_range(0..=end);
591                start = expected_text.clip_offset(start, Bias::Right);
592
593                let actual_text = suggestion_snapshot
594                    .chunks(SuggestionOffset(start)..SuggestionOffset(end), false, None)
595                    .map(|chunk| chunk.text)
596                    .collect::<String>();
597                assert_eq!(
598                    actual_text,
599                    expected_text.slice(start..end).to_string(),
600                    "incorrect text in range {:?}",
601                    start..end
602                );
603            }
604
605            for edit in suggestion_edits.into_inner() {
606                prev_suggestion_text.replace_range(
607                    edit.new.start.0..edit.new.start.0 + edit.old_len().0,
608                    &suggestion_snapshot.text()[edit.new.start.0..edit.new.end.0],
609                );
610            }
611            assert_eq!(prev_suggestion_text, suggestion_snapshot.text());
612
613            assert_eq!(expected_text.max_point(), suggestion_snapshot.max_point().0);
614            assert_eq!(expected_text.len(), suggestion_snapshot.len().0);
615            for row in 0..=suggestion_snapshot.max_point().row() {
616                assert_eq!(
617                    suggestion_snapshot.line_len(row),
618                    expected_text.line_len(row),
619                    "incorrect line len for row {}",
620                    row
621                );
622            }
623
624            let mut suggestion_point = SuggestionPoint::default();
625            for ch in expected_text.chars() {
626                let mut bytes = [0; 4];
627                for byte in ch.encode_utf8(&mut bytes).as_bytes() {
628                    if *byte == b'\n' {
629                        suggestion_point.0 += Point::new(1, 0);
630                    } else {
631                        suggestion_point.0 += Point::new(0, 1);
632                    }
633
634                    let clipped_left_point =
635                        suggestion_snapshot.clip_point(suggestion_point, Bias::Left);
636                    let clipped_right_point =
637                        suggestion_snapshot.clip_point(suggestion_point, Bias::Right);
638                    assert!(
639                        clipped_left_point <= clipped_right_point,
640                        "clipped left point {:?} is greater than clipped right point {:?}",
641                        clipped_left_point,
642                        clipped_right_point
643                    );
644                    assert_eq!(
645                        clipped_left_point.0,
646                        expected_text.clip_point(clipped_left_point.0, Bias::Left)
647                    );
648                    assert_eq!(
649                        clipped_right_point.0,
650                        expected_text.clip_point(clipped_right_point.0, Bias::Right)
651                    );
652                    assert!(clipped_left_point <= suggestion_snapshot.max_point());
653                    assert!(clipped_right_point <= suggestion_snapshot.max_point());
654
655                    if let Some(suggestion) = suggestion_snapshot.suggestion.as_ref() {
656                        let suggestion_start = suggestion.position.to_point(&fold_snapshot).0;
657                        let suggestion_end = suggestion_start + suggestion.text.max_point();
658                        let invalid_range = (
659                            Bound::Excluded(suggestion_start),
660                            Bound::Included(suggestion_end),
661                        );
662                        assert!(
663                            !invalid_range.contains(&clipped_left_point.0),
664                            "clipped left point {:?} is inside invalid suggestion range {:?}",
665                            clipped_left_point,
666                            invalid_range
667                        );
668                        assert!(
669                            !invalid_range.contains(&clipped_right_point.0),
670                            "clipped right point {:?} is inside invalid suggestion range {:?}",
671                            clipped_right_point,
672                            invalid_range
673                        );
674                    }
675                }
676            }
677        }
678    }
679}