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