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:
1 parent
f8f489271e
commit
c26784f094
3 files changed
+155
-35
No files matched your search
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user