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