@@ -1019,6 +1019,8 @@ fn compare_hunks(
old_hunks: &SumTree<InternalDiffHunk>,
old_snapshot: &text::BufferSnapshot,
new_snapshot: &text::BufferSnapshot,
+ old_base_text: &text::BufferSnapshot,
+ new_base_text: &text::BufferSnapshot,
) -> DiffChanged {
let mut new_cursor = new_hunks.cursor::<()>(new_snapshot);
let mut old_cursor = old_hunks.cursor::<()>(new_snapshot);
@@ -1026,8 +1028,8 @@ fn compare_hunks(
new_cursor.next();
let mut start = None;
let mut end = None;
- let mut base_text_start = None;
- let mut base_text_end = None;
+ let mut base_text_start: Option<Anchor> = None;
+ let mut base_text_end: Option<Anchor> = None;
let mut last_unchanged_new_hunk_end: Option<text::Anchor> = None;
let mut has_changes = false;
@@ -1045,9 +1047,19 @@ fn compare_hunks(
has_changes = true;
extended_end_candidate = None;
start.get_or_insert(new_hunk.buffer_range.start);
- base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
+ base_text_start.get_or_insert(
+ new_base_text.anchor_before(new_hunk.diff_base_byte_range.start),
+ );
end.replace(new_hunk.buffer_range.end);
- base_text_end = base_text_end.max(Some(new_hunk.diff_base_byte_range.end));
+ let new_diff_range_end =
+ new_base_text.anchor_after(new_hunk.diff_base_byte_range.end);
+ if base_text_end.is_none_or(|base_text_end| {
+ new_diff_range_end
+ .cmp(&base_text_end, &new_base_text)
+ .is_gt()
+ }) {
+ base_text_end = Some(new_diff_range_end)
+ }
new_cursor.next();
}
Ordering::Equal => {
@@ -1055,7 +1067,9 @@ fn compare_hunks(
has_changes = true;
extended_end_candidate = None;
start.get_or_insert(new_hunk.buffer_range.start);
- base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
+ base_text_start.get_or_insert(
+ new_base_text.anchor_before(new_hunk.diff_base_byte_range.start),
+ );
if old_hunk
.buffer_range
.end
@@ -1067,11 +1081,14 @@ fn compare_hunks(
end.replace(new_hunk.buffer_range.end);
}
+ let old_hunk_diff_base_range_end =
+ old_base_text.anchor_after(old_hunk.diff_base_byte_range.end);
+ let new_hunk_diff_base_range_end =
+ new_base_text.anchor_after(new_hunk.diff_base_byte_range.end);
+
base_text_end.replace(
- old_hunk
- .diff_base_byte_range
- .end
- .max(new_hunk.diff_base_byte_range.end),
+ *old_hunk_diff_base_range_end
+ .max(&new_hunk_diff_base_range_end, new_base_text),
);
} else {
if !has_changes {
@@ -1088,9 +1105,19 @@ fn compare_hunks(
has_changes = true;
extended_end_candidate = None;
start.get_or_insert(old_hunk.buffer_range.start);
- base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start);
+ base_text_start.get_or_insert(
+ old_base_text.anchor_after(old_hunk.diff_base_byte_range.start),
+ );
end.replace(old_hunk.buffer_range.end);
- base_text_end = base_text_end.max(Some(old_hunk.diff_base_byte_range.end));
+ let old_diff_range_end =
+ old_base_text.anchor_after(old_hunk.diff_base_byte_range.end);
+ if base_text_end.is_none_or(|base_text_end| {
+ old_diff_range_end
+ .cmp(&base_text_end, new_base_text)
+ .is_gt()
+ }) {
+ base_text_end = Some(old_diff_range_end)
+ }
old_cursor.next();
}
}
@@ -1099,24 +1126,38 @@ fn compare_hunks(
has_changes = true;
extended_end_candidate = None;
start.get_or_insert(new_hunk.buffer_range.start);
- base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
+ base_text_start
+ .get_or_insert(new_base_text.anchor_after(new_hunk.diff_base_byte_range.start));
if end.is_none_or(|end| end.cmp(&new_hunk.buffer_range.end, &new_snapshot).is_le())
{
end.replace(new_hunk.buffer_range.end);
}
- base_text_end = base_text_end.max(Some(new_hunk.diff_base_byte_range.end));
+ let new_base_text_end =
+ new_base_text.anchor_after(new_hunk.diff_base_byte_range.end);
+ if base_text_end.is_none_or(|base_text_end| {
+ new_base_text_end.cmp(&base_text_end, new_base_text).is_gt()
+ }) {
+ base_text_end = Some(new_base_text_end)
+ }
new_cursor.next();
}
(None, Some(old_hunk)) => {
has_changes = true;
extended_end_candidate = None;
start.get_or_insert(old_hunk.buffer_range.start);
- base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start);
+ base_text_start
+ .get_or_insert(old_base_text.anchor_after(old_hunk.diff_base_byte_range.start));
if end.is_none_or(|end| end.cmp(&old_hunk.buffer_range.end, &new_snapshot).is_le())
{
end.replace(old_hunk.buffer_range.end);
}
- base_text_end = base_text_end.max(Some(old_hunk.diff_base_byte_range.end));
+ let old_base_text_end =
+ old_base_text.anchor_after(old_hunk.diff_base_byte_range.end);
+ if base_text_end.is_none_or(|base_text_end| {
+ old_base_text_end.cmp(&base_text_end, new_base_text).is_gt()
+ }) {
+ base_text_end = Some(old_base_text_end);
+ }
old_cursor.next();
}
(None, None) => break,
@@ -1126,7 +1167,7 @@ fn compare_hunks(
let changed_range = start.zip(end).map(|(start, end)| start..end);
let base_text_changed_range = base_text_start
.zip(base_text_end)
- .map(|(start, end)| start..end);
+ .map(|(start, end)| (start..end).to_offset(new_base_text));
let extended_range = if has_changes && let Some(changed_range) = changed_range.clone() {
let extended_start = *last_unchanged_new_hunk_end
@@ -1570,22 +1611,46 @@ impl BufferDiff {
log::debug!("set snapshot with secondary {secondary_diff_change:?}");
let old_snapshot = self.snapshot(cx);
- let state = &mut self.inner;
let new_state = update.inner;
let base_text_changed = update.base_text_changed;
+ let state = &mut self.inner;
+ state.base_text_exists = new_state.base_text_exists;
+ let should_compare_hunks = update.base_text_edits.is_some() || !base_text_changed;
+ let parsing_idle = if let Some(diff) = update.base_text_edits {
+ state.base_text.update(cx, |base_text, cx| {
+ base_text.set_capability(Capability::ReadWrite, cx);
+ base_text.apply_diff(diff, cx);
+ base_text.set_capability(Capability::ReadOnly, cx);
+ Some(base_text.parsing_idle())
+ })
+ } else if update.base_text_changed {
+ state.base_text.update(cx, |base_text, cx| {
+ base_text.set_capability(Capability::ReadWrite, cx);
+ base_text.set_text(new_state.base_text.clone(), cx);
+ base_text.set_capability(Capability::ReadOnly, cx);
+ Some(base_text.parsing_idle())
+ })
+ } else {
+ None
+ };
+
let old_buffer_snapshot = &old_snapshot.inner.buffer_snapshot;
+ let old_base_snapshot = &old_snapshot.inner.base_text;
+ let new_base_snapshot = state.base_text.read(cx).snapshot();
let DiffChanged {
mut changed_range,
mut base_text_changed_range,
mut extended_range,
} = match (state.base_text_exists, new_state.base_text_exists) {
(false, false) => DiffChanged::default(),
- (true, true) if !base_text_changed => compare_hunks(
+ (true, true) if should_compare_hunks => compare_hunks(
&new_state.hunks,
&old_snapshot.inner.hunks,
old_buffer_snapshot,
buffer,
+ old_base_snapshot,
+ &new_base_snapshot,
),
_ => {
let full_range = text::Anchor::min_max_range_for_buffer(self.buffer_id);
@@ -1597,54 +1662,9 @@ impl BufferDiff {
}
}
};
-
- if let Some(secondary_changed_range) = secondary_diff_change
- && let (Some(secondary_hunk_range), Some(secondary_base_range)) =
- old_snapshot.range_to_hunk_range(secondary_changed_range, buffer)
- {
- if let Some(range) = &mut changed_range {
- range.start = *secondary_hunk_range.start.min(&range.start, buffer);
- range.end = *secondary_hunk_range.end.max(&range.end, buffer);
- } else {
- changed_range = Some(secondary_hunk_range.clone());
- }
-
- if let Some(base_text_range) = base_text_changed_range.as_mut() {
- base_text_range.start = secondary_base_range.start.min(base_text_range.start);
- base_text_range.end = secondary_base_range.end.max(base_text_range.end);
- } else {
- base_text_changed_range = Some(secondary_base_range);
- }
-
- if let Some(ext) = &mut extended_range {
- ext.start = *ext.start.min(&secondary_hunk_range.start, buffer);
- ext.end = *ext.end.max(&secondary_hunk_range.end, buffer);
- } else {
- extended_range = Some(secondary_hunk_range);
- }
- }
-
- let state = &mut self.inner;
- state.base_text_exists = new_state.base_text_exists;
- let parsing_idle = if let Some(diff) = update.base_text_edits {
- state.base_text.update(cx, |base_text, cx| {
- base_text.set_capability(Capability::ReadWrite, cx);
- base_text.apply_diff(diff, cx);
- base_text.set_capability(Capability::ReadOnly, cx);
- Some(base_text.parsing_idle())
- })
- } else if update.base_text_changed {
- state.base_text.update(cx, |base_text, cx| {
- base_text.set_capability(Capability::ReadWrite, cx);
- base_text.set_text(new_state.base_text.clone(), cx);
- base_text.set_capability(Capability::ReadOnly, cx);
- Some(base_text.parsing_idle())
- })
- } else {
- None
- };
state.hunks = new_state.hunks;
state.buffer_snapshot = update.buffer_snapshot;
+
if base_text_changed || clear_pending_hunks {
if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
{
@@ -1675,6 +1695,32 @@ impl BufferDiff {
state.pending_hunks = SumTree::new(buffer);
}
+ if let Some(secondary_changed_range) = secondary_diff_change
+ && let (Some(secondary_hunk_range), Some(secondary_base_range)) =
+ old_snapshot.range_to_hunk_range(secondary_changed_range, buffer)
+ {
+ if let Some(range) = &mut changed_range {
+ range.start = *secondary_hunk_range.start.min(&range.start, buffer);
+ range.end = *secondary_hunk_range.end.max(&range.end, buffer);
+ } else {
+ changed_range = Some(secondary_hunk_range.clone());
+ }
+
+ if let Some(base_text_range) = base_text_changed_range.as_mut() {
+ base_text_range.start = secondary_base_range.start.min(base_text_range.start);
+ base_text_range.end = secondary_base_range.end.max(base_text_range.end);
+ } else {
+ base_text_changed_range = Some(secondary_base_range);
+ }
+
+ if let Some(ext) = &mut extended_range {
+ ext.start = *ext.start.min(&secondary_hunk_range.start, buffer);
+ ext.end = *ext.end.max(&secondary_hunk_range.end, buffer);
+ } else {
+ extended_range = Some(secondary_hunk_range);
+ }
+ }
+
async move {
if let Some(parsing_idle) = parsing_idle {
parsing_idle.await;
@@ -2595,6 +2641,8 @@ mod tests {
&empty_diff.inner.hunks,
&buffer,
&buffer,
+ &diff_1.base_text(),
+ &diff_1.base_text(),
);
let range = changed_range.unwrap();
assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
@@ -2623,7 +2671,14 @@ mod tests {
changed_range,
base_text_changed_range,
extended_range: _,
- } = compare_hunks(&diff_2.inner.hunks, &diff_1.inner.hunks, &buffer, &buffer);
+ } = compare_hunks(
+ &diff_2.inner.hunks,
+ &diff_1.inner.hunks,
+ &buffer,
+ &buffer,
+ diff_2.base_text(),
+ diff_2.base_text(),
+ );
assert_eq!(
changed_range.unwrap().to_point(&buffer),
Point::new(4, 0)..Point::new(5, 0),
@@ -2654,7 +2709,14 @@ mod tests {
changed_range,
base_text_changed_range,
extended_range: _,
- } = compare_hunks(&diff_3.inner.hunks, &diff_2.inner.hunks, &buffer, &buffer);
+ } = compare_hunks(
+ &diff_3.inner.hunks,
+ &diff_2.inner.hunks,
+ &buffer,
+ &buffer,
+ diff_3.base_text(),
+ diff_3.base_text(),
+ );
let range = changed_range.unwrap();
assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
let base_text_range = base_text_changed_range.unwrap();
@@ -2681,7 +2743,14 @@ mod tests {
changed_range,
base_text_changed_range,
extended_range: _,
- } = compare_hunks(&diff_4.inner.hunks, &diff_3.inner.hunks, &buffer, &buffer);
+ } = compare_hunks(
+ &diff_4.inner.hunks,
+ &diff_3.inner.hunks,
+ &buffer,
+ &buffer,
+ diff_4.base_text(),
+ diff_4.base_text(),
+ );
let range = changed_range.unwrap();
assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
let base_text_range = base_text_changed_range.unwrap();
@@ -2709,7 +2778,14 @@ mod tests {
changed_range,
base_text_changed_range,
extended_range: _,
- } = compare_hunks(&diff_5.inner.hunks, &diff_4.inner.hunks, &buffer, &buffer);
+ } = compare_hunks(
+ &diff_5.inner.hunks,
+ &diff_4.inner.hunks,
+ &buffer,
+ &buffer,
+ diff_5.base_text(),
+ diff_5.base_text(),
+ );
let range = changed_range.unwrap();
assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
let base_text_range = base_text_changed_range.unwrap();
@@ -2737,7 +2813,14 @@ mod tests {
changed_range,
base_text_changed_range,
extended_range: _,
- } = compare_hunks(&diff_6.inner.hunks, &diff_5.inner.hunks, &buffer, &buffer);
+ } = compare_hunks(
+ &diff_6.inner.hunks,
+ &diff_5.inner.hunks,
+ &buffer,
+ &buffer,
+ diff_6.base_text(),
+ diff_6.base_text(),
+ );
let range = changed_range.unwrap();
assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
let base_text_range = base_text_changed_range.unwrap();
@@ -2764,7 +2847,14 @@ mod tests {
changed_range,
base_text_changed_range,
extended_range: _,
- } = compare_hunks(&diff_7.inner.hunks, &diff_6.inner.hunks, &buffer, &buffer);
+ } = compare_hunks(
+ &diff_7.inner.hunks,
+ &diff_6.inner.hunks,
+ &buffer,
+ &buffer,
+ diff_7.base_text(),
+ diff_7.base_text(),
+ );
let range = changed_range.unwrap();
assert_eq!(range.to_point(&buffer), Point::new(2, 4)..Point::new(7, 0));
let base_text_range = base_text_changed_range.unwrap();
@@ -2790,7 +2880,14 @@ mod tests {
changed_range,
base_text_changed_range,
extended_range: _,
- } = compare_hunks(&diff_8.inner.hunks, &diff_7.inner.hunks, &buffer, &buffer);
+ } = compare_hunks(
+ &diff_8.inner.hunks,
+ &diff_7.inner.hunks,
+ &buffer,
+ &buffer,
+ diff_8.base_text(),
+ diff_8.base_text(),
+ );
let range = changed_range.unwrap();
assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(3, 4));
let base_text_range = base_text_changed_range.unwrap();
@@ -3090,6 +3187,8 @@ mod tests {
&diff_a.inner.hunks,
&old_buffer,
&buffer,
+ &diff_a.base_text(),
+ &diff_a.base_text(),
);
let changed_range = changed_range.unwrap();
@@ -3151,6 +3250,8 @@ mod tests {
&diff_2a.inner.hunks,
&old_buffer_2,
&buffer_2,
+ &diff_2a.base_text(),
+ &diff_2a.base_text(),
);
let changed_range = changed_range.unwrap();
@@ -3167,4 +3268,280 @@ mod tests {
"extended_range should equal changed_range when edit is within the hunk"
);
}
+
+ #[gpui::test]
+ async fn test_buffer_diff_compare_with_base_text_change(_cx: &mut TestAppContext) {
+ // Use a shared base text buffer so that anchors from old and new snapshots
+ // share the same remote_id and resolve correctly across versions.
+ let initial_base = "aaa\nbbb\nccc\nddd\neee\n";
+ let mut base_text_buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(99).unwrap(),
+ initial_base.to_string(),
+ );
+
+ // --- Scenario 1: Base text gains a line, producing a new deletion hunk ---
+ //
+ // Buffer has a modification (ccc β CCC). When the base text gains
+ // a new line "XXX" after "aaa", the diff now also contains a
+ // deletion for that line, and the modification hunk shifts in the
+ // base text.
+ let buffer_text_1 = "aaa\nbbb\nCCC\nddd\neee\n";
+ let buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ buffer_text_1.to_string(),
+ );
+
+ let old_base_snapshot_1 = base_text_buffer.snapshot();
+ let old_hunks_1 = compute_hunks(
+ Some((Arc::from(initial_base), Rope::from(initial_base))),
+ buffer.snapshot(),
+ None,
+ );
+
+ // Insert "XXX\n" after "aaa\n" in the base text.
+ base_text_buffer.edit([(4..4, "XXX\n")]);
+ let new_base_str_1: Arc<str> = Arc::from(base_text_buffer.text().as_str());
+ let new_base_snapshot_1 = base_text_buffer.snapshot();
+
+ let new_hunks_1 = compute_hunks(
+ Some((new_base_str_1.clone(), Rope::from(new_base_str_1.as_ref()))),
+ buffer.snapshot(),
+ None,
+ );
+
+ let DiffChanged {
+ changed_range,
+ base_text_changed_range,
+ extended_range: _,
+ } = compare_hunks(
+ &new_hunks_1,
+ &old_hunks_1,
+ &buffer.snapshot(),
+ &buffer.snapshot(),
+ &old_base_snapshot_1,
+ &new_base_snapshot_1,
+ );
+
+ // The new deletion hunk (XXX) starts at buffer row 1 and the
+ // modification hunk (ccc β CCC) now has a different
+ // diff_base_byte_range, so the changed range spans both.
+ let range = changed_range.unwrap();
+ assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(3, 0),);
+ let base_range = base_text_changed_range.unwrap();
+ assert_eq!(
+ base_range.to_point(&new_base_snapshot_1),
+ Point::new(1, 0)..Point::new(4, 0),
+ );
+
+ // --- Scenario 2: Base text changes to match the buffer (hunk disappears) ---
+ //
+ // Start fresh with a simple base text.
+ let simple_base = "one\ntwo\nthree\n";
+ let mut base_buf_2 = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(100).unwrap(),
+ simple_base.to_string(),
+ );
+
+ let buffer_text_2 = "one\nTWO\nthree\n";
+ let buffer_2 = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(2).unwrap(),
+ buffer_text_2.to_string(),
+ );
+
+ let old_base_snapshot_2 = base_buf_2.snapshot();
+ let old_hunks_2 = compute_hunks(
+ Some((Arc::from(simple_base), Rope::from(simple_base))),
+ buffer_2.snapshot(),
+ None,
+ );
+
+ // The base text is edited so "two" becomes "TWO", now matching the buffer.
+ base_buf_2.edit([(4..7, "TWO")]);
+ let new_base_str_2: Arc<str> = Arc::from(base_buf_2.text().as_str());
+ let new_base_snapshot_2 = base_buf_2.snapshot();
+
+ let new_hunks_2 = compute_hunks(
+ Some((new_base_str_2.clone(), Rope::from(new_base_str_2.as_ref()))),
+ buffer_2.snapshot(),
+ None,
+ );
+
+ let DiffChanged {
+ changed_range,
+ base_text_changed_range,
+ extended_range: _,
+ } = compare_hunks(
+ &new_hunks_2,
+ &old_hunks_2,
+ &buffer_2.snapshot(),
+ &buffer_2.snapshot(),
+ &old_base_snapshot_2,
+ &new_base_snapshot_2,
+ );
+
+ // The old modification hunk (two β TWO) is now gone because the
+ // base text matches the buffer. The changed range covers where the
+ // old hunk used to be.
+ let range = changed_range.unwrap();
+ assert_eq!(
+ range.to_point(&buffer_2),
+ Point::new(1, 0)..Point::new(2, 0),
+ );
+ let base_range = base_text_changed_range.unwrap();
+ // The old hunk's diff_base_byte_range covered "two\n" (bytes 4..8).
+ // anchor_after(4) is right-biased at the start of the deleted "two",
+ // so after the edit replacing "two" with "TWO" it resolves past the
+ // insertion to Point(1, 3).
+ assert_eq!(
+ base_range.to_point(&new_base_snapshot_2),
+ Point::new(1, 3)..Point::new(2, 0),
+ );
+
+ // --- Scenario 3: Base text edit changes one hunk but not another ---
+ //
+ // Two modification hunks exist. Only one of them is resolved by
+ // the base text change; the other remains identical.
+ let base_3 = "aaa\nbbb\nccc\nddd\neee\n";
+ let mut base_buf_3 = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(101).unwrap(),
+ base_3.to_string(),
+ );
+
+ let buffer_text_3 = "aaa\nBBB\nccc\nDDD\neee\n";
+ let buffer_3 = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(3).unwrap(),
+ buffer_text_3.to_string(),
+ );
+
+ let old_base_snapshot_3 = base_buf_3.snapshot();
+ let old_hunks_3 = compute_hunks(
+ Some((Arc::from(base_3), Rope::from(base_3))),
+ buffer_3.snapshot(),
+ None,
+ );
+
+ // Change "ddd" to "DDD" in the base text so that hunk disappears,
+ // but "bbb" stays, so its hunk remains.
+ base_buf_3.edit([(12..15, "DDD")]);
+ let new_base_str_3: Arc<str> = Arc::from(base_buf_3.text().as_str());
+ let new_base_snapshot_3 = base_buf_3.snapshot();
+
+ let new_hunks_3 = compute_hunks(
+ Some((new_base_str_3.clone(), Rope::from(new_base_str_3.as_ref()))),
+ buffer_3.snapshot(),
+ None,
+ );
+
+ let DiffChanged {
+ changed_range,
+ base_text_changed_range,
+ extended_range: _,
+ } = compare_hunks(
+ &new_hunks_3,
+ &old_hunks_3,
+ &buffer_3.snapshot(),
+ &buffer_3.snapshot(),
+ &old_base_snapshot_3,
+ &new_base_snapshot_3,
+ );
+
+ // Only the second hunk (ddd β DDD) disappeared; the first hunk
+ // (bbb β BBB) is unchanged, so the changed range covers only line 3.
+ let range = changed_range.unwrap();
+ assert_eq!(
+ range.to_point(&buffer_3),
+ Point::new(3, 0)..Point::new(4, 0),
+ );
+ let base_range = base_text_changed_range.unwrap();
+ // anchor_after(12) is right-biased at the start of deleted "ddd",
+ // so after the edit replacing "ddd" with "DDD" it resolves past
+ // the insertion to Point(3, 3).
+ assert_eq!(
+ base_range.to_point(&new_base_snapshot_3),
+ Point::new(3, 3)..Point::new(4, 0),
+ );
+
+ // --- Scenario 4: Both buffer and base text change simultaneously ---
+ //
+ // The buffer gains an edit that introduces a new hunk while the
+ // base text also changes.
+ let base_4 = "alpha\nbeta\ngamma\ndelta\n";
+ let mut base_buf_4 = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(102).unwrap(),
+ base_4.to_string(),
+ );
+
+ let buffer_text_4 = "alpha\nBETA\ngamma\ndelta\n";
+ let mut buffer_4 = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(4).unwrap(),
+ buffer_text_4.to_string(),
+ );
+
+ let old_base_snapshot_4 = base_buf_4.snapshot();
+ let old_buffer_snapshot_4 = buffer_4.snapshot();
+ let old_hunks_4 = compute_hunks(
+ Some((Arc::from(base_4), Rope::from(base_4))),
+ buffer_4.snapshot(),
+ None,
+ );
+
+ // Edit the buffer: change "delta" to "DELTA" (new modification hunk).
+ buffer_4.edit_via_marked_text(
+ &"
+ alpha
+ BETA
+ gamma
+ Β«DELTAΒ»
+ "
+ .unindent(),
+ );
+
+ // Edit the base text: change "beta" to "BETA" (resolves that hunk).
+ base_buf_4.edit([(6..10, "BETA")]);
+ let new_base_str_4: Arc<str> = Arc::from(base_buf_4.text().as_str());
+ let new_base_snapshot_4 = base_buf_4.snapshot();
+
+ let new_hunks_4 = compute_hunks(
+ Some((new_base_str_4.clone(), Rope::from(new_base_str_4.as_ref()))),
+ buffer_4.snapshot(),
+ None,
+ );
+
+ let DiffChanged {
+ changed_range,
+ base_text_changed_range,
+ extended_range: _,
+ } = compare_hunks(
+ &new_hunks_4,
+ &old_hunks_4,
+ &old_buffer_snapshot_4,
+ &buffer_4.snapshot(),
+ &old_base_snapshot_4,
+ &new_base_snapshot_4,
+ );
+
+ // The old BETA hunk (line 1) is gone and a new DELTA hunk (line 3)
+ // appeared, so the changed range spans from line 1 through line 4.
+ let range = changed_range.unwrap();
+ assert_eq!(
+ range.to_point(&buffer_4),
+ Point::new(1, 0)..Point::new(4, 0),
+ );
+ let base_range = base_text_changed_range.unwrap();
+ // The old BETA hunk's base range started at byte 6 ("beta"). After
+ // the base text edit replacing "beta" with "BETA", anchor_after(6)
+ // resolves past the insertion to Point(1, 4).
+ assert_eq!(
+ base_range.to_point(&new_base_snapshot_4),
+ Point::new(1, 4)..Point::new(4, 0),
+ );
+ }
}