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