@@ -237,17 +237,19 @@ impl BufferDiffSnapshot {
base_text_pair = Some((text.clone(), base_text_rope.clone()));
base_text_buffer.update(cx, |base_text_buffer, cx| {
// TODO(split-diff) arguably an unnecessary allocation
- base_text_buffer.set_text(text.clone(), cx);
+ base_text_buffer.set_text(dbg!(text.clone()), cx);
});
base_text_exists = true;
} else {
base_text_pair = None;
base_text_buffer.update(cx, |base_text_buffer, cx| {
+ dbg!("SET TO EMPTY");
base_text_buffer.set_text("", cx);
});
base_text_exists = false;
};
base_text_snapshot = base_text_buffer.read(cx).snapshot();
+ dbg!(base_text_snapshot.text());
let hunks = cx
.background_executor()
@@ -310,11 +312,11 @@ impl BufferDiffSnapshot {
cx: &mut gpui::TestAppContext,
) -> BufferDiffSnapshot {
cx.executor().block(cx.update(|cx| {
- let base_text_buffer = cx.new(|cx| language::Buffer::local("", cx));
+ let base_text_buffer = cx.new(|cx| language::Buffer::local(diff_base.clone(), cx));
Self::new_with_base_text(
base_text_buffer,
buffer,
- Some(diff_base.as_str().into()),
+ Some(dbg!(diff_base.as_str().into())),
None,
None,
cx,
@@ -484,7 +486,7 @@ impl BufferDiffInner<Entity<language::Buffer>> {
// If the file doesn't exist in either HEAD or the index, then the
// entire file must be either created or deleted in the index.
- let (index_text, head_text) = match (index_text, head_text) {
+ let (index_text, head_text) = match (dbg!(index_text), head_text) {
(Some(index_text), Some(head_text)) if file_exists || !stage => (index_text, head_text),
(index_text, head_text) => {
let (new_index_text, new_status) = if stage {
@@ -663,11 +665,12 @@ impl BufferDiffInner<Entity<language::Buffer>> {
}
let mut new_index_text = Rope::new();
+ dbg!(index_text.len());
let mut index_cursor = index_text.cursor(0);
for (old_range, replacement_text) in edits {
- new_index_text.append(index_cursor.slice(old_range.start));
- index_cursor.seek_forward(old_range.end);
+ new_index_text.append(index_cursor.slice(dbg!(old_range.start)));
+ index_cursor.seek_forward(dbg!(old_range.end));
new_index_text.push(&replacement_text);
}
new_index_text.append(index_cursor.suffix());
@@ -1194,15 +1197,15 @@ impl BufferDiff {
#[cfg(any(test, feature = "test-support"))]
pub fn new_with_base_text(
base_text: &str,
- buffer: &Entity<language::Buffer>,
+ buffer: &text::BufferSnapshot,
cx: &mut App,
) -> Self {
- let base_buffer = cx.new(|cx| language::Buffer::local("", cx));
+ let base_buffer = cx.new(|cx| language::Buffer::local(base_text, cx));
let mut base_text = base_text.to_owned();
text::LineEnding::normalize(&mut base_text);
let snapshot = BufferDiffSnapshot::new_with_base_text(
base_buffer.clone(),
- buffer.read(cx).text_snapshot(),
+ buffer.clone(),
Some(base_text.into()),
None,
None,
@@ -1210,7 +1213,7 @@ impl BufferDiff {
);
let snapshot = cx.background_executor().block(snapshot);
Self {
- buffer_id: buffer.read(cx).remote_id(),
+ buffer_id: buffer.remote_id(),
inner: BufferDiffInner {
hunks: snapshot.inner.hunks.clone(),
pending_hunks: snapshot.inner.pending_hunks.clone(),
@@ -1254,6 +1257,7 @@ impl BufferDiff {
.secondary_diff
.as_ref()?
.update(cx, |secondary_diff, cx| {
+ dbg!(secondary_diff.base_text_string(cx));
self.inner.stage_or_unstage_hunks_impl(
&secondary_diff.inner,
stage,
@@ -1386,6 +1390,7 @@ impl BufferDiff {
let state = &mut self.inner;
state.base_text_exists = new_state.base_text_exists;
+ // FIXME state.base_text = ...;
state.hunks = new_state.hunks;
if base_text_changed || clear_pending_hunks {
if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
@@ -1632,938 +1637,939 @@ pub fn assert_hunks<ExpectedText, HunkIter>(
pretty_assertions::assert_eq!(actual_hunks, expected_hunks);
}
-// FIXME
-// #[cfg(test)]
-// mod tests {
-// use std::fmt::Write as _;
-//
-// use super::*;
-// use gpui::TestAppContext;
-// use pretty_assertions::{assert_eq, assert_ne};
-// use rand::{Rng as _, rngs::StdRng};
-// use text::{Buffer, BufferId, ReplicaId, Rope};
-// use unindent::Unindent as _;
-// use util::test::marked_text_ranges;
-//
-// #[ctor::ctor]
-// fn init_logger() {
-// zlog::init_test();
-// }
-//
-// #[gpui::test]
-// async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
-// let diff_base = "
-// one
-// two
-// three
-// "
-// .unindent();
-//
-// let buffer_text = "
-// one
-// HELLO
-// three
-// "
-// .unindent();
-//
-// let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
-// let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
-// assert_hunks(
-// diff.hunks_intersecting_range(
-// Anchor::min_max_range_for_buffer(buffer.remote_id()),
-// &buffer,
-// ),
-// &buffer,
-// &diff_base,
-// &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
-// );
-//
-// buffer.edit([(0..0, "point five\n")]);
-// diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
-// assert_hunks(
-// diff.hunks_intersecting_range(
-// Anchor::min_max_range_for_buffer(buffer.remote_id()),
-// &buffer,
-// ),
-// &buffer,
-// &diff_base,
-// &[
-// (0..1, "", "point five\n", DiffHunkStatus::added_none()),
-// (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified_none()),
-// ],
-// );
-//
-// diff = cx.update(|cx| BufferDiffSnapshot::empty(&buffer, cx));
-// assert_hunks::<&str, _>(
-// diff.hunks_intersecting_range(
-// Anchor::min_max_range_for_buffer(buffer.remote_id()),
-// &buffer,
-// ),
-// &buffer,
-// &diff_base,
-// &[],
-// );
-// }
-//
-// #[gpui::test]
-// async fn test_buffer_diff_with_secondary(cx: &mut gpui::TestAppContext) {
-// let head_text = "
-// zero
-// one
-// two
-// three
-// four
-// five
-// six
-// seven
-// eight
-// nine
-// "
-// .unindent();
-//
-// let index_text = "
-// zero
-// one
-// TWO
-// three
-// FOUR
-// five
-// six
-// seven
-// eight
-// NINE
-// "
-// .unindent();
-//
-// let buffer_text = "
-// zero
-// one
-// TWO
-// three
-// FOUR
-// FIVE
-// six
-// SEVEN
-// eight
-// nine
-// "
-// .unindent();
-//
-// let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
-// let unstaged_diff = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
-// let mut uncommitted_diff =
-// BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
-// uncommitted_diff.secondary_diff = Some(Box::new(unstaged_diff));
-//
-// let expected_hunks = vec![
-// (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
-// (
-// 4..6,
-// "four\nfive\n",
-// "FOUR\nFIVE\n",
-// DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
-// ),
-// (
-// 7..8,
-// "seven\n",
-// "SEVEN\n",
-// DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
-// ),
-// ];
-//
-// assert_hunks(
-// uncommitted_diff.hunks_intersecting_range(
-// Anchor::min_max_range_for_buffer(buffer.remote_id()),
-// &buffer,
-// ),
-// &buffer,
-// &head_text,
-// &expected_hunks,
-// );
-// }
-//
-// #[gpui::test]
-// async fn test_buffer_diff_range(cx: &mut TestAppContext) {
-// let diff_base = Arc::new(
-// "
-// one
-// two
-// three
-// four
-// five
-// six
-// seven
-// eight
-// nine
-// ten
-// "
-// .unindent(),
-// );
-//
-// let buffer_text = "
-// A
-// one
-// B
-// two
-// C
-// three
-// HELLO
-// four
-// five
-// SIXTEEN
-// seven
-// eight
-// WORLD
-// nine
-//
-// ten
-//
-// "
-// .unindent();
-//
-// let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
-// let diff = cx
-// .update(|cx| {
-// BufferDiffSnapshot::new_with_base_text(
-// buffer.snapshot(),
-// Some(diff_base.clone()),
-// None,
-// None,
-// cx,
-// )
-// })
-// .await;
-// assert_eq!(
-// diff.hunks_intersecting_range(
-// Anchor::min_max_range_for_buffer(buffer.remote_id()),
-// &buffer
-// )
-// .count(),
-// 8
-// );
-//
-// assert_hunks(
-// diff.hunks_intersecting_range(
-// buffer.anchor_before(Point::new(7, 0))..buffer.anchor_before(Point::new(12, 0)),
-// &buffer,
-// ),
-// &buffer,
-// &diff_base,
-// &[
-// (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
-// (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
-// (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
-// ],
-// );
-// }
-//
-// #[gpui::test]
-// async fn test_stage_hunk(cx: &mut TestAppContext) {
-// struct Example {
-// name: &'static str,
-// head_text: String,
-// index_text: String,
-// buffer_marked_text: String,
-// final_index_text: String,
-// }
-//
-// let table = [
-// Example {
-// name: "uncommitted hunk straddles end of unstaged hunk",
-// head_text: "
-// one
-// two
-// three
-// four
-// five
-// "
-// .unindent(),
-// index_text: "
-// one
-// TWO_HUNDRED
-// three
-// FOUR_HUNDRED
-// five
-// "
-// .unindent(),
-// buffer_marked_text: "
-// ZERO
-// one
-// two
-// «THREE_HUNDRED
-// FOUR_HUNDRED»
-// five
-// SIX
-// "
-// .unindent(),
-// final_index_text: "
-// one
-// two
-// THREE_HUNDRED
-// FOUR_HUNDRED
-// five
-// "
-// .unindent(),
-// },
-// Example {
-// name: "uncommitted hunk straddles start of unstaged hunk",
-// head_text: "
-// one
-// two
-// three
-// four
-// five
-// "
-// .unindent(),
-// index_text: "
-// one
-// TWO_HUNDRED
-// three
-// FOUR_HUNDRED
-// five
-// "
-// .unindent(),
-// buffer_marked_text: "
-// ZERO
-// one
-// «TWO_HUNDRED
-// THREE_HUNDRED»
-// four
-// five
-// SIX
-// "
-// .unindent(),
-// final_index_text: "
-// one
-// TWO_HUNDRED
-// THREE_HUNDRED
-// four
-// five
-// "
-// .unindent(),
-// },
-// Example {
-// name: "uncommitted hunk strictly contains unstaged hunks",
-// head_text: "
-// one
-// two
-// three
-// four
-// five
-// six
-// seven
-// "
-// .unindent(),
-// index_text: "
-// one
-// TWO
-// THREE
-// FOUR
-// FIVE
-// SIX
-// seven
-// "
-// .unindent(),
-// buffer_marked_text: "
-// one
-// TWO
-// «THREE_HUNDRED
-// FOUR
-// FIVE_HUNDRED»
-// SIX
-// seven
-// "
-// .unindent(),
-// final_index_text: "
-// one
-// TWO
-// THREE_HUNDRED
-// FOUR
-// FIVE_HUNDRED
-// SIX
-// seven
-// "
-// .unindent(),
-// },
-// Example {
-// name: "uncommitted deletion hunk",
-// head_text: "
-// one
-// two
-// three
-// four
-// five
-// "
-// .unindent(),
-// index_text: "
-// one
-// two
-// three
-// four
-// five
-// "
-// .unindent(),
-// buffer_marked_text: "
-// one
-// ˇfive
-// "
-// .unindent(),
-// final_index_text: "
-// one
-// five
-// "
-// .unindent(),
-// },
-// Example {
-// name: "one unstaged hunk that contains two uncommitted hunks",
-// head_text: "
-// one
-// two
-//
-// three
-// four
-// "
-// .unindent(),
-// index_text: "
-// one
-// two
-// three
-// four
-// "
-// .unindent(),
-// buffer_marked_text: "
-// «one
-//
-// three // modified
-// four»
-// "
-// .unindent(),
-// final_index_text: "
-// one
-//
-// three // modified
-// four
-// "
-// .unindent(),
-// },
-// Example {
-// name: "one uncommitted hunk that contains two unstaged hunks",
-// head_text: "
-// one
-// two
-// three
-// four
-// five
-// "
-// .unindent(),
-// index_text: "
-// ZERO
-// one
-// TWO
-// THREE
-// FOUR
-// five
-// "
-// .unindent(),
-// buffer_marked_text: "
-// «one
-// TWO_HUNDRED
-// THREE
-// FOUR_HUNDRED
-// five»
-// "
-// .unindent(),
-// final_index_text: "
-// ZERO
-// one
-// TWO_HUNDRED
-// THREE
-// FOUR_HUNDRED
-// five
-// "
-// .unindent(),
-// },
-// ];
-//
-// for example in table {
-// let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
-// let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
-// let hunk_range =
-// buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
-//
-// let unstaged =
-// BufferDiffSnapshot::new_sync(buffer.clone(), example.index_text.clone(), cx);
-// let uncommitted =
-// BufferDiffSnapshot::new_sync(buffer.clone(), example.head_text.clone(), cx);
-//
-// let unstaged_diff = cx.new(|cx| {
-// let mut diff = BufferDiff::new(&buffer, cx);
-// diff.set_snapshot(unstaged, &buffer, cx);
-// diff
-// });
-//
-// let uncommitted_diff = cx.new(|cx| {
-// let mut diff = BufferDiff::new(&buffer, cx);
-// diff.set_snapshot(uncommitted, &buffer, cx);
-// diff.set_secondary_diff(unstaged_diff);
-// diff
-// });
-//
-// uncommitted_diff.update(cx, |diff, cx| {
-// let hunks = diff
-// .hunks_intersecting_range(hunk_range.clone(), &buffer, cx)
-// .collect::<Vec<_>>();
-// for hunk in &hunks {
-// assert_ne!(
-// hunk.secondary_status,
-// DiffHunkSecondaryStatus::NoSecondaryHunk
-// )
-// }
-//
-// let new_index_text = diff
-// .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
-// .unwrap()
-// .to_string();
-//
-// let hunks = diff
-// .hunks_intersecting_range(hunk_range.clone(), &buffer, cx)
-// .collect::<Vec<_>>();
-// for hunk in &hunks {
-// assert_eq!(
-// hunk.secondary_status,
-// DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
-// )
-// }
-//
-// pretty_assertions::assert_eq!(
-// new_index_text,
-// example.final_index_text,
-// "example: {}",
-// example.name
-// );
-// });
-// }
-// }
-//
-// #[gpui::test]
-// async fn test_toggling_stage_and_unstage_same_hunk(cx: &mut TestAppContext) {
-// let head_text = "
-// one
-// two
-// three
-// "
-// .unindent();
-// let index_text = head_text.clone();
-// let buffer_text = "
-// one
-// three
-// "
-// .unindent();
-//
-// let buffer = Buffer::new(
-// ReplicaId::LOCAL,
-// BufferId::new(1).unwrap(),
-// buffer_text.clone(),
-// );
-// let unstaged = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
-// let uncommitted = BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
-// let unstaged_diff = cx.new(|cx| {
-// let mut diff = BufferDiff::new(&buffer, cx);
-// diff.set_snapshot(unstaged, &buffer, cx);
-// diff
-// });
-// let uncommitted_diff = cx.new(|cx| {
-// let mut diff = BufferDiff::new(&buffer, cx);
-// diff.set_snapshot(uncommitted, &buffer, cx);
-// diff.set_secondary_diff(unstaged_diff.clone());
-// diff
-// });
-//
-// uncommitted_diff.update(cx, |diff, cx| {
-// let hunk = diff.hunks(&buffer, cx).next().unwrap();
-//
-// let new_index_text = diff
-// .stage_or_unstage_hunks(true, std::slice::from_ref(&hunk), &buffer, true, cx)
-// .unwrap()
-// .to_string();
-// assert_eq!(new_index_text, buffer_text);
-//
-// let hunk = diff.hunks(&buffer, cx).next().unwrap();
-// assert_eq!(
-// hunk.secondary_status,
-// DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
-// );
-//
-// let index_text = diff
-// .stage_or_unstage_hunks(false, &[hunk], &buffer, true, cx)
-// .unwrap()
-// .to_string();
-// assert_eq!(index_text, head_text);
-//
-// let hunk = diff.hunks(&buffer, cx).next().unwrap();
-// // optimistically unstaged (fine, could also be HasSecondaryHunk)
-// assert_eq!(
-// hunk.secondary_status,
-// DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
-// );
-// });
-// }
-//
-// #[gpui::test]
-// async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
-// let base_text = "
-// zero
-// one
-// two
-// three
-// four
-// five
-// six
-// seven
-// eight
-// nine
-// "
-// .unindent();
-//
-// let buffer_text_1 = "
-// one
-// three
-// four
-// five
-// SIX
-// seven
-// eight
-// NINE
-// "
-// .unindent();
-//
-// let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
-//
-// let empty_diff = cx.update(|cx| BufferDiffSnapshot::empty(&buffer, cx));
-// let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
-// let range = diff_1.inner.compare(&empty_diff.inner, &buffer).0.unwrap();
-// assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
-//
-// // Edit does not affect the diff.
-// buffer.edit_via_marked_text(
-// &"
-// one
-// three
-// four
-// five
-// «SIX.5»
-// seven
-// eight
-// NINE
-// "
-// .unindent(),
-// );
-// let diff_2 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
-// assert_eq!(None, diff_2.inner.compare(&diff_1.inner, &buffer).0);
-//
-// // Edit turns a deletion hunk into a modification.
-// buffer.edit_via_marked_text(
-// &"
-// one
-// «THREE»
-// four
-// five
-// SIX.5
-// seven
-// eight
-// NINE
-// "
-// .unindent(),
-// );
-// let diff_3 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
-// let range = diff_3.inner.compare(&diff_2.inner, &buffer).0.unwrap();
-// assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
-//
-// // Edit turns a modification hunk into a deletion.
-// buffer.edit_via_marked_text(
-// &"
-// one
-// THREE
-// four
-// five«»
-// seven
-// eight
-// NINE
-// "
-// .unindent(),
-// );
-// let diff_4 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
-// let range = diff_4.inner.compare(&diff_3.inner, &buffer).0.unwrap();
-// assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
-//
-// // Edit introduces a new insertion hunk.
-// buffer.edit_via_marked_text(
-// &"
-// one
-// THREE
-// four«
-// FOUR.5
-// »five
-// seven
-// eight
-// NINE
-// "
-// .unindent(),
-// );
-// let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
-// let range = diff_5.inner.compare(&diff_4.inner, &buffer).0.unwrap();
-// assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
-//
-// // Edit removes a hunk.
-// buffer.edit_via_marked_text(
-// &"
-// one
-// THREE
-// four
-// FOUR.5
-// five
-// seven
-// eight
-// «nine»
-// "
-// .unindent(),
-// );
-// let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
-// let range = diff_6.inner.compare(&diff_5.inner, &buffer).0.unwrap();
-// assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
-// }
-//
-// #[gpui::test(iterations = 100)]
-// async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
-// fn gen_line(rng: &mut StdRng) -> String {
-// if rng.random_bool(0.2) {
-// "\n".to_owned()
-// } else {
-// let c = rng.random_range('A'..='Z');
-// format!("{c}{c}{c}\n")
-// }
-// }
-//
-// fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
-// let mut old_lines = {
-// let mut old_lines = Vec::new();
-// let old_lines_iter = head.lines();
-// for line in old_lines_iter {
-// assert!(!line.ends_with("\n"));
-// old_lines.push(line.to_owned());
-// }
-// if old_lines.last().is_some_and(|line| line.is_empty()) {
-// old_lines.pop();
-// }
-// old_lines.into_iter()
-// };
-// let mut result = String::new();
-// let unchanged_count = rng.random_range(0..=old_lines.len());
-// result +=
-// &old_lines
-// .by_ref()
-// .take(unchanged_count)
-// .fold(String::new(), |mut s, line| {
-// writeln!(&mut s, "{line}").unwrap();
-// s
-// });
-// while old_lines.len() > 0 {
-// let deleted_count = rng.random_range(0..=old_lines.len());
-// let _advance = old_lines
-// .by_ref()
-// .take(deleted_count)
-// .map(|line| line.len() + 1)
-// .sum::<usize>();
-// let minimum_added = if deleted_count == 0 { 1 } else { 0 };
-// let added_count = rng.random_range(minimum_added..=5);
-// let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
-// result += &addition;
-//
-// if old_lines.len() > 0 {
-// let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
-// if blank_lines == old_lines.len() {
-// break;
-// };
-// let unchanged_count =
-// rng.random_range((blank_lines + 1).max(1)..=old_lines.len());
-// result += &old_lines.by_ref().take(unchanged_count).fold(
-// String::new(),
-// |mut s, line| {
-// writeln!(&mut s, "{line}").unwrap();
-// s
-// },
-// );
-// }
-// }
-// result
-// }
-//
-// fn uncommitted_diff(
-// working_copy: &language::BufferSnapshot,
-// index_text: &Rope,
-// head_text: String,
-// cx: &mut TestAppContext,
-// ) -> Entity<BufferDiff> {
-// let inner =
-// BufferDiffSnapshot::new_sync(working_copy.text.clone(), head_text, cx).inner;
-// let secondary = BufferDiff {
-// buffer_id: working_copy.remote_id(),
-// inner: BufferDiffSnapshot::new_sync(
-// working_copy.text.clone(),
-// index_text.to_string(),
-// cx,
-// )
-// .inner,
-// secondary_diff: None,
-// };
-// let secondary = cx.new(|_| secondary);
-// cx.new(|_| BufferDiff {
-// buffer_id: working_copy.remote_id(),
-// inner,
-// secondary_diff: Some(secondary),
-// })
-// }
-//
-// let operations = std::env::var("OPERATIONS")
-// .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-// .unwrap_or(10);
-//
-// let rng = &mut rng;
-// let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
-// writeln!(&mut s, "{c}{c}{c}").unwrap();
-// s
-// });
-// let working_copy = gen_working_copy(rng, &head_text);
-// let working_copy = cx.new(|cx| {
-// language::Buffer::local_normalized(
-// Rope::from(working_copy.as_str()),
-// text::LineEnding::default(),
-// cx,
-// )
-// });
-// let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
-// let mut index_text = if rng.random() {
-// Rope::from(head_text.as_str())
-// } else {
-// working_copy.as_rope().clone()
-// };
-//
-// let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
-// let mut hunks = diff.update(cx, |diff, cx| {
-// diff.hunks_intersecting_range(
-// Anchor::min_max_range_for_buffer(diff.buffer_id),
-// &working_copy,
-// cx,
-// )
-// .collect::<Vec<_>>()
-// });
-// if hunks.is_empty() {
-// return;
-// }
-//
-// for _ in 0..operations {
-// let i = rng.random_range(0..hunks.len());
-// let hunk = &mut hunks[i];
-// let hunk_to_change = hunk.clone();
-// let stage = match hunk.secondary_status {
-// DiffHunkSecondaryStatus::HasSecondaryHunk => {
-// hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
-// true
-// }
-// DiffHunkSecondaryStatus::NoSecondaryHunk => {
-// hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
-// false
-// }
-// _ => unreachable!(),
-// };
-//
-// index_text = diff.update(cx, |diff, cx| {
-// diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
-// .unwrap()
-// });
-//
-// diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
-// let found_hunks = diff.update(cx, |diff, cx| {
-// diff.hunks_intersecting_range(
-// Anchor::min_max_range_for_buffer(diff.buffer_id),
-// &working_copy,
-// cx,
-// )
-// .collect::<Vec<_>>()
-// });
-// assert_eq!(hunks.len(), found_hunks.len());
-//
-// for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
-// assert_eq!(
-// expected_hunk.buffer_range.to_point(&working_copy),
-// found_hunk.buffer_range.to_point(&working_copy)
-// );
-// assert_eq!(
-// expected_hunk.diff_base_byte_range,
-// found_hunk.diff_base_byte_range
-// );
-// assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
-// }
-// hunks = found_hunks;
-// }
-// }
-//
-// #[gpui::test]
-// async fn test_row_to_base_text_row(cx: &mut TestAppContext) {
-// let base_text = "
-// zero
-// one
-// two
-// three
-// four
-// five
-// six
-// seven
-// eight
-// "
-// .unindent();
-// let buffer_text = "
-// zero
-// ONE
-// two
-// NINE
-// five
-// seven
-// "
-// .unindent();
-//
-// // zero
-// // - one
-// // + ONE
-// // two
-// // - three
-// // - four
-// // + NINE
-// // five
-// // - six
-// // seven
-// // + eight
-//
-// let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
-// let buffer_snapshot = buffer.snapshot();
-// let diff = BufferDiffSnapshot::new_sync(buffer_snapshot.clone(), base_text, cx);
-// let expected_results = [
-// // don't format me
-// (0, 0),
-// (1, 2),
-// (2, 2),
-// (3, 5),
-// (4, 5),
-// (5, 7),
-// (6, 9),
-// ];
-// for (buffer_row, expected) in expected_results {
-// assert_eq!(
-// diff.row_to_base_text_row(buffer_row, &buffer_snapshot),
-// expected,
-// "{buffer_row}"
-// );
-// }
-// }
-// }
-//
+#[cfg(test)]
+mod tests {
+ use std::fmt::Write as _;
+
+ use super::*;
+ use gpui::TestAppContext;
+ use pretty_assertions::{assert_eq, assert_ne};
+ use rand::{Rng as _, rngs::StdRng};
+ use text::{Buffer, BufferId, ReplicaId, Rope};
+ use unindent::Unindent as _;
+ use util::test::marked_text_ranges;
+
+ #[ctor::ctor]
+ fn init_logger() {
+ zlog::init_test();
+ }
+
+ #[gpui::test]
+ async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
+ let diff_base = "
+ one
+ two
+ three
+ "
+ .unindent();
+
+ let buffer_text = "
+ one
+ HELLO
+ three
+ "
+ .unindent();
+
+ let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
+ let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
+ assert_hunks(
+ diff.hunks_intersecting_range(
+ Anchor::min_max_range_for_buffer(buffer.remote_id()),
+ &buffer,
+ ),
+ &buffer,
+ &diff_base,
+ &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
+ );
+
+ buffer.edit([(0..0, "point five\n")]);
+ diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
+ assert_hunks(
+ diff.hunks_intersecting_range(
+ Anchor::min_max_range_for_buffer(buffer.remote_id()),
+ &buffer,
+ ),
+ &buffer,
+ &diff_base,
+ &[
+ (0..1, "", "point five\n", DiffHunkStatus::added_none()),
+ (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified_none()),
+ ],
+ );
+
+ diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
+ assert_hunks::<&str, _>(
+ diff.hunks_intersecting_range(
+ Anchor::min_max_range_for_buffer(buffer.remote_id()),
+ &buffer,
+ ),
+ &buffer,
+ &diff_base,
+ &[],
+ );
+ }
+
+ #[gpui::test]
+ async fn test_buffer_diff_with_secondary(cx: &mut gpui::TestAppContext) {
+ let head_text = "
+ zero
+ one
+ two
+ three
+ four
+ five
+ six
+ seven
+ eight
+ nine
+ "
+ .unindent();
+
+ let index_text = "
+ zero
+ one
+ TWO
+ three
+ FOUR
+ five
+ six
+ seven
+ eight
+ NINE
+ "
+ .unindent();
+
+ let buffer_text = "
+ zero
+ one
+ TWO
+ three
+ FOUR
+ FIVE
+ six
+ SEVEN
+ eight
+ nine
+ "
+ .unindent();
+
+ let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
+ let unstaged_diff = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
+ let mut uncommitted_diff =
+ BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
+ uncommitted_diff.secondary_diff = Some(Box::new(unstaged_diff));
+
+ let expected_hunks = vec![
+ (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
+ (
+ 4..6,
+ "four\nfive\n",
+ "FOUR\nFIVE\n",
+ DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
+ ),
+ (
+ 7..8,
+ "seven\n",
+ "SEVEN\n",
+ DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
+ ),
+ ];
+
+ assert_hunks(
+ uncommitted_diff.hunks_intersecting_range(
+ Anchor::min_max_range_for_buffer(buffer.remote_id()),
+ &buffer,
+ ),
+ &buffer,
+ &head_text,
+ &expected_hunks,
+ );
+ }
+
+ #[gpui::test]
+ async fn test_buffer_diff_range(cx: &mut TestAppContext) {
+ let diff_base = "
+ one
+ two
+ three
+ four
+ five
+ six
+ seven
+ eight
+ nine
+ ten
+ "
+ .unindent();
+
+ let buffer_text = "
+ A
+ one
+ B
+ two
+ C
+ three
+ HELLO
+ four
+ five
+ SIXTEEN
+ seven
+ eight
+ WORLD
+ nine
+
+ ten
+
+ "
+ .unindent();
+
+ let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
+ let base_text_buffer = cx.new(|cx| language::Buffer::local(diff_base.clone(), cx));
+ let diff = cx
+ .update(|cx| {
+ BufferDiffSnapshot::new_with_base_text(
+ base_text_buffer,
+ buffer.snapshot(),
+ Some(diff_base.as_str().into()),
+ None,
+ None,
+ cx,
+ )
+ })
+ .await;
+ assert_eq!(
+ diff.hunks_intersecting_range(
+ Anchor::min_max_range_for_buffer(buffer.remote_id()),
+ &buffer
+ )
+ .count(),
+ 8
+ );
+
+ assert_hunks(
+ diff.hunks_intersecting_range(
+ buffer.anchor_before(Point::new(7, 0))..buffer.anchor_before(Point::new(12, 0)),
+ &buffer,
+ ),
+ &buffer,
+ &diff_base,
+ &[
+ (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
+ (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
+ (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
+ ],
+ );
+ }
+
+ #[gpui::test]
+ async fn test_stage_hunk(cx: &mut TestAppContext) {
+ struct Example {
+ name: &'static str,
+ head_text: String,
+ index_text: String,
+ buffer_marked_text: String,
+ final_index_text: String,
+ }
+
+ let table = [
+ Example {
+ name: "uncommitted hunk straddles end of unstaged hunk",
+ head_text: "
+ one
+ two
+ three
+ four
+ five
+ "
+ .unindent(),
+ index_text: "
+ one
+ TWO_HUNDRED
+ three
+ FOUR_HUNDRED
+ five
+ "
+ .unindent(),
+ buffer_marked_text: "
+ ZERO
+ one
+ two
+ «THREE_HUNDRED
+ FOUR_HUNDRED»
+ five
+ SIX
+ "
+ .unindent(),
+ final_index_text: "
+ one
+ two
+ THREE_HUNDRED
+ FOUR_HUNDRED
+ five
+ "
+ .unindent(),
+ },
+ Example {
+ name: "uncommitted hunk straddles start of unstaged hunk",
+ head_text: "
+ one
+ two
+ three
+ four
+ five
+ "
+ .unindent(),
+ index_text: "
+ one
+ TWO_HUNDRED
+ three
+ FOUR_HUNDRED
+ five
+ "
+ .unindent(),
+ buffer_marked_text: "
+ ZERO
+ one
+ «TWO_HUNDRED
+ THREE_HUNDRED»
+ four
+ five
+ SIX
+ "
+ .unindent(),
+ final_index_text: "
+ one
+ TWO_HUNDRED
+ THREE_HUNDRED
+ four
+ five
+ "
+ .unindent(),
+ },
+ Example {
+ name: "uncommitted hunk strictly contains unstaged hunks",
+ head_text: "
+ one
+ two
+ three
+ four
+ five
+ six
+ seven
+ "
+ .unindent(),
+ index_text: "
+ one
+ TWO
+ THREE
+ FOUR
+ FIVE
+ SIX
+ seven
+ "
+ .unindent(),
+ buffer_marked_text: "
+ one
+ TWO
+ «THREE_HUNDRED
+ FOUR
+ FIVE_HUNDRED»
+ SIX
+ seven
+ "
+ .unindent(),
+ final_index_text: "
+ one
+ TWO
+ THREE_HUNDRED
+ FOUR
+ FIVE_HUNDRED
+ SIX
+ seven
+ "
+ .unindent(),
+ },
+ Example {
+ name: "uncommitted deletion hunk",
+ head_text: "
+ one
+ two
+ three
+ four
+ five
+ "
+ .unindent(),
+ index_text: "
+ one
+ two
+ three
+ four
+ five
+ "
+ .unindent(),
+ buffer_marked_text: "
+ one
+ ˇfive
+ "
+ .unindent(),
+ final_index_text: "
+ one
+ five
+ "
+ .unindent(),
+ },
+ Example {
+ name: "one unstaged hunk that contains two uncommitted hunks",
+ head_text: "
+ one
+ two
+
+ three
+ four
+ "
+ .unindent(),
+ index_text: "
+ one
+ two
+ three
+ four
+ "
+ .unindent(),
+ buffer_marked_text: "
+ «one
+
+ three // modified
+ four»
+ "
+ .unindent(),
+ final_index_text: "
+ one
+
+ three // modified
+ four
+ "
+ .unindent(),
+ },
+ Example {
+ name: "one uncommitted hunk that contains two unstaged hunks",
+ head_text: "
+ one
+ two
+ three
+ four
+ five
+ "
+ .unindent(),
+ index_text: "
+ ZERO
+ one
+ TWO
+ THREE
+ FOUR
+ five
+ "
+ .unindent(),
+ buffer_marked_text: "
+ «one
+ TWO_HUNDRED
+ THREE
+ FOUR_HUNDRED
+ five»
+ "
+ .unindent(),
+ final_index_text: "
+ ZERO
+ one
+ TWO_HUNDRED
+ THREE
+ FOUR_HUNDRED
+ five
+ "
+ .unindent(),
+ },
+ ];
+
+ for example in table {
+ dbg!("----------------------------------");
+ let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
+ let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
+ let hunk_range =
+ buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
+
+ dbg!();
+
+ let unstaged =
+ BufferDiffSnapshot::new_sync(buffer.clone(), dbg!(example.index_text.clone()), cx);
+ dbg!(unstaged.base_text().text());
+ let uncommitted =
+ BufferDiffSnapshot::new_sync(buffer.clone(), example.head_text.clone(), cx);
+ dbg!();
+
+ let unstaged_diff = cx.new(|cx| {
+ let mut diff = BufferDiff::new(&buffer, cx);
+ diff.set_snapshot(unstaged, &buffer, cx);
+ dbg!(diff.base_text(cx).text());
+ diff
+ });
+ dbg!();
+
+ let uncommitted_diff = cx.new(|cx| {
+ let mut diff = BufferDiff::new(&buffer, cx);
+ diff.set_snapshot(uncommitted, &buffer, cx);
+ dbg!(unstaged_diff.read(cx).base_text_string(cx));
+ diff.set_secondary_diff(unstaged_diff);
+ diff
+ });
+ dbg!();
+
+ uncommitted_diff.update(cx, |diff, cx| {
+ let hunks = diff
+ .snapshot(cx)
+ .hunks_intersecting_range(hunk_range.clone(), &buffer)
+ .collect::<Vec<_>>();
+ for hunk in &hunks {
+ assert_ne!(
+ hunk.secondary_status,
+ DiffHunkSecondaryStatus::NoSecondaryHunk
+ )
+ }
+
+ dbg!();
+ let new_index_text = diff
+ .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
+ .unwrap()
+ .to_string();
+ dbg!();
+
+ let hunks = diff
+ .snapshot(cx)
+ .hunks_intersecting_range(hunk_range.clone(), &buffer)
+ .collect::<Vec<_>>();
+ for hunk in &hunks {
+ assert_eq!(
+ hunk.secondary_status,
+ DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
+ )
+ }
+
+ pretty_assertions::assert_eq!(
+ new_index_text,
+ example.final_index_text,
+ "example: {}",
+ example.name
+ );
+ });
+ }
+ }
+
+ #[gpui::test]
+ async fn test_toggling_stage_and_unstage_same_hunk(cx: &mut TestAppContext) {
+ let head_text = "
+ one
+ two
+ three
+ "
+ .unindent();
+ let index_text = head_text.clone();
+ let buffer_text = "
+ one
+ three
+ "
+ .unindent();
+
+ let buffer = Buffer::new(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ buffer_text.clone(),
+ );
+ let unstaged = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
+ let uncommitted = BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
+ let unstaged_diff = cx.new(|cx| {
+ let mut diff = BufferDiff::new(&buffer, cx);
+ diff.set_snapshot(unstaged, &buffer, cx);
+ diff
+ });
+ let uncommitted_diff = cx.new(|cx| {
+ let mut diff = BufferDiff::new(&buffer, cx);
+ diff.set_snapshot(uncommitted, &buffer, cx);
+ diff.set_secondary_diff(unstaged_diff.clone());
+ diff
+ });
+
+ uncommitted_diff.update(cx, |diff, cx| {
+ let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
+
+ let new_index_text = diff
+ .stage_or_unstage_hunks(true, std::slice::from_ref(&hunk), &buffer, true, cx)
+ .unwrap()
+ .to_string();
+ assert_eq!(new_index_text, buffer_text);
+
+ let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
+ assert_eq!(
+ hunk.secondary_status,
+ DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
+ );
+
+ let index_text = diff
+ .stage_or_unstage_hunks(false, &[hunk], &buffer, true, cx)
+ .unwrap()
+ .to_string();
+ assert_eq!(index_text, head_text);
+
+ let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
+ // optimistically unstaged (fine, could also be HasSecondaryHunk)
+ assert_eq!(
+ hunk.secondary_status,
+ DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
+ );
+ });
+ }
+
+ #[gpui::test]
+ async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
+ let base_text = "
+ zero
+ one
+ two
+ three
+ four
+ five
+ six
+ seven
+ eight
+ nine
+ "
+ .unindent();
+
+ let buffer_text_1 = "
+ one
+ three
+ four
+ five
+ SIX
+ seven
+ eight
+ NINE
+ "
+ .unindent();
+
+ let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
+
+ let empty_diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
+ let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
+ let range = diff_1.inner.compare(&empty_diff.inner, &buffer).0.unwrap();
+ assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
+
+ // Edit does not affect the diff.
+ buffer.edit_via_marked_text(
+ &"
+ one
+ three
+ four
+ five
+ «SIX.5»
+ seven
+ eight
+ NINE
+ "
+ .unindent(),
+ );
+ let diff_2 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
+ assert_eq!(None, diff_2.inner.compare(&diff_1.inner, &buffer).0);
+
+ // Edit turns a deletion hunk into a modification.
+ buffer.edit_via_marked_text(
+ &"
+ one
+ «THREE»
+ four
+ five
+ SIX.5
+ seven
+ eight
+ NINE
+ "
+ .unindent(),
+ );
+ let diff_3 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
+ let range = diff_3.inner.compare(&diff_2.inner, &buffer).0.unwrap();
+ assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
+
+ // Edit turns a modification hunk into a deletion.
+ buffer.edit_via_marked_text(
+ &"
+ one
+ THREE
+ four
+ five«»
+ seven
+ eight
+ NINE
+ "
+ .unindent(),
+ );
+ let diff_4 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
+ let range = diff_4.inner.compare(&diff_3.inner, &buffer).0.unwrap();
+ assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
+
+ // Edit introduces a new insertion hunk.
+ buffer.edit_via_marked_text(
+ &"
+ one
+ THREE
+ four«
+ FOUR.5
+ »five
+ seven
+ eight
+ NINE
+ "
+ .unindent(),
+ );
+ let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
+ let range = diff_5.inner.compare(&diff_4.inner, &buffer).0.unwrap();
+ assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
+
+ // Edit removes a hunk.
+ buffer.edit_via_marked_text(
+ &"
+ one
+ THREE
+ four
+ FOUR.5
+ five
+ seven
+ eight
+ «nine»
+ "
+ .unindent(),
+ );
+ let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
+ let range = diff_6.inner.compare(&diff_5.inner, &buffer).0.unwrap();
+ assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
+ }
+
+ #[gpui::test(iterations = 100)]
+ async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
+ fn gen_line(rng: &mut StdRng) -> String {
+ if rng.random_bool(0.2) {
+ "\n".to_owned()
+ } else {
+ let c = rng.random_range('A'..='Z');
+ format!("{c}{c}{c}\n")
+ }
+ }
+
+ fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
+ let mut old_lines = {
+ let mut old_lines = Vec::new();
+ let old_lines_iter = head.lines();
+ for line in old_lines_iter {
+ assert!(!line.ends_with("\n"));
+ old_lines.push(line.to_owned());
+ }
+ if old_lines.last().is_some_and(|line| line.is_empty()) {
+ old_lines.pop();
+ }
+ old_lines.into_iter()
+ };
+ let mut result = String::new();
+ let unchanged_count = rng.random_range(0..=old_lines.len());
+ result +=
+ &old_lines
+ .by_ref()
+ .take(unchanged_count)
+ .fold(String::new(), |mut s, line| {
+ writeln!(&mut s, "{line}").unwrap();
+ s
+ });
+ while old_lines.len() > 0 {
+ let deleted_count = rng.random_range(0..=old_lines.len());
+ let _advance = old_lines
+ .by_ref()
+ .take(deleted_count)
+ .map(|line| line.len() + 1)
+ .sum::<usize>();
+ let minimum_added = if deleted_count == 0 { 1 } else { 0 };
+ let added_count = rng.random_range(minimum_added..=5);
+ let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
+ result += &addition;
+
+ if old_lines.len() > 0 {
+ let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
+ if blank_lines == old_lines.len() {
+ break;
+ };
+ let unchanged_count =
+ rng.random_range((blank_lines + 1).max(1)..=old_lines.len());
+ result += &old_lines.by_ref().take(unchanged_count).fold(
+ String::new(),
+ |mut s, line| {
+ writeln!(&mut s, "{line}").unwrap();
+ s
+ },
+ );
+ }
+ }
+ result
+ }
+
+ fn uncommitted_diff(
+ working_copy: &language::BufferSnapshot,
+ index_text: &Rope,
+ head_text: String,
+ cx: &mut TestAppContext,
+ ) -> Entity<BufferDiff> {
+ let secondary = cx.new(|cx| {
+ BufferDiff::new_with_base_text(&index_text.to_string(), &working_copy.text, cx)
+ });
+ cx.new(|cx| {
+ let mut diff = BufferDiff::new_with_base_text(&head_text, &working_copy.text, cx);
+ diff.secondary_diff = Some(secondary);
+ diff
+ })
+ }
+
+ let operations = std::env::var("OPERATIONS")
+ .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
+ .unwrap_or(10);
+
+ let rng = &mut rng;
+ let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
+ writeln!(&mut s, "{c}{c}{c}").unwrap();
+ s
+ });
+ let working_copy = gen_working_copy(rng, &head_text);
+ let working_copy = cx.new(|cx| {
+ language::Buffer::local_normalized(
+ Rope::from(working_copy.as_str()),
+ text::LineEnding::default(),
+ cx,
+ )
+ });
+ let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
+ let mut index_text = if rng.random() {
+ Rope::from(head_text.as_str())
+ } else {
+ working_copy.as_rope().clone()
+ };
+
+ let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
+ let mut hunks = diff.update(cx, |diff, cx| {
+ diff.snapshot(cx)
+ .hunks_intersecting_range(
+ Anchor::min_max_range_for_buffer(diff.buffer_id),
+ &working_copy,
+ )
+ .collect::<Vec<_>>()
+ });
+ if hunks.is_empty() {
+ return;
+ }
+
+ for _ in 0..operations {
+ let i = rng.random_range(0..hunks.len());
+ let hunk = &mut hunks[i];
+ let hunk_to_change = hunk.clone();
+ let stage = match hunk.secondary_status {
+ DiffHunkSecondaryStatus::HasSecondaryHunk => {
+ hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
+ true
+ }
+ DiffHunkSecondaryStatus::NoSecondaryHunk => {
+ hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
+ false
+ }
+ _ => unreachable!(),
+ };
+
+ index_text = diff.update(cx, |diff, cx| {
+ diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
+ .unwrap()
+ });
+
+ diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
+ let found_hunks = diff.update(cx, |diff, cx| {
+ diff.snapshot(cx)
+ .hunks_intersecting_range(
+ Anchor::min_max_range_for_buffer(diff.buffer_id),
+ &working_copy,
+ )
+ .collect::<Vec<_>>()
+ });
+ assert_eq!(hunks.len(), found_hunks.len());
+
+ for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
+ assert_eq!(
+ expected_hunk.buffer_range.to_point(&working_copy),
+ found_hunk.buffer_range.to_point(&working_copy)
+ );
+ assert_eq!(
+ expected_hunk.diff_base_byte_range,
+ found_hunk.diff_base_byte_range
+ );
+ assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
+ }
+ hunks = found_hunks;
+ }
+ }
+
+ #[gpui::test]
+ async fn test_row_to_base_text_row(cx: &mut TestAppContext) {
+ let base_text = "
+ zero
+ one
+ two
+ three
+ four
+ five
+ six
+ seven
+ eight
+ "
+ .unindent();
+ let buffer_text = "
+ zero
+ ONE
+ two
+ NINE
+ five
+ seven
+ "
+ .unindent();
+
+ // zero
+ // - one
+ // + ONE
+ // two
+ // - three
+ // - four
+ // + NINE
+ // five
+ // - six
+ // seven
+ // + eight
+
+ let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
+ let buffer_snapshot = buffer.snapshot();
+ let diff = BufferDiffSnapshot::new_sync(buffer_snapshot.clone(), base_text, cx);
+ let expected_results = [
+ // don't format me
+ (0, 0),
+ (1, 2),
+ (2, 2),
+ (3, 5),
+ (4, 5),
+ (5, 7),
+ (6, 9),
+ ];
+ for (buffer_row, expected) in expected_results {
+ assert_eq!(
+ diff.row_to_base_text_row(buffer_row, &buffer_snapshot),
+ expected,
+ "{buffer_row}"
+ );
+ }
+ }
+}