git: Fix missing spacers in side-by-side diff when editing inside an addition hunk (#47568)

Cole Miller created

Release Notes:

- N/A

Change summary

crates/editor/src/display_map/block_map.rs | 18 +++
crates/editor/src/split.rs                 | 94 ++++++++++++++++++++++++
2 files changed, 109 insertions(+), 3 deletions(-)

Detailed changes

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

@@ -664,11 +664,23 @@ impl BlockMap {
                         .map(|(_, range)| range.end)
                         .unwrap_or(wrap_snapshot.buffer_snapshot().max_point());
 
-                    let my_start = wrap_snapshot.make_wrap_point(my_start, Bias::Left);
+                    let mut my_start = wrap_snapshot.make_wrap_point(my_start, Bias::Left);
                     let mut my_end = wrap_snapshot.make_wrap_point(my_end, Bias::Left);
                     if my_end.column() > 0 {
-                        my_end.0.row += 1;
-                        my_end.0.column = 0;
+                        *my_end.row_mut() += 1;
+                        *my_end.column_mut() = 0;
+                    }
+
+                    // Empty edits won't survive Patch::compose, but we still need to make sure
+                    // we recompute spacers when we get them.
+                    if my_start.row() == my_end.row() {
+                        if my_end.row() <= wrap_snapshot.max_point().row() {
+                            *my_end.row_mut() += 1;
+                            *my_end.column_mut() = 0;
+                        } else if my_start.row() > WrapRow(0) {
+                            *my_start.row_mut() += 1;
+                            *my_start.column_mut() = 0;
+                        }
                     }
 
                     WrapEdit {

crates/editor/src/split.rs 🔗

@@ -3355,4 +3355,98 @@ mod tests {
             &mut cx,
         );
     }
+
+    #[gpui::test]
+    async fn test_adding_line_to_addition_hunk(cx: &mut gpui::TestAppContext) {
+        use rope::Point;
+        use unindent::Unindent as _;
+
+        let (editor, mut cx) = init_test(cx).await;
+
+        let base_text = "
+            aaa
+            bbb
+            ccc
+        "
+        .unindent();
+
+        let current_text = "
+            aaa
+            bbb
+            xxx
+            yyy
+            ccc
+        "
+        .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(
+            &editor,
+            "
+            § <no file>
+            § -----
+            aaa
+            bbb
+            xxx
+            yyy
+            ccc"
+            .unindent(),
+            "
+            § <no file>
+            § -----
+            aaa
+            bbb
+            § spacer
+            § spacer
+            ccc"
+            .unindent(),
+            &mut cx,
+        );
+
+        buffer.update(cx, |buffer, cx| {
+            buffer.edit([(Point::new(3, 3)..Point::new(3, 3), "\nzzz")], None, cx);
+        });
+
+        cx.run_until_parked();
+
+        assert_split_content(
+            &editor,
+            "
+            § <no file>
+            § -----
+            aaa
+            bbb
+            xxx
+            yyy
+            zzz
+            ccc"
+            .unindent(),
+            "
+            § <no file>
+            § -----
+            aaa
+            bbb
+            § spacer
+            § spacer
+            § spacer
+            ccc"
+            .unindent(),
+            &mut cx,
+        );
+    }
 }