Avoid expensive re-computation of sticky header lines (#46653)

Max Brunsfeld created

Fixes a performance regression introduced in
https://github.com/zed-industries/zed/pull/45377

Release Notes:

- Fixed a performance regression that caused lag in large documents

Change summary

crates/editor/src/element.rs           |  5 +++++
crates/editor/src/scroll.rs            | 12 ++++++++++++
crates/editor/src/scroll/autoscroll.rs | 15 ++-------------
3 files changed, 19 insertions(+), 13 deletions(-)

Detailed changes

crates/editor/src/element.rs 🔗

@@ -10164,6 +10164,11 @@ impl Element for EditorElement {
                     } else {
                         None
                     };
+                    self.editor.update(cx, |editor, _| {
+                        editor.scroll_manager.set_sticky_header_line_count(
+                            sticky_headers.as_ref().map_or(0, |h| h.lines.len()),
+                        );
+                    });
                     let indent_guides = self.layout_indent_guides(
                         content_origin,
                         text_hitbox.origin,

crates/editor/src/scroll.rs 🔗

@@ -152,6 +152,8 @@ pub struct ScrollManager {
     pub(crate) vertical_scroll_margin: ScrollOffset,
     anchor: ScrollAnchor,
     ongoing: OngoingScroll,
+    /// Number of sticky header lines currently being rendered for the current scroll position.
+    sticky_header_line_count: usize,
     /// The second element indicates whether the autoscroll request is local
     /// (true) or remote (false). Local requests are initiated by user actions,
     /// while remote requests come from external sources.
@@ -177,6 +179,7 @@ impl ScrollManager {
             vertical_scroll_margin: EditorSettings::get_global(cx).vertical_scroll_margin,
             anchor: ScrollAnchor::new(),
             ongoing: OngoingScroll::new(),
+            sticky_header_line_count: 0,
             autoscroll_request: None,
             show_scrollbars: true,
             hide_scrollbar_task: None,
@@ -192,6 +195,7 @@ impl ScrollManager {
     pub fn clone_state(&mut self, other: &Self) {
         self.anchor = other.anchor;
         self.ongoing = other.ongoing;
+        self.sticky_header_line_count = other.sticky_header_line_count;
     }
 
     pub fn anchor(&self) -> ScrollAnchor {
@@ -211,6 +215,14 @@ impl ScrollManager {
         self.anchor.scroll_position(snapshot)
     }
 
+    pub fn sticky_header_line_count(&self) -> usize {
+        self.sticky_header_line_count
+    }
+
+    pub fn set_sticky_header_line_count(&mut self, count: usize) {
+        self.sticky_header_line_count = count;
+    }
+
     fn set_scroll_position(
         &mut self,
         scroll_position: gpui::Point<ScrollOffset>,

crates/editor/src/scroll/autoscroll.rs 🔗

@@ -5,7 +5,7 @@ use crate::{
 };
 use gpui::{Bounds, Context, Pixels, Window};
 use language::Point;
-use multi_buffer::{Anchor, ToPoint};
+use multi_buffer::Anchor;
 use std::cmp;
 
 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
@@ -186,18 +186,7 @@ impl Editor {
             }
         }
 
-        let style = self.style(cx).clone();
-        let sticky_headers = self.sticky_headers(&style, cx).unwrap_or_default();
-        let visible_sticky_headers = sticky_headers
-            .iter()
-            .filter(|h| {
-                let buffer_snapshot = display_map.buffer_snapshot();
-                let buffer_range =
-                    h.range.start.to_point(buffer_snapshot)..h.range.end.to_point(buffer_snapshot);
-
-                buffer_range.contains(&Point::new(target_top as u32, 0))
-            })
-            .count();
+        let visible_sticky_headers = self.scroll_manager.sticky_header_line_count();
 
         let margin = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
             0.