wip

Cole Miller created

Change summary

crates/editor/src/display_map/wrap_map.rs     |   1 
crates/editor/src/editor_tests.rs             | 406 ++++++++++----------
crates/editor/src/element.rs                  |   2 
crates/editor/src/split.rs                    | 193 ++++++++-
crates/multi_buffer/src/multi_buffer.rs       | 238 ++++++++---
crates/multi_buffer/src/multi_buffer_tests.rs |  98 -----
crates/multi_buffer/src/path_key.rs           |  80 ---
crates/project/src/project_tests.rs           |   4 
8 files changed, 547 insertions(+), 475 deletions(-)

Detailed changes

crates/editor/src/display_map/wrap_map.rs 🔗

@@ -1056,7 +1056,6 @@ impl Iterator for WrapRows<'_> {
             RowInfo {
                 buffer_id: None,
                 buffer_row: None,
-                base_text_row: None,
                 multibuffer_row: None,
                 diff_status,
                 expand_info: None,

crates/editor/src/editor_tests.rs 🔗

@@ -35,9 +35,7 @@ use language_settings::Formatter;
 use languages::markdown_lang;
 use languages::rust_lang;
 use lsp::CompletionParams;
-use multi_buffer::{
-    IndentGuide, MultiBufferFilterMode, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
-};
+use multi_buffer::{IndentGuide, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey};
 use parking_lot::Mutex;
 use pretty_assertions::{assert_eq, assert_ne};
 use project::{
@@ -28375,207 +28373,207 @@ async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
         "});
 }
 
-#[gpui::test]
-async fn test_filtered_editor_pair(cx: &mut gpui::TestAppContext) {
-    init_test(cx, |_| {});
-    let mut leader_cx = EditorTestContext::new(cx).await;
-
-    let diff_base = indoc!(
-        r#"
-        one
-        two
-        three
-        four
-        five
-        six
-        "#
-    );
-
-    let initial_state = indoc!(
-        r#"
-        ˇone
-        two
-        THREE
-        four
-        five
-        six
-        "#
-    );
-
-    leader_cx.set_state(initial_state);
-
-    leader_cx.set_head_text(&diff_base);
-    leader_cx.run_until_parked();
-
-    let follower = leader_cx.update_multibuffer(|leader, cx| {
-        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
-        leader.set_all_diff_hunks_expanded(cx);
-        leader.get_or_create_follower(cx)
-    });
-    follower.update(cx, |follower, cx| {
-        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
-        follower.set_all_diff_hunks_expanded(cx);
-    });
-
-    let follower_editor =
-        leader_cx.new_window_entity(|window, cx| build_editor(follower, window, cx));
-    // leader_cx.window.focus(&follower_editor.focus_handle(cx));
-
-    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor, &mut leader_cx).await;
-    cx.run_until_parked();
-
-    leader_cx.assert_editor_state(initial_state);
-    follower_cx.assert_editor_state(indoc! {
-        r#"
-        ˇone
-        two
-        three
-        four
-        five
-        six
-        "#
-    });
-
-    follower_cx.editor(|editor, _window, cx| {
-        assert!(editor.read_only(cx));
-    });
-
-    leader_cx.update_editor(|editor, _window, cx| {
-        editor.edit([(Point::new(4, 0)..Point::new(5, 0), "FIVE\n")], cx);
-    });
-    cx.run_until_parked();
-
-    leader_cx.assert_editor_state(indoc! {
-        r#"
-        ˇone
-        two
-        THREE
-        four
-        FIVE
-        six
-        "#
-    });
-
-    follower_cx.assert_editor_state(indoc! {
-        r#"
-        ˇone
-        two
-        three
-        four
-        five
-        six
-        "#
-    });
-
-    leader_cx.update_editor(|editor, _window, cx| {
-        editor.edit([(Point::new(6, 0)..Point::new(6, 0), "SEVEN")], cx);
-    });
-    cx.run_until_parked();
-
-    leader_cx.assert_editor_state(indoc! {
-        r#"
-        ˇone
-        two
-        THREE
-        four
-        FIVE
-        six
-        SEVEN"#
-    });
-
-    follower_cx.assert_editor_state(indoc! {
-        r#"
-        ˇone
-        two
-        three
-        four
-        five
-        six
-        "#
-    });
-
-    leader_cx.update_editor(|editor, window, cx| {
-        editor.move_down(&MoveDown, window, cx);
-        editor.refresh_selected_text_highlights(true, window, cx);
-    });
-    leader_cx.run_until_parked();
-}
-
-#[gpui::test]
-async fn test_filtered_editor_pair_complex(cx: &mut gpui::TestAppContext) {
-    init_test(cx, |_| {});
-    let base_text = "base\n";
-    let buffer_text = "buffer\n";
-
-    let buffer1 = cx.new(|cx| Buffer::local(buffer_text, cx));
-    let diff1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer1, cx));
-
-    let extra_buffer_1 = cx.new(|cx| Buffer::local("dummy text 1\n", cx));
-    let extra_diff_1 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_1, cx));
-    let extra_buffer_2 = cx.new(|cx| Buffer::local("dummy text 2\n", cx));
-    let extra_diff_2 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_2, cx));
-
-    let leader = cx.new(|cx| {
-        let mut leader = MultiBuffer::new(Capability::ReadWrite);
-        leader.set_all_diff_hunks_expanded(cx);
-        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
-        leader
-    });
-    let follower = leader.update(cx, |leader, cx| leader.get_or_create_follower(cx));
-    follower.update(cx, |follower, _| {
-        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
-    });
-
-    leader.update(cx, |leader, cx| {
-        leader.insert_excerpts_after(
-            ExcerptId::min(),
-            extra_buffer_2.clone(),
-            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
-            cx,
-        );
-        leader.add_diff(extra_diff_2.clone(), cx);
-
-        leader.insert_excerpts_after(
-            ExcerptId::min(),
-            extra_buffer_1.clone(),
-            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
-            cx,
-        );
-        leader.add_diff(extra_diff_1.clone(), cx);
-
-        leader.insert_excerpts_after(
-            ExcerptId::min(),
-            buffer1.clone(),
-            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
-            cx,
-        );
-        leader.add_diff(diff1.clone(), cx);
-    });
-
-    cx.run_until_parked();
-    let mut cx = cx.add_empty_window();
-
-    let leader_editor = cx
-        .new_window_entity(|window, cx| Editor::for_multibuffer(leader.clone(), None, window, cx));
-    let follower_editor = cx.new_window_entity(|window, cx| {
-        Editor::for_multibuffer(follower.clone(), None, window, cx)
-    });
-
-    let mut leader_cx = EditorTestContext::for_editor_in(leader_editor.clone(), &mut cx).await;
-    leader_cx.assert_editor_state(indoc! {"
-       ˇbuffer
-
-       dummy text 1
-
-       dummy text 2
-    "});
-    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor.clone(), &mut cx).await;
-    follower_cx.assert_editor_state(indoc! {"
-        ˇbase
-
-
-    "});
-}
+// FIXME restore these tests in some form
+// #[gpui::test]
+// async fn test_filtered_editor_pair(cx: &mut gpui::TestAppContext) {
+//     init_test(cx, |_| {});
+//     let mut leader_cx = EditorTestContext::new(cx).await;
+
+//     let diff_base = indoc!(
+//         r#"
+//         one
+//         two
+//         three
+//         four
+//         five
+//         six
+//         "#
+//     );
+
+//     let initial_state = indoc!(
+//         r#"
+//         ˇone
+//         two
+//         THREE
+//         four
+//         five
+//         six
+//         "#
+//     );
+
+//     leader_cx.set_state(initial_state);
+
+//     leader_cx.set_head_text(&diff_base);
+//     leader_cx.run_until_parked();
+
+//     let follower = leader_cx.update_multibuffer(|leader, cx| {
+//         leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
+//         leader.set_all_diff_hunks_expanded(cx);
+//         leader.get_or_create_follower(cx)
+//     });
+//     follower.update(cx, |follower, cx| {
+//         follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
+//         follower.set_all_diff_hunks_expanded(cx);
+//     });
+
+//     let follower_editor =
+//         leader_cx.new_window_entity(|window, cx| build_editor(follower, window, cx));
+//     // leader_cx.window.focus(&follower_editor.focus_handle(cx));
+
+//     let mut follower_cx = EditorTestContext::for_editor_in(follower_editor, &mut leader_cx).await;
+//     cx.run_until_parked();
+
+//     leader_cx.assert_editor_state(initial_state);
+//     follower_cx.assert_editor_state(indoc! {
+//         r#"
+//         ˇone
+//         two
+//         three
+//         four
+//         five
+//         six
+//         "#
+//     });
+
+//     follower_cx.editor(|editor, _window, cx| {
+//         assert!(editor.read_only(cx));
+//     });
+
+//     leader_cx.update_editor(|editor, _window, cx| {
+//         editor.edit([(Point::new(4, 0)..Point::new(5, 0), "FIVE\n")], cx);
+//     });
+//     cx.run_until_parked();
+
+//     leader_cx.assert_editor_state(indoc! {
+//         r#"
+//         ˇone
+//         two
+//         THREE
+//         four
+//         FIVE
+//         six
+//         "#
+//     });
+
+//     follower_cx.assert_editor_state(indoc! {
+//         r#"
+//         ˇone
+//         two
+//         three
+//         four
+//         five
+//         six
+//         "#
+//     });
+
+//     leader_cx.update_editor(|editor, _window, cx| {
+//         editor.edit([(Point::new(6, 0)..Point::new(6, 0), "SEVEN")], cx);
+//     });
+//     cx.run_until_parked();
+
+//     leader_cx.assert_editor_state(indoc! {
+//         r#"
+//         ˇone
+//         two
+//         THREE
+//         four
+//         FIVE
+//         six
+//         SEVEN"#
+//     });
+
+//     follower_cx.assert_editor_state(indoc! {
+//         r#"
+//         ˇone
+//         two
+//         three
+//         four
+//         five
+//         six
+//         "#
+//     });
+
+//     leader_cx.update_editor(|editor, window, cx| {
+//         editor.move_down(&MoveDown, window, cx);
+//         editor.refresh_selected_text_highlights(true, window, cx);
+//     });
+//     leader_cx.run_until_parked();
+// }
+
+// #[gpui::test]
+// async fn test_filtered_editor_pair_complex(cx: &mut gpui::TestAppContext) {
+//     init_test(cx, |_| {});
+//     let base_text = "base\n";
+//     let buffer_text = "buffer\n";
+
+//     let buffer1 = cx.new(|cx| Buffer::local(buffer_text, cx));
+//     let diff1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer1, cx));
+
+//     let extra_buffer_1 = cx.new(|cx| Buffer::local("dummy text 1\n", cx));
+//     let extra_diff_1 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_1, cx));
+//     let extra_buffer_2 = cx.new(|cx| Buffer::local("dummy text 2\n", cx));
+//     let extra_diff_2 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_2, cx));
+
+//     let leader = cx.new(|cx| {
+//         let mut leader = MultiBuffer::new(Capability::ReadWrite);
+//         leader.set_all_diff_hunks_expanded(cx);
+//         leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
+//         leader
+//     });
+//     let follower = leader.update(cx, |leader, cx| leader.get_or_create_follower(cx));
+//     follower.update(cx, |follower, _| {
+//         follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
+//     });
+
+//     leader.update(cx, |leader, cx| {
+//         leader.insert_excerpts_after(
+//             ExcerptId::min(),
+//             extra_buffer_2.clone(),
+//             vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
+//             cx,
+//         );
+//         leader.add_diff(extra_diff_2.clone(), cx);
+
+//         leader.insert_excerpts_after(
+//             ExcerptId::min(),
+//             extra_buffer_1.clone(),
+//             vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
+//             cx,
+//         );
+//         leader.add_diff(extra_diff_1.clone(), cx);
+
+//         leader.insert_excerpts_after(
+//             ExcerptId::min(),
+//             buffer1.clone(),
+//             vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
+//             cx,
+//         );
+//         leader.add_diff(diff1.clone(), cx);
+//     });
+
+//     cx.run_until_parked();
+//     let mut cx = cx.add_empty_window();
+
+//     let leader_editor = cx
+//         .new_window_entity(|window, cx| Editor::for_multibuffer(leader.clone(), None, window, cx));
+//     let follower_editor = cx.new_window_entity(|window, cx| {
+//         Editor::for_multibuffer(follower.clone(), None, window, cx)
+//     });
+
+//     let mut leader_cx = EditorTestContext::for_editor_in(leader_editor.clone(), &mut cx).await;
+//     leader_cx.assert_editor_state(indoc! {"
+//        ˇbuffer
+
+//        dummy text 1
+
+//        dummy text 2
+//     "});
+//     let mut follower_cx = EditorTestContext::for_editor_in(follower_editor.clone(), &mut cx).await;
+//     follower_cx.assert_editor_state(indoc! {"
+//         ˇbase
+
+//     "});
+// }
 
 #[gpui::test]
 async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {

crates/editor/src/element.rs 🔗

@@ -3274,8 +3274,6 @@ impl EditorElement {
             line_number.clear();
             let non_relative_number = if relative.wrapped() {
                 row_info.buffer_row.or(row_info.wrapped_buffer_row)? + 1
-            } else if self.editor.read(cx).use_base_text_line_numbers {
-                row_info.base_text_row?.0 + 1
             } else {
                 row_info.buffer_row? + 1
             };

crates/editor/src/split.rs 🔗

@@ -1,9 +1,16 @@
+use std::ops::Range;
+
+use buffer_diff::{BufferDiff, BufferDiffSnapshot};
+use collections::HashMap;
 use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};
 use gpui::{
     Action, AppContext as _, Entity, EventEmitter, Focusable, NoAction, Subscription, WeakEntity,
 };
-use multi_buffer::{MultiBuffer, MultiBufferFilterMode};
+use language::{Buffer, BufferSnapshot, Capability};
+use multi_buffer::{Anchor, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey};
 use project::Project;
+use rope::Point;
+use text::{BufferId, OffsetRangeExt as _};
 use ui::{
     App, Context, InteractiveElement as _, IntoElement as _, ParentElement as _, Render,
     Styled as _, Window, div,
@@ -133,25 +140,86 @@ impl SplittableEditor {
             return;
         };
         let project = workspace.read(cx).project().clone();
-        let follower = self.primary_editor.update(cx, |primary, cx| {
-            primary.buffer().update(cx, |buffer, cx| {
-                let follower = buffer.get_or_create_follower(cx);
-                buffer.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
-                follower
-            })
-        });
-        follower.update(cx, |follower, _| {
-            follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
-        });
-        let secondary_editor = workspace.update(cx, |workspace, cx| {
-            cx.new(|cx| {
-                let mut editor = Editor::for_multibuffer(follower, Some(project), window, cx);
-                // TODO(split-diff) this should be at the multibuffer level
-                editor.set_use_base_text_line_numbers(true, cx);
-                editor.added_to_workspace(workspace, window, cx);
-                editor
+        let language_registry = project.read(cx).languages().clone();
+
+        let primary_multibuffer = self.primary_editor.read(cx).buffer().read(cx);
+
+        let base_text_buffers_by_main_buffer_id: HashMap<
+            BufferId,
+            (Entity<Buffer>, BufferDiffSnapshot),
+        > = primary_multibuffer
+            .all_buffer_ids_iter()
+            .filter_map(|main_buffer_id| {
+                let diff = primary_multibuffer.diff_for(main_buffer_id)?;
+                let base_text_buffer = cx.new(|cx| {
+                    let base_text = diff.read(cx).base_text();
+                    let mut buffer = Buffer::local_normalized(
+                        base_text.as_rope().clone(),
+                        base_text.line_ending(),
+                        cx,
+                    );
+                    buffer.set_language(base_text.language().cloned(), cx);
+                    buffer.set_language_registry(language_registry.clone());
+                    buffer
+                });
+                Some((
+                    main_buffer_id,
+                    (base_text_buffer, diff.read(cx).snapshot(cx)),
+                ))
             })
+            .collect();
+        let snapshot = primary_multibuffer.snapshot(cx);
+        let mut excerpt_ranges_by_base_buffer: HashMap<
+            Entity<Buffer>,
+            (PathKey, Vec<ExcerptRange<Point>>),
+        > = HashMap::default();
+        for (path_key, excerpt_id) in primary_multibuffer.excerpts_with_paths() {
+            let main_buffer = snapshot.buffer_for_excerpt(*excerpt_id).unwrap();
+            let excerpt_range = snapshot.excerpt_range_for_excerpt(*excerpt_id).unwrap();
+            let (base_text_buffer, diff) = base_text_buffers_by_main_buffer_id
+                .get(&main_buffer.remote_id())
+                .unwrap();
+            let point_to_base_text_point = |point: Point| {
+                let row = diff.row_to_base_text_row(point.row, &main_buffer);
+                let column = diff.base_text().line_len(row);
+                Point::new(row, column)
+            };
+            let primary = excerpt_range.primary.to_point(&main_buffer);
+            let context = excerpt_range.context.to_point(&main_buffer);
+            let translated_range = ExcerptRange {
+                primary: point_to_base_text_point(primary.start)
+                    ..point_to_base_text_point(primary.end),
+                context: point_to_base_text_point(context.start)
+                    ..point_to_base_text_point(context.end),
+            };
+            excerpt_ranges_by_base_buffer
+                .entry(base_text_buffer.clone())
+                .or_insert((path_key.clone(), Vec::new()))
+                .1
+                .push(translated_range);
+        }
+
+        let secondary_multibuffer = cx.new(|cx| {
+            let mut multibuffer = MultiBuffer::new(Capability::ReadOnly);
+            for (base_text_buffer, (path_key, ranges)) in excerpt_ranges_by_base_buffer {
+                let base_text_buffer_snapshot = base_text_buffer.read(cx).snapshot();
+                multibuffer.update_path_excerpts(
+                    path_key,
+                    base_text_buffer,
+                    &base_text_buffer_snapshot,
+                    ranges,
+                    cx,
+                );
+            }
+            multibuffer
         });
+        let secondary_editor =
+            cx.new(|cx| Editor::for_multibuffer(secondary_multibuffer, Some(project), window, cx));
+
+        // FIXME
+        // - have to subscribe to the diffs to update the base text buffers (and handle language changed I think?)
+        // - implement SplittableEditor::set_excerpts_for_path
+
         let secondary_pane = cx.new(|cx| {
             let mut pane = Pane::new(
                 workspace.downgrade(),
@@ -186,12 +254,23 @@ impl SplittableEditor {
                     cx.emit(event.clone())
                 }),
             ];
-        self.secondary = Some(SecondaryEditor {
+        let mut secondary = SecondaryEditor {
             editor: secondary_editor,
             pane: secondary_pane.clone(),
             has_latest_selection: false,
             _subscriptions: subscriptions,
-        });
+        };
+        for (path_key, diff, original_range, original_buffer) in whatever {
+            secondary.sync_path_excerpts_for_buffer(
+                path_key,
+                diff,
+                original_range,
+                original_buffer,
+                cx,
+            );
+        }
+        self.secondary = Some(secondary);
+
         let primary_pane = self.panes.first_pane();
         self.panes
             .split(&primary_pane, &secondary_pane, SplitDirection::Left)
@@ -228,6 +307,27 @@ impl SplittableEditor {
             });
         }
     }
+
+    pub fn set_excerpts_for_path(
+        &mut self,
+        path: PathKey,
+        buffer: Entity<Buffer>,
+        ranges: impl IntoIterator<Item = Range<Point>>,
+        context_line_count: u32,
+        cx: &mut Context<Self>,
+    ) -> (Vec<Range<Anchor>>, bool) {
+        let (anchors, added_a_new_excerpt) =
+            self.primary_editor
+                .read(cx)
+                .buffer()
+                .update(cx, |multibuffer, cx| {
+                    multibuffer.set_excerpts_for_path(path, buffer, ranges, context_line_count, cx)
+                });
+        if let Some(secondary) = &mut self.secondary {
+            secondary.sync_path_excerpts_for_buffer(cx);
+        }
+        (anchors, added_a_new_excerpt)
+    }
 }
 
 impl EventEmitter<EditorEvent> for SplittableEditor {}
@@ -265,3 +365,56 @@ impl Render for SplittableEditor {
             .child(inner)
     }
 }
+
+impl SecondaryEditor {
+    fn sync_path_excerpts_for_buffer(
+        &mut self,
+        path_key: PathKey,
+        main_buffer: &BufferSnapshot,
+        primary_multibuffer: &MultiBuffer,
+        cx: &mut App,
+    ) {
+        let diff = primary_multibuffer
+            .diff_for(main_buffer.remote_id())
+            .unwrap();
+        let diff = diff.read(cx).snapshot(cx);
+        // option 1: hold onto the base text buffers in splittable editor so that we can check whether they exist yet
+        // option 2: have the multibuffer continue to be fully responsible for holding the base text buffers; then need to be able to get a buffer out of the multibuffer based on a pathkey
+        let base_text_buffer = self.editor.update(cx, |editor, cx| {
+            editor
+                .buffer()
+                .update(cx, |buffer, cx| buffer.buffer_for_path_key)
+        });
+        let new = primary_multibuffer
+            .excerpts_for_buffer(main_buffer.remote_id(), cx)
+            .into_iter()
+            .map(|(excerpt_id, excerpt_range)| {
+                let point_to_base_text_point = |point: Point| {
+                    let row = diff.row_to_base_text_row(point.row, &main_buffer);
+                    let column = diff.base_text().line_len(row);
+                    Point::new(row, column)
+                };
+                let primary = excerpt_range.primary.to_point(&main_buffer);
+                let context = excerpt_range.context.to_point(&main_buffer);
+                ExcerptRange {
+                    primary: point_to_base_text_point(primary.start)
+                        ..point_to_base_text_point(primary.end),
+                    context: point_to_base_text_point(context.start)
+                        ..point_to_base_text_point(context.end),
+                }
+            })
+            .collect();
+
+        self.editor.update(cx, |editor, cx| {
+            editor.buffer().update(cx, |buffer, cx| {
+                buffer.update_path_excerpts(
+                    path_key,
+                    base_text_buffer,
+                    base_text_buffer_snapshot,
+                    new,
+                    cx,
+                )
+            })
+        });
+    }
+}

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -10,8 +10,8 @@ pub use anchor::{Anchor, AnchorRangeExt};
 
 use anyhow::{Result, anyhow};
 use buffer_diff::{
-    BufferDiff, BufferDiffEvent, BufferDiffSnapshot, DiffHunkSecondaryStatus, DiffHunkStatus,
-    DiffHunkStatusKind,
+    BufferDiff, BufferDiffEvent, BufferDiffSnapshot, DiffHunk, DiffHunkSecondaryStatus,
+    DiffHunkStatus, DiffHunkStatusKind,
 };
 use clock::ReplicaId;
 use collections::{BTreeMap, Bound, HashMap, HashSet};
@@ -87,8 +87,6 @@ pub struct MultiBuffer {
     /// The writing capability of the multi-buffer.
     capability: Capability,
     buffer_changed_since_sync: Rc<Cell<bool>>,
-    follower: Option<Entity<MultiBuffer>>,
-    base_text_buffers_by_main_buffer_id: HashMap<BufferId, Entity<Buffer>>,
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]
@@ -141,6 +139,8 @@ pub struct MultiBufferDiffHunk {
     pub diff_base_byte_range: Range<BufferOffset>,
     /// Whether or not this hunk also appears in the 'secondary diff'.
     pub secondary_status: DiffHunkSecondaryStatus,
+    /// The word diffs for this hunk.
+    pub word_diffs: Vec<Range<MultiBufferOffset>>,
 }
 
 impl MultiBufferDiffHunk {
@@ -508,17 +508,31 @@ struct BufferState {
 
 struct DiffState {
     diff: Entity<BufferDiff>,
+    /// Whether the [`MultiBuffer`] this is associated with is inverted (i.e.
+    /// showing additions as deletions). This is used in the side-by-side diff
+    /// view.
+    ///
+    /// The [`DiffState`]s in a [`MultiBuffer`] must either be all inverted, or
+    // all not inverted.
     is_inverted: bool,
     _subscription: gpui::Subscription,
 }
 
+impl DiffState {
+    fn snapshot(&self, cx: &App) -> DiffStateSnapshot {
+        DiffStateSnapshot {
+            diff: self.diff.read(cx).snapshot(cx),
+            is_inverted: self.is_inverted,
+        }
+    }
+}
+
 #[derive(Clone)]
 struct DiffStateSnapshot {
     diff: BufferDiffSnapshot,
     is_inverted: bool,
 }
 
-// FIXME
 impl std::ops::Deref for DiffStateSnapshot {
     type Target = BufferDiffSnapshot;
 
@@ -550,7 +564,7 @@ impl DiffState {
 
     fn new_inverted(
         diff: Entity<BufferDiff>,
-        base_text_buffer: Entity<Buffer>,
+        base_text_buffer_id: BufferId,
         cx: &mut Context<MultiBuffer>,
     ) -> Self {
         DiffState {
@@ -563,7 +577,7 @@ impl DiffState {
                         this.inverted_buffer_diff_changed(
                             diff,
                             base_text_changed_range,
-                            base_text_buffer.clone(),
+                            base_text_buffer_id,
                             cx,
                         )
                     }
@@ -577,10 +591,6 @@ impl DiffState {
             is_inverted: true,
         }
     }
-
-    fn snapshot(&self, cx: &App) -> DiffStateSnapshot {
-        todo!()
-    }
 }
 
 /// The contents of a [`MultiBuffer`] at a single point in time.
@@ -935,7 +945,7 @@ pub struct MultiBufferChunks<'a> {
     excerpts: Cursor<'a, 'static, Excerpt, ExcerptOffset>,
     diff_transforms:
         Cursor<'a, 'static, DiffTransform, Dimensions<MultiBufferOffset, ExcerptOffset>>,
-    diffs: &'a TreeMap<BufferId, BufferDiffSnapshot>,
+    diffs: &'a TreeMap<BufferId, DiffStateSnapshot>,
     diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>,
     buffer_chunk: Option<Chunk<'a>>,
     range: Range<MultiBufferOffset>,
@@ -995,7 +1005,7 @@ impl<'a, MBD: MultiBufferDimension> Dimension<'a, DiffTransformSummary> for Diff
 struct MultiBufferCursor<'a, MBD, BD> {
     excerpts: Cursor<'a, 'static, Excerpt, ExcerptDimension<MBD>>,
     diff_transforms: Cursor<'a, 'static, DiffTransform, DiffTransforms<MBD>>,
-    diffs: &'a TreeMap<BufferId, BufferDiffSnapshot>,
+    diffs: &'a TreeMap<BufferId, DiffStateSnapshot>,
     cached_region: Option<MultiBufferRegion<'a, MBD, BD>>,
 }
 
@@ -2252,10 +2262,30 @@ impl MultiBuffer {
     fn buffer_diff_language_changed(&mut self, diff: Entity<BufferDiff>, cx: &mut Context<Self>) {
         let diff = diff.read(cx);
         let buffer_id = diff.buffer_id;
-        let diff = diff.snapshot(cx);
+        let diff = DiffStateSnapshot {
+            diff: diff.snapshot(cx),
+            is_inverted: false,
+        };
         self.snapshot.get_mut().diffs.insert(buffer_id, diff);
     }
 
+    fn inverted_buffer_diff_language_changed(
+        &mut self,
+        base_text_buffer_id: BufferId,
+        diff: Entity<BufferDiff>,
+        cx: &mut Context<Self>,
+    ) {
+        let diff = diff.read(cx);
+        let diff = DiffStateSnapshot {
+            diff: diff.snapshot(cx),
+            is_inverted: true,
+        };
+        self.snapshot
+            .get_mut()
+            .diffs
+            .insert(base_text_buffer_id, diff);
+    }
+
     fn buffer_diff_changed(
         &mut self,
         diff: Entity<BufferDiff>,
@@ -2274,7 +2304,10 @@ impl MultiBuffer {
         let buffer = buffer_state.buffer.read(cx);
         let diff_change_range = range.to_offset(buffer);
 
-        let new_diff = diff.snapshot(cx);
+        let new_diff = DiffStateSnapshot {
+            diff: diff.snapshot(cx),
+            is_inverted: false,
+        };
         let mut snapshot = self.snapshot.get_mut();
         let base_text_changed = snapshot
             .diffs
@@ -2330,41 +2363,41 @@ impl MultiBuffer {
         });
     }
 
-    // FIXME should take main_text_buffer_id right?
     fn inverted_buffer_diff_changed(
         &mut self,
         diff: Entity<BufferDiff>,
         diff_change_range: Range<usize>,
-        base_text_buffer: Entity<Buffer>,
+        base_text_buffer_id: BufferId,
         cx: &mut Context<Self>,
     ) {
-        let base_text_buffer_id = base_text_buffer.read(cx).remote_id();
-        let snapshot = self.snapshot.get_mut();
-        let new_diff = diff.read(cx).snapshot(cx);
-        let base_text_changed = snapshot
-            .diffs
-            .get(&base_text_buffer_id)
-            .is_none_or(|old_diff| !new_diff.base_texts_eq(old_diff));
-        if base_text_changed {
-            base_text_buffer.update(cx, |buffer, cx| {
-                // FIXME use the rope directly
-                buffer.set_text(diff.read(cx).base_text().text(), cx);
-            });
-        }
+        // FIXME move this to the level of the splittableeditor
+        // if base_text_changed && let Some(buffer_state) = self.buffers.get(&base_text_buffer_id) {
+        //     buffer_state.buffer.update(cx, |buffer, cx| {
+        //         // FIXME use the rope directly
+        //         buffer.set_text(diff.read(cx).base_text().text(), cx);
+        //     });
+        // }
         self.sync_mut(cx);
 
+        let diff = diff.read(cx);
         let Some(buffer_state) = self.buffers.get(&base_text_buffer_id) else {
             return;
         };
         self.buffer_changed_since_sync.replace(true);
+
+        let new_diff = DiffStateSnapshot {
+            diff: diff.snapshot(cx),
+            is_inverted: true,
+        };
         let mut snapshot = self.snapshot.get_mut();
-        snapshot.diffs.insert_or_replace(
-            base_text_buffer_id,
-            DiffStateSnapshot {
-                diff: new_diff,
-                is_inverted: true,
-            },
-        );
+        let base_text_changed = snapshot
+            .diffs
+            .get(&base_text_buffer_id)
+            .is_none_or(|old_diff| !new_diff.base_texts_eq(old_diff));
+
+        snapshot
+            .diffs
+            .insert_or_replace(base_text_buffer_id, new_diff);
 
         let mut excerpt_edits = Vec::new();
         for locator in &buffer_state.excerpts {
@@ -2413,15 +2446,20 @@ impl MultiBuffer {
         });
     }
 
+    pub fn all_buffers_iter(&self) -> impl Iterator<Item = Entity<Buffer>> {
+        self.buffers.values().map(|state| state.buffer.clone())
+    }
+
     pub fn all_buffers(&self) -> HashSet<Entity<Buffer>> {
-        self.buffers
-            .values()
-            .map(|state| state.buffer.clone())
-            .collect()
+        self.all_buffers_iter().collect()
+    }
+
+    pub fn all_buffer_ids_iter(&self) -> impl Iterator<Item = BufferId> {
+        self.buffers.keys().copied()
     }
 
     pub fn all_buffer_ids(&self) -> Vec<BufferId> {
-        self.buffers.keys().copied().collect()
+        self.all_buffer_ids_iter().collect()
     }
 
     pub fn buffer(&self, buffer_id: BufferId) -> Option<Entity<Buffer>> {
@@ -2560,6 +2598,8 @@ impl MultiBuffer {
     }
 
     pub fn add_diff(&mut self, diff: Entity<BufferDiff>, cx: &mut Context<Self>) {
+        debug_assert!(self.diffs.values().all(|diff| !diff.is_inverted));
+
         let buffer_id = diff.read(cx).buffer_id;
         self.buffer_diff_changed(
             diff.clone(),
@@ -2568,23 +2608,22 @@ impl MultiBuffer {
         );
         self.diffs
             .insert(buffer_id, DiffState::new(diff.clone(), cx));
+    }
 
-        if let Some(follower) = &self.follower {
-            follower.update(cx, |follower, cx| {
-                let diff_change_range = 0..diff.read(cx).base_text().len();
-                let base_text_buffer: Entity<Buffer> = create_base_text_buffer(&diff);
-                follower.inverted_buffer_diff_changed(
-                    diff,
-                    diff_change_range,
-                    base_text_buffer.clone(),
-                    cx,
-                );
-                follower.diffs.insert(
-                    base_text_buffer.read(cx).remote_id(),
-                    DiffState::new_inverted(diff, base_text_buffer, cx),
-                );
-            });
-        }
+    pub fn add_inverted_diff(
+        &mut self,
+        base_text_buffer_id: BufferId,
+        diff: Entity<BufferDiff>,
+        cx: &mut Context<Self>,
+    ) {
+        debug_assert!(self.diffs.values().all(|diff| diff.is_inverted));
+
+        let diff_change_range = 0..diff.read(cx).base_text().len();
+        self.inverted_buffer_diff_changed(diff.clone(), diff_change_range, base_text_buffer_id, cx);
+        self.diffs.insert(
+            base_text_buffer_id,
+            DiffState::new_inverted(diff, base_text_buffer_id, cx),
+        );
     }
 
     pub fn diff_for(&self, buffer_id: BufferId) -> Option<Entity<BufferDiff>> {
@@ -2997,7 +3036,7 @@ impl MultiBuffer {
 
         for (id, diff) in diffs.iter() {
             if buffer_diff.get(id).is_none() {
-                buffer_diff.insert(*id, diff.diff.read(cx).snapshot(cx));
+                buffer_diff.insert(*id, diff.snapshot(cx));
             }
         }
 
@@ -3343,6 +3382,7 @@ impl MultiBuffer {
                             excerpt.id
                         );
 
+                        // FIXME don't push the deleted region if this is the RHS of a split
                         if !hunk.diff_base_byte_range.is_empty()
                             && hunk_buffer_range.start >= edit_buffer_start
                             && hunk_buffer_range.start <= excerpt_buffer_end
@@ -3788,19 +3828,35 @@ impl MultiBufferSnapshot {
         let query_range = range.start.to_point(self)..range.end.to_point(self);
         self.lift_buffer_metadata(query_range.clone(), move |buffer, buffer_range| {
             let diff = self.diffs.get(&buffer.remote_id())?;
-            let buffer_start = buffer.anchor_before(buffer_range.start);
-            let buffer_end = buffer.anchor_after(buffer_range.end);
-            Some(
-                diff.hunks_intersecting_range(buffer_start..buffer_end, buffer)
-                    .filter_map(|hunk| {
-                        if hunk.is_created_file() && !self.all_diff_hunks_expanded {
-                            return None;
-                        }
-                        Some((hunk.range.clone(), hunk))
-                    }),
-            )
+            let iter: Box<dyn Iterator<Item = (DiffHunk, &BufferSnapshot, bool)>> =
+                if diff.is_inverted {
+                    let buffer_start = buffer.point_to_offset(buffer_range.start);
+                    let buffer_end = buffer.point_to_offset(buffer_range.end);
+                    Box::new(
+                        diff.hunks_intersecting_base_text_range(buffer_start..buffer_end)
+                            .map(move |hunk| (hunk, buffer, true)),
+                    )
+                } else {
+                    let buffer_start = buffer.anchor_before(buffer_range.start);
+                    let buffer_end = buffer.anchor_after(buffer_range.end);
+                    Box::new(
+                        diff.hunks_intersecting_range(buffer_start..buffer_end, buffer)
+                            .map(move |hunk| (hunk, buffer, false)),
+                    )
+                };
+            Some(iter.filter_map(|(hunk, buffer, is_inverted)| {
+                if hunk.is_created_file() && !self.all_diff_hunks_expanded {
+                    return None;
+                }
+                let range = if is_inverted {
+                    hunk.diff_base_byte_range.to_point(&buffer)
+                } else {
+                    hunk.range.clone()
+                };
+                Some((range, (hunk, is_inverted)))
+            }))
         })
-        .filter_map(move |(range, hunk, excerpt)| {
+        .filter_map(move |(range, (hunk, is_inverted), excerpt)| {
             if range.start != range.end && range.end == query_range.start && !hunk.range.is_empty()
             {
                 return None;
@@ -3810,11 +3866,37 @@ impl MultiBufferSnapshot {
             } else {
                 range.end.row + 1
             };
+
+            let word_diffs = (!hunk.base_word_diffs.is_empty()
+                || !hunk.buffer_word_diffs.is_empty())
+            .then(|| {
+                let hunk_start_offset =
+                    Anchor::in_buffer(excerpt.id, hunk.buffer_range.start).to_offset(self);
+
+                hunk.base_word_diffs
+                    .iter()
+                    .map(|diff| hunk_start_offset + diff.start..hunk_start_offset + diff.end)
+                    .chain(
+                        hunk.buffer_word_diffs
+                            .into_iter()
+                            .map(|diff| Anchor::range_in_buffer(excerpt.id, diff).to_offset(self)),
+                    )
+                    .collect()
+            })
+            .unwrap_or_default();
+
+            let buffer_range = if is_inverted {
+                excerpt.buffer.anchor_after(hunk.diff_base_byte_range.start)
+                    ..excerpt.buffer.anchor_before(hunk.diff_base_byte_range.end)
+            } else {
+                hunk.buffer_range.clone()
+            };
             Some(MultiBufferDiffHunk {
                 row_range: MultiBufferRow(range.start.row)..MultiBufferRow(end_row),
                 buffer_id: excerpt.buffer_id,
                 excerpt_id: excerpt.id,
-                buffer_range: hunk.buffer_range.clone(),
+                buffer_range,
+                word_diffs,
                 diff_base_byte_range: BufferOffset(hunk.diff_base_byte_range.start)
                     ..BufferOffset(hunk.diff_base_byte_range.end),
                 secondary_status: hunk.secondary_status,
@@ -4120,6 +4202,7 @@ impl MultiBufferSnapshot {
         })
     }
 
+    // FIXME need to make this work with inverted diffs
     pub fn diff_hunk_before<T: ToOffset>(&self, position: T) -> Option<MultiBufferRow> {
         let offset = position.to_offset(self);
 
@@ -5340,6 +5423,13 @@ impl MultiBufferSnapshot {
         Some(self.excerpt(excerpt_id)?.range.context.clone())
     }
 
+    pub fn excerpt_range_for_excerpt(
+        &self,
+        excerpt_id: ExcerptId,
+    ) -> Option<ExcerptRange<text::Anchor>> {
+        Some(self.excerpt(excerpt_id)?.range.clone())
+    }
+
     pub fn can_resolve(&self, anchor: &Anchor) -> bool {
         if anchor.is_min() || anchor.is_max() {
             // todo(lw): should be `!self.is_empty()`
@@ -6454,10 +6544,6 @@ impl MultiBufferSnapshot {
         self.show_headers
     }
 
-    pub fn diff_for_buffer_id(&self, buffer_id: BufferId) -> Option<&BufferDiffSnapshot> {
-        self.diffs.get(&buffer_id)
-    }
-
     /// Visually annotates a position or range with the `Debug` representation of a value. The
     /// callsite of this function is used as a key - previous annotations will be removed.
     #[cfg(debug_assertions)]

crates/multi_buffer/src/multi_buffer_tests.rs 🔗

@@ -29,7 +29,6 @@ fn test_empty_singleton(cx: &mut App) {
         [RowInfo {
             buffer_id: Some(buffer_id),
             buffer_row: Some(0),
-            base_text_row: None,
             multibuffer_row: Some(MultiBufferRow(0)),
             diff_status: None,
             expand_info: None,
@@ -2511,38 +2510,6 @@ impl ReferenceMultibuffer {
                             .iter()
                             .find(|e| e.id == region.excerpt_id.unwrap())
                             .map(|e| e.buffer.clone());
-                        let base_text_row = match region.status {
-                            None => Some(
-                                main_buffer
-                                    .as_ref()
-                                    .map(|main_buffer| {
-                                        let diff = self
-                                            .diffs
-                                            .get(&main_buffer.read(cx).remote_id())
-                                            .unwrap();
-                                        let buffer_row = buffer_row.unwrap();
-                                        BaseTextRow(
-                                            diff.read(cx).snapshot(cx).row_to_base_text_row(
-                                                buffer_row,
-                                                &main_buffer.read(cx).snapshot(),
-                                            ),
-                                        )
-                                    })
-                                    .unwrap_or_default(),
-                            ),
-                            Some(DiffHunkStatus {
-                                kind: DiffHunkStatusKind::Added,
-                                ..
-                            }) => None,
-                            Some(DiffHunkStatus {
-                                kind: DiffHunkStatusKind::Deleted,
-                                ..
-                            }) => Some(BaseTextRow(buffer_row.unwrap())),
-                            Some(DiffHunkStatus {
-                                kind: DiffHunkStatusKind::Modified,
-                                ..
-                            }) => unreachable!(),
-                        };
                         let is_excerpt_start = region_ix == 0
                             || &regions[region_ix - 1].excerpt_id != &region.excerpt_id
                             || regions[region_ix - 1].range.is_empty();
@@ -2589,7 +2556,6 @@ impl ReferenceMultibuffer {
                             buffer_id: region.buffer_id,
                             diff_status: region.status,
                             buffer_row,
-                            base_text_row,
                             wrapped_buffer_row: None,
 
                             multibuffer_row: Some(multibuffer_row),
@@ -3817,70 +3783,6 @@ async fn test_basic_filtering(cx: &mut TestAppContext) {
     );
 }
 
-#[gpui::test]
-async fn test_base_text_line_numbers(cx: &mut TestAppContext) {
-    let base_text = indoc! {"
-        one
-        two
-        three
-        four
-        five
-        six
-    "};
-    let buffer_text = indoc! {"
-        two
-        THREE
-        five
-        six
-        SEVEN
-    "};
-    let multibuffer = cx.update(|cx| MultiBuffer::build_simple(buffer_text, cx));
-    multibuffer.update(cx, |multibuffer, cx| {
-        let buffer = multibuffer.all_buffers().into_iter().next().unwrap();
-        let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
-        multibuffer.set_all_diff_hunks_expanded(cx);
-        multibuffer.add_diff(diff, cx);
-    });
-    let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
-        (multibuffer.snapshot(cx), multibuffer.subscribe())
-    });
-
-    assert_new_snapshot(
-        &multibuffer,
-        &mut snapshot,
-        &mut subscription,
-        cx,
-        indoc! {"
-            - one
-              two
-            - three
-            - four
-            + THREE
-              five
-              six
-            + SEVEN
-        "},
-    );
-    let base_text_rows = snapshot
-        .row_infos(MultiBufferRow(0))
-        .map(|row_info| row_info.base_text_row)
-        .collect::<Vec<_>>();
-    pretty_assertions::assert_eq!(
-        base_text_rows,
-        vec![
-            Some(BaseTextRow(0)),
-            Some(BaseTextRow(1)),
-            Some(BaseTextRow(2)),
-            Some(BaseTextRow(3)),
-            None,
-            Some(BaseTextRow(4)),
-            Some(BaseTextRow(5)),
-            None,
-            Some(BaseTextRow(6)),
-        ]
-    )
-}
-
 #[track_caller]
 fn assert_excerpts_match(
     multibuffer: &Entity<MultiBuffer>,

crates/multi_buffer/src/path_key.rs 🔗

@@ -50,11 +50,6 @@ impl MultiBuffer {
         if let Some(to_remove) = self.excerpts_by_path.remove(&path) {
             self.remove_excerpts(to_remove, cx)
         }
-        if let Some(follower) = &self.follower {

-            follower.update(cx, |follower, cx| {

-                follower.remove_excerpts_for_path(path, cx);

-            });

-        }

     }
 
     pub fn location_for_path(&self, path: &PathKey, cx: &App) -> Option<Anchor> {
@@ -68,25 +63,11 @@ impl MultiBuffer {
         self.excerpts_by_path.keys()
     }
 
-    

-    // need:

-    // MultiBuffer::add_inverted_diff

-    // 

-    // SplittableEditor will handle:

-    // - creating diff base buffers

-    // - calling add_diff on one side and add_inverted_diff on the other side

-    // - calling set_excerpts_for_path on both sides (using the diff base buffers for the LHS)

-    //   - and translating excerpt ranges for the LHS

-    // - we have to make very sure that at all times, the sequence of excerpts on the two sides is the same

-    

-    // let b = Buffer::new(text);

-    // let mb = MutliBuffer::new([b1, b2, b3], ...);

-    // mb.attach_diff_hunks(...);

-    // let mb2 = mb.inverted();

-    //

-    // fn inverted(&self) -> Self {

-    //

-    // }

+    pub fn excerpts_with_paths(&self) -> impl Iterator<Item = (&PathKey, &ExcerptId)> {

+        self.excerpts_by_path

+            .iter()

+            .flat_map(|(key, ex_ids)| ex_ids.iter().map(move |id| (key, id)))

+    }

 
     /// Sets excerpts, returns `true` if at least one new excerpt was added.
     pub fn set_excerpts_for_path(
@@ -173,7 +154,6 @@ impl MultiBuffer {
         }
     }
 
-    // FIXME need to sync excerpt removal to the follower

     pub fn remove_excerpts_for_buffer(&mut self, buffer: BufferId, cx: &mut Context<Self>) {
         self.remove_excerpts(
             self.excerpts_for_buffer(buffer, cx)
@@ -294,15 +274,7 @@ impl MultiBuffer {
         (result, added_a_new_excerpt)
     }
 
-    // SplittableEditor (SplitEditor | Editor)

-    //   - lhs: Editor

-    //     - mb: MultiBuffer

-    //   - rhs: Editor

-    //     - mb: MultiBuffer

-    //

-    // editor.rhs.mb.follower = Some(editor.lhs.mb)

-    // editor.lhs.mb.has_inverted_diffs = true

-    fn update_path_excerpts(

+    pub fn update_path_excerpts(

         &mut self,
         path: PathKey,
         buffer: Entity<Buffer>,
@@ -459,46 +431,6 @@ impl MultiBuffer {
             self.excerpts_by_path.insert(path.clone(), excerpt_ids);
         }
 
-        if let Some(follower) = &self.follower {

-            if let Some(diff) = snapshot.diffs.get(&buffer_snapshot.remote_id()) {

-                follower.update(cx, |follower, cx| {

-                    let Some(base_text_buffer) = follower

-                        .base_text_buffers_by_main_buffer_id

-                        .get(&buffer_snapshot.remote_id())

-                        .cloned()

-                    else {

-                        return;

-                    };

-                    let new = new

-                        .into_iter()

-                        .map(|range| {

-                            let point_to_base_text_point = |point: Point| {

-                                let row = diff.row_to_base_text_row(point.row, buffer_snapshot);

-                                let column = diff.base_text().line_len(row);

-                                Point::new(row, column)

-                            };

-                            ExcerptRange {

-                                primary: point_to_base_text_point(range.primary.start)

-                                    ..point_to_base_text_point(range.primary.end),

-                                context: point_to_base_text_point(range.context.start)

-                                    ..point_to_base_text_point(range.context.end),

-                            }

-                        })

-                        .collect();

-                    let base_text_buffer_snapshot = base_text_buffer.read(cx).snapshot();

-                    follower.update_path_excerpts(

-                        path,

-                        base_text_buffer,

-                        &base_text_buffer_snapshot,

-                        new,

-                        cx,

-                    );

-                });

-            } else {

-                // FIXME

-            }

-        }

-

         (excerpt_ids, added_a_new_excerpt)
     }
 }

crates/project/src/project_tests.rs 🔗

@@ -7571,6 +7571,7 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
     let event = diff_events.next().await.unwrap();
     if let BufferDiffEvent::DiffChanged {
         changed_range: Some(changed_range),
+        base_text_changed_range: _,
     } = event
     {
         let changed_range = changed_range.to_point(&snapshot);
@@ -7613,6 +7614,7 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
     let event = diff_events.next().await.unwrap();
     if let BufferDiffEvent::DiffChanged {
         changed_range: Some(changed_range),
+        base_text_changed_range: _,
     } = event
     {
         let changed_range = changed_range.to_point(&snapshot);
@@ -7669,6 +7671,7 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
     let event = diff_events.next().await.unwrap();
     if let BufferDiffEvent::DiffChanged {
         changed_range: Some(changed_range),
+        base_text_changed_range: _,
     } = event
     {
         let changed_range = changed_range.to_point(&snapshot);
@@ -7710,6 +7713,7 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
     let event = diff_events.next().await.unwrap();
     if let BufferDiffEvent::DiffChanged {
         changed_range: Some(changed_range),
+        base_text_changed_range: _,
     } = event
     {
         let changed_range = changed_range.to_point(&snapshot);