diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 053fe3265dadcbbe2c8fba7013e88ed082540a9b..03d19d8b8de674b3b120e28adfe0831baeede833 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -200,7 +200,7 @@ use std::{ time::{Duration, Instant}, }; use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables}; -use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _}; +use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _, ToPoint as _}; use theme::{ AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings, observe_buffer_font_size_adjustment, @@ -1992,20 +1992,21 @@ impl Editor { return; } let multi_buffer = display_snapshot.buffer_snapshot(); - let multi_buffer_visible_start = self + let scroll_anchor = self .scroll_manager .native_anchor(display_snapshot, cx) - .anchor - .to_point(&multi_buffer); - let max_row = multi_buffer.max_point().row; - - let start_row = (multi_buffer_visible_start.row).min(max_row); - let end_row = (multi_buffer_visible_start.row + 10).min(max_row); + .anchor; let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else { return; }; let buffer = buffer.clone(); let &excerpt_id = excerpt_id; + + let buffer_visible_start = scroll_anchor.text_anchor.to_point(&buffer); + let max_row = buffer.max_point().row; + let start_row = buffer_visible_start.row.min(max_row); + let end_row = (buffer_visible_start.row + 10).min(max_row); + let syntax = self.style(cx).syntax.clone(); let background_task = cx.background_spawn(async move { buffer diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index f0e1d601d0454c85b466532a84ebbe7db6b87297..71fd78a680341bcb89b00fe60f9e2d0036004ba4 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -29058,6 +29058,95 @@ async fn test_sticky_scroll(cx: &mut TestAppContext) { assert_eq!(sticky_headers(10.0), vec![]); } +#[gpui::test] +async fn test_sticky_scroll_with_expanded_deleted_diff_hunks( + executor: BackgroundExecutor, + cx: &mut TestAppContext, +) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx).await; + + let diff_base = indoc! {" + fn foo() { + let a = 1; + let b = 2; + let c = 3; + let d = 4; + let e = 5; + } + "}; + + let buffer = indoc! {" + ˇfn foo() { + } + "}; + + cx.set_state(&buffer); + + cx.update_editor(|e, _, cx| { + e.buffer() + .read(cx) + .as_singleton() + .unwrap() + .update(cx, |buffer, cx| { + buffer.set_language(Some(rust_lang()), cx); + }) + }); + + cx.set_head_text(diff_base); + executor.run_until_parked(); + + cx.update_editor(|editor, window, cx| { + editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx); + }); + executor.run_until_parked(); + + // After expanding, the display should look like: + // row 0: fn foo() { + // row 1: - let a = 1; (deleted) + // row 2: - let b = 2; (deleted) + // row 3: - let c = 3; (deleted) + // row 4: - let d = 4; (deleted) + // row 5: - let e = 5; (deleted) + // row 6: } + // + // fn foo() spans display rows 0-6. Scrolling into the deleted region + // (rows 1-5) should still show fn foo() as a sticky header. + + let fn_foo = Point { row: 0, column: 0 }; + + let mut sticky_headers = |offset: ScrollOffset| { + cx.update_editor(|e, window, cx| { + e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx); + }); + cx.run_until_parked(); + cx.update_editor(|e, window, cx| { + EditorElement::sticky_headers(&e, &e.snapshot(window, cx)) + .into_iter() + .map( + |StickyHeader { + start_point, + offset, + .. + }| { (start_point, offset) }, + ) + .collect::>() + }) + }; + + assert_eq!(sticky_headers(0.0), vec![]); + assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]); + assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]); + // Scrolling into deleted lines: fn foo() should still be a sticky header. + assert_eq!(sticky_headers(2.0), vec![(fn_foo, 0.0)]); + assert_eq!(sticky_headers(3.0), vec![(fn_foo, 0.0)]); + assert_eq!(sticky_headers(4.0), vec![(fn_foo, 0.0)]); + assert_eq!(sticky_headers(5.0), vec![(fn_foo, 0.0)]); + assert_eq!(sticky_headers(5.5), vec![(fn_foo, -0.5)]); + // Past the closing brace: no more sticky header. + assert_eq!(sticky_headers(6.0), vec![]); +} + #[gpui::test] fn test_relative_line_numbers(cx: &mut TestAppContext) { init_test(cx, |_| {});