diff --git a/crates/acp_thread/src/diff.rs b/crates/acp_thread/src/diff.rs index f17e9d0fce404483ae99efc95bf666586c1f644b..6623c6df6e67a119370581e843423038af35970d 100644 --- a/crates/acp_thread/src/diff.rs +++ b/crates/acp_thread/src/diff.rs @@ -90,7 +90,7 @@ impl Diff { let base_text = base_text_snapshot.text(); debug_assert_eq!(buffer_text_snapshot.text(), base_text); let buffer_diff = cx.new(|cx| { - let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, base_text_snapshot); + let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, cx); let snapshot = diff.snapshot(cx); let secondary_diff = cx.new(|cx| { let mut diff = BufferDiff::new(&buffer_text_snapshot, cx); @@ -193,7 +193,7 @@ impl Diff { pub struct PendingDiff { multibuffer: Entity, - base_text: Arc, + base_text: Arc, new_buffer: Entity, diff: Entity, revealed_ranges: Vec>, diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs index e57fbb30dd0b28bfe84c2bebf7ba49dfad860c75..a113217c22372737924ab77451d2762699cda1a3 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/crates/buffer_diff/src/buffer_diff.rs @@ -1,6 +1,6 @@ use futures::channel::oneshot; use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch}; -use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, TaskLabel}; +use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, TaskLabel}; use language::{ BufferRow, DiffOptions, File, Language, LanguageName, LanguageRegistry, language_settings::language_settings, word_diff_ranges, @@ -22,7 +22,7 @@ pub const MAX_WORD_DIFF_LINE_COUNT: usize = 5; pub struct BufferDiff { pub buffer_id: BufferId, - inner: BufferDiffInner, + inner: BufferDiffInner>, // diff of the index vs head secondary_diff: Option>, } @@ -43,10 +43,10 @@ impl std::fmt::Debug for BufferDiffSnapshot { } #[derive(Clone)] -struct BufferDiffInner { +struct BufferDiffInner { hunks: SumTree, pending_hunks: SumTree, - base_text: language::BufferSnapshot, + base_text: BaseText, base_text_exists: bool, } @@ -197,18 +197,6 @@ impl BufferDiffSnapshot { self.inner.base_text.remote_id() } - fn empty(buffer: &text::BufferSnapshot, cx: &mut App) -> BufferDiffSnapshot { - BufferDiffSnapshot { - inner: BufferDiffInner { - base_text: language::Buffer::build_empty_snapshot(cx), - hunks: SumTree::new(buffer), - pending_hunks: SumTree::new(buffer), - base_text_exists: false, - }, - secondary_diff: None, - } - } - fn unchanged( buffer: &text::BufferSnapshot, base_text: language::BufferSnapshot, @@ -226,8 +214,9 @@ impl BufferDiffSnapshot { } fn new_with_base_text( + base_text_buffer: Entity, buffer: text::BufferSnapshot, - base_text: Option>, + base_text: Option>, language: Option>, language_registry: Option>, cx: &mut App, @@ -243,17 +232,22 @@ impl BufferDiffSnapshot { ); if let Some(text) = &base_text { - let base_text_rope = Rope::from(text.as_str()); + let text_str: &str = &text; + let base_text_rope = Rope::from(text_str); base_text_pair = Some((text.clone(), base_text_rope.clone())); - let snapshot = - language::Buffer::build_snapshot(base_text_rope, language, language_registry, cx); - base_text_snapshot = cx.background_spawn(snapshot); + 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_exists = true; } else { base_text_pair = None; - base_text_snapshot = Task::ready(language::Buffer::build_empty_snapshot(cx)); + base_text_buffer.update(cx, |base_text_buffer, cx| { + base_text_buffer.set_text("", cx); + }); base_text_exists = false; }; + base_text_snapshot = base_text_buffer.read(cx).snapshot(); let hunks = cx .background_executor() @@ -263,10 +257,10 @@ impl BufferDiffSnapshot { }); async move { - let (base_text, hunks) = futures::join!(base_text_snapshot, hunks); + let hunks = hunks.await; Self { inner: BufferDiffInner { - base_text, + base_text: base_text_snapshot, hunks, base_text_exists, pending_hunks: SumTree::new(&buffer), @@ -276,12 +270,14 @@ impl BufferDiffSnapshot { } } + /// Compute a diff using an existing/unchanged base text. pub fn new_with_base_buffer( buffer: text::BufferSnapshot, - base_text: Option>, - base_text_snapshot: language::BufferSnapshot, + base_text: Option>, + base_text_buffer: Entity, cx: &App, ) -> impl Future + use<> { + let base_text_snapshot = base_text_buffer.read(cx).snapshot(); let diff_options = build_diff_options( base_text_snapshot.file(), base_text_snapshot.language().map(|l| l.name()), @@ -314,7 +310,15 @@ impl BufferDiffSnapshot { cx: &mut gpui::TestAppContext, ) -> BufferDiffSnapshot { cx.executor().block(cx.update(|cx| { - Self::new_with_base_text(buffer, Some(Arc::new(diff_base)), None, None, cx) + let base_text_buffer = cx.new(|cx| language::Buffer::local("", cx)); + Self::new_with_base_text( + base_text_buffer, + buffer, + Some(diff_base.as_str().into()), + None, + None, + cx, + ) })) } @@ -359,6 +363,43 @@ impl BufferDiffSnapshot { .hunks_intersecting_range_impl(filter, main_buffer, unstaged_counterpart) } + pub fn hunks<'a>( + &'a self, + buffer_snapshot: &'a text::BufferSnapshot, + ) -> impl 'a + Iterator { + self.hunks_intersecting_range( + Anchor::min_max_range_for_buffer(buffer_snapshot.remote_id()), + buffer_snapshot, + ) + } + + pub fn hunks_in_row_range<'a>( + &'a self, + range: Range, + buffer: &'a text::BufferSnapshot, + ) -> impl 'a + Iterator { + let start = buffer.anchor_before(Point::new(range.start, 0)); + let end = buffer.anchor_after(Point::new(range.end, 0)); + self.hunks_intersecting_range(start..end, buffer) + } + + pub fn range_to_hunk_range( + &self, + range: Range, + buffer: &text::BufferSnapshot, + ) -> (Option>, Option>) { + let first_hunk = self.hunks_intersecting_range(range.clone(), buffer).next(); + let last_hunk = self.hunks_intersecting_range_rev(range, buffer).next(); + let range = first_hunk + .as_ref() + .zip(last_hunk.as_ref()) + .map(|(first, last)| first.buffer_range.start..last.buffer_range.end); + let base_text_range = first_hunk + .zip(last_hunk) + .map(|(first, last)| first.diff_base_byte_range.start..last.diff_base_byte_range.end); + (range, base_text_range) + } + pub fn base_text(&self) -> &language::BufferSnapshot { &self.inner.base_text } @@ -423,7 +464,7 @@ impl BufferDiffSnapshot { } } -impl BufferDiffInner { +impl BufferDiffInner> { /// Returns the new index text and new pending hunks. fn stage_or_unstage_hunks_impl( &mut self, @@ -432,13 +473,14 @@ impl BufferDiffInner { hunks: &[DiffHunk], buffer: &text::BufferSnapshot, file_exists: bool, + cx: &mut Context, ) -> Option { let head_text = self .base_text_exists - .then(|| self.base_text.as_rope().clone()); + .then(|| self.base_text.read(cx).as_rope().clone()); let index_text = unstaged_diff .base_text_exists - .then(|| unstaged_diff.base_text.as_rope().clone()); + .then(|| unstaged_diff.base_text.read(cx).as_rope().clone()); // 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. @@ -631,7 +673,9 @@ impl BufferDiffInner { new_index_text.append(index_cursor.suffix()); Some(new_index_text) } +} +impl BufferDiffInner { fn hunks_intersecting_range<'a>( &'a self, range: Range, @@ -917,7 +961,7 @@ fn build_diff_options( } fn compute_hunks( - diff_base: Option<(Arc, Rope)>, + diff_base: Option<(Arc, Rope)>, buffer: text::BufferSnapshot, diff_options: Option, ) -> SumTree { @@ -1103,7 +1147,6 @@ impl std::fmt::Debug for BufferDiff { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("BufferChangeSet") .field("buffer_id", &self.buffer_id) - .field("snapshot", &self.inner) .finish() } } @@ -1124,19 +1167,26 @@ impl BufferDiff { pub fn new(buffer: &text::BufferSnapshot, cx: &mut App) -> Self { BufferDiff { buffer_id: buffer.remote_id(), - inner: BufferDiffSnapshot::empty(buffer, cx).inner, + inner: BufferDiffInner { + base_text: cx.new(|cx| language::Buffer::local("", cx)), + hunks: SumTree::new(buffer), + pending_hunks: SumTree::new(buffer), + base_text_exists: false, + }, secondary_diff: None, } } - pub fn new_unchanged( - buffer: &text::BufferSnapshot, - base_text: language::BufferSnapshot, - ) -> Self { - debug_assert_eq!(buffer.text(), base_text.text()); + pub fn new_unchanged(buffer: &text::BufferSnapshot, cx: &mut Context) -> Self { + let base_text = buffer.text(); BufferDiff { buffer_id: buffer.remote_id(), - inner: BufferDiffSnapshot::unchanged(buffer, base_text).inner, + inner: BufferDiffInner { + base_text: cx.new(|cx| language::Buffer::local(base_text, cx)), + hunks: SumTree::new(buffer), + pending_hunks: SumTree::new(buffer), + base_text_exists: false, + }, secondary_diff: None, } } @@ -1147,9 +1197,11 @@ impl BufferDiff { buffer: &Entity, cx: &mut App, ) -> Self { + let base_buffer = cx.new(|cx| language::Buffer::local("", 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(), Some(base_text.into()), None, @@ -1159,7 +1211,12 @@ impl BufferDiff { let snapshot = cx.background_executor().block(snapshot); Self { buffer_id: buffer.read(cx).remote_id(), - inner: snapshot.inner, + inner: BufferDiffInner { + hunks: snapshot.inner.hunks.clone(), + pending_hunks: snapshot.inner.pending_hunks.clone(), + base_text: base_buffer, + base_text_exists: snapshot.inner.base_text_exists, + }, secondary_diff: None, } } @@ -1180,7 +1237,7 @@ impl BufferDiff { }); cx.emit(BufferDiffEvent::DiffChanged { changed_range: Some(Anchor::min_max_range_for_buffer(self.buffer_id)), - base_text_changed_range: Some(0..self.base_text().len()), + base_text_changed_range: Some(0..self.base_text(cx).len()), }); } } @@ -1193,13 +1250,19 @@ impl BufferDiff { file_exists: bool, cx: &mut Context, ) -> Option { - let new_index_text = self.inner.stage_or_unstage_hunks_impl( - &self.secondary_diff.as_ref()?.read(cx).inner, - stage, - hunks, - buffer, - file_exists, - ); + let new_index_text = self + .secondary_diff + .as_ref()? + .update(cx, |secondary_diff, cx| { + self.inner.stage_or_unstage_hunks_impl( + &secondary_diff.inner, + stage, + hunks, + buffer, + file_exists, + cx, + ) + }); cx.emit(BufferDiffEvent::HunksStagedOrUnstaged( new_index_text.clone(), @@ -1216,30 +1279,10 @@ impl BufferDiff { new_index_text } - pub fn range_to_hunk_range( - &self, - range: Range, - buffer: &text::BufferSnapshot, - cx: &App, - ) -> (Option>, Option>) { - let first_hunk = self - .hunks_intersecting_range(range.clone(), buffer, cx) - .next(); - let last_hunk = self.hunks_intersecting_range_rev(range, buffer).next(); - let range = first_hunk - .as_ref() - .zip(last_hunk.as_ref()) - .map(|(first, last)| first.buffer_range.start..last.buffer_range.end); - let base_text_range = first_hunk - .zip(last_hunk) - .map(|(first, last)| first.diff_base_byte_range.start..last.diff_base_byte_range.end); - (range, base_text_range) - } - pub async fn update_diff( this: Entity, buffer: text::BufferSnapshot, - base_text: Option>, + base_text: Option>, base_text_changed: bool, language_changed: bool, language: Option>, @@ -1248,7 +1291,10 @@ impl BufferDiff { ) -> anyhow::Result { Ok(if base_text_changed || language_changed { cx.update(|cx| { + // in this path we should mutate the Entity on `this` + // we want to return a BufferDiffSnapshot for which the `base_text` is a BufferSnapshot of the persistent `Entity` BufferDiffSnapshot::new_with_base_text( + this.read(cx).inner.base_text.clone(), buffer.clone(), base_text, language.clone(), @@ -1259,10 +1305,11 @@ impl BufferDiff { .await } else { this.read_with(cx, |this, cx| { + // FIXME we think the base text hasn't changed, so we should _not_ mutate the base text buffer or create a new buffersnapshot BufferDiffSnapshot::new_with_base_buffer( buffer.clone(), base_text, - this.base_text().clone(), + this.inner.base_text.clone(), cx, ) })? @@ -1293,17 +1340,21 @@ impl BufferDiff { ) -> Option> { log::debug!("set snapshot with secondary {secondary_diff_change:?}"); + let old_snapshot = self.snapshot(cx); let state = &mut self.inner; let new_state = new_snapshot.inner; let (base_text_changed, (mut changed_range, mut base_text_changed_range)) = match (state.base_text_exists, new_state.base_text_exists) { (false, false) => (true, (None, None)), (true, true) - if state.base_text.remote_id() == new_state.base_text.remote_id() - && state.base_text.syntax_update_count() - == new_state.base_text.syntax_update_count() => + if old_snapshot.inner.base_text.version() == new_state.base_text.version() + && old_snapshot.inner.base_text.non_text_state_update_count() + == new_state.base_text.non_text_state_update_count() => { - (false, new_state.compare(state, buffer)) + debug_assert!( + old_snapshot.inner.base_text.remote_id() == new_state.base_text.remote_id() + ); + (false, new_state.compare(&old_snapshot.inner, buffer)) } _ => ( true, @@ -1316,7 +1367,7 @@ impl BufferDiff { if let Some(secondary_changed_range) = secondary_diff_change && let (Some(secondary_hunk_range), Some(secondary_base_range)) = - self.range_to_hunk_range(secondary_changed_range, buffer, cx) + old_snapshot.range_to_hunk_range(secondary_changed_range, buffer) { if let Some(range) = &mut changed_range { range.start = *secondary_hunk_range.start.min(&range.start, buffer); @@ -1335,7 +1386,6 @@ impl BufferDiff { let state = &mut self.inner; state.base_text_exists = new_state.base_text_exists; - state.base_text = new_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()) @@ -1366,8 +1416,8 @@ impl BufferDiff { changed_range } - pub fn base_text(&self) -> &language::BufferSnapshot { - &self.inner.base_text + pub fn base_text(&self, cx: &App) -> language::BufferSnapshot { + self.inner.base_text.read(cx).snapshot() } pub fn base_text_exists(&self) -> bool { @@ -1376,7 +1426,12 @@ impl BufferDiff { pub fn snapshot(&self, cx: &App) -> BufferDiffSnapshot { BufferDiffSnapshot { - inner: self.inner.clone(), + inner: BufferDiffInner { + hunks: self.inner.hunks.clone(), + pending_hunks: self.inner.pending_hunks.clone(), + base_text: self.inner.base_text.read(cx).snapshot(), + base_text_exists: self.inner.base_text_exists, + }, secondary_diff: self .secondary_diff .as_ref() @@ -1384,56 +1439,10 @@ impl BufferDiff { } } - pub fn hunks<'a>( - &'a self, - buffer_snapshot: &'a text::BufferSnapshot, - cx: &'a App, - ) -> impl 'a + Iterator { - self.hunks_intersecting_range( - Anchor::min_max_range_for_buffer(buffer_snapshot.remote_id()), - buffer_snapshot, - cx, - ) - } - - pub fn hunks_intersecting_range<'a>( - &'a self, - range: Range, - buffer_snapshot: &'a text::BufferSnapshot, - cx: &'a App, - ) -> impl 'a + Iterator { - let unstaged_counterpart = self - .secondary_diff - .as_ref() - .map(|diff| &diff.read(cx).inner); - self.inner - .hunks_intersecting_range(range, buffer_snapshot, unstaged_counterpart) - } - - pub fn hunks_intersecting_range_rev<'a>( - &'a self, - range: Range, - buffer_snapshot: &'a text::BufferSnapshot, - ) -> impl 'a + Iterator { - self.inner - .hunks_intersecting_range_rev(range, buffer_snapshot) - } - - pub fn hunks_in_row_range<'a>( - &'a self, - range: Range, - buffer: &'a text::BufferSnapshot, - cx: &'a App, - ) -> impl 'a + Iterator { - let start = buffer.anchor_before(Point::new(range.start, 0)); - let end = buffer.anchor_after(Point::new(range.end, 0)); - self.hunks_intersecting_range(start..end, buffer, cx) - } - /// Used in cases where the change set isn't derived from git. pub fn set_base_text( &mut self, - base_text: Option>, + base_text: Option>, language: Option>, language_registry: Option>, buffer: text::BufferSnapshot, @@ -1443,6 +1452,7 @@ impl BufferDiff { let this = cx.weak_entity(); let snapshot = BufferDiffSnapshot::new_with_base_text( + self.inner.base_text.clone(), buffer.clone(), base_text, language, @@ -1467,15 +1477,15 @@ impl BufferDiff { rx } - pub fn base_text_string(&self) -> Option { + pub fn base_text_string(&self, cx: &App) -> Option { self.inner .base_text_exists - .then(|| self.inner.base_text.text()) + .then(|| self.inner.base_text.read(cx).text()) } #[cfg(any(test, feature = "test-support"))] pub fn recalculate_diff_sync(&mut self, buffer: text::BufferSnapshot, cx: &mut Context) { - let base_text = self.base_text_string().map(Arc::new); + let base_text = self.base_text_string(cx).map(|s| s.as_str().into()); let snapshot = BufferDiffSnapshot::new_with_base_buffer( buffer.clone(), base_text, @@ -1622,936 +1632,938 @@ pub fn assert_hunks( pretty_assertions::assert_eq!(actual_hunks, expected_hunks); } -#[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}" - ); - } - } -} +// 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}" +// ); +// } +// } +// } +// diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 2a1a68a94ae1c33bbc954e831bc78e1f6f33ead5..24da46458b3da44fbc116543ef2a3a07a50052f6 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -506,6 +506,13 @@ 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, /// Whether the [`MultiBuffer`] this is associated with is inverted (i.e. diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 6ed04c6e25a4ab6bc6db5993bad10bc7f3c2ba2a..61f8b2aa1192ec0c4d74e8f122540baa17e02fed 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -7735,9 +7735,9 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) { // Both staged hunks appear as pending. uncommitted_diff.update(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, @@ -7765,9 +7765,9 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) { cx.run_until_parked(); uncommitted_diff.update(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, "zero\n", "", DiffHunkStatus::deleted(NoSecondaryHunk)), (