Files
zed/crates/multi_buffer/src/path_key.rs
T
Lukas Wirth 37d54d8482 multi_buffer: Bring update_path_excerpts ordering workaround back (#49563)
cc https://github.com/zed-industries/zed/pull/49290

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2026-02-19 08:39:20 +00:00

581 lines
22 KiB
Rust

use std::{mem, ops::Range, sync::Arc};
use collections::HashSet;
use gpui::{App, AppContext, Context, Entity};
use itertools::Itertools;
use language::{Buffer, BufferSnapshot};
use rope::Point;
use text::{Bias, BufferId, OffsetRangeExt, locator::Locator};
use util::{post_inc, rel_path::RelPath};
use ztracing::instrument;
use crate::{
Anchor, ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer, build_excerpt_ranges,
};
#[derive(Debug, Clone)]
pub struct PathExcerptInsertResult {
pub inserted_ranges: Vec<Range<Anchor>>,
pub excerpt_ids: Vec<ExcerptId>,
pub added_new_excerpt: bool,
}
#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Hash, Debug)]
pub struct PathKey {
// Used by the derived PartialOrd & Ord
pub sort_prefix: Option<u64>,
pub path: Arc<RelPath>,
}
impl PathKey {
pub fn with_sort_prefix(sort_prefix: u64, path: Arc<RelPath>) -> Self {
Self {
sort_prefix: Some(sort_prefix),
path,
}
}
pub fn for_buffer(buffer: &Entity<Buffer>, cx: &App) -> Self {
if let Some(file) = buffer.read(cx).file() {
Self::with_sort_prefix(file.worktree_id(cx).to_proto(), file.path().clone())
} else {
Self {
sort_prefix: None,
path: RelPath::unix(&buffer.entity_id().to_string())
.unwrap()
.into_arc(),
}
}
}
}
impl MultiBuffer {
pub fn paths(&self) -> impl Iterator<Item = &PathKey> + '_ {
self.excerpts_by_path.keys()
}
pub fn excerpts_for_path(&self, path: &PathKey) -> impl '_ + Iterator<Item = ExcerptId> {
self.excerpts_by_path
.get(path)
.map(|excerpts| excerpts.as_slice())
.unwrap_or_default()
.iter()
.copied()
}
pub fn path_for_excerpt(&self, excerpt: ExcerptId) -> Option<PathKey> {
self.paths_by_excerpt.get(&excerpt).cloned()
}
pub fn remove_excerpts_for_path(&mut self, path: PathKey, cx: &mut Context<Self>) {
if let Some(to_remove) = self.excerpts_by_path.remove(&path) {
self.remove_excerpts(to_remove, cx)
}
}
pub fn buffer_for_path(&self, path: &PathKey, cx: &App) -> Option<Entity<Buffer>> {
let excerpt_id = self.excerpts_by_path.get(path)?.first()?;
let snapshot = self.read(cx);
let excerpt = snapshot.excerpt(*excerpt_id)?;
self.buffer(excerpt.buffer_id)
}
pub fn location_for_path(&self, path: &PathKey, cx: &App) -> Option<Anchor> {
let excerpt_id = self.excerpts_by_path.get(path)?.first()?;
let snapshot = self.read(cx);
let excerpt = snapshot.excerpt(*excerpt_id)?;
Some(Anchor::in_buffer(excerpt.id, excerpt.range.context.start))
}
/// Sets excerpts, returns `true` if at least one new excerpt was added.
#[instrument(skip_all)]
pub fn set_excerpts_for_path(
&mut self,
path: PathKey,
buffer: Entity<Buffer>,
ranges: impl IntoIterator<Item = Range<Point>>,
context_line_count: u32,
cx: &mut Context<Self>,
) -> (Vec<Range<Anchor>>, bool) {
let buffer_snapshot = buffer.read(cx).snapshot();
let excerpt_ranges = build_excerpt_ranges(ranges, context_line_count, &buffer_snapshot);
let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
let excerpt_insertion_result = self.set_merged_excerpt_ranges_for_path(
path,
buffer,
excerpt_ranges,
&buffer_snapshot,
new,
counts,
cx,
);
(
excerpt_insertion_result.inserted_ranges,
excerpt_insertion_result.added_new_excerpt,
)
}
pub fn set_excerpt_ranges_for_path(
&mut self,
path: PathKey,
buffer: Entity<Buffer>,
buffer_snapshot: &BufferSnapshot,
excerpt_ranges: Vec<ExcerptRange<Point>>,
cx: &mut Context<Self>,
) -> (Vec<Range<Anchor>>, bool) {
let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
let excerpt_insertion_result = self.set_merged_excerpt_ranges_for_path(
path,
buffer,
excerpt_ranges,
buffer_snapshot,
new,
counts,
cx,
);
(
excerpt_insertion_result.inserted_ranges,
excerpt_insertion_result.added_new_excerpt,
)
}
pub fn set_anchored_excerpts_for_path(
&self,
path_key: PathKey,
buffer: Entity<Buffer>,
ranges: Vec<Range<text::Anchor>>,
context_line_count: u32,
cx: &Context<Self>,
) -> impl Future<Output = Vec<Range<Anchor>>> + use<> {
let buffer_snapshot = buffer.read(cx).snapshot();
let multi_buffer = cx.weak_entity();
let mut app = cx.to_async();
async move {
let snapshot = buffer_snapshot.clone();
let (excerpt_ranges, new, counts) = app
.background_spawn(async move {
let ranges = ranges.into_iter().map(|range| range.to_point(&snapshot));
let excerpt_ranges =
build_excerpt_ranges(ranges, context_line_count, &snapshot);
let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
(excerpt_ranges, new, counts)
})
.await;
multi_buffer
.update(&mut app, move |multi_buffer, cx| {
let excerpt_insertion_result = multi_buffer.set_merged_excerpt_ranges_for_path(
path_key,
buffer,
excerpt_ranges,
&buffer_snapshot,
new,
counts,
cx,
);
excerpt_insertion_result.inserted_ranges
})
.ok()
.unwrap_or_default()
}
}
pub fn remove_excerpts_for_buffer(&mut self, buffer: BufferId, cx: &mut Context<Self>) {
self.remove_excerpts(
self.excerpts_for_buffer(buffer, cx)
.into_iter()
.map(|(excerpt, _)| excerpt),
cx,
);
}
pub(super) fn expand_excerpts_with_paths(
&mut self,
ids: impl IntoIterator<Item = ExcerptId>,
line_count: u32,
direction: ExpandExcerptDirection,
cx: &mut Context<Self>,
) {
let grouped = ids
.into_iter()
.chunk_by(|id| self.paths_by_excerpt.get(id).cloned())
.into_iter()
.filter_map(|(k, v)| Some((k?, v.into_iter().collect::<Vec<_>>())))
.collect::<Vec<_>>();
let snapshot = self.snapshot(cx);
for (path, ids) in grouped.into_iter() {
let Some(excerpt_ids) = self.excerpts_by_path.get(&path) else {
continue;
};
let ids_to_expand = HashSet::from_iter(ids);
let mut excerpt_id_ = None;
let expanded_ranges = excerpt_ids.iter().filter_map(|excerpt_id| {
let excerpt = snapshot.excerpt(*excerpt_id)?;
let excerpt_id = excerpt.id;
if excerpt_id_.is_none() {
excerpt_id_ = Some(excerpt_id);
}
let mut context = excerpt.range.context.to_point(&excerpt.buffer);
if ids_to_expand.contains(&excerpt_id) {
match direction {
ExpandExcerptDirection::Up => {
context.start.row = context.start.row.saturating_sub(line_count);
context.start.column = 0;
}
ExpandExcerptDirection::Down => {
context.end.row =
(context.end.row + line_count).min(excerpt.buffer.max_point().row);
context.end.column = excerpt.buffer.line_len(context.end.row);
}
ExpandExcerptDirection::UpAndDown => {
context.start.row = context.start.row.saturating_sub(line_count);
context.start.column = 0;
context.end.row =
(context.end.row + line_count).min(excerpt.buffer.max_point().row);
context.end.column = excerpt.buffer.line_len(context.end.row);
}
}
}
Some(ExcerptRange {
context,
primary: excerpt.range.primary.to_point(&excerpt.buffer),
})
});
let mut merged_ranges: Vec<ExcerptRange<Point>> = Vec::new();
for range in expanded_ranges {
if let Some(last_range) = merged_ranges.last_mut()
&& last_range.context.end >= range.context.start
{
last_range.context.end = range.context.end;
continue;
}
merged_ranges.push(range)
}
let Some(excerpt_id) = excerpt_id_ else {
continue;
};
let Some(buffer_id) = &snapshot.buffer_id_for_excerpt(excerpt_id) else {
continue;
};
let Some(buffer) = self.buffers.get(buffer_id).map(|b| b.buffer.clone()) else {
continue;
};
let buffer_snapshot = buffer.read(cx).snapshot();
self.update_path_excerpts(path.clone(), buffer, &buffer_snapshot, merged_ranges, cx);
}
}
/// Sets excerpts, returns `true` if at least one new excerpt was added.
pub fn set_merged_excerpt_ranges_for_path(
&mut self,
path: PathKey,
buffer: Entity<Buffer>,
ranges: Vec<ExcerptRange<Point>>,
buffer_snapshot: &BufferSnapshot,
new: Vec<ExcerptRange<Point>>,
counts: Vec<usize>,
cx: &mut Context<Self>,
) -> PathExcerptInsertResult {
let (new, counts) =
self.expand_new_ranges_to_existing(&path, buffer_snapshot, new, counts, cx);
let (excerpt_ids, added_new_excerpt) =
self.update_path_excerpts(path, buffer, buffer_snapshot, new, cx);
let mut inserted_ranges = Vec::new();
let mut ranges = ranges.into_iter();
for (&excerpt_id, range_count) in excerpt_ids.iter().zip(counts.into_iter()) {
for range in ranges.by_ref().take(range_count) {
let range = Anchor::range_in_buffer(
excerpt_id,
buffer_snapshot.anchor_before(&range.primary.start)
..buffer_snapshot.anchor_after(&range.primary.end),
);
inserted_ranges.push(range)
}
}
PathExcerptInsertResult {
inserted_ranges,
excerpt_ids,
added_new_excerpt,
}
}
/// Expand each new merged range to encompass any overlapping existing
/// excerpt, then re-merge. This turns "partial overlap where the union
/// equals the existing range" into an exact match, avoiding unnecessary
/// remove+insert churn that floods the wrap map with edits.
fn expand_new_ranges_to_existing(
&self,
path: &PathKey,
buffer_snapshot: &BufferSnapshot,
mut new: Vec<ExcerptRange<Point>>,
counts: Vec<usize>,
cx: &App,
) -> (Vec<ExcerptRange<Point>>, Vec<usize>) {
let existing = self.excerpts_by_path.get(path).cloned().unwrap_or_default();
if existing.is_empty() || new.is_empty() {
return (new, counts);
}
let snapshot = self.snapshot(cx);
let buffer_id = buffer_snapshot.remote_id();
let existing_ranges: Vec<Range<Point>> = existing
.iter()
.filter_map(|&id| {
let excerpt = snapshot.excerpt(id)?;
(excerpt.buffer_id == buffer_id)
.then(|| excerpt.range.context.to_point(buffer_snapshot))
})
.collect();
let mut changed = false;
for new_range in &mut new {
for existing_range in &existing_ranges {
if new_range.context.start <= existing_range.end
&& new_range.context.end >= existing_range.start
{
let expanded_start = new_range.context.start.min(existing_range.start);
let expanded_end = new_range.context.end.max(existing_range.end);
if expanded_start != new_range.context.start
|| expanded_end != new_range.context.end
{
new_range.context.start = expanded_start;
new_range.context.end = expanded_end;
changed = true;
}
}
}
}
if !changed {
return (new, counts);
}
let mut result_ranges: Vec<ExcerptRange<Point>> = Vec::new();
let mut result_counts: Vec<usize> = Vec::new();
for (range, count) in new.into_iter().zip(counts) {
if let Some(last) = result_ranges.last_mut() {
if last.context.end >= range.context.start
|| last.context.end.row + 1 == range.context.start.row
{
last.context.end = last.context.end.max(range.context.end);
*result_counts.last_mut().unwrap() += count;
continue;
}
}
result_ranges.push(range);
result_counts.push(count);
}
(result_ranges, result_counts)
}
fn update_path_excerpts(
&mut self,
path: PathKey,
buffer: Entity<Buffer>,
buffer_snapshot: &BufferSnapshot,
new: Vec<ExcerptRange<Point>>,
cx: &mut Context<Self>,
) -> (Vec<ExcerptId>, bool) {
let mut insert_after = self
.excerpts_by_path
.range(..path.clone())
.next_back()
.and_then(|(_, value)| value.last().copied())
.unwrap_or(ExcerptId::min());
let existing = self
.excerpts_by_path
.get(&path)
.cloned()
.unwrap_or_default();
let mut new_iter = new.into_iter().peekable();
let mut existing_iter = existing.into_iter().peekable();
let mut excerpt_ids = Vec::new();
let mut to_remove = Vec::new();
let mut to_insert: Vec<(ExcerptId, ExcerptRange<Point>)> = Vec::new();
let mut added_a_new_excerpt = false;
let snapshot = self.snapshot(cx);
let mut next_excerpt_id =
if let Some(last_entry) = self.snapshot.get_mut().excerpt_ids.last() {
last_entry.id.0 + 1
} else {
1
};
let mut next_excerpt_id = move || ExcerptId(post_inc(&mut next_excerpt_id));
let mut excerpts_cursor = snapshot.excerpts.cursor::<Option<&Locator>>(());
excerpts_cursor.next();
loop {
let existing = if let Some(&existing_id) = existing_iter.peek() {
let locator = snapshot.excerpt_locator_for_id(existing_id);
excerpts_cursor.seek_forward(&Some(locator), Bias::Left);
if let Some(excerpt) = excerpts_cursor.item() {
if excerpt.buffer_id != buffer_snapshot.remote_id() {
to_remove.push(existing_id);
existing_iter.next();
continue;
}
Some((existing_id, excerpt.range.context.to_point(buffer_snapshot)))
} else {
None
}
} else {
None
};
let new = new_iter.peek();
// Try to merge the next new range or existing excerpt into the last
// queued insert.
if let Some((last_id, last)) = to_insert.last_mut() {
// Next new range overlaps the last queued insert: absorb it by
// extending the insert's end.
if let Some(new) = new
&& last.context.end >= new.context.start
{
last.context.end = last.context.end.max(new.context.end);
excerpt_ids.push(*last_id);
new_iter.next();
continue;
}
// Next existing excerpt overlaps the last queued insert: absorb
// it by extending the insert's end, and record the existing
// excerpt as replaced so anchors in it resolve to the new one.
if let Some((existing_id, existing_range)) = &existing
&& last.context.end >= existing_range.start
{
last.context.end = last.context.end.max(existing_range.end);
to_remove.push(*existing_id);
self.snapshot
.get_mut()
.replaced_excerpts
.insert(*existing_id, *last_id);
existing_iter.next();
continue;
}
}
match (new, existing) {
(None, None) => break,
// No more new ranges; remove the remaining existing excerpt.
(None, Some((existing_id, _))) => {
existing_iter.next();
to_remove.push(existing_id);
}
// No more existing excerpts; queue the new range for insertion.
(Some(_), None) => {
added_a_new_excerpt = true;
let new_id = next_excerpt_id();
excerpt_ids.push(new_id);
to_insert.push((new_id, new_iter.next().unwrap()));
}
// Existing excerpt ends before the new range starts, so it
// has no corresponding new range and must be removed. Flush
// pending inserts and advance `insert_after` past it so that
// future inserts receive locators *after* this excerpt's
// locator, preserving forward ordering.
(Some(new), Some((_, existing_range)))
if existing_range.end < new.context.start =>
{
self.insert_excerpts_with_ids_after(
insert_after,
buffer.clone(),
mem::take(&mut to_insert),
cx,
);
insert_after = existing_iter.next().unwrap();
to_remove.push(insert_after);
}
// New range ends before the existing excerpt starts, so the
// new range has no corresponding existing excerpt. Queue it
// for insertion at the current `insert_after` position
// (before the existing excerpt), which is the correct
// spatial ordering.
(Some(new), Some((_, existing_range)))
if existing_range.start > new.context.end =>
{
let new_id = next_excerpt_id();
excerpt_ids.push(new_id);
to_insert.push((new_id, new_iter.next().unwrap()));
}
// Exact match: keep the existing excerpt in place, flush
// any pending inserts before it, and use it as the new
// `insert_after` anchor.
(Some(new), Some((_, existing_range)))
if existing_range.start == new.context.start
&& existing_range.end == new.context.end =>
{
self.insert_excerpts_with_ids_after(
insert_after,
buffer.clone(),
mem::take(&mut to_insert),
cx,
);
insert_after = existing_iter.next().unwrap();
excerpt_ids.push(insert_after);
new_iter.next();
}
// Partial overlap: replace the existing excerpt with a new
// one whose range is the union of both, and record the
// replacement so that anchors in the old excerpt resolve to
// the new one.
(Some(_), Some((_, existing_range))) => {
let existing_id = existing_iter.next().unwrap();
let new_id = next_excerpt_id();
self.snapshot
.get_mut()
.replaced_excerpts
.insert(existing_id, new_id);
to_remove.push(existing_id);
let mut range = new_iter.next().unwrap();
range.context.start = range.context.start.min(existing_range.start);
range.context.end = range.context.end.max(existing_range.end);
excerpt_ids.push(new_id);
to_insert.push((new_id, range));
}
};
}
self.insert_excerpts_with_ids_after(insert_after, buffer, to_insert, cx);
// todo(lw): There is a logic bug somewhere that causes the to_remove vector to be not ordered correctly
to_remove.sort_by_cached_key(|&id| snapshot.excerpt_locator_for_id(id));
self.remove_excerpts(to_remove, cx);
if excerpt_ids.is_empty() {
self.excerpts_by_path.remove(&path);
} else {
let snapshot = &*self.snapshot.get_mut();
let excerpt_ids = excerpt_ids
.iter()
.dedup()
.cloned()
// todo(lw): There is a logic bug somewhere that causes excerpt_ids to not necessarily be in order by locator
.sorted_by_cached_key(|&id| snapshot.excerpt_locator_for_id(id))
.collect();
for &excerpt_id in &excerpt_ids {
self.paths_by_excerpt.insert(excerpt_id, path.clone());
}
self.excerpts_by_path.insert(path, excerpt_ids);
}
(excerpt_ids, added_a_new_excerpt)
}
}