@@ -22233,6 +22233,40 @@ async fn test_toggle_deletion_hunk_at_start_of_file(
cx.assert_state_with_diff(hunk_expanded);
}
+#[gpui::test]
+async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
+ executor: BackgroundExecutor,
+ cx: &mut TestAppContext,
+) {
+ init_test(cx, |_| {});
+ let mut cx = EditorTestContext::new(cx).await;
+
+ cx.set_state("หnew\nsecond\nthird\n");
+ cx.set_head_text("old\nsecond\nthird\n");
+ cx.update_editor(|editor, window, cx| {
+ editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
+ });
+ executor.run_until_parked();
+ assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
+
+ // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
+ cx.update_editor(|editor, window, cx| {
+ let snapshot = editor.snapshot(window, cx);
+ let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
+ let hunks = editor
+ .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
+ .collect::<Vec<_>>();
+ assert_eq!(hunks.len(), 1);
+ let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
+ editor.toggle_single_diff_hunk(hunk_range, cx)
+ });
+ executor.run_until_parked();
+ cx.assert_state_with_diff("- old\n+ หnew\n second\n third\n".to_string());
+
+ // Keep the editor scrolled to the top so the full hunk remains visible.
+ assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
+}
+
#[gpui::test]
async fn test_display_diff_hunks(cx: &mut TestAppContext) {
init_test(cx, |_| {});
@@ -251,7 +251,11 @@ impl ScrollManager {
Bias::Left,
)
.to_point(map);
- let top_anchor = map.buffer_snapshot().anchor_after(scroll_top_buffer_point);
+ // Anchor the scroll position to the *left* of the first visible buffer point.
+ //
+ // This prevents the viewport from shifting down when blocks (e.g. expanded diff hunk
+ // deletions) are inserted *above* the first buffer character in the file.
+ let top_anchor = map.buffer_snapshot().anchor_before(scroll_top_buffer_point);
self.set_anchor(
ScrollAnchor {