git: Fix a potential misalignment in the side-by-side diff (#48690)

Cole Miller created

Release Notes:

- N/A

Change summary

crates/editor/src/display_map/block_map.rs |  24 ++-
crates/editor/src/split.rs                 | 130 ++++++++++++++++++++++++
2 files changed, 146 insertions(+), 8 deletions(-)

Detailed changes

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

@@ -1337,14 +1337,22 @@ impl BlockMap {
                     break;
                 }
 
-                let (delta_at_end, spacer_at_end) = determine_spacer(
-                    &mut our_wrapper,
-                    &mut companion_wrapper,
-                    current_boundary,
-                    current_range.end,
-                    delta,
-                );
-                delta = delta_at_end;
+                let edit_for_current_boundary =
+                    excerpt.patch.edit_for_old_position(current_boundary);
+
+                let spacer_at_end = if current_boundary == edit_for_current_boundary.old.end {
+                    let (delta_at_end, spacer_at_end) = determine_spacer(
+                        &mut our_wrapper,
+                        &mut companion_wrapper,
+                        current_boundary,
+                        current_range.end,
+                        delta,
+                    );
+                    delta = delta_at_end;
+                    spacer_at_end
+                } else {
+                    None
+                };
 
                 if let Some((wrap_row, mut height)) = spacer_at_start {
                     if let Some((_, additional_height)) = spacer_at_end {

crates/editor/src/split.rs 🔗

@@ -3945,6 +3945,136 @@ mod tests {
         assert_eq!(
             lhs_pos.x, rhs_pos.x,
             "LHS should have same horizontal scroll position as RHS after autoscroll"
+        )
+    }
+
+    #[gpui::test]
+    async fn test_edit_line_before_soft_wrapped_line_preceding_hunk(cx: &mut gpui::TestAppContext) {
+        use rope::Point;
+        use unindent::Unindent as _;
+
+        let (editor, mut cx) = init_test(cx, SoftWrap::EditorWidth).await;
+
+        let base_text = "
+            first line
+            aaaa bbbb cccc dddd eeee ffff
+            original
+        "
+        .unindent();
+
+        let current_text = "
+            first line
+            aaaa bbbb cccc dddd eeee ffff
+            modified
+        "
+        .unindent();
+
+        let (buffer, diff) = buffer_with_diff(&base_text, &current_text, &mut cx);
+
+        editor.update(cx, |editor, cx| {
+            let path = PathKey::for_buffer(&buffer, cx);
+            editor.set_excerpts_for_path(
+                path,
+                buffer.clone(),
+                vec![Point::new(0, 0)..buffer.read(cx).max_point()],
+                0,
+                diff.clone(),
+                cx,
+            );
+        });
+
+        cx.run_until_parked();
+
+        assert_split_content_with_widths(
+            &editor,
+            px(400.0),
+            px(200.0),
+            "
+                    § <no file>
+                    § -----
+                    first line
+                    aaaa bbbb cccc dddd eeee ffff
+                    § spacer
+                    § spacer
+                    modified"
+                .unindent(),
+            "
+                    § <no file>
+                    § -----
+                    first line
+                    aaaa bbbb\x20
+                    cccc dddd\x20
+                    eeee ffff
+                    original"
+                .unindent(),
+            &mut cx,
+        );
+
+        buffer.update(cx, |buffer, cx| {
+            buffer.edit(
+                [(Point::new(0, 0)..Point::new(0, 10), "edited first")],
+                None,
+                cx,
+            );
+        });
+
+        cx.run_until_parked();
+
+        assert_split_content_with_widths(
+            &editor,
+            px(400.0),
+            px(200.0),
+            "
+                    § <no file>
+                    § -----
+                    edited first
+                    aaaa bbbb cccc dddd eeee ffff
+                    § spacer
+                    § spacer
+                    modified"
+                .unindent(),
+            "
+                    § <no file>
+                    § -----
+                    first line
+                    aaaa bbbb\x20
+                    cccc dddd\x20
+                    eeee ffff
+                    original"
+                .unindent(),
+            &mut cx,
+        );
+
+        let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
+        diff.update(cx, |diff, cx| {
+            diff.recalculate_diff_sync(&buffer_snapshot, cx);
+        });
+
+        cx.run_until_parked();
+
+        assert_split_content_with_widths(
+            &editor,
+            px(400.0),
+            px(200.0),
+            "
+                    § <no file>
+                    § -----
+                    edited first
+                    aaaa bbbb cccc dddd eeee ffff
+                    § spacer
+                    § spacer
+                    modified"
+                .unindent(),
+            "
+                    § <no file>
+                    § -----
+                    first line
+                    aaaa bbbb\x20
+                    cccc dddd\x20
+                    eeee ffff
+                    original"
+                .unindent(),
+            &mut cx,
         );
     }