editor: Improve buffer_into_editor_highlights performance (#49349)

This brings the runtime down to logarithmic scaling of excerpts / linear
scaling of tokens.

Release Notes:

- N/A *or* Added/Fixed/Improved ...
This commit is contained in:
Lukas Wirth authored and GitHub committed 2026-02-17 13:02:45 +01:00
1 parent f8f489271e
commit c26784f094
3 files changed
+155 -35

No files matched your search

+117 -10
View File
@@ -515,6 +515,7 @@ struct BufferState {
buffer: Entity<Buffer>,
last_version: RefCell<clock::Global>,
last_non_text_state_update_count: Cell<usize>,
// Note, any changes to this field value require updating snapshot.buffer_locators as well
excerpts: Vec<Locator>,
_subscriptions: [gpui::Subscription; 2],
}
@@ -606,18 +607,18 @@ impl DiffState {
#[derive(Clone, Default)]
pub struct MultiBufferSnapshot {
excerpts: SumTree<Excerpt>,
buffer_locators: TreeMap<BufferId, Arc<[Locator]>>,
diffs: TreeMap<BufferId, DiffStateSnapshot>,
diff_transforms: SumTree<DiffTransform>,
excerpt_ids: SumTree<ExcerptIdMapping>,
replaced_excerpts: TreeMap<ExcerptId, ExcerptId>,
non_text_state_update_count: usize,
edit_count: usize,
is_dirty: bool,
has_deleted_file: bool,
has_conflict: bool,
has_inverted_diff: bool,
/// immutable fields
singleton: bool,
excerpt_ids: SumTree<ExcerptIdMapping>,
replaced_excerpts: TreeMap<ExcerptId, ExcerptId>,
trailing_excerpt_update_count: usize,
all_diff_hunks_expanded: bool,
show_deleted_hunks: bool,
@@ -1202,7 +1203,7 @@ impl MultiBuffer {
}
Self {
snapshot: RefCell::new(self.snapshot.borrow().clone()),
buffers: buffers,
buffers,
excerpts_by_path: Default::default(),
paths_by_excerpt: Default::default(),
diffs: diff_bases,
@@ -1878,6 +1879,9 @@ impl MultiBuffer {
}
new_excerpt_ids.push(ExcerptIdMapping { id, locator }, ());
}
snapshot
.buffer_locators
.insert(buffer_id, buffer_state.excerpts.iter().cloned().collect());
let edit_end = ExcerptDimension(new_excerpts.summary().text.len);
@@ -1922,6 +1926,7 @@ impl MultiBuffer {
self.paths_by_excerpt.clear();
let MultiBufferSnapshot {
excerpts,
buffer_locators,
diffs: _,
diff_transforms: _,
non_text_state_update_count: _,
@@ -1939,6 +1944,7 @@ impl MultiBuffer {
use_extended_diff_range: _,
show_headers: _,
} = self.snapshot.get_mut();
buffer_locators.clear();
let start = ExcerptDimension(MultiBufferOffset::ZERO);
let prev_len = ExcerptDimension(excerpts.summary().text.len);
*excerpts = Default::default();
@@ -1979,8 +1985,8 @@ impl MultiBuffer {
let mut excerpts = Vec::new();
let snapshot = self.read(cx);
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>(());
if let Some(locators) = self.buffers.get(&buffer_id).map(|state| &state.excerpts) {
for locator in locators {
if let Some(locators) = snapshot.buffer_locators.get(&buffer_id) {
for locator in &**locators {
cursor.seek_forward(&Some(locator), Bias::Left);
if let Some(excerpt) = cursor.item()
&& excerpt.locator == *locator
@@ -2002,11 +2008,11 @@ impl MultiBuffer {
.diff_transforms
.cursor::<Dimensions<ExcerptPoint, OutputDimension<Point>>>(());
diff_transforms.next();
let locators = self
.buffers
let locators = snapshot
.buffer_locators
.get(&buffer_id)
.into_iter()
.flat_map(|state| &state.excerpts);
.flat_map(|v| &**v);
let mut result = Vec::new();
for locator in locators {
excerpts.seek_forward(&Some(locator), Bias::Left);
@@ -2111,6 +2117,7 @@ impl MultiBuffer {
pub fn buffer_point_to_anchor(
&self,
// todo(lw): We shouldn't need this?
buffer: &Entity<Buffer>,
point: Point,
cx: &App,
@@ -2140,6 +2147,7 @@ impl MultiBuffer {
pub fn buffer_anchor_to_anchor(
&self,
// todo(lw): We shouldn't need this?
buffer: &Entity<Buffer>,
anchor: text::Anchor,
cx: &App,
@@ -2205,6 +2213,7 @@ impl MultiBuffer {
let mut edits = Vec::new();
let mut excerpt_ids = ids.iter().copied().peekable();
let mut removed_buffer_ids = Vec::new();
let mut removed_excerpts_for_buffers = HashSet::default();
while let Some(excerpt_id) = excerpt_ids.next() {
self.paths_by_excerpt.remove(&excerpt_id);
@@ -2221,6 +2230,7 @@ impl MultiBuffer {
// Skip over the removed excerpt.
'remove_excerpts: loop {
if let Some(buffer_state) = self.buffers.get_mut(&excerpt.buffer_id) {
removed_excerpts_for_buffers.insert(excerpt.buffer_id);
buffer_state.excerpts.retain(|l| l != &excerpt.locator);
if buffer_state.excerpts.is_empty() {
log::debug!(
@@ -2268,6 +2278,18 @@ impl MultiBuffer {
let changed_trailing_excerpt = suffix.is_empty();
new_excerpts.append(suffix, ());
drop(cursor);
for buffer_id in removed_excerpts_for_buffers {
match self.buffers.get(&buffer_id) {
Some(buffer_state) => {
snapshot
.buffer_locators
.insert(buffer_id, buffer_state.excerpts.iter().cloned().collect());
}
None => {
snapshot.buffer_locators.remove(&buffer_id);
}
}
}
snapshot.excerpts = new_excerpts;
for buffer_id in &removed_buffer_ids {
self.diffs.remove(buffer_id);
@@ -3022,6 +3044,7 @@ impl MultiBuffer {
) -> Vec<Edit<MultiBufferOffset>> {
let MultiBufferSnapshot {
excerpts,
buffer_locators: _,
diffs: buffer_diff,
diff_transforms: _,
non_text_state_update_count,
@@ -4046,6 +4069,90 @@ impl MultiBufferSnapshot {
.map(|excerpt| excerpt.buffer_id)
}
/// Resolves the given [`text::Anchor`]s to [`crate::Anchor`]s if the anchor is within a visible excerpt.
///
/// The passed in anchors must be ordered.
pub fn text_anchors_to_visible_anchors(
&self,
anchors: impl IntoIterator<Item = text::Anchor>,
) -> Vec<Option<Anchor>> {
let mut cursor = self.excerpts.cursor::<Option<&Locator>>(());
let mut anchors = anchors.into_iter().peekable();
let mut result = Vec::new();
'anchors: while let Some(anchor) = anchors.peek() {
let Some(buffer_id) = anchor.buffer_id else {
anchors.next();
result.push(None);
continue 'anchors;
};
let mut same_buffer_anchors =
anchors.peeking_take_while(|a| a.buffer_id.is_some_and(|b| buffer_id == b));
if let Some(locators) = self.buffer_locators.get(&buffer_id) {
let Some(mut next) = same_buffer_anchors.next() else {
continue 'anchors;
};
'excerpts: for locator in locators.iter() {
if cursor.seek_forward(&Some(locator), Bias::Left)
&& let Some(excerpt) = cursor.item()
{
loop {
// anchor is before the first excerpt
if excerpt
.range
.context
.start
.cmp(&next, &excerpt.buffer)
.is_gt()
{
// so we skip it and try the next anchor
result.push(None);
match same_buffer_anchors.next() {
Some(anchor) => next = anchor,
None => continue 'anchors,
}
// anchor is within the excerpt
} else if excerpt
.range
.context
.end
.cmp(&next, &excerpt.buffer)
.is_ge()
{
// record it and all following anchors that are within
result.push(Some(Anchor::in_buffer(excerpt.id, next)));
result.extend(
same_buffer_anchors
.peeking_take_while(|a| {
excerpt
.range
.context
.end
.cmp(a, &excerpt.buffer)
.is_ge()
})
.map(|a| Some(Anchor::in_buffer(excerpt.id, a))),
);
match same_buffer_anchors.next() {
Some(anchor) => next = anchor,
None => continue 'anchors,
}
// anchor is after the excerpt, try the next one
} else {
continue 'excerpts;
}
}
}
}
// account for `next`
result.push(None);
}
result.extend(same_buffer_anchors.map(|_| None));
}
result
}
pub fn ranges_to_buffer_ranges<T: ToOffset>(
&self,
ranges: impl Iterator<Item = Range<T>>,
@@ -5678,7 +5785,7 @@ impl MultiBufferSnapshot {
}
}
/// Wraps the [`text::Anchor`] in a [`multi_buffer::Anchor`] if this multi-buffer is a singleton.
/// Wraps the [`text::Anchor`] in a [`crate::Anchor`] if this multi-buffer is a singleton.
pub fn as_singleton_anchor(&self, text_anchor: text::Anchor) -> Option<Anchor> {
let (excerpt, buffer, _) = self.as_singleton()?;
if text_anchor.buffer_id.is_none_or(|id| id == buffer) {