editor: Improve `colorize_bracket` highlight performance (#49803) (cherry-pick to preview) (#49808)

zed-zippy[bot] and Lukas Wirth created

Cherry-pick of #49803 to preview

----
The binary search insertion scheme in `highlight_text` works fine for
small numbers of elements but does not handle large amounts of ranges
well, as that will cause constant memcpying of the latter half of the
vec. Bracket colorization tends to have a huge amount of entries though,
so this can cause massive lags on the foreground thread. The better
approach here is to just collect all elements and re-sort them once.

Release Notes:

- Reduced mini-stutters occuring due to large amount of bracket
colorization in big buffers and agent diffs

Co-authored-by: Lukas Wirth <lukas@zed.dev>

Change summary

crates/editor/src/bracket_colorization.rs |  3 +
crates/editor/src/display_map.rs          | 28 ++++++++++++------------
crates/theme/src/fallback_themes.rs       |  4 ++
crates/theme/src/styles/accents.rs        | 14 +++++++-----
4 files changed, 27 insertions(+), 22 deletions(-)

Detailed changes

crates/editor/src/bracket_colorization.rs 🔗

@@ -129,8 +129,9 @@ impl Editor {
         }
 
         let editor_background = cx.theme().colors().editor_background;
+        let accents = cx.theme().accents().clone();
         for (accent_number, bracket_highlights) in bracket_matches_by_accent {
-            let bracket_color = cx.theme().accents().color_for_index(accent_number as u32);
+            let bracket_color = accents.color_for_index(accent_number as u32);
             let adjusted_color = ensure_minimum_contrast(bracket_color, editor_background, 55.0);
             let style = HighlightStyle {
                 color: Some(adjusted_color),

crates/editor/src/display_map.rs 🔗

@@ -1207,26 +1207,26 @@ impl DisplayMap {
     pub fn highlight_text(
         &mut self,
         key: HighlightKey,
-        ranges: Vec<Range<Anchor>>,
+        mut ranges: Vec<Range<Anchor>>,
         style: HighlightStyle,
         merge: bool,
         cx: &App,
     ) {
         let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
-        let to_insert = match self.text_highlights.remove(&key).filter(|_| merge) {
-            Some(previous) => {
-                let mut merged_ranges = previous.1.clone();
-                for new_range in ranges {
-                    let i = merged_ranges
-                        .binary_search_by(|probe| {
-                            probe.start.cmp(&new_range.start, &multi_buffer_snapshot)
-                        })
-                        .unwrap_or_else(|i| i);
-                    merged_ranges.insert(i, new_range);
+        let to_insert = match self.text_highlights.remove(&key) {
+            Some(mut previous) if merge => match Arc::get_mut(&mut previous) {
+                Some((_, previous_ranges)) => {
+                    previous_ranges.extend(ranges.iter().cloned());
+                    previous_ranges.sort_by(|a, b| a.start.cmp(&b.start, &multi_buffer_snapshot));
+                    previous
                 }
-                Arc::new((style, merged_ranges))
-            }
-            None => Arc::new((style, ranges)),
+                None => Arc::new((style, {
+                    ranges.extend(previous.1.iter().cloned());
+                    ranges.sort_by(|a, b| a.start.cmp(&b.start, &multi_buffer_snapshot));
+                    ranges
+                })),
+            },
+            _ => Arc::new((style, ranges)),
         };
         self.text_highlights.insert(key, to_insert);
     }

crates/theme/src/fallback_themes.rs 🔗

@@ -109,7 +109,9 @@ pub(crate) fn zed_default_dark() -> Theme {
         styles: ThemeStyles {
             window_background_appearance: WindowBackgroundAppearance::Opaque,
             system: SystemColors::default(),
-            accents: AccentColors(vec![blue, orange, purple, teal, red, green, yellow]),
+            accents: AccentColors(Arc::from(vec![
+                blue, orange, purple, teal, red, green, yellow,
+            ])),
             colors: ThemeColors {
                 border: hsla(225. / 360., 13. / 100., 12. / 100., 1.),
                 border_variant: hsla(228. / 360., 8. / 100., 25. / 100., 1.),

crates/theme/src/styles/accents.rs 🔗

@@ -1,3 +1,5 @@
+use std::sync::Arc;
+
 use gpui::Hsla;
 use serde::Deserialize;
 
@@ -8,7 +10,7 @@ use crate::{
 
 /// A collection of colors that are used to color indent aware lines in the editor.
 #[derive(Clone, Debug, Deserialize, PartialEq)]
-pub struct AccentColors(pub Vec<Hsla>);
+pub struct AccentColors(pub Arc<[Hsla]>);
 
 impl Default for AccentColors {
     /// Don't use this!
@@ -22,7 +24,7 @@ impl Default for AccentColors {
 impl AccentColors {
     /// Returns the set of dark accent colors.
     pub fn dark() -> Self {
-        Self(vec![
+        Self(Arc::from(vec![
             blue().dark().step_9(),
             orange().dark().step_9(),
             pink().dark().step_9(),
@@ -36,12 +38,12 @@ impl AccentColors {
             grass().dark().step_9(),
             indigo().dark().step_9(),
             iris().dark().step_9(),
-        ])
+        ]))
     }
 
     /// Returns the set of light accent colors.
     pub fn light() -> Self {
-        Self(vec![
+        Self(Arc::from(vec![
             blue().light().step_9(),
             orange().light().step_9(),
             pink().light().step_9(),
@@ -55,7 +57,7 @@ impl AccentColors {
             grass().light().step_9(),
             indigo().light().step_9(),
             iris().light().step_9(),
-        ])
+        ]))
     }
 }
 
@@ -82,7 +84,7 @@ impl AccentColors {
             .collect::<Vec<_>>();
 
         if !colors.is_empty() {
-            self.0 = colors;
+            self.0 = Arc::from(colors);
         }
     }
 }