From 3a6faf2b4a65deb6e8e2c38fce6f6e8babe76159 Mon Sep 17 00:00:00 2001 From: Tim Vermeulen Date: Tue, 31 Mar 2026 20:56:49 +0200 Subject: [PATCH] editor: Deduplicate sticky header rows (#52844) Fixes a bug that caused duplicate sticky header rows to appear if multiple outline items start on the same row. Sort of addresses #52722, although arguably the real issue there is that duplicate outline items are being created in the first place. Before: https://github.com/user-attachments/assets/7941cbe8-9b62-470c-b475-f08f2f20fac6 After: https://github.com/user-attachments/assets/c4e291ea-6414-483f-8ff7-3d89d10000b6 Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed a bug that caused duplicate sticky header rows to appear if multiple outline items start on the same row. --- crates/editor/src/editor_tests.rs | 59 +++++++++++++++++++++++++++++++ crates/editor/src/element.rs | 7 ++++ 2 files changed, 66 insertions(+) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 48c92f0f22762f95b1d6ef681951355a340d221e..65a872e6035565bb01fdd78e00d6cf0f35d35ef8 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -31952,6 +31952,65 @@ async fn test_sticky_scroll_with_expanded_deleted_diff_hunks( assert_eq!(sticky_headers(6.0), vec![]); } +#[gpui::test] +async fn test_no_duplicated_sticky_headers(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx).await; + + cx.set_state(indoc! {" + ˇimpl Foo { fn bar() { + let x = 1; + fn baz() { + let y = 2; + } + } } + "}); + + cx.update_editor(|e, _, cx| { + e.buffer() + .read(cx) + .as_singleton() + .unwrap() + .update(cx, |buffer, cx| { + buffer.set_language(Some(rust_lang()), cx); + }) + }); + + 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::>() + }) + }; + + let struct_foo = Point { row: 0, column: 0 }; + let fn_baz = Point { row: 2, column: 4 }; + + assert_eq!(sticky_headers(0.0), vec![]); + assert_eq!(sticky_headers(0.5), vec![(struct_foo, 0.0)]); + assert_eq!(sticky_headers(1.0), vec![(struct_foo, 0.0)]); + assert_eq!(sticky_headers(1.5), vec![(struct_foo, 0.0), (fn_baz, 1.0)]); + assert_eq!(sticky_headers(2.0), vec![(struct_foo, 0.0), (fn_baz, 1.0)]); + assert_eq!(sticky_headers(2.5), vec![(struct_foo, 0.0), (fn_baz, 0.5)]); + assert_eq!(sticky_headers(3.0), vec![(struct_foo, 0.0)]); + assert_eq!(sticky_headers(3.5), vec![(struct_foo, 0.0)]); + assert_eq!(sticky_headers(4.0), vec![(struct_foo, 0.0)]); + assert_eq!(sticky_headers(4.5), vec![(struct_foo, -0.5)]); + assert_eq!(sticky_headers(5.0), vec![]); +} + #[gpui::test] fn test_relative_line_numbers(cx: &mut TestAppContext) { init_test(cx, |_| {}); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 968048f68513a09c460bb06789103923bbbca828..9ce080c87bf82ec1098e2a4b1db6bc6a65d22828 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -4674,6 +4674,13 @@ impl EditorElement { .display_snapshot .point_to_display_point(start_point, Bias::Left) .row(); + if rows + .last() + .is_some_and(|last| last.sticky_row == sticky_row) + { + continue; + } + let end_row = snapshot .display_snapshot .point_to_display_point(end_point, Bias::Left)