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