wip

Cole Miller and Cameron created

Co-authored-by: Cameron <cameron@zed.dev>

Change summary

crates/acp_thread/src/diff.rs                 |  12 
crates/buffer_diff/src/buffer_diff.rs         | 554 +++++++-------------
crates/multi_buffer/src/anchor.rs             |   4 
crates/multi_buffer/src/multi_buffer.rs       |   7 
crates/multi_buffer/src/multi_buffer_tests.rs | 102 ++
crates/project/src/git_store.rs               |  73 +-
crates/project/src/git_store/branch_diff.rs   |   4 
crates/rope/src/chunk.rs                      |   2 
8 files changed, 319 insertions(+), 439 deletions(-)

Detailed changes

crates/acp_thread/src/diff.rs 🔗

@@ -86,17 +86,9 @@ impl Diff {
 
     pub fn new(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self {
         let buffer_text_snapshot = buffer.read(cx).text_snapshot();
-        let base_text_snapshot = buffer.read(cx).snapshot();
-        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, cx);
-            let snapshot = diff.snapshot(cx);
-            let secondary_diff = cx.new(|cx| {
-                let mut diff = BufferDiff::new(&buffer_text_snapshot, cx);
-                diff.set_snapshot(snapshot, &buffer_text_snapshot, cx);
-                diff
-            });
+            let secondary_diff = cx.new(|cx| BufferDiff::new_unchanged(&buffer_text_snapshot, cx));
             diff.set_secondary_diff(secondary_diff);
             diff
         });
@@ -109,7 +101,7 @@ impl Diff {
 
         Self::Pending(PendingDiff {
             multibuffer,
-            base_text: Arc::new(base_text),
+            base_text: Arc::from(buffer_text_snapshot.text().as_str()),
             _subscription: cx.observe(&buffer, |this, _, cx| {
                 if let Diff::Pending(diff) = this {
                     diff.update(cx);

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, TaskLabel};
+use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, TaskLabel};
 use language::{
     BufferRow, DiffOptions, File, Language, LanguageName, LanguageRegistry,
     language_settings::language_settings, word_diff_ranges,
@@ -42,8 +42,9 @@ impl std::fmt::Debug for BufferDiffSnapshot {
     }
 }
 
+// FIXME figure out how to hide this
 #[derive(Clone)]
-struct BufferDiffInner<BaseText = language::BufferSnapshot> {
+pub struct BufferDiffInner<BaseText = language::BufferSnapshot> {
     hunks: SumTree<InternalDiffHunk>,
     pending_hunks: SumTree<PendingHunk>,
     base_text: BaseText,
@@ -197,131 +198,14 @@ impl BufferDiffSnapshot {
         self.inner.base_text.remote_id()
     }
 
-    fn unchanged(
-        buffer: &text::BufferSnapshot,
-        base_text: language::BufferSnapshot,
-    ) -> BufferDiffSnapshot {
-        debug_assert_eq!(buffer.text(), base_text.text());
-        BufferDiffSnapshot {
-            inner: BufferDiffInner {
-                base_text,
-                hunks: SumTree::new(buffer),
-                pending_hunks: SumTree::new(buffer),
-                base_text_exists: false,
-            },
-            secondary_diff: None,
-        }
-    }
-
-    fn new_with_base_text(
-        base_text_buffer: Entity<language::Buffer>,
-        buffer: text::BufferSnapshot,
-        base_text: Option<Arc<str>>,
-        language: Option<Arc<Language>>,
-        language_registry: Option<Arc<LanguageRegistry>>,
-        cx: &mut App,
-    ) -> impl Future<Output = Self> + use<> {
-        let base_text_pair;
-        let base_text_exists;
-        let base_text_snapshot;
-        let diff_options = build_diff_options(
-            None,
-            language.as_ref().map(|l| l.name()),
-            language.as_ref().map(|l| l.default_scope()),
-            cx,
-        );
-
-        if let Some(text) = &base_text {
-            let text_str: &str = &text;
-            let base_text_rope = Rope::from(text_str);
-            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(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()
-            .spawn_labeled(*CALCULATE_DIFF_TASK, {
-                let buffer = buffer.clone();
-                async move { compute_hunks(base_text_pair, buffer, diff_options) }
-            });
-
-        async move {
-            let hunks = hunks.await;
-            Self {
-                inner: BufferDiffInner {
-                    base_text: base_text_snapshot,
-                    hunks,
-                    base_text_exists,
-                    pending_hunks: SumTree::new(&buffer),
-                },
-                secondary_diff: None,
-            }
-        }
-    }
-
-    /// Compute a diff using an existing/unchanged base text.
-    pub fn new_with_base_buffer(
-        buffer: text::BufferSnapshot,
-        base_text: Option<Arc<str>>,
-        base_text_buffer: Entity<language::Buffer>,
-        cx: &App,
-    ) -> impl Future<Output = Self> + 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()),
-            base_text_snapshot.language().map(|l| l.default_scope()),
-            cx,
-        );
-        let base_text_exists = base_text.is_some();
-        let base_text_pair = base_text.map(|text| {
-            debug_assert_eq!(&*text, &base_text_snapshot.text());
-            (text, base_text_snapshot.as_rope().clone())
-        });
-        cx.background_executor()
-            .spawn_labeled(*CALCULATE_DIFF_TASK, async move {
-                Self {
-                    inner: BufferDiffInner {
-                        base_text: base_text_snapshot,
-                        pending_hunks: SumTree::new(&buffer),
-                        hunks: compute_hunks(base_text_pair, buffer, diff_options),
-                        base_text_exists,
-                    },
-                    secondary_diff: None,
-                }
-            })
-    }
-
     #[cfg(test)]
     fn new_sync(
         buffer: text::BufferSnapshot,
         diff_base: String,
         cx: &mut gpui::TestAppContext,
     ) -> BufferDiffSnapshot {
-        cx.executor().block(cx.update(|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(dbg!(diff_base.as_str().into())),
-                None,
-                None,
-                cx,
-            )
-        }))
+        let buffer_diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
+        buffer_diff.update(cx, |buffer_diff, cx| buffer_diff.snapshot(cx))
     }
 
     pub fn is_empty(&self) -> bool {
@@ -486,7 +370,7 @@ impl BufferDiffInner<Entity<language::Buffer>> {
 
         // If the file doesn't exist in either HEAD or the index, then the
         // entire file must be either created or deleted in the index.
-        let (index_text, head_text) = match (dbg!(index_text), head_text) {
+        let (index_text, head_text) = match (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 {
@@ -665,12 +549,11 @@ impl BufferDiffInner<Entity<language::Buffer>> {
         }
 
         let mut new_index_text = Rope::new();
-        dbg!(index_text.len());
         let mut index_cursor = index_text.cursor(0);
 
         for (old_range, replacement_text) in edits {
-            new_index_text.append(index_cursor.slice(dbg!(old_range.start)));
-            index_cursor.seek_forward(dbg!(old_range.end));
+            new_index_text.append(index_cursor.slice(old_range.start));
+            index_cursor.seek_forward(old_range.end);
             new_index_text.push(&replacement_text);
         }
         new_index_text.append(index_cursor.suffix());
@@ -850,91 +733,6 @@ impl BufferDiffInner {
             })
         })
     }
-
-    fn compare(
-        &self,
-        old: &Self,
-        new_snapshot: &text::BufferSnapshot,
-    ) -> (Option<Range<Anchor>>, Option<Range<usize>>) {
-        let mut new_cursor = self.hunks.cursor::<()>(new_snapshot);
-        let mut old_cursor = old.hunks.cursor::<()>(new_snapshot);
-        old_cursor.next();
-        new_cursor.next();
-        let mut start = None;
-        let mut end = None;
-        let mut base_text_start = None;
-        let mut base_text_end = None;
-
-        loop {
-            match (new_cursor.item(), old_cursor.item()) {
-                (Some(new_hunk), Some(old_hunk)) => {
-                    match new_hunk
-                        .buffer_range
-                        .start
-                        .cmp(&old_hunk.buffer_range.start, new_snapshot)
-                    {
-                        Ordering::Less => {
-                            start.get_or_insert(new_hunk.buffer_range.start);
-                            base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
-                            end.replace(new_hunk.buffer_range.end);
-                            base_text_end.get_or_insert(new_hunk.diff_base_byte_range.end);
-                            new_cursor.next();
-                        }
-                        Ordering::Equal => {
-                            if new_hunk != old_hunk {
-                                start.get_or_insert(new_hunk.buffer_range.start);
-                                base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
-                                if old_hunk
-                                    .buffer_range
-                                    .end
-                                    .cmp(&new_hunk.buffer_range.end, new_snapshot)
-                                    .is_ge()
-                                {
-                                    end.replace(old_hunk.buffer_range.end);
-                                    base_text_end.replace(old_hunk.diff_base_byte_range.end);
-                                } else {
-                                    end.replace(new_hunk.buffer_range.end);
-                                    base_text_end.replace(new_hunk.diff_base_byte_range.end);
-                                }
-                            }
-
-                            new_cursor.next();
-                            old_cursor.next();
-                        }
-                        Ordering::Greater => {
-                            start.get_or_insert(old_hunk.buffer_range.start);
-                            base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start);
-                            end.replace(old_hunk.buffer_range.end);
-                            base_text_end.replace(old_hunk.diff_base_byte_range.end);
-                            old_cursor.next();
-                        }
-                    }
-                }
-                (Some(new_hunk), None) => {
-                    start.get_or_insert(new_hunk.buffer_range.start);
-                    base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
-                    end.replace(new_hunk.buffer_range.end);
-                    base_text_end.replace(new_hunk.diff_base_byte_range.end);
-                    new_cursor.next();
-                }
-                (None, Some(old_hunk)) => {
-                    start.get_or_insert(old_hunk.buffer_range.start);
-                    base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start);
-                    end.replace(old_hunk.buffer_range.end);
-                    base_text_end.replace(old_hunk.diff_base_byte_range.end);
-                    old_cursor.next();
-                }
-                (None, None) => break,
-            }
-        }
-
-        (
-            start.zip(end).map(|(start, end)| start..end),
-            base_text_start
-                .zip(base_text_end)
-                .map(|(start, end)| start..end),
-        )
-    }
 }
 
 fn build_diff_options(
@@ -1029,6 +827,91 @@ fn compute_hunks(
     tree
 }
 
+fn compare_hunks(
+    new_hunks: &SumTree<InternalDiffHunk>,
+    old_hunks: &SumTree<InternalDiffHunk>,
+    new_snapshot: &text::BufferSnapshot,
+) -> (Option<Range<Anchor>>, Option<Range<usize>>) {
+    let mut new_cursor = new_hunks.cursor::<()>(new_snapshot);
+    let mut old_cursor = old_hunks.cursor::<()>(new_snapshot);
+    old_cursor.next();
+    new_cursor.next();
+    let mut start = None;
+    let mut end = None;
+    let mut base_text_start = None;
+    let mut base_text_end = None;
+
+    loop {
+        match (new_cursor.item(), old_cursor.item()) {
+            (Some(new_hunk), Some(old_hunk)) => {
+                match new_hunk
+                    .buffer_range
+                    .start
+                    .cmp(&old_hunk.buffer_range.start, new_snapshot)
+                {
+                    Ordering::Less => {
+                        start.get_or_insert(new_hunk.buffer_range.start);
+                        base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
+                        end.replace(new_hunk.buffer_range.end);
+                        base_text_end.get_or_insert(new_hunk.diff_base_byte_range.end);
+                        new_cursor.next();
+                    }
+                    Ordering::Equal => {
+                        if new_hunk != old_hunk {
+                            start.get_or_insert(new_hunk.buffer_range.start);
+                            base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
+                            if old_hunk
+                                .buffer_range
+                                .end
+                                .cmp(&new_hunk.buffer_range.end, new_snapshot)
+                                .is_ge()
+                            {
+                                end.replace(old_hunk.buffer_range.end);
+                                base_text_end.replace(old_hunk.diff_base_byte_range.end);
+                            } else {
+                                end.replace(new_hunk.buffer_range.end);
+                                base_text_end.replace(new_hunk.diff_base_byte_range.end);
+                            }
+                        }
+
+                        new_cursor.next();
+                        old_cursor.next();
+                    }
+                    Ordering::Greater => {
+                        start.get_or_insert(old_hunk.buffer_range.start);
+                        base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start);
+                        end.replace(old_hunk.buffer_range.end);
+                        base_text_end.replace(old_hunk.diff_base_byte_range.end);
+                        old_cursor.next();
+                    }
+                }
+            }
+            (Some(new_hunk), None) => {
+                start.get_or_insert(new_hunk.buffer_range.start);
+                base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
+                end.replace(new_hunk.buffer_range.end);
+                base_text_end.replace(new_hunk.diff_base_byte_range.end);
+                new_cursor.next();
+            }
+            (None, Some(old_hunk)) => {
+                start.get_or_insert(old_hunk.buffer_range.start);
+                base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start);
+                end.replace(old_hunk.buffer_range.end);
+                base_text_end.replace(old_hunk.diff_base_byte_range.end);
+                old_cursor.next();
+            }
+            (None, None) => break,
+        }
+    }
+
+    (
+        start.zip(end).map(|(start, end)| start..end),
+        base_text_start
+            .zip(base_text_end)
+            .map(|(start, end)| start..end),
+    )
+}
+
 fn process_patch_hunk(
     patch: &GitPatch<'_>,
     hunk_index: usize,
@@ -1198,30 +1081,19 @@ impl BufferDiff {
     pub fn new_with_base_text(
         base_text: &str,
         buffer: &text::BufferSnapshot,
-        cx: &mut App,
+        cx: &mut Context<Self>,
     ) -> Self {
-        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(),
+        let mut this = BufferDiff::new(&buffer, cx);
+        let executor = cx.background_executor().clone();
+        let inner = executor.block(this.update_diff(
             buffer.clone(),
-            Some(base_text.into()),
-            None,
+            Some(Arc::from(base_text)),
+            true,
             None,
             cx,
-        );
-        let snapshot = cx.background_executor().block(snapshot);
-        Self {
-            buffer_id: buffer.remote_id(),
-            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,
-        }
+        ));
+        this.set_snapshot(inner, &buffer, true, cx);
+        this
     }
 
     pub fn set_secondary_diff(&mut self, diff: Entity<BufferDiff>) {
@@ -1257,7 +1129,6 @@ 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,
@@ -1283,62 +1154,72 @@ impl BufferDiff {
         new_index_text
     }
 
-    pub async fn update_diff(
-        this: Entity<BufferDiff>,
+    pub fn update_diff(
+        &self,
         buffer: text::BufferSnapshot,
         base_text: Option<Arc<str>>,
         base_text_changed: bool,
-        language_changed: bool,
         language: Option<Arc<Language>>,
-        language_registry: Option<Arc<LanguageRegistry>>,
-        cx: &mut AsyncApp,
-    ) -> anyhow::Result<BufferDiffSnapshot> {
-        Ok(if base_text_changed || language_changed {
-            cx.update(|cx| {
-                // in this path we should mutate the Entity<Buffer> on `this`
-                // we want to return a BufferDiffSnapshot for which the `base_text` is a BufferSnapshot of the persistent `Entity<Buffer>`
-                BufferDiffSnapshot::new_with_base_text(
-                    this.read(cx).inner.base_text.clone(),
-                    buffer.clone(),
-                    base_text,
-                    language.clone(),
-                    language_registry.clone(),
-                    cx,
-                )
-            })?
-            .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(
+        cx: &App,
+    ) -> Task<BufferDiffInner<Arc<str>>> {
+        let prev_base_text = self.base_text(cx).as_rope().clone();
+        let diff_options = build_diff_options(
+            None,
+            language.as_ref().map(|l| l.name()),
+            language.as_ref().map(|l| l.default_scope()),
+            cx,
+        );
+
+        cx.background_executor()
+            .spawn_labeled(*CALCULATE_DIFF_TASK, async move {
+                let base_text_rope = if let Some(base_text) = &base_text {
+                    if base_text_changed {
+                        Rope::from(base_text.as_ref())
+                    } else {
+                        prev_base_text
+                    }
+                } else {
+                    Rope::new()
+                };
+                let base_text_exists = base_text.is_some();
+                let hunks = compute_hunks(
+                    base_text
+                        .clone()
+                        .map(|base_text| (base_text, base_text_rope.clone())),
                     buffer.clone(),
-                    base_text,
-                    this.inner.base_text.clone(),
-                    cx,
-                )
-            })?
-            .await
-        })
+                    diff_options,
+                );
+                BufferDiffInner {
+                    base_text: base_text.unwrap_or_default(),
+                    hunks,
+                    base_text_exists,
+                    pending_hunks: SumTree::new(&buffer),
+                }
+            })
     }
 
     pub fn language_changed(&mut self, cx: &mut Context<Self>) {
         cx.emit(BufferDiffEvent::LanguageChanged);
     }
 
+    // FIXME name
     pub fn set_snapshot(
         &mut self,
-        new_snapshot: BufferDiffSnapshot,
+        new_state: BufferDiffInner<Arc<str>>,
         buffer: &text::BufferSnapshot,
+        base_text_changed: bool,
         cx: &mut Context<Self>,
     ) -> Option<Range<Anchor>> {
-        self.set_snapshot_with_secondary(new_snapshot, buffer, None, false, cx)
+        self.set_snapshot_with_secondary(new_state, buffer, None, base_text_changed, false, cx)
     }
 
+    // FIXME name
     pub fn set_snapshot_with_secondary(
         &mut self,
-        new_snapshot: BufferDiffSnapshot,
+        new_state: BufferDiffInner<Arc<str>>,
         buffer: &text::BufferSnapshot,
         secondary_diff_change: Option<Range<Anchor>>,
+        base_text_changed: bool,
         clear_pending_hunks: bool,
         cx: &mut Context<Self>,
     ) -> Option<Range<Anchor>> {
@@ -1346,26 +1227,15 @@ impl BufferDiff {
 
         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)) =
+        let (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 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() =>
-                {
-                    debug_assert!(
-                        old_snapshot.inner.base_text.remote_id() == new_state.base_text.remote_id()
-                    );
-                    (false, new_state.compare(&old_snapshot.inner, buffer))
+                (false, false) => (None, None),
+                (true, true) if !base_text_changed => {
+                    compare_hunks(&new_state.hunks, &old_snapshot.inner.hunks, buffer)
                 }
                 _ => (
-                    true,
-                    (
-                        Some(text::Anchor::min_max_range_for_buffer(self.buffer_id)),
-                        Some(0..new_state.base_text.len()),
-                    ),
+                    Some(text::Anchor::min_max_range_for_buffer(self.buffer_id)),
+                    Some(0..new_state.base_text.len()),
                 ),
             };
 
@@ -1390,7 +1260,11 @@ impl BufferDiff {
 
         let state = &mut self.inner;
         state.base_text_exists = new_state.base_text_exists;
-        // FIXME state.base_text = ...;
+        if base_text_changed {
+            state.base_text.update(cx, |base_text, cx| {
+                base_text.set_text(new_state.base_text, cx);
+            })
+        }
         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())
@@ -1449,31 +1323,25 @@ impl BufferDiff {
         &mut self,
         base_text: Option<Arc<str>>,
         language: Option<Arc<Language>>,
-        language_registry: Option<Arc<LanguageRegistry>>,
         buffer: text::BufferSnapshot,
         cx: &mut Context<Self>,
     ) -> oneshot::Receiver<()> {
         let (tx, rx) = oneshot::channel();
-        let this = cx.weak_entity();
-
-        let snapshot = BufferDiffSnapshot::new_with_base_text(
-            self.inner.base_text.clone(),
-            buffer.clone(),
-            base_text,
-            language,
-            language_registry,
-            cx,
-        );
         let complete_on_drop = util::defer(|| {
             tx.send(()).ok();
         });
-        cx.spawn(async move |_, cx| {
-            let snapshot = snapshot.await;
-            let Some(this) = this.upgrade() else {
+        cx.spawn(async move |this, cx| {
+            let Some(state) = this
+                .update(cx, |this, cx| {
+                    this.update_diff(buffer.clone(), base_text, true, language, cx)
+                })
+                .log_err()
+            else {
                 return;
             };
+            let state = state.await;
             this.update(cx, |this, cx| {
-                this.set_snapshot(snapshot, &buffer, cx);
+                this.set_snapshot(state, &buffer, true, cx);
             })
             .log_err();
             drop(complete_on_drop)
@@ -1490,15 +1358,11 @@ impl BufferDiff {
 
     #[cfg(any(test, feature = "test-support"))]
     pub fn recalculate_diff_sync(&mut self, buffer: text::BufferSnapshot, cx: &mut Context<Self>) {
+        let language = self.base_text(cx).language().cloned();
         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,
-            self.inner.base_text.clone(),
-            cx,
-        );
-        let snapshot = cx.background_executor().block(snapshot);
-        self.set_snapshot(snapshot, &buffer, cx);
+        let fut = self.update_diff(buffer.clone(), base_text, false, language, cx);
+        let snapshot = cx.background_executor().block(fut);
+        self.set_snapshot(snapshot, &buffer, false, cx);
     }
 }
 
@@ -1824,19 +1688,7 @@ mod tests {
         .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;
+        let diff = BufferDiffSnapshot::new_sync(buffer.snapshot(), diff_base.clone(), cx);
         assert_eq!(
             diff.hunks_intersecting_range(
                 Anchor::min_max_range_for_buffer(buffer.remote_id()),
@@ -2090,37 +1942,19 @@ mod tests {
         ];
 
         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 unstaged_diff =
+                cx.new(|cx| BufferDiff::new_with_base_text(&example.index_text, &buffer, cx));
 
             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));
+                let mut diff = BufferDiff::new_with_base_text(&example.head_text, &buffer, cx);
                 diff.set_secondary_diff(unstaged_diff);
                 diff
             });
-            dbg!();
 
             uncommitted_diff.update(cx, |diff, cx| {
                 let hunks = diff
@@ -2134,12 +1968,10 @@ mod tests {
                     )
                 }
 
-                dbg!();
                 let new_index_text = diff
                     .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
                     .unwrap()
                     .to_string();
-                dbg!();
 
                 let hunks = diff
                     .snapshot(cx)
@@ -2182,16 +2014,9 @@ mod tests {
             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 unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
         let uncommitted_diff = cx.new(|cx| {
-            let mut diff = BufferDiff::new(&buffer, cx);
-            diff.set_snapshot(uncommitted, &buffer, cx);
+            let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
             diff.set_secondary_diff(unstaged_diff.clone());
             diff
         });
@@ -2258,7 +2083,10 @@ mod tests {
 
         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();
+        // FIXME assert the other half of the range
+        let (range, base_text_range) =
+            compare_hunks(&diff_1.inner.hunks, &empty_diff.inner.hunks, &buffer);
+        let range = range.unwrap();
         assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
 
         // Edit does not affect the diff.
@@ -2276,7 +2104,10 @@ mod tests {
             .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);
+        assert_eq!(
+            (None, None),
+            compare_hunks(&diff_2.inner.hunks, &diff_1.inner.hunks, &buffer)
+        );
 
         // Edit turns a deletion hunk into a modification.
         buffer.edit_via_marked_text(
@@ -2293,7 +2124,10 @@ mod tests {
             .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();
+        // FIXME assert the other range
+        let (range, base_text_range) =
+            compare_hunks(&diff_3.inner.hunks, &diff_2.inner.hunks, &buffer);
+        let range = range.unwrap();
         assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
 
         // Edit turns a modification hunk into a deletion.
@@ -2310,7 +2144,9 @@ mod tests {
             .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();
+        let (range, base_text_range) =
+            compare_hunks(&diff_4.inner.hunks, &diff_3.inner.hunks, &buffer);
+        let range = range.unwrap();
         assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
 
         // Edit introduces a new insertion hunk.
@@ -2328,7 +2164,9 @@ mod tests {
             .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();
+        let (range, base_text_range) =
+            compare_hunks(&diff_5.inner.hunks, &diff_4.inner.hunks, &buffer);
+        let range = range.unwrap();
         assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
 
         // Edit removes a hunk.
@@ -2346,7 +2184,9 @@ mod tests {
             .unindent(),
         );
         let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
-        let range = diff_6.inner.compare(&diff_5.inner, &buffer).0.unwrap();
+        let (range, base_text_range) =
+            compare_hunks(&diff_6.inner.hunks, &diff_5.inner.hunks, &buffer);
+        let range = range.unwrap();
         assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
     }
 

crates/multi_buffer/src/anchor.rs 🔗

@@ -111,8 +111,8 @@ impl Anchor {
                     .get(&excerpt.buffer_id)
                     .map(|diff| diff.base_text())
             {
-                let self_anchor = self.diff_base_anchor.filter(|a| base_text.can_resolve(a));
-                let other_anchor = other.diff_base_anchor.filter(|a| base_text.can_resolve(a));
+                let self_anchor = self.diff_base_anchor.filter(|a| a.is_valid(base_text));
+                let other_anchor = other.diff_base_anchor.filter(|a| a.is_valid(base_text));
                 return match (self_anchor, other_anchor) {
                     (Some(a), Some(b)) => a.cmp(&b, base_text),
                     (Some(_), None) => match other.text_anchor.bias {

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -506,8 +506,6 @@ struct BufferState {
     _subscriptions: [gpui::Subscription; 2],
 }
 
-
-
 struct DiffState {
     diff: Entity<BufferDiff>,
     /// Whether the [`MultiBuffer`] this is associated with is inverted (i.e.
@@ -1123,6 +1121,7 @@ impl MultiBuffer {
             buffer.read(cx).capability(),
             MultiBufferSnapshot {
                 singleton: true,
+                show_deleted_hunks: true,
                 ..MultiBufferSnapshot::default()
             },
         );
@@ -3420,7 +3419,7 @@ impl MultiBuffer {
                             if !hunk.diff_base_byte_range.is_empty()
                                 && hunk_buffer_range.start >= edit_buffer_start
                                 && hunk_buffer_range.start <= excerpt_buffer_end
-                                && snapshot.show_deleted_hunks
+                                && dbg!(snapshot.show_deleted_hunks)
                             {
                                 let base_text = diff.base_text();
                                 let mut text_cursor =
@@ -5036,7 +5035,7 @@ impl MultiBufferSnapshot {
                     if let Some(diff_base_anchor) = &anchor.diff_base_anchor
                         && let Some(base_text) =
                             self.diffs.get(buffer_id).map(|diff| diff.base_text())
-                        && base_text.can_resolve(diff_base_anchor)
+                        && diff_base_anchor.is_valid(&base_text)
                     {
                         let base_text_offset = diff_base_anchor.to_offset(base_text);
                         if base_text_offset >= base_text_byte_range.start

crates/multi_buffer/src/multi_buffer_tests.rs 🔗

@@ -361,7 +361,8 @@ async fn test_diff_boundary_anchors(cx: &mut TestAppContext) {
     let base_text = "one\ntwo\nthree\n";
     let text = "one\nthree\n";
     let buffer = cx.new(|cx| Buffer::local(text, cx));
-    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
+    let diff = cx
+        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
     let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
     multibuffer.update(cx, |multibuffer, cx| multibuffer.add_diff(diff, cx));
 
@@ -403,7 +404,8 @@ async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
     let base_text = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\n";
     let text = "one\nfour\nseven\n";
     let buffer = cx.new(|cx| Buffer::local(text, cx));
-    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
+    let diff = cx
+        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
     let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
     let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
         (multibuffer.snapshot(cx), multibuffer.subscribe())
@@ -483,7 +485,8 @@ async fn test_editing_text_in_diff_hunks(cx: &mut TestAppContext) {
     let base_text = "one\ntwo\nfour\nfive\nsix\nseven\n";
     let text = "one\ntwo\nTHREE\nfour\nfive\nseven\n";
     let buffer = cx.new(|cx| Buffer::local(text, cx));
-    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
+    let diff = cx
+        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
     let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
 
     let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
@@ -916,7 +919,8 @@ async fn test_empty_diff_excerpt(cx: &mut TestAppContext) {
     let buffer = cx.new(|cx| Buffer::local("", cx));
     let base_text = "a\nb\nc";
 
-    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
+    let diff = cx
+        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
     multibuffer.update(cx, |multibuffer, cx| {
         multibuffer.push_excerpts(buffer.clone(), [ExcerptRange::new(0..0)], cx);
         multibuffer.set_all_diff_hunks_expanded(cx);
@@ -1263,7 +1267,8 @@ async fn test_basic_diff_hunks(cx: &mut TestAppContext) {
     );
 
     let buffer = cx.new(|cx| Buffer::local(text, cx));
-    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
+    let diff = cx
+        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
     cx.run_until_parked();
 
     let multibuffer = cx.new(|cx| {
@@ -1507,7 +1512,8 @@ async fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) {
     );
 
     let buffer = cx.new(|cx| Buffer::local(text, cx));
-    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
+    let diff = cx
+        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
     cx.run_until_parked();
 
     let multibuffer = cx.new(|cx| {
@@ -2042,8 +2048,12 @@ async fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
 
     let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
     let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
-    let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_1, &buffer_1, cx));
-    let diff_2 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_2, &buffer_2, cx));
+    let diff_1 = cx.new(|cx| {
+        BufferDiff::new_with_base_text(base_text_1, &buffer_1.read(cx).text_snapshot(), cx)
+    });
+    let diff_2 = cx.new(|cx| {
+        BufferDiff::new_with_base_text(base_text_2, &buffer_2.read(cx).text_snapshot(), cx)
+    });
     cx.run_until_parked();
 
     let multibuffer = cx.new(|cx| {
@@ -2119,8 +2129,8 @@ async fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
 
     let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id());
     let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id());
-    let base_id_1 = diff_1.read_with(cx, |diff, _| diff.base_text().remote_id());
-    let base_id_2 = diff_2.read_with(cx, |diff, _| diff.base_text().remote_id());
+    let base_id_1 = diff_1.read_with(cx, |diff, cx| diff.base_text(cx).remote_id());
+    let base_id_2 = diff_2.read_with(cx, |diff, cx| diff.base_text(cx).remote_id());
 
     let buffer_lines = (0..=snapshot.max_row().0)
         .map(|row| {
@@ -2336,7 +2346,11 @@ impl ReferenceMultibuffer {
             return;
         };
         let excerpt_range = excerpt.range.to_offset(&buffer);
-        for hunk in diff.read(cx).hunks_intersecting_range(range, &buffer, cx) {
+        for hunk in diff
+            .read(cx)
+            .snapshot(cx)
+            .hunks_intersecting_range(range, &buffer)
+        {
             let hunk_range = hunk.buffer_range.to_offset(&buffer);
             if hunk_range.start < excerpt_range.start || hunk_range.start > excerpt_range.end {
                 continue;
@@ -2374,12 +2388,17 @@ impl ReferenceMultibuffer {
             excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32));
             let buffer = excerpt.buffer.read(cx);
             let buffer_range = excerpt.range.to_offset(buffer);
-            let diff = self.diffs.get(&buffer.remote_id()).unwrap().read(cx);
+            let diff = self
+                .diffs
+                .get(&buffer.remote_id())
+                .unwrap()
+                .read(cx)
+                .snapshot(cx);
             let base_buffer = diff.base_text();
 
             let mut offset = buffer_range.start;
             let hunks = diff
-                .hunks_intersecting_range(excerpt.range.clone(), buffer, cx)
+                .hunks_intersecting_range(excerpt.range.clone(), buffer)
                 .peekable();
 
             for hunk in hunks {
@@ -2587,8 +2606,8 @@ impl ReferenceMultibuffer {
             let buffer = excerpt.buffer.read(cx).snapshot();
             let excerpt_range = excerpt.range.to_offset(&buffer);
             let buffer_id = buffer.remote_id();
-            let diff = self.diffs.get(&buffer_id).unwrap().read(cx);
-            let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer, cx).peekable();
+            let diff = self.diffs.get(&buffer_id).unwrap().read(cx).snapshot(cx);
+            let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer).peekable();
             excerpt.expanded_diff_hunks.retain(|hunk_anchor| {
                 if !hunk_anchor.is_valid(&buffer) {
                     return false;
@@ -2936,8 +2955,13 @@ async fn test_random_multibuffer_impl(
                     let id = buffer_handle.read(cx).remote_id();
                     if multibuffer.diff_for(id).is_none() {
                         let base_text = base_texts.get(&id).unwrap();
-                        let diff = cx
-                            .new(|cx| BufferDiff::new_with_base_text(base_text, buffer_handle, cx));
+                        let diff = cx.new(|cx| {
+                            BufferDiff::new_with_base_text(
+                                base_text,
+                                &buffer_handle.read(cx).text_snapshot(),
+                                cx,
+                            )
+                        });
                         reference.add_diff(diff.clone(), cx);
                         multibuffer.add_diff(diff, cx)
                     }
@@ -3456,8 +3480,12 @@ async fn test_summaries_for_anchors(cx: &mut TestAppContext) {
 
     let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
     let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx));
-    let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_1, &buffer_1, cx));
-    let diff_2 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_2, &buffer_2, cx));
+    let diff_1 = cx.new(|cx| {
+        BufferDiff::new_with_base_text(base_text_1, &buffer_1.read(cx).text_snapshot(), cx)
+    });
+    let diff_2 = cx.new(|cx| {
+        BufferDiff::new_with_base_text(base_text_2, &buffer_2.read(cx).text_snapshot(), cx)
+    });
     cx.run_until_parked();
 
     let mut ids = vec![];
@@ -3514,7 +3542,9 @@ async fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) {
     let text_1 = "one\n".to_owned();
 
     let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx));
-    let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(&base_text_1, &buffer_1, cx));
+    let diff_1 = cx.new(|cx| {
+        BufferDiff::new_with_base_text(&base_text_1, &buffer_1.read(cx).text_snapshot(), cx)
+    });
     cx.run_until_parked();
 
     let multibuffer = cx.new(|cx| {
@@ -3707,7 +3737,8 @@ async fn test_inverted_diff(cx: &mut TestAppContext) {
     );
 
     let buffer = cx.new(|cx| Buffer::local(text, cx));
-    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
+    let diff = cx
+        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
     cx.run_until_parked();
 
     let base_text_buffer = cx.new(|cx| Buffer::local(base_text, cx));
@@ -3762,6 +3793,22 @@ async fn test_inverted_diff(cx: &mut TestAppContext) {
         );
     });
     cx.run_until_parked();
+    let update = diff
+        .update(cx, |diff, cx| {
+            diff.update_diff(
+                buffer.read(cx).text_snapshot(),
+                Some(base_text.into()),
+                false,
+                None,
+                cx,
+            )
+        })
+        .await;
+    diff.update(cx, |diff, cx| {
+        diff.set_snapshot(update, &buffer.read(cx).text_snapshot(), false, cx)
+    });
+    cx.run_until_parked();
+
     assert_new_snapshot(
         &multibuffer,
         &mut snapshot,
@@ -3771,6 +3818,7 @@ async fn test_inverted_diff(cx: &mut TestAppContext) {
             "
               one
             - two
+            - three
             - four
             - five
               six
@@ -4231,8 +4279,13 @@ fn test_random_chunk_bitmaps_with_diffs(cx: &mut App, mut rng: StdRng) {
                         }
                     }
 
-                    let diff =
-                        cx.new(|cx| BufferDiff::new_with_base_text(&base_text, &buffer_handle, cx));
+                    let diff = cx.new(|cx| {
+                        BufferDiff::new_with_base_text(
+                            &base_text,
+                            &buffer_handle.read(cx).text_snapshot(),
+                            cx,
+                        )
+                    });
                     diffs.push(diff.clone());
                     multibuffer.add_diff(diff, cx);
                 }
@@ -4342,7 +4395,8 @@ fn collect_word_diffs(
     cx: &mut TestAppContext,
 ) -> Vec<String> {
     let buffer = cx.new(|cx| Buffer::local(modified_text, cx));
-    let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
+    let diff = cx
+        .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
     cx.run_until_parked();
 
     let multibuffer = cx.new(|cx| {

crates/project/src/git_store.rs 🔗

@@ -129,8 +129,8 @@ struct BufferGitState {
     hunk_staging_operation_count: usize,
     hunk_staging_operation_count_as_of_write: usize,
 
-    head_text: Option<Arc<String>>,
-    index_text: Option<Arc<String>>,
+    head_text: Option<Arc<str>>,
+    index_text: Option<Arc<str>>,
     head_changed: bool,
     index_changed: bool,
     language_changed: bool,
@@ -691,7 +691,6 @@ impl GitStore {
         oid: Option<git::Oid>,
         buffer: Entity<Buffer>,
         repo: Entity<Repository>,
-        languages: Arc<LanguageRegistry>,
         cx: &mut Context<Self>,
     ) -> Task<Result<Entity<BufferDiff>>> {
         cx.spawn(async move |this, cx| {
@@ -708,9 +707,8 @@ impl GitStore {
             buffer_diff
                 .update(cx, |buffer_diff, cx| {
                     buffer_diff.set_base_text(
-                        content.map(|s| s.as_ref().into()),
+                        content.map(|s| s.as_str().into()),
                         buffer_snapshot.language().cloned(),
-                        Some(languages.clone()),
                         buffer_snapshot.text,
                         cx,
                     )
@@ -2611,7 +2609,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(cx))?;
+        let staged_text = diff.read_with(&cx, |diff, cx| diff.base_text_string(cx))?;
         Ok(proto::OpenUnstagedDiffResponse { staged_text })
     }
 
@@ -2984,21 +2982,21 @@ impl BufferGitState {
             Some(DiffBasesChange::SetIndex(index)) => {
                 self.index_text = index.map(|mut index| {
                     text::LineEnding::normalize(&mut index);
-                    Arc::new(index)
+                    Arc::from(index.as_str())
                 });
                 self.index_changed = true;
             }
             Some(DiffBasesChange::SetHead(head)) => {
                 self.head_text = head.map(|mut head| {
                     text::LineEnding::normalize(&mut head);
-                    Arc::new(head)
+                    Arc::from(head.as_str())
                 });
                 self.head_changed = true;
             }
             Some(DiffBasesChange::SetBoth(text)) => {
                 let text = text.map(|mut text| {
                     text::LineEnding::normalize(&mut text);
-                    Arc::new(text)
+                    Arc::from(text.as_str())
                 });
                 self.head_text = text.clone();
                 self.index_text = text;
@@ -3008,12 +3006,12 @@ impl BufferGitState {
             Some(DiffBasesChange::SetEach { index, head }) => {
                 self.index_text = index.map(|mut index| {
                     text::LineEnding::normalize(&mut index);
-                    Arc::new(index)
+                    Arc::from(index.as_str())
                 });
                 self.index_changed = true;
                 self.head_text = head.map(|mut head| {
                     text::LineEnding::normalize(&mut head);
-                    Arc::new(head)
+                    Arc::from(head.as_str())
                 });
                 self.head_changed = true;
             }
@@ -3050,17 +3048,16 @@ impl BufferGitState {
             let mut new_unstaged_diff = None;
             if let Some(unstaged_diff) = &unstaged_diff {
                 new_unstaged_diff = Some(
-                    BufferDiff::update_diff(
-                        unstaged_diff.clone(),
-                        buffer.clone(),
-                        index,
-                        index_changed,
-                        language_changed,
-                        language.clone(),
-                        language_registry.clone(),
-                        cx,
-                    )
-                    .await?,
+                    cx.update(|cx| {
+                        unstaged_diff.read(cx).update_diff(
+                            buffer.clone(),
+                            index,
+                            index_changed,
+                            language.clone(),
+                            cx,
+                        )
+                    })?
+                    .await,
                 );
             }
 
@@ -3074,17 +3071,16 @@ impl BufferGitState {
                     new_unstaged_diff.clone()
                 } else {
                     Some(
-                        BufferDiff::update_diff(
-                            uncommitted_diff.clone(),
-                            buffer.clone(),
-                            head,
-                            head_changed,
-                            language_changed,
-                            language.clone(),
-                            language_registry.clone(),
-                            cx,
-                        )
-                        .await?,
+                        cx.update(|cx| {
+                            uncommitted_diff.read(cx).update_diff(
+                                buffer.clone(),
+                                head,
+                                head_changed,
+                                language.clone(),
+                                cx,
+                            )
+                        })?
+                        .await,
                     )
                 }
             }
@@ -3125,7 +3121,7 @@ impl BufferGitState {
                     if language_changed {
                         diff.language_changed(cx);
                     }
-                    diff.set_snapshot(new_unstaged_diff, &buffer, cx)
+                    diff.set_snapshot(new_unstaged_diff, &buffer, index_changed, cx)
                 })?
             } else {
                 None
@@ -3144,6 +3140,7 @@ impl BufferGitState {
                         new_uncommitted_diff,
                         &buffer,
                         unstaged_changed_range,
+                        head_changed,
                         true,
                         cx,
                     );
@@ -3644,9 +3641,9 @@ impl Repository {
                                 match (current_index_text.as_ref(), current_head_text.as_ref()) {
                                     (Some(current_index), Some(current_head)) => {
                                         let index_changed =
-                                            index_text.as_ref() != current_index.as_deref();
+                                            index_text.as_deref() != current_index.as_deref();
                                         let head_changed =
-                                            head_text.as_ref() != current_head.as_deref();
+                                            head_text.as_deref() != current_head.as_deref();
                                         if index_changed && head_changed {
                                             if index_text == head_text {
                                                 Some(DiffBasesChange::SetBoth(head_text))
@@ -3666,13 +3663,13 @@ impl Repository {
                                     }
                                     (Some(current_index), None) => {
                                         let index_changed =
-                                            index_text.as_ref() != current_index.as_deref();
+                                            index_text.as_deref() != current_index.as_deref();
                                         index_changed
                                             .then_some(DiffBasesChange::SetIndex(index_text))
                                     }
                                     (None, Some(current_head)) => {
                                         let head_changed =
-                                            head_text.as_ref() != current_head.as_deref();
+                                            head_text.as_deref() != current_head.as_deref();
                                         head_changed.then_some(DiffBasesChange::SetHead(head_text))
                                     }
                                     (None, None) => None,

crates/project/src/git_store/branch_diff.rs 🔗

@@ -329,8 +329,6 @@ impl BranchDiff {
                 .update(cx, |project, cx| project.open_buffer(project_path, cx))?
                 .await?;
 
-            let languages = project.update(cx, |project, _cx| project.languages().clone())?;
-
             let changes = if let Some(entry) = branch_diff {
                 let oid = match entry {
                     git::status::TreeDiffStatus::Added { .. } => None,
@@ -340,7 +338,7 @@ impl BranchDiff {
                 project
                     .update(cx, |project, cx| {
                         project.git_store().update(cx, |git_store, cx| {
-                            git_store.open_diff_since(oid, buffer.clone(), repo, languages, cx)
+                            git_store.open_diff_since(oid, buffer.clone(), repo, cx)
                         })
                     })?
                     .await?

crates/rope/src/chunk.rs 🔗

@@ -131,7 +131,7 @@ impl Chunk {
         if self.is_char_boundary(offset) {
             return true;
         }
-        if PANIC {
+        if PANIC || cfg!(debug_assertions) {
             panic_char_boundary(&self.text, offset);
         } else {
             log_err_char_boundary(&self.text, offset);