From a9d9b3c887c107d848ec0139daa7746c1ab85a0d Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Thu, 4 Dec 2025 19:04:14 -0500 Subject: [PATCH] wip --- crates/buffer_diff/src/buffer_diff.rs | 1896 +++++++++--------- crates/collab/src/tests/integration_tests.rs | 34 +- crates/multi_buffer/src/multi_buffer.rs | 9 +- crates/project/src/git_store.rs | 8 +- crates/project/src/project_tests.rs | 24 +- 5 files changed, 986 insertions(+), 985 deletions(-) diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs index a113217c22372737924ab77451d2762699cda1a3..9ea39b2ddcaf10d519b13f97674c4e5841980dc5 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/crates/buffer_diff/src/buffer_diff.rs @@ -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> { // 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> { } 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, + 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( 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::>(); -// 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::>(); -// 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::(); -// 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::(); -// 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 { -// 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::>() -// }); -// 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::>() -// }); -// 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::>(); + 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::>(); + 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::(); + 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::(); + 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 { + 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::>() + }); + 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::>() + }); + 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}" + ); + } + } +} diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index fcda8688d427f3e6b937f00edc7c3586dfdbef36..b78e3cdab3a656261fce8b1352a5c5ac860c1d03 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -2647,13 +2647,13 @@ async fn test_git_diff_base_change( local_unstaged_diff_a.read_with(cx_a, |diff, cx| { let buffer = buffer_local_a.read(cx); assert_eq!( - diff.base_text_string().as_deref(), + diff.base_text_string(cx).as_deref(), Some(staged_text.as_str()) ); assert_hunks( diff.hunks_in_row_range(0..4, buffer, cx), buffer, - &diff.base_text_string().unwrap(), + &diff.base_text_string(cx).unwrap(), &[(1..2, "", "two\n", DiffHunkStatus::added_none())], ); }); @@ -2677,13 +2677,13 @@ async fn test_git_diff_base_change( remote_unstaged_diff_a.read_with(cx_b, |diff, cx| { let buffer = remote_buffer_a.read(cx); assert_eq!( - diff.base_text_string().as_deref(), + diff.base_text_string(cx).as_deref(), Some(staged_text.as_str()) ); assert_hunks( diff.hunks_in_row_range(0..4, buffer, cx), buffer, - &diff.base_text_string().unwrap(), + &diff.base_text_string(cx).unwrap(), &[(1..2, "", "two\n", DiffHunkStatus::added_none())], ); }); @@ -2699,13 +2699,13 @@ async fn test_git_diff_base_change( remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| { let buffer = remote_buffer_a.read(cx); assert_eq!( - diff.base_text_string().as_deref(), + diff.base_text_string(cx).as_deref(), Some(committed_text.as_str()) ); assert_hunks( diff.hunks_in_row_range(0..4, buffer, cx), buffer, - &diff.base_text_string().unwrap(), + &diff.base_text_string(cx).unwrap(), &[( 1..2, "TWO\n", @@ -2731,13 +2731,13 @@ async fn test_git_diff_base_change( local_unstaged_diff_a.read_with(cx_a, |diff, cx| { let buffer = buffer_local_a.read(cx); assert_eq!( - diff.base_text_string().as_deref(), + diff.base_text_string(cx).as_deref(), Some(new_staged_text.as_str()) ); assert_hunks( diff.hunks_in_row_range(0..4, buffer, cx), buffer, - &diff.base_text_string().unwrap(), + &diff.base_text_string(cx).unwrap(), &[(2..3, "", "three\n", DiffHunkStatus::added_none())], ); }); @@ -2746,13 +2746,13 @@ async fn test_git_diff_base_change( remote_unstaged_diff_a.read_with(cx_b, |diff, cx| { let buffer = remote_buffer_a.read(cx); assert_eq!( - diff.base_text_string().as_deref(), + diff.base_text_string(cx).as_deref(), Some(new_staged_text.as_str()) ); assert_hunks( diff.hunks_in_row_range(0..4, buffer, cx), buffer, - &diff.base_text_string().unwrap(), + &diff.base_text_string(cx).unwrap(), &[(2..3, "", "three\n", DiffHunkStatus::added_none())], ); }); @@ -2760,13 +2760,13 @@ async fn test_git_diff_base_change( remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| { let buffer = remote_buffer_a.read(cx); assert_eq!( - diff.base_text_string().as_deref(), + diff.base_text_string(cx).as_deref(), Some(new_committed_text.as_str()) ); assert_hunks( diff.hunks_in_row_range(0..4, buffer, cx), buffer, - &diff.base_text_string().unwrap(), + &diff.base_text_string(cx).unwrap(), &[( 1..2, "TWO_HUNDRED\n", @@ -2813,13 +2813,13 @@ async fn test_git_diff_base_change( local_unstaged_diff_b.read_with(cx_a, |diff, cx| { let buffer = buffer_local_b.read(cx); assert_eq!( - diff.base_text_string().as_deref(), + diff.base_text_string(cx).as_deref(), Some(staged_text.as_str()) ); assert_hunks( diff.hunks_in_row_range(0..4, buffer, cx), buffer, - &diff.base_text_string().unwrap(), + &diff.base_text_string(cx).unwrap(), &[(1..2, "", "two\n", DiffHunkStatus::added_none())], ); }); @@ -2842,7 +2842,7 @@ async fn test_git_diff_base_change( remote_unstaged_diff_b.read_with(cx_b, |diff, cx| { let buffer = remote_buffer_b.read(cx); assert_eq!( - diff.base_text_string().as_deref(), + diff.base_text_string(cx).as_deref(), Some(staged_text.as_str()) ); assert_hunks( @@ -2864,7 +2864,7 @@ async fn test_git_diff_base_change( local_unstaged_diff_b.read_with(cx_a, |diff, cx| { let buffer = buffer_local_b.read(cx); assert_eq!( - diff.base_text_string().as_deref(), + diff.base_text_string(cx).as_deref(), Some(new_staged_text.as_str()) ); assert_hunks( @@ -2878,7 +2878,7 @@ async fn test_git_diff_base_change( remote_unstaged_diff_b.read_with(cx_b, |diff, cx| { let buffer = remote_buffer_b.read(cx); assert_eq!( - diff.base_text_string().as_deref(), + diff.base_text_string(cx).as_deref(), Some(new_staged_text.as_str()) ); assert_hunks( diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 24da46458b3da44fbc116543ef2a3a07a50052f6..fc749a83e0742ed9ebe29269019c82493ccc0504 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -506,12 +506,7 @@ struct BufferState { _subscriptions: [gpui::Subscription; 2], } -fn foo(buffer: Entity, cx: &mut App) { - buffer.update(cx, |buffer, cx| { - let new_buffer = Buffer::new(); - std::mem::replace(buffer, new_buffer); - }) -} + struct DiffState { diff: Entity, @@ -2644,7 +2639,7 @@ impl MultiBuffer { ) { debug_assert!(self.diffs.values().all(|diff| diff.main_buffer.is_some())); - let diff_change_range = 0..diff.read(cx).base_text().len(); + let diff_change_range = 0..diff.read(cx).base_text(cx).len(); self.inverted_buffer_diff_changed( diff.clone(), diff_change_range, diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 58181e20e961685f34c3298add113f847c3d93c5..8f5f42295c64e5d2e72272d45d35343b4cb165e1 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -708,7 +708,7 @@ impl GitStore { buffer_diff .update(cx, |buffer_diff, cx| { buffer_diff.set_base_text( - content.map(Arc::new), + content.map(|s| s.as_ref().into()), buffer_snapshot.language().cloned(), Some(languages.clone()), buffer_snapshot.text, @@ -2611,7 +2611,7 @@ impl GitStore { .or_default(); shared_diffs.entry(buffer_id).or_default().unstaged = Some(diff.clone()); })?; - let staged_text = diff.read_with(&cx, |diff, _| diff.base_text_string())?; + let staged_text = diff.read_with(&cx, |diff, _| diff.base_text_string(cx))?; Ok(proto::OpenUnstagedDiffResponse { staged_text }) } @@ -2641,14 +2641,14 @@ impl GitStore { let unstaged_diff = diff.secondary_diff(); let index_snapshot = unstaged_diff.and_then(|diff| { let diff = diff.read(cx); - diff.base_text_exists().then(|| diff.base_text()) + diff.base_text_exists().then(|| diff.base_text(cx)) }); let mode; let staged_text; let committed_text; if diff.base_text_exists() { - let committed_snapshot = diff.base_text(); + let committed_snapshot = diff.base_text(cx); committed_text = Some(committed_snapshot.text()); if let Some(index_text) = index_snapshot { if index_text.remote_id() == committed_snapshot.remote_id() { diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 61f8b2aa1192ec0c4d74e8f122540baa17e02fed..0387358f1b6acee6ac9382467d438bf1454a8632 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -7849,9 +7849,9 @@ async fn test_staging_hunks_with_delayed_fs_event(cx: &mut gpui::TestAppContext) // The hunks are initially unstaged. uncommitted_diff.read_with(cx, |diff, cx| { assert_hunks( - diff.hunks(&snapshot, cx), + diff.snapshot(cx).hunks(&snapshot), &snapshot, - &diff.base_text_string().unwrap(), + &diff.base_text_string(cx).unwrap(), &[ ( 0..0, @@ -7880,12 +7880,12 @@ async fn test_staging_hunks_with_delayed_fs_event(cx: &mut gpui::TestAppContext) // Stage the first hunk. uncommitted_diff.update(cx, |diff, cx| { - let hunk = diff.hunks(&snapshot, cx).next().unwrap(); + let hunk = diff.snapshot(cx).hunks(&snapshot).next().unwrap(); diff.stage_or_unstage_hunks(true, &[hunk], &snapshot, true, cx); assert_hunks( - diff.hunks(&snapshot, cx), + diff.snapshot(cx).hunks(&snapshot), &snapshot, - &diff.base_text_string().unwrap(), + &diff.base_text_string(cx).unwrap(), &[ ( 0..0, @@ -7912,12 +7912,12 @@ async fn test_staging_hunks_with_delayed_fs_event(cx: &mut gpui::TestAppContext) // Stage the second hunk *before* receiving the FS event for the first hunk. cx.run_until_parked(); uncommitted_diff.update(cx, |diff, cx| { - let hunk = diff.hunks(&snapshot, cx).nth(1).unwrap(); + let hunk = diff.snapshot(cx).hunks(&snapshot).nth(1).unwrap(); diff.stage_or_unstage_hunks(true, &[hunk], &snapshot, true, cx); assert_hunks( diff.hunks(&snapshot, cx), &snapshot, - &diff.base_text_string().unwrap(), + &diff.base_text_string(cx).unwrap(), &[ ( 0..0, @@ -7961,7 +7961,7 @@ async fn test_staging_hunks_with_delayed_fs_event(cx: &mut gpui::TestAppContext) assert_hunks( diff.hunks(&snapshot, cx), &snapshot, - &diff.base_text_string().unwrap(), + &diff.base_text_string(cx).unwrap(), &[ (0..0, "zero\n", "", DiffHunkStatus::deleted(NoSecondaryHunk)), ( @@ -8164,7 +8164,7 @@ async fn test_single_file_diffs(cx: &mut gpui::TestAppContext) { assert_hunks( uncommitted_diff.hunks(&snapshot, cx), &snapshot, - &uncommitted_diff.base_text_string().unwrap(), + &uncommitted_diff.base_text_string(cx).unwrap(), &[( 1..2, " println!(\"hello from HEAD\");\n", @@ -10345,7 +10345,7 @@ async fn test_buffer_changed_file_path_updates_git_diff(cx: &mut gpui::TestAppCo cx.run_until_parked(); unstaged_diff.update(cx, |unstaged_diff, _cx| { - let base_text = unstaged_diff.base_text_string().unwrap(); + let base_text = unstaged_diff.base_text_string(cx).unwrap(); assert_eq!(base_text, file_1_staged, "Should start with file_1 staged"); }); @@ -10369,7 +10369,7 @@ async fn test_buffer_changed_file_path_updates_git_diff(cx: &mut gpui::TestAppCo // the `BufferChangedFilePath` event being handled. unstaged_diff.update(cx, |unstaged_diff, cx| { let snapshot = buffer.read(cx).snapshot(); - let base_text = unstaged_diff.base_text_string().unwrap(); + let base_text = unstaged_diff.base_text_string(cx).unwrap(); assert_eq!( base_text, file_2_staged, "Diff bases should be automatically updated to file_2 staged content" @@ -10389,7 +10389,7 @@ async fn test_buffer_changed_file_path_updates_git_diff(cx: &mut gpui::TestAppCo cx.run_until_parked(); uncommitted_diff.update(cx, |uncommitted_diff, _cx| { - let base_text = uncommitted_diff.base_text_string().unwrap(); + let base_text = uncommitted_diff.base_text_string(cx).unwrap(); assert_eq!( base_text, file_2_committed, "Uncommitted diff should compare against file_2 committed content"