History

Merge remote-tracking branch 'origin/main' into modeline

Marc-André Lureau committed 9 months ago

Commit d2fdc1d2 parents bf0ae28 27ab8981109 files · +120203 −56017Browse files

Showing the changes to crates/buffer_diff/src/buffer_diff.rs (1 of 1109 files). Show all

Unified · Split

crates/buffer_diff/src/buffer_diff.rs+894−1423
@@ -6,42 +6,19 @@ use language::{ · 5 unmodified lines
6 language_settings::LanguageSettings, word_diff_ranges,6 language_settings::LanguageSettings, word_diff_ranges,
7};7};
8use rope::Rope;8use rope::Rope;
9use std::{cmp::Ordering, future::Future, iter, ops::Range, sync::Arc};9use std::{
10 cmp::Ordering,
11 future::Future,
12 iter,
13 ops::{Range, RangeInclusive},
14 sync::Arc,
15};
10use sum_tree::SumTree;16use sum_tree::SumTree;
11use text::{17use text::{
12 Anchor, Bias, BufferId, Edit, OffsetRangeExt, Patch, Point, ToOffset as _, ToPoint as _,18 Anchor, Bias, BufferId, Edit, OffsetRangeExt, Patch, Point, ToOffset as _, ToPoint as _,
13};19};
14use util::ResultExt;20use util::ResultExt;
15 21
16fn translate_point_through_patch(
17 patch: &Patch<Point>,
18 point: Point,
19) -> (Range<Point>, Range<Point>) {
20 let edits = patch.edits();
21
22 let ix = match edits.binary_search_by(|probe| probe.old.start.cmp(&point)) {
23 Ok(ix) => ix,
24 Err(ix) => {
25 if ix == 0 {
26 return (point..point, point..point);
27 } else {
28 ix - 1
29 }
30 }
31 };
32
33 if let Some(edit) = edits.get(ix) {
34 if point > edit.old.end {
35 let translated = edit.new.end + (point - edit.old.end);
36 (translated..translated, point..point)
37 } else {
38 (edit.new.start..edit.new.end, edit.old.start..edit.old.end)
39 }
40 } else {
41 (point..point, point..point)
42 }
43}
44
45pub const MAX_WORD_DIFF_LINE_COUNT: usize = 5;22pub const MAX_WORD_DIFF_LINE_COUNT: usize = 5;
46 23
47pub struct BufferDiff {24pub struct BufferDiff {
@@ -53,7 +30,7 @@ pub struct BufferDiff { · 5 unmodified lines
53#[derive(Clone)]30#[derive(Clone)]
54pub struct BufferDiffSnapshot {31pub struct BufferDiffSnapshot {
55 inner: BufferDiffInner<language::BufferSnapshot>,32 inner: BufferDiffInner<language::BufferSnapshot>,
56 secondary_diff: Option<Box<BufferDiffSnapshot>>,33 secondary_diff: Option<Arc<BufferDiffSnapshot>>,
57}34}
58 35
59impl std::fmt::Debug for BufferDiffSnapshot {36impl std::fmt::Debug for BufferDiffSnapshot {
@@ -245,11 +222,11 @@ impl std::fmt::Debug for BufferDiffInner<language::BufferSnapshot> { · 185 unmodified lines
245impl BufferDiffSnapshot {222impl BufferDiffSnapshot {
246 #[cfg(test)]223 #[cfg(test)]
247 fn new_sync(224 fn new_sync(
248 buffer: text::BufferSnapshot,225 buffer: &text::BufferSnapshot,
249 diff_base: String,226 diff_base: String,
250 cx: &mut gpui::TestAppContext,227 cx: &mut gpui::TestAppContext,
251 ) -> BufferDiffSnapshot {228 ) -> BufferDiffSnapshot {
252 let buffer_diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));229 let buffer_diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, buffer, cx));
253 buffer_diff.update(cx, |buffer_diff, cx| buffer_diff.snapshot(cx))230 buffer_diff.update(cx, |buffer_diff, cx| buffer_diff.snapshot(cx))
254 }231 }
255 232
@@ -385,7 +362,7 @@ impl BufferDiffSnapshot { · 129 unmodified lines
385 (new_id == old_id && new_version == old_version) || (new_empty && old_empty)362 (new_id == old_id && new_version == old_version) || (new_empty && old_empty)
386 }363 }
387 364
388 #[allow(unused)]365 /// Returns the last hunk whose start is less than or equal to the given position.
389 fn hunk_before_base_text_offset<'a>(366 fn hunk_before_base_text_offset<'a>(
390 &self,367 &self,
391 target: usize,368 target: usize,
@@ -398,16 +375,11 @@ impl BufferDiffSnapshot { · 6 unmodified lines
398 {375 {
399 cursor.prev();376 cursor.prev();
400 }377 }
401 let result = cursor378 cursor
402 .item()379 .item()
403 .filter(|hunk| target >= hunk.diff_base_byte_range.start);380 .filter(|hunk| target >= hunk.diff_base_byte_range.start)
404 if cursor.item().is_none() {
405 cursor.reset();
406 }
407 result
408 }381 }
409 382
410 #[allow(unused)]
411 fn hunk_before_buffer_anchor<'a>(383 fn hunk_before_buffer_anchor<'a>(
412 &self,384 &self,
413 target: Anchor,385 target: Anchor,
@@ -421,24 +393,92 @@ impl BufferDiffSnapshot { · 7 unmodified lines
421 {393 {
422 cursor.prev();394 cursor.prev();
423 }395 }
424 let result = cursor396 cursor
425 .item()397 .item()
426 .filter(|hunk| target.cmp(&hunk.buffer_range.start, buffer).is_ge());398 .filter(|hunk| target.cmp(&hunk.buffer_range.start, buffer).is_ge())
399 }
400
401 /// Returns a patch mapping the provided main buffer snapshot to the base text of this diff.
402 ///
403 /// The returned patch is guaranteed to be accurate for all main buffer points in the provided range,
404 /// but not necessarily for points outside that range.
405 pub fn patch_for_buffer_range<'a>(
406 &'a self,
407 range: RangeInclusive<Point>,
408 buffer: &'a text::BufferSnapshot,
409 ) -> Patch<Point> {
410 if !self.inner.base_text_exists {
411 return Patch::new(vec![Edit {
412 old: Point::zero()..buffer.max_point(),
413 new: Point::zero()..Point::zero(),
414 }]);
415 }
416
417 let mut edits_since_diff = Patch::new(
418 buffer
419 .edits_since::<Point>(&self.inner.buffer_snapshot.version)
420 .collect::<Vec<_>>(),
421 );
422 edits_since_diff.invert();
423
424 let mut start_point = edits_since_diff.old_to_new(*range.start());
425 if let Some(first_edit) = edits_since_diff.edits().first() {
426 start_point = start_point.min(first_edit.new.start);
427 }
428
429 let original_snapshot = self.original_buffer_snapshot();
430 let base_text = self.base_text();
431
432 let mut cursor = self.inner.hunks.cursor(original_snapshot);
433 self.hunk_before_buffer_anchor(
434 original_snapshot.anchor_before(start_point),
435 &mut cursor,
436 original_snapshot,
437 );
427 if cursor.item().is_none() {438 if cursor.item().is_none() {
428 cursor.reset();439 cursor.next();
440 }
441
442 let mut prefix_edit = cursor.prev_item().map(|prev_hunk| Edit {
443 old: Point::zero()..prev_hunk.buffer_range.end.to_point(original_snapshot),
444 new: Point::zero()..prev_hunk.diff_base_byte_range.end.to_point(base_text),
445 });
446
447 let mut range_end = edits_since_diff.old_to_new(*range.end());
448 if let Some(last_edit) = edits_since_diff.edits().last() {
449 range_end = range_end.max(last_edit.new.end);
429 }450 }
430 result451 let range_end = original_snapshot.anchor_before(range_end);
452
453 let hunk_iter = std::iter::from_fn(move || {
454 if let Some(edit) = prefix_edit.take() {
455 return Some(edit);
456 }
457 let hunk = cursor.item()?;
458 if hunk
459 .buffer_range
460 .start
461 .cmp(&range_end, original_snapshot)
462 .is_gt()
463 {
464 return None;
465 }
466 let edit = Edit {
467 old: hunk.buffer_range.to_point(original_snapshot),
468 new: hunk.diff_base_byte_range.to_point(base_text),
469 };
470 cursor.next();
471 Some(edit)
472 });
473
474 edits_since_diff.compose(hunk_iter)
431 }475 }
432 476
433 pub fn points_to_base_text_points<'a>(477 #[cfg(test)]
478 pub(crate) fn patch_for_buffer_range_naive<'a>(
434 &'a self,479 &'a self,
435 points: impl IntoIterator<Item = Point> + 'a,
436 buffer: &'a text::BufferSnapshot,480 buffer: &'a text::BufferSnapshot,
437 ) -> (481 ) -> Patch<Point> {
438 impl 'a + Iterator<Item = Range<Point>>,
439 Option<Range<Point>>,
440 Option<(Point, Range<Point>)>,
441 ) {
442 let original_snapshot = self.original_buffer_snapshot();482 let original_snapshot = self.original_buffer_snapshot();
443 483
444 let edits_since: Vec<Edit<Point>> = buffer484 let edits_since: Vec<Edit<Point>> = buffer
@@ -447,7 +487,7 @@ impl BufferDiffSnapshot { · 2 unmodified lines
447 let mut inverted_edits_since = Patch::new(edits_since);487 let mut inverted_edits_since = Patch::new(edits_since);
448 inverted_edits_since.invert();488 inverted_edits_since.invert();
449 489
450 let composed = inverted_edits_since.compose(490 inverted_edits_since.compose(
451 self.inner491 self.inner
452 .hunks492 .hunks
453 .iter()493 .iter()
@@ -475,42 +515,92 @@ impl BufferDiffSnapshot { · 21 unmodified lines
475 None515 None
476 },516 },
477 ),517 ),
478 );518 )
479 519 }
480 let mut points = points.into_iter().peekable();
481 520
482 let first_group = points.peek().map(|point| {521 /// Returns a patch mapping the base text of this diff to the provided main buffer snapshot.
483 let (_, old_range) = translate_point_through_patch(&composed, *point);522 ///
484 old_range523 /// The returned patch is guaranteed to be accurate for all base text points in the provided range,
485 });524 /// but not necessarily for points outside that range.
525 pub fn patch_for_base_text_range<'a>(
526 &'a self,
527 range: RangeInclusive<Point>,
528 buffer: &'a text::BufferSnapshot,
529 ) -> Patch<Point> {
530 if !self.inner.base_text_exists {
531 return Patch::new(vec![Edit {
532 old: Point::zero()..Point::zero(),
533 new: Point::zero()..buffer.max_point(),
534 }]);
535 }
486 536
487 let prev_boundary = points.peek().and_then(|first_point| {537 let edits_since_diff = buffer
488 if first_point.row > 0 {538 .edits_since::<Point>(&self.inner.buffer_snapshot.version)
489 let prev_point = Point::new(first_point.row - 1, 0);539 .collect::<Vec<_>>();
490 let (range, _) = translate_point_through_patch(&composed, prev_point);
491 Some((prev_point, range))
492 } else {
493 None
494 }
495 });
496 540
497 let iter = points.map(move |point| {541 let mut hunk_patch = Vec::new();
498 let (range, _) = translate_point_through_patch(&composed, point);542 let mut cursor = self.inner.hunks.cursor(self.original_buffer_snapshot());
499 range543 let hunk_before = self
500 });544 .hunk_before_base_text_offset(range.start().to_offset(self.base_text()), &mut cursor);
545
546 if let Some(hunk) = hunk_before
547 && let Some(first_edit) = edits_since_diff.first()
548 && hunk
549 .buffer_range
550 .start
551 .to_point(self.original_buffer_snapshot())
552 > first_edit.old.start
553 {
554 cursor.reset();
555 self.hunk_before_buffer_anchor(
556 self.original_buffer_snapshot()
557 .anchor_before(first_edit.old.start),
558 &mut cursor,
559 self.original_buffer_snapshot(),
560 );
561 }
562 if cursor.item().is_none() {
563 cursor.next();
564 }
565 if let Some(prev_hunk) = cursor.prev_item() {
566 hunk_patch.push(Edit {
567 old: Point::zero()
568 ..prev_hunk
569 .diff_base_byte_range
570 .end
571 .to_point(self.base_text()),
572 new: Point::zero()
573 ..prev_hunk
574 .buffer_range
575 .end
576 .to_point(self.original_buffer_snapshot()),
577 })
578 }
579 let range_end = range.end().to_offset(self.base_text());
580 while let Some(hunk) = cursor.item()
581 && (hunk.diff_base_byte_range.start <= range_end
582 || edits_since_diff.last().is_some_and(|last_edit| {
583 hunk.buffer_range
584 .start
585 .to_point(self.original_buffer_snapshot())
586 <= last_edit.old.end
587 }))
588 {
589 hunk_patch.push(Edit {
590 old: hunk.diff_base_byte_range.to_point(self.base_text()),
591 new: hunk.buffer_range.to_point(self.original_buffer_snapshot()),
592 });
593 cursor.next();
594 }
501 595
502 (iter, first_group, prev_boundary)596 Patch::new(hunk_patch).compose(edits_since_diff)
503 }597 }
504 598
505 pub fn base_text_points_to_points<'a>(599 #[cfg(test)]
600 pub(crate) fn patch_for_base_text_range_naive<'a>(
506 &'a self,601 &'a self,
507 points: impl IntoIterator<Item = Point> + 'a,
508 buffer: &'a text::BufferSnapshot,602 buffer: &'a text::BufferSnapshot,
509 ) -> (603 ) -> Patch<Point> {
510 impl 'a + Iterator<Item = Range<Point>>,
511 Option<Range<Point>>,
512 Option<(Point, Range<Point>)>,
513 ) {
514 let original_snapshot = self.original_buffer_snapshot();604 let original_snapshot = self.original_buffer_snapshot();
515 605
516 let mut hunk_edits: Vec<Edit<Point>> = Vec::new();606 let mut hunk_edits: Vec<Edit<Point>> = Vec::new();
@@ -536,31 +626,55 @@ impl BufferDiffSnapshot { · 19 unmodified lines
536 }626 }
537 let hunk_patch = Patch::new(hunk_edits);627 let hunk_patch = Patch::new(hunk_edits);
538 628
539 let composed = hunk_patch.compose(buffer.edits_since::<Point>(original_snapshot.version()));629 hunk_patch.compose(buffer.edits_since::<Point>(original_snapshot.version()))
540 630 }
541 let mut points = points.into_iter().peekable();
542 631
543 let first_group = points.peek().map(|point| {632 pub fn buffer_point_to_base_text_range(
544 let (_, result) = translate_point_through_patch(&composed, *point);633 &self,
545 result634 point: Point,
546 });635 buffer: &text::BufferSnapshot,
636 ) -> Range<Point> {
637 let patch = self.patch_for_buffer_range(point..=point, buffer);
638 let edit = patch.edit_for_old_position(point);
639 edit.new
640 }
547 641
548 let prev_boundary = points.peek().and_then(|first_point| {642 pub fn base_text_point_to_buffer_range(
549 if first_point.row > 0 {643 &self,
550 let prev_point = Point::new(first_point.row - 1, 0);644 point: Point,
551 let (range, _) = translate_point_through_patch(&composed, prev_point);645 buffer: &text::BufferSnapshot,
552 Some((prev_point, range))646 ) -> Range<Point> {
553 } else {647 let patch = self.patch_for_base_text_range(point..=point, buffer);
554 None648 let edit = patch.edit_for_old_position(point);
555 }649 edit.new
556 });650 }
557 651
558 let iter = points.map(move |point| {652 pub fn buffer_point_to_base_text_point(
559 let (range, _) = translate_point_through_patch(&composed, point);653 &self,
560 range654 point: Point,
561 });655 buffer: &text::BufferSnapshot,
656 ) -> Point {
657 let patch = self.patch_for_buffer_range(point..=point, buffer);
658 let edit = patch.edit_for_old_position(point);
659 if point == edit.old.end {
660 edit.new.end
661 } else {
662 edit.new.start
663 }
664 }
562 665
563 (iter, first_group, prev_boundary)666 pub fn base_text_point_to_buffer_point(
667 &self,
668 point: Point,
669 buffer: &text::BufferSnapshot,
670 ) -> Point {
671 let patch = self.patch_for_base_text_range(point..=point, buffer);
672 let edit = patch.edit_for_old_position(point);
673 if point == edit.old.end {
674 edit.new.end
675 } else {
676 edit.new.start
677 }
564 }678 }
565}679}
566 680
@@ -972,10 +1086,10 @@ fn build_diff_options( · 405 unmodified lines
972 1086
973fn compute_hunks(1087fn compute_hunks(
974 diff_base: Option<(Arc<str>, Rope)>,1088 diff_base: Option<(Arc<str>, Rope)>,
975 buffer: text::BufferSnapshot,1089 buffer: &text::BufferSnapshot,
976 diff_options: Option<DiffOptions>,1090 diff_options: Option<DiffOptions>,
977) -> SumTree<InternalDiffHunk> {1091) -> SumTree<InternalDiffHunk> {
978 let mut tree = SumTree::new(&buffer);1092 let mut tree = SumTree::new(buffer);
979 1093
980 if let Some((diff_base, diff_base_rope)) = diff_base {1094 if let Some((diff_base, diff_base_rope)) = diff_base {
981 let buffer_text = buffer.as_rope().to_string();1095 let buffer_text = buffer.as_rope().to_string();
@@ -1002,7 +1116,7 @@ fn compute_hunks( · 20 unmodified lines
1002 base_word_diffs: Vec::default(),1116 base_word_diffs: Vec::default(),
1003 buffer_word_diffs: Vec::default(),1117 buffer_word_diffs: Vec::default(),
1004 },1118 },
1005 &buffer,1119 buffer,
1006 );1120 );
1007 return tree;1121 return tree;
1008 }1122 }
@@ -1014,11 +1128,11 @@ fn compute_hunks( · 5 unmodified lines
1014 &patch,1128 &patch,
1015 hunk_index,1129 hunk_index,
1016 &diff_base_rope,1130 &diff_base_rope,
1017 &buffer,1131 buffer,
1018 &mut divergence,1132 &mut divergence,
1019 diff_options.as_ref(),1133 diff_options.as_ref(),
1020 );1134 );
1021 tree.push(hunk, &buffer);1135 tree.push(hunk, buffer);
1022 }1136 }
1023 }1137 }
1024 } else {1138 } else {
@@ -1029,7 +1143,7 @@ fn compute_hunks( · 4 unmodified lines
1029 base_word_diffs: Vec::default(),1143 base_word_diffs: Vec::default(),
1030 buffer_word_diffs: Vec::default(),1144 buffer_word_diffs: Vec::default(),
1031 },1145 },
1032 &buffer,1146 buffer,
1033 );1147 );
1034 }1148 }
1035 1149
@@ -1041,6 +1155,8 @@ fn compare_hunks( · 5 unmodified lines
1041 old_hunks: &SumTree<InternalDiffHunk>,1155 old_hunks: &SumTree<InternalDiffHunk>,
1042 old_snapshot: &text::BufferSnapshot,1156 old_snapshot: &text::BufferSnapshot,
1043 new_snapshot: &text::BufferSnapshot,1157 new_snapshot: &text::BufferSnapshot,
1158 old_base_text: &text::BufferSnapshot,
1159 new_base_text: &text::BufferSnapshot,
1044) -> DiffChanged {1160) -> DiffChanged {
1045 let mut new_cursor = new_hunks.cursor::<()>(new_snapshot);1161 let mut new_cursor = new_hunks.cursor::<()>(new_snapshot);
1046 let mut old_cursor = old_hunks.cursor::<()>(new_snapshot);1162 let mut old_cursor = old_hunks.cursor::<()>(new_snapshot);
@@ -1048,8 +1164,8 @@ fn compare_hunks( · 1 unmodified line
1048 new_cursor.next();1164 new_cursor.next();
1049 let mut start = None;1165 let mut start = None;
1050 let mut end = None;1166 let mut end = None;
1051 let mut base_text_start = None;1167 let mut base_text_start: Option<Anchor> = None;
1052 let mut base_text_end = None;1168 let mut base_text_end: Option<Anchor> = None;
1053 1169
1054 let mut last_unchanged_new_hunk_end: Option<text::Anchor> = None;1170 let mut last_unchanged_new_hunk_end: Option<text::Anchor> = None;
1055 let mut has_changes = false;1171 let mut has_changes = false;
@@ -1067,9 +1183,19 @@ fn compare_hunks( · 11 unmodified lines
1067 has_changes = true;1183 has_changes = true;
1068 extended_end_candidate = None;1184 extended_end_candidate = None;
1069 start.get_or_insert(new_hunk.buffer_range.start);1185 start.get_or_insert(new_hunk.buffer_range.start);
1070 base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);1186 base_text_start.get_or_insert(
1187 new_base_text.anchor_before(new_hunk.diff_base_byte_range.start),
1188 );
1071 end.replace(new_hunk.buffer_range.end);1189 end.replace(new_hunk.buffer_range.end);
1072 base_text_end.replace(new_hunk.diff_base_byte_range.end);1190 let new_diff_range_end =
1191 new_base_text.anchor_after(new_hunk.diff_base_byte_range.end);
1192 if base_text_end.is_none_or(|base_text_end| {
1193 new_diff_range_end
1194 .cmp(&base_text_end, &new_base_text)
1195 .is_gt()
1196 }) {
1197 base_text_end = Some(new_diff_range_end)
1198 }
1073 new_cursor.next();1199 new_cursor.next();
1074 }1200 }
1075 Ordering::Equal => {1201 Ordering::Equal => {
@@ -1077,7 +1203,9 @@ fn compare_hunks( · 1 unmodified line
1077 has_changes = true;1203 has_changes = true;
1078 extended_end_candidate = None;1204 extended_end_candidate = None;
1079 start.get_or_insert(new_hunk.buffer_range.start);1205 start.get_or_insert(new_hunk.buffer_range.start);
1080 base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);1206 base_text_start.get_or_insert(
1207 new_base_text.anchor_before(new_hunk.diff_base_byte_range.start),
1208 );
1081 if old_hunk1209 if old_hunk
1082 .buffer_range1210 .buffer_range
1083 .end1211 .end
@@ -1089,11 +1217,14 @@ fn compare_hunks( · 5 unmodified lines
1089 end.replace(new_hunk.buffer_range.end);1217 end.replace(new_hunk.buffer_range.end);
1090 }1218 }
1091 1219
1220 let old_hunk_diff_base_range_end =
1221 old_base_text.anchor_after(old_hunk.diff_base_byte_range.end);
1222 let new_hunk_diff_base_range_end =
1223 new_base_text.anchor_after(new_hunk.diff_base_byte_range.end);
1224
1092 base_text_end.replace(1225 base_text_end.replace(
1093 old_hunk1226 *old_hunk_diff_base_range_end
1094 .diff_base_byte_range1227 .max(&new_hunk_diff_base_range_end, new_base_text),
1095 .end
1096 .max(new_hunk.diff_base_byte_range.end),
1097 );1228 );
1098 } else {1229 } else {
1099 if !has_changes {1230 if !has_changes {
@@ -1110,9 +1241,19 @@ fn compare_hunks( · 10 unmodified lines
1110 has_changes = true;1241 has_changes = true;
1111 extended_end_candidate = None;1242 extended_end_candidate = None;
1112 start.get_or_insert(old_hunk.buffer_range.start);1243 start.get_or_insert(old_hunk.buffer_range.start);
1113 base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start);1244 base_text_start.get_or_insert(
1245 old_base_text.anchor_after(old_hunk.diff_base_byte_range.start),
1246 );
1114 end.replace(old_hunk.buffer_range.end);1247 end.replace(old_hunk.buffer_range.end);
1115 base_text_end.replace(old_hunk.diff_base_byte_range.end);1248 let old_diff_range_end =
1249 old_base_text.anchor_after(old_hunk.diff_base_byte_range.end);
1250 if base_text_end.is_none_or(|base_text_end| {
1251 old_diff_range_end
1252 .cmp(&base_text_end, new_base_text)
1253 .is_gt()
1254 }) {
1255 base_text_end = Some(old_diff_range_end)
1256 }
1116 old_cursor.next();1257 old_cursor.next();
1117 }1258 }
1118 }1259 }
@@ -1121,24 +1262,38 @@ fn compare_hunks( · 2 unmodified lines
1121 has_changes = true;1262 has_changes = true;
1122 extended_end_candidate = None;1263 extended_end_candidate = None;
1123 start.get_or_insert(new_hunk.buffer_range.start);1264 start.get_or_insert(new_hunk.buffer_range.start);
1124 base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);1265 base_text_start
1266 .get_or_insert(new_base_text.anchor_after(new_hunk.diff_base_byte_range.start));
1125 if end.is_none_or(|end| end.cmp(&new_hunk.buffer_range.end, &new_snapshot).is_le())1267 if end.is_none_or(|end| end.cmp(&new_hunk.buffer_range.end, &new_snapshot).is_le())
1126 {1268 {
1127 end.replace(new_hunk.buffer_range.end);1269 end.replace(new_hunk.buffer_range.end);
1128 }1270 }
1129 base_text_end = base_text_end.max(Some(new_hunk.diff_base_byte_range.end));1271 let new_base_text_end =
1272 new_base_text.anchor_after(new_hunk.diff_base_byte_range.end);
1273 if base_text_end.is_none_or(|base_text_end| {
1274 new_base_text_end.cmp(&base_text_end, new_base_text).is_gt()
1275 }) {
1276 base_text_end = Some(new_base_text_end)
1277 }
1130 new_cursor.next();1278 new_cursor.next();
1131 }1279 }
1132 (None, Some(old_hunk)) => {1280 (None, Some(old_hunk)) => {
1133 has_changes = true;1281 has_changes = true;
1134 extended_end_candidate = None;1282 extended_end_candidate = None;
1135 start.get_or_insert(old_hunk.buffer_range.start);1283 start.get_or_insert(old_hunk.buffer_range.start);
1136 base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start);1284 base_text_start
1285 .get_or_insert(old_base_text.anchor_after(old_hunk.diff_base_byte_range.start));
1137 if end.is_none_or(|end| end.cmp(&old_hunk.buffer_range.end, &new_snapshot).is_le())1286 if end.is_none_or(|end| end.cmp(&old_hunk.buffer_range.end, &new_snapshot).is_le())
1138 {1287 {
1139 end.replace(old_hunk.buffer_range.end);1288 end.replace(old_hunk.buffer_range.end);
1140 }1289 }
1141 base_text_end = base_text_end.max(Some(old_hunk.diff_base_byte_range.end));1290 let old_base_text_end =
1291 old_base_text.anchor_after(old_hunk.diff_base_byte_range.end);
1292 if base_text_end.is_none_or(|base_text_end| {
1293 old_base_text_end.cmp(&base_text_end, new_base_text).is_gt()
1294 }) {
1295 base_text_end = Some(old_base_text_end);
1296 }
1142 old_cursor.next();1297 old_cursor.next();
1143 }1298 }
1144 (None, None) => break,1299 (None, None) => break,
@@ -1148,7 +1303,7 @@ fn compare_hunks( · 3 unmodified lines
1148 let changed_range = start.zip(end).map(|(start, end)| start..end);1303 let changed_range = start.zip(end).map(|(start, end)| start..end);
1149 let base_text_changed_range = base_text_start1304 let base_text_changed_range = base_text_start
1150 .zip(base_text_end)1305 .zip(base_text_end)
1151 .map(|(start, end)| start..end);1306 .map(|(start, end)| (start..end).to_offset(new_base_text));
1152 1307
1153 let extended_range = if has_changes && let Some(changed_range) = changed_range.clone() {1308 let extended_range = if has_changes && let Some(changed_range) = changed_range.clone() {
1154 let extended_start = *last_unchanged_new_hunk_end1309 let extended_start = *last_unchanged_new_hunk_end
@@ -1524,7 +1679,7 @@ impl BufferDiff { · 369 unmodified lines
1524 base_text1679 base_text
1525 .clone()1680 .clone()
1526 .map(|base_text| (base_text, base_text_rope.clone())),1681 .map(|base_text| (base_text, base_text_rope.clone())),
1527 buffer.clone(),1682 &buffer,
1528 diff_options,1683 diff_options,
1529 );1684 );
1530 let base_text = base_text.unwrap_or_default();1685 let base_text = base_text.unwrap_or_default();
@@ -1591,22 +1746,46 @@ impl BufferDiff { · 60 unmodified lines
1591 log::debug!("set snapshot with secondary {secondary_diff_change:?}");1746 log::debug!("set snapshot with secondary {secondary_diff_change:?}");
1592 1747
1593 let old_snapshot = self.snapshot(cx);1748 let old_snapshot = self.snapshot(cx);
1594 let state = &mut self.inner;
1595 let new_state = update.inner;1749 let new_state = update.inner;
1596 let base_text_changed = update.base_text_changed;1750 let base_text_changed = update.base_text_changed;
1597 1751
1752 let state = &mut self.inner;
1753 state.base_text_exists = new_state.base_text_exists;
1754 let should_compare_hunks = update.base_text_edits.is_some() || !base_text_changed;
1755 let parsing_idle = if let Some(diff) = update.base_text_edits {
1756 state.base_text.update(cx, |base_text, cx| {
1757 base_text.set_capability(Capability::ReadWrite, cx);
1758 base_text.apply_diff(diff, cx);
1759 base_text.set_capability(Capability::ReadOnly, cx);
1760 Some(base_text.parsing_idle())
1761 })
1762 } else if update.base_text_changed {
1763 state.base_text.update(cx, |base_text, cx| {
1764 base_text.set_capability(Capability::ReadWrite, cx);
1765 base_text.set_text(new_state.base_text.clone(), cx);
1766 base_text.set_capability(Capability::ReadOnly, cx);
1767 Some(base_text.parsing_idle())
1768 })
1769 } else {
1770 None
1771 };
1772
1598 let old_buffer_snapshot = &old_snapshot.inner.buffer_snapshot;1773 let old_buffer_snapshot = &old_snapshot.inner.buffer_snapshot;
1774 let old_base_snapshot = &old_snapshot.inner.base_text;
1775 let new_base_snapshot = state.base_text.read(cx).snapshot();
1599 let DiffChanged {1776 let DiffChanged {
1600 mut changed_range,1777 mut changed_range,
1601 mut base_text_changed_range,1778 mut base_text_changed_range,
1602 mut extended_range,1779 mut extended_range,
1603 } = match (state.base_text_exists, new_state.base_text_exists) {1780 } = match (state.base_text_exists, new_state.base_text_exists) {
1604 (false, false) => DiffChanged::default(),1781 (false, false) => DiffChanged::default(),
1605 (true, true) if !base_text_changed => compare_hunks(1782 (true, true) if should_compare_hunks => compare_hunks(
1606 &new_state.hunks,1783 &new_state.hunks,
1607 &old_snapshot.inner.hunks,1784 &old_snapshot.inner.hunks,
1608 old_buffer_snapshot,1785 old_buffer_snapshot,
1609 buffer,1786 buffer,
1787 old_base_snapshot,
1788 &new_base_snapshot,
1610 ),1789 ),
1611 _ => {1790 _ => {
1612 let full_range = text::Anchor::min_max_range_for_buffer(self.buffer_id);1791 let full_range = text::Anchor::min_max_range_for_buffer(self.buffer_id);
@@ -1618,54 +1797,9 @@ impl BufferDiff { · 5 unmodified lines
1618 }1797 }
1619 }1798 }
1620 };1799 };
1621
1622 if let Some(secondary_changed_range) = secondary_diff_change
1623 && let (Some(secondary_hunk_range), Some(secondary_base_range)) =
1624 old_snapshot.range_to_hunk_range(secondary_changed_range, buffer)
1625 {
1626 if let Some(range) = &mut changed_range {
1627 range.start = *secondary_hunk_range.start.min(&range.start, buffer);
1628 range.end = *secondary_hunk_range.end.max(&range.end, buffer);
1629 } else {
1630 changed_range = Some(secondary_hunk_range.clone());
1631 }
1632
1633 if let Some(base_text_range) = base_text_changed_range.as_mut() {
1634 base_text_range.start = secondary_base_range.start.min(base_text_range.start);
1635 base_text_range.end = secondary_base_range.end.max(base_text_range.end);
1636 } else {
1637 base_text_changed_range = Some(secondary_base_range);
1638 }
1639
1640 if let Some(ext) = &mut extended_range {
1641 ext.start = *ext.start.min(&secondary_hunk_range.start, buffer);
1642 ext.end = *ext.end.max(&secondary_hunk_range.end, buffer);
1643 } else {
1644 extended_range = Some(secondary_hunk_range);
1645 }
1646 }
1647
1648 let state = &mut self.inner;
1649 state.base_text_exists = new_state.base_text_exists;
1650 let parsing_idle = if let Some(diff) = update.base_text_edits {
1651 state.base_text.update(cx, |base_text, cx| {
1652 base_text.set_capability(Capability::ReadWrite, cx);
1653 base_text.apply_diff(diff, cx);
1654 base_text.set_capability(Capability::ReadOnly, cx);
1655 Some(base_text.parsing_idle())
1656 })
1657 } else if update.base_text_changed {
1658 state.base_text.update(cx, |base_text, cx| {
1659 base_text.set_capability(Capability::ReadWrite, cx);
1660 base_text.set_text(new_state.base_text.clone(), cx);
1661 base_text.set_capability(Capability::ReadOnly, cx);
1662 Some(base_text.parsing_idle())
1663 })
1664 } else {
1665 None
1666 };
1667 state.hunks = new_state.hunks;1800 state.hunks = new_state.hunks;
1668 state.buffer_snapshot = update.buffer_snapshot;1801 state.buffer_snapshot = update.buffer_snapshot;
1802
1669 if base_text_changed || clear_pending_hunks {1803 if base_text_changed || clear_pending_hunks {
1670 if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())1804 if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
1671 {1805 {
@@ -1696,6 +1830,32 @@ impl BufferDiff { · 24 unmodified lines
1696 state.pending_hunks = SumTree::new(buffer);1830 state.pending_hunks = SumTree::new(buffer);
1697 }1831 }
1698 1832
1833 if let Some(secondary_changed_range) = secondary_diff_change
1834 && let (Some(secondary_hunk_range), Some(secondary_base_range)) =
1835 old_snapshot.range_to_hunk_range(secondary_changed_range, buffer)
1836 {
1837 if let Some(range) = &mut changed_range {
1838 range.start = *secondary_hunk_range.start.min(&range.start, buffer);
1839 range.end = *secondary_hunk_range.end.max(&range.end, buffer);
1840 } else {
1841 changed_range = Some(secondary_hunk_range.clone());
1842 }
1843
1844 if let Some(base_text_range) = base_text_changed_range.as_mut() {
1845 base_text_range.start = secondary_base_range.start.min(base_text_range.start);
1846 base_text_range.end = secondary_base_range.end.max(base_text_range.end);
1847 } else {
1848 base_text_changed_range = Some(secondary_base_range);
1849 }
1850
1851 if let Some(ext) = &mut extended_range {
1852 ext.start = *ext.start.min(&secondary_hunk_range.start, buffer);
1853 ext.end = *ext.end.max(&secondary_hunk_range.end, buffer);
1854 } else {
1855 extended_range = Some(secondary_hunk_range);
1856 }
1857 }
1858
1699 async move {1859 async move {
1700 if let Some(parsing_idle) = parsing_idle {1860 if let Some(parsing_idle) = parsing_idle {
1701 parsing_idle.await;1861 parsing_idle.await;
@@ -1760,10 +1920,10 @@ impl BufferDiff { · 58 unmodified lines
1760 base_text_exists: self.inner.base_text_exists,1920 base_text_exists: self.inner.base_text_exists,
1761 buffer_snapshot: self.inner.buffer_snapshot.clone(),1921 buffer_snapshot: self.inner.buffer_snapshot.clone(),
1762 },1922 },
1763 secondary_diff: self1923 secondary_diff: self.secondary_diff.as_ref().map(|diff| {
1764 .secondary_diff1924 debug_assert!(diff.read(cx).secondary_diff.is_none());
1765 .as_ref()1925 Arc::new(diff.read(cx).snapshot(cx))
1766 .map(|diff| Box::new(diff.read(cx).snapshot(cx))),1926 }),
1767 }1927 }
1768 }1928 }
1769 1929
@@ -1993,7 +2153,7 @@ mod tests { · 223 unmodified lines
1993 .unindent();2153 .unindent();
1994 2154
1995 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);2155 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1996 let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);2156 let mut diff = BufferDiffSnapshot::new_sync(&buffer, diff_base.clone(), cx);
1997 assert_hunks(2157 assert_hunks(
1998 diff.hunks_intersecting_range(2158 diff.hunks_intersecting_range(
1999 Anchor::min_max_range_for_buffer(buffer.remote_id()),2159 Anchor::min_max_range_for_buffer(buffer.remote_id()),
@@ -2005,7 +2165,7 @@ mod tests { · 5 unmodified lines
2005 );2165 );
2006 2166
2007 buffer.edit([(0..0, "point five\n")]);2167 buffer.edit([(0..0, "point five\n")]);
2008 diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);2168 diff = BufferDiffSnapshot::new_sync(&buffer, diff_base.clone(), cx);
2009 assert_hunks(2169 assert_hunks(
2010 diff.hunks_intersecting_range(2170 diff.hunks_intersecting_range(
2011 Anchor::min_max_range_for_buffer(buffer.remote_id()),2171 Anchor::min_max_range_for_buffer(buffer.remote_id()),
@@ -2076,10 +2236,9 @@ mod tests { · 64 unmodified lines
2076 .unindent();2236 .unindent();
2077 2237
2078 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);2238 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2079 let unstaged_diff = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);2239 let unstaged_diff = BufferDiffSnapshot::new_sync(&buffer, index_text, cx);
2080 let mut uncommitted_diff =2240 let mut uncommitted_diff = BufferDiffSnapshot::new_sync(&buffer, head_text.clone(), cx);
2081 BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);2241 uncommitted_diff.secondary_diff = Some(Arc::new(unstaged_diff));
2082 uncommitted_diff.secondary_diff = Some(Box::new(unstaged_diff));
2083 2242
2084 let expected_hunks = vec![2243 let expected_hunks = vec![
2085 (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),2244 (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
@@ -2606,7 +2765,7 @@ mod tests { · 520 unmodified lines
2606 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);2765 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
2607 2766
2608 let empty_diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));2767 let empty_diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
2609 let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);2768 let diff_1 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
2610 let DiffChanged {2769 let DiffChanged {
2611 changed_range,2770 changed_range,
2612 base_text_changed_range,2771 base_text_changed_range,
@@ -2616,6 +2775,8 @@ mod tests { · 3 unmodified lines
2616 &empty_diff.inner.hunks,2775 &empty_diff.inner.hunks,
2617 &buffer,2776 &buffer,
2618 &buffer,2777 &buffer,
2778 &diff_1.base_text(),
2779 &diff_1.base_text(),
2619 );2780 );
2620 let range = changed_range.unwrap();2781 let range = changed_range.unwrap();
2621 assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));2782 assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
@@ -2639,12 +2800,19 @@ mod tests { · 17 unmodified lines
2639 "2800 "
2640 .unindent(),2801 .unindent(),
2641 );2802 );
2642 let diff_2 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);2803 let diff_2 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
2643 let DiffChanged {2804 let DiffChanged {
2644 changed_range,2805 changed_range,
2645 base_text_changed_range,2806 base_text_changed_range,
2646 extended_range: _,2807 extended_range: _,
2647 } = compare_hunks(&diff_2.inner.hunks, &diff_1.inner.hunks, &buffer, &buffer);2808 } = compare_hunks(
2809 &diff_2.inner.hunks,
2810 &diff_1.inner.hunks,
2811 &buffer,
2812 &buffer,
2813 diff_2.base_text(),
2814 diff_2.base_text(),
2815 );
2648 assert_eq!(2816 assert_eq!(
2649 changed_range.unwrap().to_point(&buffer),2817 changed_range.unwrap().to_point(&buffer),
2650 Point::new(4, 0)..Point::new(5, 0),2818 Point::new(4, 0)..Point::new(5, 0),
@@ -2670,12 +2838,19 @@ mod tests { · 19 unmodified lines
2670 "2838 "
2671 .unindent(),2839 .unindent(),
2672 );2840 );
2673 let diff_3 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);2841 let diff_3 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
2674 let DiffChanged {2842 let DiffChanged {
2675 changed_range,2843 changed_range,
2676 base_text_changed_range,2844 base_text_changed_range,
2677 extended_range: _,2845 extended_range: _,
2678 } = compare_hunks(&diff_3.inner.hunks, &diff_2.inner.hunks, &buffer, &buffer);2846 } = compare_hunks(
2847 &diff_3.inner.hunks,
2848 &diff_2.inner.hunks,
2849 &buffer,
2850 &buffer,
2851 diff_3.base_text(),
2852 diff_3.base_text(),
2853 );
2679 let range = changed_range.unwrap();2854 let range = changed_range.unwrap();
2680 assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));2855 assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
2681 let base_text_range = base_text_changed_range.unwrap();2856 let base_text_range = base_text_changed_range.unwrap();
@@ -2697,12 +2872,19 @@ mod tests { · 15 unmodified lines
2697 "2872 "
2698 .unindent(),2873 .unindent(),
2699 );2874 );
2700 let diff_4 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);2875 let diff_4 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
2701 let DiffChanged {2876 let DiffChanged {
2702 changed_range,2877 changed_range,
2703 base_text_changed_range,2878 base_text_changed_range,
2704 extended_range: _,2879 extended_range: _,
2705 } = compare_hunks(&diff_4.inner.hunks, &diff_3.inner.hunks, &buffer, &buffer);2880 } = compare_hunks(
2881 &diff_4.inner.hunks,
2882 &diff_3.inner.hunks,
2883 &buffer,
2884 &buffer,
2885 diff_4.base_text(),
2886 diff_4.base_text(),
2887 );
2706 let range = changed_range.unwrap();2888 let range = changed_range.unwrap();
2707 assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));2889 assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
2708 let base_text_range = base_text_changed_range.unwrap();2890 let base_text_range = base_text_changed_range.unwrap();
@@ -2730,7 +2912,14 @@ mod tests { · 21 unmodified lines
2730 changed_range,2912 changed_range,
2731 base_text_changed_range,2913 base_text_changed_range,
2732 extended_range: _,2914 extended_range: _,
2733 } = compare_hunks(&diff_5.inner.hunks, &diff_4.inner.hunks, &buffer, &buffer);2915 } = compare_hunks(
2916 &diff_5.inner.hunks,
2917 &diff_4.inner.hunks,
2918 &buffer,
2919 &buffer,
2920 diff_5.base_text(),
2921 diff_5.base_text(),
2922 );
2734 let range = changed_range.unwrap();2923 let range = changed_range.unwrap();
2735 assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));2924 assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
2736 let base_text_range = base_text_changed_range.unwrap();2925 let base_text_range = base_text_changed_range.unwrap();
@@ -2753,12 +2942,19 @@ mod tests { · 16 unmodified lines
2753 "2942 "
2754 .unindent(),2943 .unindent(),
2755 );2944 );
2756 let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);2945 let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2757 let DiffChanged {2946 let DiffChanged {
2758 changed_range,2947 changed_range,
2759 base_text_changed_range,2948 base_text_changed_range,
2760 extended_range: _,2949 extended_range: _,
2761 } = compare_hunks(&diff_6.inner.hunks, &diff_5.inner.hunks, &buffer, &buffer);2950 } = compare_hunks(
2951 &diff_6.inner.hunks,
2952 &diff_5.inner.hunks,
2953 &buffer,
2954 &buffer,
2955 diff_6.base_text(),
2956 diff_6.base_text(),
2957 );
2762 let range = changed_range.unwrap();2958 let range = changed_range.unwrap();
2763 assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));2959 assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
2764 let base_text_range = base_text_changed_range.unwrap();2960 let base_text_range = base_text_changed_range.unwrap();
@@ -2766,24 +2962,91 @@ mod tests { · 1 unmodified line
2766 base_text_range.to_point(diff_6.base_text()),2962 base_text_range.to_point(diff_6.base_text()),
2767 Point::new(9, 0)..Point::new(10, 0)2963 Point::new(9, 0)..Point::new(10, 0)
2768 );2964 );
2769 }
2770 2965
2771 #[gpui::test(iterations = 100)]2966 buffer.edit_via_marked_text(
2772 async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {2967 &"
2773 fn gen_line(rng: &mut StdRng) -> String {2968 one
2774 if rng.random_bool(0.2) {2969 THREE
2775 "\n".to_owned()2970 four«»
2776 } else {2971 five
2777 let c = rng.random_range('A'..='Z');2972 seven
2778 format!("{c}{c}{c}\n")2973 eight
2779 }2974 «NINE»
2780 }2975 "
2976 .unindent(),
2977 );
2781 2978
2782 fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {2979 let diff_7 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2783 let mut old_lines = {2980 let DiffChanged {
2784 let mut old_lines = Vec::new();2981 changed_range,
2785 let old_lines_iter = head.lines();2982 base_text_changed_range,
2786 for line in old_lines_iter {2983 extended_range: _,
2984 } = compare_hunks(
2985 &diff_7.inner.hunks,
2986 &diff_6.inner.hunks,
2987 &buffer,
2988 &buffer,
2989 diff_7.base_text(),
2990 diff_7.base_text(),
2991 );
2992 let range = changed_range.unwrap();
2993 assert_eq!(range.to_point(&buffer), Point::new(2, 4)..Point::new(7, 0));
2994 let base_text_range = base_text_changed_range.unwrap();
2995 assert_eq!(
2996 base_text_range.to_point(diff_7.base_text()),
2997 Point::new(5, 0)..Point::new(10, 0)
2998 );
2999
3000 buffer.edit_via_marked_text(
3001 &"
3002 one
3003 THREE
3004 four
3005 five«»seven
3006 eight
3007 NINE
3008 "
3009 .unindent(),
3010 );
3011
3012 let diff_8 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
3013 let DiffChanged {
3014 changed_range,
3015 base_text_changed_range,
3016 extended_range: _,
3017 } = compare_hunks(
3018 &diff_8.inner.hunks,
3019 &diff_7.inner.hunks,
3020 &buffer,
3021 &buffer,
3022 diff_8.base_text(),
3023 diff_8.base_text(),
3024 );
3025 let range = changed_range.unwrap();
3026 assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(3, 4));
3027 let base_text_range = base_text_changed_range.unwrap();
3028 assert_eq!(
3029 base_text_range.to_point(diff_8.base_text()),
3030 Point::new(5, 0)..Point::new(8, 0)
3031 );
3032 }
3033
3034 #[gpui::test(iterations = 100)]
3035 async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
3036 fn gen_line(rng: &mut StdRng) -> String {
3037 if rng.random_bool(0.2) {
3038 "\n".to_owned()
3039 } else {
3040 let c = rng.random_range('A'..='Z');
3041 format!("{c}{c}{c}\n")
3042 }
3043 }
3044
3045 fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
3046 let mut old_lines = {
3047 let mut old_lines = Vec::new();
3048 let old_lines_iter = head.lines();
3049 for line in old_lines_iter {
2787 assert!(!line.ends_with("\n"));3050 assert!(!line.ends_with("\n"));
2788 old_lines.push(line.to_owned());3051 old_lines.push(line.to_owned());
2789 }3052 }
@@ -3043,11 +3306,11 @@ mod tests { · 253 unmodified lines
3043 .unindent();3306 .unindent();
3044 3307
3045 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);3308 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
3046 let old_buffer = buffer.snapshot();3309 let old_buffer = buffer.snapshot().clone();
3047 let diff_a = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);3310 let diff_a = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
3048 3311
3049 buffer.edit([(Point::new(1, 3)..Point::new(1, 3), "\n")]);3312 buffer.edit([(Point::new(1, 3)..Point::new(1, 3), "\n")]);
3050 let diff_b = BufferDiffSnapshot::new_sync(buffer.clone(), base_text, cx);3313 let diff_b = BufferDiffSnapshot::new_sync(&buffer, base_text, cx);
3051 3314
3052 let DiffChanged {3315 let DiffChanged {
3053 changed_range,3316 changed_range,
@@ -3058,6 +3321,8 @@ mod tests { · 4 unmodified lines
3058 &diff_a.inner.hunks,3321 &diff_a.inner.hunks,
3059 &old_buffer,3322 &old_buffer,
3060 &buffer,3323 &buffer,
3324 &diff_a.base_text(),
3325 &diff_a.base_text(),
3061 );3326 );
3062 3327
3063 let changed_range = changed_range.unwrap();3328 let changed_range = changed_range.unwrap();
@@ -3104,11 +3369,11 @@ mod tests { · 40 unmodified lines
3104 .unindent();3369 .unindent();
3105 3370
3106 let mut buffer_2 = Buffer::new(ReplicaId::LOCAL, BufferId::new(2).unwrap(), buffer_text_2);3371 let mut buffer_2 = Buffer::new(ReplicaId::LOCAL, BufferId::new(2).unwrap(), buffer_text_2);
3107 let old_buffer_2 = buffer_2.snapshot();3372 let old_buffer_2 = buffer_2.snapshot().clone();
3108 let diff_2a = BufferDiffSnapshot::new_sync(buffer_2.clone(), base_text_2.clone(), cx);3373 let diff_2a = BufferDiffSnapshot::new_sync(&buffer_2, base_text_2.clone(), cx);
3109 3374
3110 buffer_2.edit([(Point::new(4, 0)..Point::new(4, 4), "FIVE_CHANGED")]);3375 buffer_2.edit([(Point::new(4, 0)..Point::new(4, 4), "FIVE_CHANGED")]);
3111 let diff_2b = BufferDiffSnapshot::new_sync(buffer_2.clone(), base_text_2, cx);3376 let diff_2b = BufferDiffSnapshot::new_sync(&buffer_2, base_text_2, cx);
3112 3377
3113 let DiffChanged {3378 let DiffChanged {
3114 changed_range,3379 changed_range,
@@ -3119,6 +3384,8 @@ mod tests { · 4 unmodified lines
3119 &diff_2a.inner.hunks,3384 &diff_2a.inner.hunks,
3120 &old_buffer_2,3385 &old_buffer_2,
3121 &buffer_2,3386 &buffer_2,
3387 &diff_2a.base_text(),
3388 &diff_2a.base_text(),
3122 );3389 );
3123 3390
3124 let changed_range = changed_range.unwrap();3391 let changed_range = changed_range.unwrap();
@@ -3136,1290 +3403,494 @@ mod tests { · 11 unmodified lines
3136 );3403 );
3137 }3404 }
3138 3405
3139 fn assert_rows_to_base_text_rows_visual(3406 #[gpui::test]
3140 buffer: &Entity<language::Buffer>,3407 async fn test_buffer_diff_compare_with_base_text_change(_cx: &mut TestAppContext) {
3141 diff: &Entity<BufferDiff>,3408 // Use a shared base text buffer so that anchors from old and new snapshots
3142 source_text: &str,3409 // share the same remote_id and resolve correctly across versions.
3143 annotated_target: &str,3410 let initial_base = "aaa\nbbb\nccc\nddd\neee\n";
3144 cx: &mut gpui::TestAppContext,3411 let mut base_text_buffer = Buffer::new(
3145 ) {3412 ReplicaId::LOCAL,
3146 let (target_text, expected_ranges) = parse_row_annotations(annotated_target);3413 BufferId::new(99).unwrap(),
3147 3414 initial_base.to_string(),
3148 let buffer = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());3415 );
3149 let diff = diff.update(cx, |diff, cx| diff.snapshot(cx));
3150 3416
3151 assert_eq!(3417 // --- Scenario 1: Base text gains a line, producing a new deletion hunk ---
3152 buffer.text(),3418 //
3153 source_text,3419 // Buffer has a modification (ccc → CCC). When the base text gains
3154 "buffer text does not match source text"3420 // a new line "XXX" after "aaa", the diff now also contains a
3421 // deletion for that line, and the modification hunk shifts in the
3422 // base text.
3423 let buffer_text_1 = "aaa\nbbb\nCCC\nddd\neee\n";
3424 let buffer = Buffer::new(
3425 ReplicaId::LOCAL,
3426 BufferId::new(1).unwrap(),
3427 buffer_text_1.to_string(),
3155 );3428 );
3156 3429
3157 assert_eq!(3430 let old_base_snapshot_1 = base_text_buffer.snapshot().clone();
3158 diff.base_text_string().unwrap_or_default(),3431 let old_hunks_1 = compute_hunks(
3159 target_text,3432 Some((Arc::from(initial_base), Rope::from(initial_base))),
3160 "base text does not match stripped annotated target"3433 buffer.snapshot(),
3434 None,
3161 );3435 );
3162 3436
3163 let num_rows = source_text.lines().count() as u32;3437 // Insert "XXX\n" after "aaa\n" in the base text.
3164 let max_point = buffer.max_point();3438 base_text_buffer.edit([(4..4, "XXX\n")]);
3165 let points = (0..=num_rows).map(move |row| {3439 let new_base_str_1: Arc<str> = Arc::from(base_text_buffer.text().as_str());
3166 if row == num_rows && max_point.column > 0 {3440 let new_base_snapshot_1 = base_text_buffer.snapshot();
3167 max_point
3168 } else {
3169 Point::new(row, 0)
3170 }
3171 });
3172 let actual_ranges: Vec<_> = diff.points_to_base_text_points(points, &buffer).0.collect();
3173 3441
3174 assert_eq!(3442 let new_hunks_1 = compute_hunks(
3175 actual_ranges, expected_ranges,3443 Some((new_base_str_1.clone(), Rope::from(new_base_str_1.as_ref()))),
3176 "\nsource (buffer):\n{}\ntarget (base):\n{}\nexpected: {:?}\nactual: {:?}",3444 buffer.snapshot(),
3177 source_text, target_text, expected_ranges, actual_ranges3445 None,
3178 );3446 );
3179 }
3180
3181 fn assert_base_text_rows_to_rows_visual(
3182 buffer: &Entity<language::Buffer>,
3183 diff: &Entity<BufferDiff>,
3184 source_text: &str,
3185 annotated_target: &str,
3186 cx: &mut gpui::TestAppContext,
3187 ) {
3188 let (target_text, expected_ranges) = parse_row_annotations(annotated_target);
3189 3447
3190 let buffer = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());3448 let DiffChanged {
3191 let diff = diff.update(cx, |diff, cx| diff.snapshot(cx));3449 changed_range,
3450 base_text_changed_range,
3451 extended_range: _,
3452 } = compare_hunks(
3453 &new_hunks_1,
3454 &old_hunks_1,
3455 &buffer.snapshot(),
3456 &buffer.snapshot(),
3457 &old_base_snapshot_1,
3458 &new_base_snapshot_1,
3459 );
3192 3460
3461 // The new deletion hunk (XXX) starts at buffer row 1 and the
3462 // modification hunk (ccc → CCC) now has a different
3463 // diff_base_byte_range, so the changed range spans both.
3464 let range = changed_range.unwrap();
3465 assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(3, 0),);
3466 let base_range = base_text_changed_range.unwrap();
3193 assert_eq!(3467 assert_eq!(
3194 diff.base_text_string().unwrap_or_default(),3468 base_range.to_point(&new_base_snapshot_1),
3195 source_text,3469 Point::new(1, 0)..Point::new(4, 0),
3196 "base text does not match source text"
3197 );3470 );
3198 3471
3199 assert_eq!(3472 // --- Scenario 2: Base text changes to match the buffer (hunk disappears) ---
3200 buffer.text(),3473 //
3201 target_text,3474 // Start fresh with a simple base text.
3202 "buffer text does not match stripped annotated target"3475 let simple_base = "one\ntwo\nthree\n";
3476 let mut base_buf_2 = Buffer::new(
3477 ReplicaId::LOCAL,
3478 BufferId::new(100).unwrap(),
3479 simple_base.to_string(),
3203 );3480 );
3204 3481
3205 let num_rows = source_text.lines().count() as u32;3482 let buffer_text_2 = "one\nTWO\nthree\n";
3206 let base_max_point = diff.base_text().max_point();3483 let buffer_2 = Buffer::new(
3207 let points = (0..=num_rows).map(move |row| {3484 ReplicaId::LOCAL,
3208 if row == num_rows && base_max_point.column > 0 {3485 BufferId::new(2).unwrap(),
3209 base_max_point3486 buffer_text_2.to_string(),
3210 } else {3487 );
3211 Point::new(row, 0)
3212 }
3213 });
3214 let actual_ranges: Vec<_> = diff.base_text_points_to_points(points, &buffer).0.collect();
3215 3488
3216 assert_eq!(3489 let old_base_snapshot_2 = base_buf_2.snapshot().clone();
3217 actual_ranges, expected_ranges,3490 let old_hunks_2 = compute_hunks(
3218 "\nsource (base):\n{}\ntarget (buffer):\n{}\nexpected: {:?}\nactual: {:?}",3491 Some((Arc::from(simple_base), Rope::from(simple_base))),
3219 source_text, target_text, expected_ranges, actual_ranges3492 buffer_2.snapshot(),
3493 None,
3220 );3494 );
3221 }
3222 3495
3223 fn parse_row_annotations(annotated_text: &str) -> (String, Vec<Range<Point>>) {3496 // The base text is edited so "two" becomes "TWO", now matching the buffer.
3224 let mut starts: std::collections::HashMap<u32, Point> = std::collections::HashMap::new();3497 base_buf_2.edit([(4..7, "TWO")]);
3225 let mut ends: std::collections::HashMap<u32, Point> = std::collections::HashMap::new();3498 let new_base_str_2: Arc<str> = Arc::from(base_buf_2.text().as_str());
3499 let new_base_snapshot_2 = base_buf_2.snapshot();
3226 3500
3227 let mut clean_text = String::new();3501 let new_hunks_2 = compute_hunks(
3228 let mut current_point = Point::new(0, 0);3502 Some((new_base_str_2.clone(), Rope::from(new_base_str_2.as_ref()))),
3229 let mut chars = annotated_text.chars().peekable();3503 buffer_2.snapshot(),
3504 None,
3505 );
3230 3506
3231 while let Some(c) = chars.next() {3507 let DiffChanged {
3232 if c == '<' {3508 changed_range,
3233 let mut num_str = String::new();3509 base_text_changed_range,
3234 while let Some(&next) = chars.peek() {3510 extended_range: _,
3235 if next.is_ascii_digit() {3511 } = compare_hunks(
3236 num_str.push(chars.next().unwrap());3512 &new_hunks_2,
3237 } else {3513 &old_hunks_2,
3238 break;3514 &buffer_2.snapshot(),
3239 }3515 &buffer_2.snapshot(),
3240 }3516 &old_base_snapshot_2,
3241 if !num_str.is_empty() {3517 &new_base_snapshot_2,
3242 let row_num: u32 = num_str.parse().unwrap();3518 );
3243 starts.insert(row_num, current_point);
3244 3519
3245 if chars.peek() == Some(&'>') {3520 // The old modification hunk (two → TWO) is now gone because the
3246 chars.next();3521 // base text matches the buffer. The changed range covers where the
3247 ends.insert(row_num, current_point);3522 // old hunk used to be.
3248 }3523 let range = changed_range.unwrap();
3249 } else {3524 assert_eq!(
3250 clean_text.push(c);3525 range.to_point(&buffer_2),
3251 current_point.column += 1;3526 Point::new(1, 0)..Point::new(2, 0),
3252 }3527 );
3253 } else if c.is_ascii_digit() {3528 let base_range = base_text_changed_range.unwrap();
3254 let mut num_str = String::from(c);3529 // The old hunk's diff_base_byte_range covered "two\n" (bytes 4..8).
3255 while let Some(&next) = chars.peek() {3530 // anchor_after(4) is right-biased at the start of the deleted "two",
3256 if next.is_ascii_digit() {3531 // so after the edit replacing "two" with "TWO" it resolves past the
3257 num_str.push(chars.next().unwrap());3532 // insertion to Point(1, 3).
3258 } else {3533 assert_eq!(
3259 break;3534 base_range.to_point(&new_base_snapshot_2),
3260 }3535 Point::new(1, 3)..Point::new(2, 0),
3261 }3536 );
3262 if chars.peek() == Some(&'>') {
3263 chars.next();
3264 let row_num: u32 = num_str.parse().unwrap();
3265 ends.insert(row_num, current_point);
3266 } else {
3267 for ch in num_str.chars() {
3268 clean_text.push(ch);
3269 current_point.column += 1;
3270 }
3271 }
3272 } else if c == '\n' {
3273 clean_text.push(c);
3274 current_point.row += 1;
3275 current_point.column = 0;
3276 } else {
3277 clean_text.push(c);
3278 current_point.column += 1;
3279 }
3280 }
3281 3537
3282 let max_row = starts.keys().chain(ends.keys()).max().copied().unwrap_or(0);3538 // --- Scenario 3: Base text edit changes one hunk but not another ---
3283 let mut ranges: Vec<Range<Point>> = Vec::new();3539 //
3284 for row in 0..=max_row {3540 // Two modification hunks exist. Only one of them is resolved by
3285 let start = starts.get(&row).copied().unwrap_or(Point::new(0, 0));3541 // the base text change; the other remains identical.
3286 let end = ends.get(&row).copied().unwrap_or(start);3542 let base_3 = "aaa\nbbb\nccc\nddd\neee\n";
3287 ranges.push(start..end);3543 let mut base_buf_3 = Buffer::new(
3288 }3544 ReplicaId::LOCAL,
3545 BufferId::new(101).unwrap(),
3546 base_3.to_string(),
3547 );
3289 3548
3290 (clean_text, ranges)3549 let buffer_text_3 = "aaa\nBBB\nccc\nDDD\neee\n";
3291 }3550 let buffer_3 = Buffer::new(
3551 ReplicaId::LOCAL,
3552 BufferId::new(3).unwrap(),
3553 buffer_text_3.to_string(),
3554 );
3292 3555
3293 fn make_diff(3556 let old_base_snapshot_3 = base_buf_3.snapshot().clone();
3294 base_text: &str,3557 let old_hunks_3 = compute_hunks(
3295 buffer_text: &str,3558 Some((Arc::from(base_3), Rope::from(base_3))),
3296 cx: &mut gpui::TestAppContext,3559 buffer_3.snapshot(),
3297 ) -> (Entity<language::Buffer>, Entity<BufferDiff>) {3560 None,
3298 let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));3561 );
3299 let diff = cx.new(|cx| {
3300 BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
3301 });
3302 (buffer, diff)
3303 }
3304 3562
3305 #[gpui::test]3563 // Change "ddd" to "DDD" in the base text so that hunk disappears,
3306 async fn test_row_translation_visual(cx: &mut gpui::TestAppContext) {3564 // but "bbb" stays, so its hunk remains.
3307 use unindent::Unindent;3565 base_buf_3.edit([(12..15, "DDD")]);
3566 let new_base_str_3: Arc<str> = Arc::from(base_buf_3.text().as_str());
3567 let new_base_snapshot_3 = base_buf_3.snapshot();
3308 3568
3309 {3569 let new_hunks_3 = compute_hunks(
3310 let buffer_text = "3570 Some((new_base_str_3.clone(), Rope::from(new_base_str_3.as_ref()))),
3311 aaa3571 buffer_3.snapshot(),
3312 bbb3572 None,
3313 ccc3573 );
3314 "
3315 .unindent();
3316 let annotated_base = "
3317 <0>aaa
3318 <1>bbb
3319 <2>ccc
3320 <3>"
3321 .unindent();
3322 let (base_text, _) = parse_row_annotations(&annotated_base);
3323 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3324 assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3325 }
3326 3574
3327 {3575 let DiffChanged {
3328 let base_text = "3576 changed_range,
3329 aaa3577 base_text_changed_range,
3330 bbb3578 extended_range: _,
3331 ccc3579 } = compare_hunks(
3332 "3580 &new_hunks_3,
3333 .unindent();3581 &old_hunks_3,
3334 let annotated_buffer = "3582 &buffer_3.snapshot(),
3335 <0>aaa3583 &buffer_3.snapshot(),
3336 <1>bbb3584 &old_base_snapshot_3,
3337 <2>ccc3585 &new_base_snapshot_3,
3338 <3>"3586 );
3339 .unindent();
3340 let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3341 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3342 assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3343 }
3344 3587
3345 {3588 // Only the second hunk (ddd → DDD) disappeared; the first hunk
3346 let buffer_text = "3589 // (bbb → BBB) is unchanged, so the changed range covers only line 3.
3347 XXX3590 let range = changed_range.unwrap();
3348 bbb3591 assert_eq!(
3349 ccc3592 range.to_point(&buffer_3),
3350 "3593 Point::new(3, 0)..Point::new(4, 0),
3351 .unindent();3594 );
3352 let annotated_base = "3595 let base_range = base_text_changed_range.unwrap();
3353 <0<1aaa3596 // anchor_after(12) is right-biased at the start of deleted "ddd",
3354 0>1>bbb3597 // so after the edit replacing "ddd" with "DDD" it resolves past
3355 <2>ccc3598 // the insertion to Point(3, 3).
3356 <3>"3599 assert_eq!(
3357 .unindent();3600 base_range.to_point(&new_base_snapshot_3),
3358 let (base_text, _) = parse_row_annotations(&annotated_base);3601 Point::new(3, 3)..Point::new(4, 0),
3359 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);3602 );
3360 assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3361 }
3362 3603
3363 {3604 // --- Scenario 4: Both buffer and base text change simultaneously ---
3364 let buffer_text = "3605 //
3365 aaa3606 // The buffer gains an edit that introduces a new hunk while the
3366 NEW3607 // base text also changes.
3367 ccc3608 let base_4 = "alpha\nbeta\ngamma\ndelta\n";
3368 "3609 let mut base_buf_4 = Buffer::new(
3369 .unindent();3610 ReplicaId::LOCAL,
3370 let annotated_base = "3611 BufferId::new(102).unwrap(),
3371 <0>aaa3612 base_4.to_string(),
3372 <1><2>ccc3613 );
3373 <3>"
3374 .unindent();
3375 let (base_text, _) = parse_row_annotations(&annotated_base);
3376 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3377 assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3378 }
3379 3614
3380 {3615 let buffer_text_4 = "alpha\nBETA\ngamma\ndelta\n";
3381 let base_text = "3616 let mut buffer_4 = Buffer::new(
3382 aaa3617 ReplicaId::LOCAL,
3383 ccc3618 BufferId::new(4).unwrap(),
3384 "3619 buffer_text_4.to_string(),
3385 .unindent();3620 );
3386 let annotated_buffer = "
3387 <0>aaa
3388 <1NEW
3389 1>ccc
3390 <2>"
3391 .unindent();
3392 let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3393 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3394 assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3395 }
3396 3621
3397 {3622 let old_base_snapshot_4 = base_buf_4.snapshot().clone();
3398 let buffer_text = "aaa\nbbb";3623 let old_buffer_snapshot_4 = buffer_4.snapshot().clone();
3399 let annotated_base = "<0>aaa\n<1>bbb<2>";3624 let old_hunks_4 = compute_hunks(
3400 let (base_text, _) = parse_row_annotations(annotated_base);3625 Some((Arc::from(base_4), Rope::from(base_4))),
3401 let (buffer, diff) = make_diff(&base_text, buffer_text, cx);3626 buffer_4.snapshot(),
3402 assert_rows_to_base_text_rows_visual(&buffer, &diff, buffer_text, annotated_base, cx);3627 None,
3403 assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, annotated_base, cx);3628 );
3404 }
3405 3629
3406 {3630 // Edit the buffer: change "delta" to "DELTA" (new modification hunk).
3407 let base_text = "3631 buffer_4.edit_via_marked_text(
3408 aaa3632 &"
3409 bbb3633 alpha
3410 ccc3634 BETA
3635 gamma
3636 «DELTA»
3411 "3637 "
3412 .unindent();3638 .unindent(),
3413 let annotated_buffer = "3639 );
3414 <0<1XXX
3415 0>1>bbb
3416 <2>ccc
3417 <3>"
3418 .unindent();
3419 let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3420 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3421 assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3422 }
3423 3640
3424 {3641 // Edit the base text: change "beta" to "BETA" (resolves that hunk).
3425 let buffer_text = "3642 base_buf_4.edit([(6..10, "BETA")]);
3426 aaa3643 let new_base_str_4: Arc<str> = Arc::from(base_buf_4.text().as_str());
3427 bbb3644 let new_base_snapshot_4 = base_buf_4.snapshot();
3428 XXX
3429 "
3430 .unindent();
3431 let annotated_base = "
3432 <0>aaa
3433 <1>bbb
3434 <2<3ccc
3435 2>3>"
3436 .unindent();
3437 let (base_text, _) = parse_row_annotations(&annotated_base);
3438 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3439 assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3440 }
3441 3645
3442 {3646 let new_hunks_4 = compute_hunks(
3443 let base_text = "3647 Some((new_base_str_4.clone(), Rope::from(new_base_str_4.as_ref()))),
3444 aaa3648 buffer_4.snapshot(),
3445 bbb3649 None,
3446 ccc3650 );
3447 "
3448 .unindent();
3449 let annotated_buffer = "
3450 <0>aaa
3451 <1>bbb
3452 <2<3XXX
3453 2>3>"
3454 .unindent();
3455 let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3456 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3457 assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3458 }
3459 3651
3460 {3652 let DiffChanged {
3461 let buffer_text = "3653 changed_range,
3462 aaa3654 base_text_changed_range,
3463 ccc3655 extended_range: _,
3464 "3656 } = compare_hunks(
3465 .unindent();3657 &new_hunks_4,
3466 let annotated_base = "3658 &old_hunks_4,
3467 <0>aaa3659 &old_buffer_snapshot_4,
3468 <1DELETED3660 &buffer_4.snapshot(),
3469 1>ccc3661 &old_base_snapshot_4,
3470 <2>"3662 &new_base_snapshot_4,
3471 .unindent();3663 );
3472 let (base_text, _) = parse_row_annotations(&annotated_base);
3473 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3474 assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3475 }
3476 3664
3477 {3665 // The old BETA hunk (line 1) is gone and a new DELTA hunk (line 3)
3478 let base_text = "3666 // appeared, so the changed range spans from line 1 through line 4.
3479 aaa3667 let range = changed_range.unwrap();
3480 DELETED3668 assert_eq!(
3481 ccc3669 range.to_point(&buffer_4),
3482 "3670 Point::new(1, 0)..Point::new(4, 0),
3483 .unindent();3671 );
3484 let annotated_buffer = "3672 let base_range = base_text_changed_range.unwrap();
3485 <0>aaa3673 // The old BETA hunk's base range started at byte 6 ("beta"). After
3486 <1><2>ccc3674 // the base text edit replacing "beta" with "BETA", anchor_after(6)
3487 <3>"3675 // resolves past the insertion to Point(1, 4).
3488 .unindent();3676 assert_eq!(
3489 let (buffer_text, _) = parse_row_annotations(&annotated_buffer);3677 base_range.to_point(&new_base_snapshot_4),
3490 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);3678 Point::new(1, 4)..Point::new(4, 0),
3491 assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);3679 );
3492 }
3493 }3680 }
3494 3681
3495 #[gpui::test]3682 #[gpui::test(iterations = 100)]
3496 async fn test_row_translation_with_edits_since_diff(cx: &mut gpui::TestAppContext) {3683 async fn test_patch_for_range_random(cx: &mut TestAppContext, mut rng: StdRng) {
3497 use unindent::Unindent;3684 fn gen_line(rng: &mut StdRng) -> String {
3498 3685 if rng.random_bool(0.2) {
3499 {3686 "\n".to_owned()
3500 let base_text = "3687 } else {
3501 aaa3688 let c = rng.random_range('A'..='Z');
3502 bbb3689 format!("{c}{c}{c}\n")
3503 ccc3690 }
3504 "
3505 .unindent();
3506 let buffer_text = base_text.clone();
3507 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3508
3509 buffer.update(cx, |buffer, cx| {
3510 buffer.edit([(4..7, "XXX")], None, cx);
3511 });
3512
3513 let new_buffer_text = "
3514 aaa
3515 XXX
3516 ccc
3517 "
3518 .unindent();
3519 let annotated_base = "
3520 <0>aaa
3521 <1bbb1>
3522 <2>ccc
3523 <3>"
3524 .unindent();
3525 assert_rows_to_base_text_rows_visual(
3526 &buffer,
3527 &diff,
3528 &new_buffer_text,
3529 &annotated_base,
3530 cx,
3531 );
3532 }
3533
3534 {
3535 let base_text = "
3536 aaa
3537 bbb
3538 ccc
3539 "
3540 .unindent();
3541 let buffer_text = base_text.clone();
3542 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3543
3544 buffer.update(cx, |buffer, cx| {
3545 buffer.edit([(4..7, "XXX")], None, cx);
3546 });
3547
3548 let annotated_buffer = "
3549 <0>aaa
3550 <1XXX1>
3551 <2>ccc
3552 <3>"
3553 .unindent();
3554 assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3555 }
3556
3557 {
3558 let base_text = "
3559 aaa
3560 bbb
3561 ccc
3562 "
3563 .unindent();
3564 let buffer_text = base_text.clone();
3565 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3566
3567 buffer.update(cx, |buffer, cx| {
3568 buffer.edit([(4..4, "NEW\n")], None, cx);
3569 });
3570
3571 let new_buffer_text = "
3572 aaa
3573 NEW
3574 bbb
3575 ccc
3576 "
3577 .unindent();
3578 let annotated_base = "
3579 <0>aaa
3580 <1><2>bbb
3581 <3>ccc
3582 <4>"
3583 .unindent();
3584 assert_rows_to_base_text_rows_visual(
3585 &buffer,
3586 &diff,
3587 &new_buffer_text,
3588 &annotated_base,
3589 cx,
3590 );
3591 }
3592
3593 {
3594 let base_text = "
3595 aaa
3596 bbb
3597 ccc
3598 "
3599 .unindent();
3600 let buffer_text = base_text.clone();
3601 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3602
3603 buffer.update(cx, |buffer, cx| {
3604 buffer.edit([(4..4, "NEW\n")], None, cx);
3605 });
3606
3607 let annotated_buffer = "
3608 <0>aaa
3609 <1NEW
3610 1>bbb
3611 <2>ccc
3612 <3>"
3613 .unindent();
3614 assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3615 }
3616
3617 {
3618 let base_text = "
3619 aaa
3620 bbb
3621 ccc
3622 "
3623 .unindent();
3624 let buffer_text = base_text.clone();
3625 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3626
3627 buffer.update(cx, |buffer, cx| {
3628 buffer.edit([(4..8, "")], None, cx);
3629 });
3630
3631 let new_buffer_text = "
3632 aaa
3633 ccc
3634 "
3635 .unindent();
3636 let annotated_base = "
3637 <0>aaa
3638 <1bbb
3639 1>ccc
3640 <2>"
3641 .unindent();
3642 assert_rows_to_base_text_rows_visual(
3643 &buffer,
3644 &diff,
3645 &new_buffer_text,
3646 &annotated_base,
3647 cx,
3648 );
3649 }
3650
3651 {
3652 let base_text = "
3653 aaa
3654 bbb
3655 ccc
3656 "
3657 .unindent();
3658 let buffer_text = base_text.clone();
3659 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3660
3661 buffer.update(cx, |buffer, cx| {
3662 buffer.edit([(4..8, "")], None, cx);
3663 });
3664
3665 let annotated_buffer = "
3666 <0>aaa
3667 <1><2>ccc
3668 <3>"
3669 .unindent();
3670 assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3671 }
3672
3673 {
3674 let base_text = "
3675 aaa
3676 bbb
3677 ccc
3678 ddd
3679 eee
3680 "
3681 .unindent();
3682 let buffer_text = "
3683 aaa
3684 XXX
3685 ccc
3686 ddd
3687 eee
3688 "
3689 .unindent();
3690 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3691
3692 buffer.update(cx, |buffer, cx| {
3693 buffer.edit([(12..15, "YYY")], None, cx);
3694 });
3695
3696 let new_buffer_text = "
3697 aaa
3698 XXX
3699 ccc
3700 YYY
3701 eee
3702 "
3703 .unindent();
3704 let annotated_base = "
3705 <0>aaa
3706 <1<2bbb
3707 1>2>ccc
3708 <3ddd3>
3709 <4>eee
3710 <5>"
3711 .unindent();
3712 assert_rows_to_base_text_rows_visual(
3713 &buffer,
3714 &diff,
3715 &new_buffer_text,
3716 &annotated_base,
3717 cx,
3718 );
3719 }
3720
3721 {
3722 let base_text = "
3723 aaa
3724 bbb
3725 ccc
3726 ddd
3727 eee
3728 "
3729 .unindent();
3730 let buffer_text = "
3731 aaa
3732 XXX
3733 ccc
3734 ddd
3735 eee
3736 "
3737 .unindent();
3738 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3739
3740 buffer.update(cx, |buffer, cx| {
3741 buffer.edit([(12..15, "YYY")], None, cx);
3742 });
3743
3744 let annotated_buffer = "
3745 <0>aaa
3746 <1<2XXX
3747 1>2>ccc
3748 <3YYY3>
3749 <4>eee
3750 <5>"
3751 .unindent();
3752 assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3753 }
3754
3755 {
3756 let base_text = "
3757 aaa
3758 bbb
3759 ccc
3760 "
3761 .unindent();
3762 let buffer_text = base_text.clone();
3763 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3764
3765 buffer.update(cx, |buffer, cx| {
3766 buffer.edit([(0..0, "NEW\n")], None, cx);
3767 });
3768
3769 let new_buffer_text = "
3770 NEW
3771 aaa
3772 bbb
3773 ccc
3774 "
3775 .unindent();
3776 let annotated_base = "
3777 <0><1>aaa
3778 <2>bbb
3779 <3>ccc
3780 <4>"
3781 .unindent();
3782 assert_rows_to_base_text_rows_visual(
3783 &buffer,
3784 &diff,
3785 &new_buffer_text,
3786 &annotated_base,
3787 cx,
3788 );
3789 }
3790
3791 {
3792 let base_text = "
3793 aaa
3794 bbb
3795 ccc
3796 "
3797 .unindent();
3798 let buffer_text = base_text.clone();
3799 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3800
3801 buffer.update(cx, |buffer, cx| {
3802 buffer.edit([(0..0, "NEW\n")], None, cx);
3803 });
3804
3805 let annotated_buffer = "
3806 <0NEW
3807 0>aaa
3808 <1>bbb
3809 <2>ccc
3810 <3>"
3811 .unindent();
3812 assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3813 }
3814
3815 {
3816 let base_text = "
3817 aaa
3818 bbb
3819 ccc
3820 "
3821 .unindent();
3822 let buffer_text = base_text.clone();
3823 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3824
3825 buffer.update(cx, |buffer, cx| {
3826 buffer.edit([(12..12, "NEW\n")], None, cx);
3827 });
3828
3829 let new_buffer_text = "
3830 aaa
3831 bbb
3832 ccc
3833 NEW
3834 "
3835 .unindent();
3836 let annotated_base = "
3837 <0>aaa
3838 <1>bbb
3839 <2>ccc
3840 <3><4>"
3841 .unindent();
3842 assert_rows_to_base_text_rows_visual(
3843 &buffer,
3844 &diff,
3845 &new_buffer_text,
3846 &annotated_base,
3847 cx,
3848 );
3849 }
3850
3851 {
3852 let base_text = "
3853 aaa
3854 bbb
3855 ccc
3856 "
3857 .unindent();
3858 let buffer_text = base_text.clone();
3859 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3860
3861 buffer.update(cx, |buffer, cx| {
3862 buffer.edit([(12..12, "NEW\n")], None, cx);
3863 });
3864
3865 let annotated_buffer = "
3866 <0>aaa
3867 <1>bbb
3868 <2>ccc
3869 <3NEW
3870 3>"
3871 .unindent();
3872 assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3873 }
3874
3875 {
3876 let base_text = "";
3877 let buffer_text = "aaa\n";
3878 let (buffer, diff) = make_diff(base_text, buffer_text, cx);
3879
3880 buffer.update(cx, |buffer, cx| {
3881 buffer.edit([(4..4, "bbb\n")], None, cx);
3882 });
3883
3884 let new_buffer_text = "
3885 aaa
3886 bbb
3887 "
3888 .unindent();
3889 let annotated_base = "<0><1><2>";
3890 assert_rows_to_base_text_rows_visual(
3891 &buffer,
3892 &diff,
3893 &new_buffer_text,
3894 &annotated_base,
3895 cx,
3896 );
3897 }3691 }
3898 3692
3899 {3693 fn gen_text(rng: &mut StdRng, line_count: usize) -> String {
3900 let base_text = "aaa\n";3694 (0..line_count).map(|_| gen_line(rng)).collect()
3901 let buffer_text = "";
3902 let (buffer, diff) = make_diff(base_text, buffer_text, cx);
3903
3904 buffer.update(cx, |buffer, cx| {
3905 buffer.edit([(0..0, "bbb\n")], None, cx);
3906 });
3907
3908 let new_buffer_text = "bbb\n";
3909 let annotated_base = "
3910 <0<1aaa
3911 0>1>"
3912 .unindent();
3913 assert_rows_to_base_text_rows_visual(
3914 &buffer,
3915 &diff,
3916 &new_buffer_text,
3917 &annotated_base,
3918 cx,
3919 );
3920 }3695 }
3921 3696
3922 {3697 fn gen_edits_from(rng: &mut StdRng, base: &str) -> String {
3923 let base_text = "";3698 let mut old_lines: Vec<&str> = base.lines().collect();
3924 let buffer_text = "";3699 let mut result = String::new();
3925 let (buffer, diff) = make_diff(base_text, buffer_text, cx);
3926 3700
3927 buffer.update(cx, |buffer, cx| {3701 while !old_lines.is_empty() {
3928 buffer.edit([(0..0, "aaa\n")], None, cx);3702 let unchanged_count = rng.random_range(0..=old_lines.len());
3929 });3703 for _ in 0..unchanged_count {
3704 if old_lines.is_empty() {
3705 break;
3706 }
3707 result.push_str(old_lines.remove(0));
3708 result.push('\n');
3709 }
3930 3710
3931 let new_buffer_text = "aaa\n";3711 if old_lines.is_empty() {
3932 let annotated_base = "<0><1>";3712 break;
3933 assert_rows_to_base_text_rows_visual(3713 }
3934 &buffer,
3935 &diff,
3936 &new_buffer_text,
3937 &annotated_base,
3938 cx,
3939 );
3940 }
3941 3714
3942 {3715 let deleted_count = rng.random_range(0..=old_lines.len().min(3));
3943 let base_text = "3716 for _ in 0..deleted_count {
3944 aaa3717 if old_lines.is_empty() {
3945 bbb3718 break;
3946 ccc3719 }
3947 "3720 old_lines.remove(0);
3948 .unindent();3721 }
3949 let buffer_text = "
3950 aaa
3951 XXX
3952 ccc
3953 "
3954 .unindent();
3955 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3956 3722
3957 buffer.update(cx, |buffer, cx| {3723 let minimum_added = if deleted_count == 0 { 1 } else { 0 };
3958 buffer.edit([(4..7, "YYY")], None, cx);3724 let added_count = rng.random_range(minimum_added..=3);
3959 });3725 for _ in 0..added_count {
3726 result.push_str(&gen_line(rng));
3727 }
3728 }
3960 3729
3961 let new_buffer_text = "3730 result
3962 aaa
3963 YYY
3964 ccc
3965 "
3966 .unindent();
3967 let annotated_base = "
3968 <0>aaa
3969 <1<2bbb
3970 1>2>ccc
3971 <3>"
3972 .unindent();
3973 assert_rows_to_base_text_rows_visual(
3974 &buffer,
3975 &diff,
3976 &new_buffer_text,
3977 &annotated_base,
3978 cx,
3979 );
3980 }3731 }
3981 3732
3982 {3733 fn random_point_in_text(rng: &mut StdRng, lines: &[&str]) -> Point {
3983 let base_text = "3734 if lines.is_empty() {
3984 aaa3735 return Point::zero();
3985 bbb3736 }
3986 ccc3737 let row = rng.random_range(0..lines.len() as u32);
3987 "3738 let line = lines[row as usize];
3988 .unindent();3739 let col = if line.is_empty() {
3989 let buffer_text = "3740 0
3990 aaa3741 } else {
3991 XXX3742 rng.random_range(0..=line.len() as u32)
3992 ccc3743 };
3993 "3744 Point::new(row, col)
3994 .unindent();
3995 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3996
3997 buffer.update(cx, |buffer, cx| {
3998 buffer.edit([(4..7, "YYY")], None, cx);
3999 });
4000
4001 let annotated_buffer = "
4002 <0>aaa
4003 <1<2YYY
4004 1>2>ccc
4005 <3>"
4006 .unindent();
4007 assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
4008 }3745 }
4009 3746
4010 {3747 fn random_range_in_text(rng: &mut StdRng, lines: &[&str]) -> RangeInclusive<Point> {
4011 let base_text = "3748 let start = random_point_in_text(rng, lines);
4012 aaa3749 let end = random_point_in_text(rng, lines);
4013 bbb3750 if start <= end {
4014 ccc3751 start..=end
4015 "3752 } else {
4016 .unindent();3753 end..=start
4017 let buffer_text = "3754 }
4018 aaa
4019 XXXX
4020 ccc
4021 "
4022 .unindent();
4023 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4024
4025 buffer.update(cx, |buffer, cx| {
4026 buffer.edit([(4..6, "YY")], None, cx);
4027 });
4028
4029 let new_buffer_text = "
4030 aaa
4031 YYXX
4032 ccc
4033 "
4034 .unindent();
4035 let annotated_base = "
4036 <0>aaa
4037 <1<2bbb
4038 1>2>ccc
4039 <3>"
4040 .unindent();
4041 assert_rows_to_base_text_rows_visual(
4042 &buffer,
4043 &diff,
4044 &new_buffer_text,
4045 &annotated_base,
4046 cx,
4047 );
4048 }3755 }
4049 3756
4050 {3757 fn points_in_range(range: &RangeInclusive<Point>, lines: &[&str]) -> Vec<Point> {
4051 let base_text = "3758 let mut points = Vec::new();
4052 aaa3759 for row in range.start().row..=range.end().row {
4053 bbb3760 if row as usize >= lines.len() {
4054 ccc3761 points.push(Point::new(row, 0));
4055 "3762 continue;
4056 .unindent();3763 }
4057 let buffer_text = "3764 let line = lines[row as usize];
4058 aaa3765 let start_col = if row == range.start().row {
4059 XXXX3766 range.start().column
4060 ccc3767 } else {
4061 "3768 0
4062 .unindent();3769 };
4063 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);3770 let end_col = if row == range.end().row {
4064 3771 range.end().column
4065 buffer.update(cx, |buffer, cx| {3772 } else {
4066 buffer.edit([(6..8, "YY")], None, cx);3773 line.len() as u32
4067 });3774 };
4068 3775 for col in start_col..=end_col {
4069 let new_buffer_text = "3776 points.push(Point::new(row, col));
4070 aaa3777 }
4071 XXYY3778 }
4072 ccc3779 points
4073 "
4074 .unindent();
4075 let annotated_base = "
4076 <0>aaa
4077 <1<2bbb
4078 1>2>ccc
4079 <3>"
4080 .unindent();
4081 assert_rows_to_base_text_rows_visual(
4082 &buffer,
4083 &diff,
4084 &new_buffer_text,
4085 &annotated_base,
4086 cx,
4087 );
4088 }3780 }
4089 3781
4090 {3782 let rng = &mut rng;
4091 let base_text = "
4092 aaa
4093 bbb
4094 ccc
4095 "
4096 .unindent();
4097 let buffer_text = "
4098 aaa
4099 XXX
4100 ccc
4101 "
4102 .unindent();
4103 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4104
4105 buffer.update(cx, |buffer, cx| {
4106 buffer.edit([(4..4, "NEW")], None, cx);
4107 });
4108
4109 let new_buffer_text = "
4110 aaa
4111 NEWXXX
4112 ccc
4113 "
4114 .unindent();
4115 let annotated_base = "
4116 <0>aaa
4117 <1<2bbb
4118 1>2>ccc
4119 <3>"
4120 .unindent();
4121 assert_rows_to_base_text_rows_visual(
4122 &buffer,
4123 &diff,
4124 &new_buffer_text,
4125 &annotated_base,
4126 cx,
4127 );
4128 }
4129 3783
4130 {3784 let line_count = rng.random_range(5..20);
4131 let base_text = "3785 let base_text = gen_text(rng, line_count);
4132 aaa3786 let initial_buffer_text = gen_edits_from(rng, &base_text);
4133 bbb
4134 ccc
4135 "
4136 .unindent();
4137 let buffer_text = "
4138 aaa
4139 XXX
4140 ccc
4141 "
4142 .unindent();
4143 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4144 3787
4145 buffer.update(cx, |buffer, cx| {3788 let mut buffer = Buffer::new(
4146 buffer.edit([(7..7, "NEW")], None, cx);3789 ReplicaId::LOCAL,
4147 });3790 BufferId::new(1).unwrap(),
3791 initial_buffer_text.clone(),
3792 );
4148 3793
4149 let new_buffer_text = "3794 let diff = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
4150 aaa
4151 XXXNEW
4152 ccc
4153 "
4154 .unindent();
4155 let annotated_base = "
4156 <0>aaa
4157 <1<2bbb
4158 1>2>ccc
4159 <3>"
4160 .unindent();
4161 assert_rows_to_base_text_rows_visual(
4162 &buffer,
4163 &diff,
4164 &new_buffer_text,
4165 &annotated_base,
4166 cx,
4167 );
4168 }
4169 3795
4170 {3796 let edit_count = rng.random_range(1..=5);
4171 let base_text = "3797 for _ in 0..edit_count {
4172 aaa3798 let buffer_text = buffer.text();
4173 bbb3799 if buffer_text.is_empty() {
4174 ccc3800 buffer.edit([(0..0, gen_line(rng))]);
4175 "3801 } else {
4176 .unindent();3802 let lines: Vec<&str> = buffer_text.lines().collect();
4177 let buffer_text = "3803 let start_row = rng.random_range(0..lines.len());
4178 aaa3804 let end_row = rng.random_range(start_row..=lines.len().min(start_row + 3));
4179 ccc
4180 "
4181 .unindent();
4182 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4183 3805
4184 buffer.update(cx, |buffer, cx| {3806 let start_col = if start_row < lines.len() {
4185 buffer.edit([(4..4, "NEW\n")], None, cx);3807 rng.random_range(0..=lines[start_row].len())
4186 });3808 } else {
3809 0
3810 };
3811 let end_col = if end_row < lines.len() {
3812 rng.random_range(0..=lines[end_row].len())
3813 } else {
3814 0
3815 };
4187 3816
4188 let new_buffer_text = "3817 let start_offset = buffer
4189 aaa3818 .point_to_offset(Point::new(start_row as u32, start_col as u32))
4190 NEW3819 .min(buffer.len());
4191 ccc3820 let end_offset = buffer
4192 "3821 .point_to_offset(Point::new(end_row as u32, end_col as u32))
4193 .unindent();3822 .min(buffer.len());
4194 let annotated_base = "
4195 <0>aaa
4196 <1<2bbb
4197 1>2>ccc
4198 <3>"
4199 .unindent();
4200 assert_rows_to_base_text_rows_visual(
4201 &buffer,
4202 &diff,
4203 &new_buffer_text,
4204 &annotated_base,
4205 cx,
4206 );
4207 }
4208 3823
4209 {3824 let (start, end) = if start_offset <= end_offset {
4210 let base_text = "3825 (start_offset, end_offset)
4211 aaa3826 } else {
4212 bbb3827 (end_offset, start_offset)
4213 ccc3828 };
4214 "
4215 .unindent();
4216 let buffer_text = "
4217 aaa
4218 ccc
4219 "
4220 .unindent();
4221 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4222 3829
4223 buffer.update(cx, |buffer, cx| {3830 let new_text = if rng.random_bool(0.3) {
4224 buffer.edit([(4..4, "NEW\n")], None, cx);3831 String::new()
4225 });3832 } else {
3833 let line_count = rng.random_range(0..=2);
3834 gen_text(rng, line_count)
3835 };
4226 3836
4227 let annotated_buffer = "3837 buffer.edit([(start..end, new_text)]);
4228 <0>aaa3838 }
4229 <1<2NEW
4230 1>2>ccc
4231 <3>"
4232 .unindent();
4233 assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
4234 }3839 }
4235 3840
4236 {3841 let buffer_snapshot = buffer.snapshot();
4237 let base_text = "
4238 aaa
4239 bbb
4240 ccc
4241 ddd
4242 "
4243 .unindent();
4244 let buffer_text = "
4245 aaa
4246 ddd
4247 "
4248 .unindent();
4249 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4250 3842
4251 buffer.update(cx, |buffer, cx| {3843 let buffer_text = buffer_snapshot.text();
4252 buffer.edit([(4..4, "XXX\nYYY\n")], None, cx);3844 let buffer_lines: Vec<&str> = buffer_text.lines().collect();
4253 });3845 let base_lines: Vec<&str> = base_text.lines().collect();
4254 3846
4255 let new_buffer_text = "3847 let test_count = 10;
4256 aaa3848 for _ in 0..test_count {
4257 XXX3849 let range = random_range_in_text(rng, &buffer_lines);
4258 YYY3850 let points = points_in_range(&range, &buffer_lines);
4259 ddd
4260 "
4261 .unindent();
4262 let annotated_base = "
4263 <0>aaa
4264 <1<2<3bbb
4265 ccc
4266 1>2>3>ddd
4267 <4>"
4268 .unindent();
4269 assert_rows_to_base_text_rows_visual(
4270 &buffer,
4271 &diff,
4272 &new_buffer_text,
4273 &annotated_base,
4274 cx,
4275 );
4276 }
4277 3851
4278 {3852 let optimized_patch = diff.patch_for_buffer_range(range.clone(), &buffer_snapshot);
4279 let base_text = "3853 let naive_patch = diff.patch_for_buffer_range_naive(&buffer_snapshot);
4280 aaa
4281 bbb
4282 ccc
4283 ddd
4284 "
4285 .unindent();
4286 let buffer_text = "
4287 aaa
4288 ddd
4289 "
4290 .unindent();
4291 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4292 3854
4293 buffer.update(cx, |buffer, cx| {3855 for point in points {
4294 buffer.edit([(4..4, "XXX\nYYY\n")], None, cx);3856 let optimized_edit = optimized_patch.edit_for_old_position(point);
4295 });3857 let naive_edit = naive_patch.edit_for_old_position(point);
4296 3858
4297 let annotated_buffer = "3859 assert_eq!(
4298 <0>aaa3860 optimized_edit,
4299 <1<2<3XXX3861 naive_edit,
4300 YYY3862 "patch_for_buffer_range mismatch at point {:?} in range {:?}\nbase_text: {:?}\ninitial_buffer: {:?}\ncurrent_buffer: {:?}",
4301 1>2>3>ddd3863 point,
4302 <4>"3864 range,
4303 .unindent();3865 base_text,
4304 assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);3866 initial_buffer_text,
3867 buffer_snapshot.text()
3868 );
3869 }
4305 }3870 }
4306 3871
4307 {3872 for _ in 0..test_count {
4308 let base_text = "3873 let range = random_range_in_text(rng, &base_lines);
4309 aaa3874 let points = points_in_range(&range, &base_lines);
4310 bbb
4311 ccc
4312 "
4313 .unindent();
4314 let buffer_text = "
4315 aaa
4316 XXXX
4317 ccc
4318 "
4319 .unindent();
4320 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4321 3875
4322 buffer.update(cx, |buffer, cx| {3876 let optimized_patch = diff.patch_for_base_text_range(range.clone(), &buffer_snapshot);
4323 buffer.edit([(2..10, "YY\nZZ")], None, cx);3877 let naive_patch = diff.patch_for_base_text_range_naive(&buffer_snapshot);
4324 });
4325 3878
4326 let new_buffer_text = "3879 for point in points {
4327 aaYY3880 let optimized_edit = optimized_patch.edit_for_old_position(point);
4328 ZZcc3881 let naive_edit = naive_patch.edit_for_old_position(point);
4329 "
4330 .unindent();
4331 let annotated_base = "
4332 <0>aa<1a
4333 bbb
4334 c1>cc
4335 <2>"
4336 .unindent();
4337 assert_rows_to_base_text_rows_visual(
4338 &buffer,
4339 &diff,
4340 &new_buffer_text,
4341 &annotated_base,
4342 cx,
4343 );
4344 }
4345
4346 {
4347 let base_text = "
4348 aaa
4349 bbb
4350 ccc
4351 "
4352 .unindent();
4353 let buffer_text = "
4354 aaa
4355 XXXX
4356 ccc
4357 "
4358 .unindent();
4359 let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4360
4361 buffer.update(cx, |buffer, cx| {
4362 buffer.edit([(0..9, "ZZ\n")], None, cx);
4363 });
4364 3882
4365 let new_buffer_text = "3883 assert_eq!(
4366 ZZ3884 optimized_edit,
4367 ccc3885 naive_edit,
4368 "3886 "patch_for_base_text_range mismatch at point {:?} in range {:?}\nbase_text: {:?}\ninitial_buffer: {:?}\ncurrent_buffer: {:?}",
4369 .unindent();3887 point,
4370 let annotated_base = "3888 range,
4371 <0<1aaa3889 base_text,
4372 bbb3890 initial_buffer_text,
4373 0>1>ccc3891 buffer_snapshot.text()
4374 <2>"3892 );
4375 .unindent();3893 }
4376 assert_rows_to_base_text_rows_visual(
4377 &buffer,
4378 &diff,
4379 &new_buffer_text,
4380 &annotated_base,
4381 cx,
4382 );
4383 }3894 }
4384 }3895 }
4385
4386 #[gpui::test]
4387 async fn test_row_translation_no_base_text(cx: &mut gpui::TestAppContext) {
4388 let buffer_text = "aaa\nbbb\nccc\n";
4389 let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
4390 let diff = cx.new(|cx| BufferDiff::new(&buffer.read(cx).text_snapshot(), cx));
4391
4392 let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
4393 let diff_snapshot = diff.update(cx, |diff, cx| diff.snapshot(cx));
4394
4395 let points = vec![
4396 Point::new(0, 0),
4397 Point::new(1, 0),
4398 Point::new(2, 0),
4399 Point::new(3, 0),
4400 ];
4401 let base_rows: Vec<_> = diff_snapshot
4402 .points_to_base_text_points(points, &buffer_snapshot)
4403 .0
4404 .collect();
4405
4406 let zero = Point::new(0, 0);
4407 assert_eq!(
4408 base_rows,
4409 vec![zero..zero, zero..zero, zero..zero, zero..zero],
4410 "all buffer rows should map to point 0,0 in empty base text"
4411 );
4412
4413 let base_points = vec![Point::new(0, 0)];
4414 let (rows_iter, _, _) =
4415 diff_snapshot.base_text_points_to_points(base_points, &buffer_snapshot);
4416 let buffer_rows: Vec<_> = rows_iter.collect();
4417
4418 let max_point = buffer_snapshot.max_point();
4419 assert_eq!(
4420 buffer_rows,
4421 vec![zero..max_point],
4422 "base text row 0 should map to entire buffer range"
4423 );
4424 }
4425}3896}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t