editor: Speed up `colorize_brackets` slightly in large multibuffers (#43713)

Lukas Wirth created

There is still room for improvement here as `anchor(s)_in_excerpt` is
generally a bad API here due to it reseeking the entire excerpt tree
from the start on every call which we don't really need. But this at
least cuts the seeks down by a factor of 4 for now.

Release Notes:

- Improved performance of bracket colorization in large multibuffers

Change summary

crates/editor/src/bracket_colorization.rs | 33 ++++++++++++++++++------
crates/multi_buffer/src/multi_buffer.rs   | 22 +++++++++++++---
2 files changed, 42 insertions(+), 13 deletions(-)

Detailed changes

crates/editor/src/bracket_colorization.rs 🔗

@@ -7,6 +7,7 @@ use std::ops::Range;
 use crate::Editor;
 use collections::HashMap;
 use gpui::{Context, HighlightStyle};
+use itertools::Itertools;
 use language::language_settings;
 use multi_buffer::{Anchor, ExcerptId};
 use ui::{ActiveTheme, utils::ensure_minimum_contrast};
@@ -26,17 +27,20 @@ impl Editor {
         let accents_count = cx.theme().accents().0.len();
         let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
         let all_excerpts = self.buffer().read(cx).excerpt_ids();
-        let anchor_in_multi_buffer = |current_excerpt: ExcerptId, text_anchor: text::Anchor| {
+        let anchors_in_multi_buffer = |current_excerpt: ExcerptId,
+                                       text_anchors: [text::Anchor; 4]|
+         -> Option<[Option<_>; 4]> {
             multi_buffer_snapshot
-                .anchor_in_excerpt(current_excerpt, text_anchor)
+                .anchors_in_excerpt(current_excerpt, text_anchors)
                 .or_else(|| {
                     all_excerpts
                         .iter()
                         .filter(|&&excerpt_id| excerpt_id != current_excerpt)
                         .find_map(|&excerpt_id| {
-                            multi_buffer_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)
+                            multi_buffer_snapshot.anchors_in_excerpt(excerpt_id, text_anchors)
                         })
-                })
+                })?
+                .collect_array()
         };
 
         let bracket_matches_by_accent = self.visible_excerpts(cx).into_iter().fold(
@@ -77,13 +81,24 @@ impl Editor {
                             let buffer_close_range = buffer_snapshot
                                 .anchor_before(pair.close_range.start)
                                 ..buffer_snapshot.anchor_after(pair.close_range.end);
+                            let [
+                                buffer_open_range_start,
+                                buffer_open_range_end,
+                                buffer_close_range_start,
+                                buffer_close_range_end,
+                            ] = anchors_in_multi_buffer(
+                                excerpt_id,
+                                [
+                                    buffer_open_range.start,
+                                    buffer_open_range.end,
+                                    buffer_close_range.start,
+                                    buffer_close_range.end,
+                                ],
+                            )?;
                             let multi_buffer_open_range =
-                                anchor_in_multi_buffer(excerpt_id, buffer_open_range.start)
-                                    .zip(anchor_in_multi_buffer(excerpt_id, buffer_open_range.end));
+                                buffer_open_range_start.zip(buffer_open_range_end);
                             let multi_buffer_close_range =
-                                anchor_in_multi_buffer(excerpt_id, buffer_close_range.start).zip(
-                                    anchor_in_multi_buffer(excerpt_id, buffer_close_range.end),
-                                );
+                                buffer_close_range_start.zip(buffer_close_range_end);
 
                             let mut ranges = Vec::with_capacity(2);
                             if let Some((open_start, open_end)) = multi_buffer_open_range {

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -5084,8 +5084,8 @@ impl MultiBufferSnapshot {
         let excerpt = self.excerpt(self.latest_excerpt_id(excerpt_id))?;
 
         Some(
-            self.anchor_in_excerpt_(excerpt, text_anchor.start)?
-                ..self.anchor_in_excerpt_(excerpt, text_anchor.end)?,
+            Self::anchor_in_excerpt_(excerpt, text_anchor.start)?
+                ..Self::anchor_in_excerpt_(excerpt, text_anchor.end)?,
         )
     }
 
@@ -5097,10 +5097,24 @@ impl MultiBufferSnapshot {
         text_anchor: text::Anchor,
     ) -> Option<Anchor> {
         let excerpt = self.excerpt(self.latest_excerpt_id(excerpt_id))?;
-        self.anchor_in_excerpt_(excerpt, text_anchor)
+        Self::anchor_in_excerpt_(excerpt, text_anchor)
     }
 
-    fn anchor_in_excerpt_(&self, excerpt: &Excerpt, text_anchor: text::Anchor) -> Option<Anchor> {
+    /// Same as [`MultiBuffer::anchor_in_excerpt`], but more efficient than calling it multiple times.
+    pub fn anchors_in_excerpt(
+        &self,
+        excerpt_id: ExcerptId,
+        text_anchors: impl IntoIterator<Item = text::Anchor>,
+    ) -> Option<impl Iterator<Item = Option<Anchor>>> {
+        let excerpt = self.excerpt(self.latest_excerpt_id(excerpt_id))?;
+        Some(
+            text_anchors
+                .into_iter()
+                .map(|text_anchor| Self::anchor_in_excerpt_(excerpt, text_anchor)),
+        )
+    }
+
+    fn anchor_in_excerpt_(excerpt: &Excerpt, text_anchor: text::Anchor) -> Option<Anchor> {
         match text_anchor.buffer_id {
             Some(buffer_id) if buffer_id == excerpt.buffer_id => (),
             Some(_) => return None,