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}