Merge excerpts in project diff (#26739)
This adds code to merge excerpts when you expand them and they would overlap. It is only enabled for callers who use the `set_excerpts_for_path` API for multibuffers (which is currently just project diff), as other users of multibuffer care too much about the exact excerpts that they have. Release Notes: - N/A
This commit is contained in:
parent
bfe4c40f73
commit
a4a9f6bd07
1 changed files with 126 additions and 13 deletions
|
@ -68,7 +68,8 @@ pub struct MultiBuffer {
|
|||
/// Contains the state of the buffers being edited
|
||||
buffers: RefCell<HashMap<BufferId, BufferState>>,
|
||||
// only used by consumers using `set_excerpts_for_buffer`
|
||||
buffers_by_path: BTreeMap<PathKey, Vec<ExcerptId>>,
|
||||
excerpts_by_path: BTreeMap<PathKey, Vec<ExcerptId>>,
|
||||
paths_by_excerpt: HashMap<ExcerptId, PathKey>,
|
||||
diffs: HashMap<BufferId, DiffState>,
|
||||
// all_diff_hunks_expanded: bool,
|
||||
subscriptions: Topic,
|
||||
|
@ -577,7 +578,8 @@ impl MultiBuffer {
|
|||
singleton: false,
|
||||
capability,
|
||||
title: None,
|
||||
buffers_by_path: Default::default(),
|
||||
excerpts_by_path: Default::default(),
|
||||
paths_by_excerpt: Default::default(),
|
||||
buffer_changed_since_sync: Default::default(),
|
||||
history: History {
|
||||
next_transaction_id: clock::Lamport::default(),
|
||||
|
@ -593,7 +595,8 @@ impl MultiBuffer {
|
|||
Self {
|
||||
snapshot: Default::default(),
|
||||
buffers: Default::default(),
|
||||
buffers_by_path: Default::default(),
|
||||
excerpts_by_path: Default::default(),
|
||||
paths_by_excerpt: Default::default(),
|
||||
diffs: HashMap::default(),
|
||||
subscriptions: Default::default(),
|
||||
singleton: false,
|
||||
|
@ -638,7 +641,8 @@ impl MultiBuffer {
|
|||
Self {
|
||||
snapshot: RefCell::new(self.snapshot.borrow().clone()),
|
||||
buffers: RefCell::new(buffers),
|
||||
buffers_by_path: Default::default(),
|
||||
excerpts_by_path: Default::default(),
|
||||
paths_by_excerpt: Default::default(),
|
||||
diffs: diff_bases,
|
||||
subscriptions: Default::default(),
|
||||
singleton: self.singleton,
|
||||
|
@ -1478,7 +1482,7 @@ impl MultiBuffer {
|
|||
}
|
||||
|
||||
pub fn location_for_path(&self, path: &PathKey, cx: &App) -> Option<Anchor> {
|
||||
let excerpt_id = self.buffers_by_path.get(path)?.first()?;
|
||||
let excerpt_id = self.excerpts_by_path.get(path)?.first()?;
|
||||
let snapshot = self.snapshot(cx);
|
||||
let excerpt = snapshot.excerpt(*excerpt_id)?;
|
||||
Some(Anchor::in_buffer(
|
||||
|
@ -1489,7 +1493,93 @@ impl MultiBuffer {
|
|||
}
|
||||
|
||||
pub fn excerpt_paths(&self) -> impl Iterator<Item = &PathKey> {
|
||||
self.buffers_by_path.keys()
|
||||
self.excerpts_by_path.keys()
|
||||
}
|
||||
|
||||
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()
|
||||
.flat_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 expanded_ranges = excerpt_ids.iter().filter_map(|excerpt_id| {
|
||||
let excerpt = snapshot.excerpt(*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
|
||||
.as_ref()
|
||||
.map(|range| range.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() {
|
||||
if last_range.context.end >= range.context.start {
|
||||
last_range.context.end = range.context.end;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
merged_ranges.push(range)
|
||||
}
|
||||
let Some(excerpt_id) = excerpt_ids.first() else {
|
||||
continue;
|
||||
};
|
||||
let Some(buffer_id) = &snapshot.buffer_id_for_excerpt(*excerpt_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(buffer) = self
|
||||
.buffers
|
||||
.borrow()
|
||||
.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.
|
||||
|
@ -1503,15 +1593,30 @@ impl MultiBuffer {
|
|||
) -> bool {
|
||||
let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
|
||||
|
||||
let (new, _) = build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count);
|
||||
self.update_path_excerpts(path, buffer, &buffer_snapshot, new, cx)
|
||||
}
|
||||
|
||||
fn update_path_excerpts(
|
||||
&mut self,
|
||||
path: PathKey,
|
||||
buffer: Entity<Buffer>,
|
||||
buffer_snapshot: &BufferSnapshot,
|
||||
new: Vec<ExcerptRange<Point>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
let mut insert_after = self
|
||||
.buffers_by_path
|
||||
.excerpts_by_path
|
||||
.range(..path.clone())
|
||||
.next_back()
|
||||
.map(|(_, value)| *value.last().unwrap())
|
||||
.unwrap_or(ExcerptId::min());
|
||||
let existing = self.buffers_by_path.get(&path).cloned().unwrap_or_default();
|
||||
|
||||
let (new, _) = build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count);
|
||||
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();
|
||||
|
@ -1594,20 +1699,23 @@ impl MultiBuffer {
|
|||
));
|
||||
self.remove_excerpts(to_remove, cx);
|
||||
if new_excerpt_ids.is_empty() {
|
||||
self.buffers_by_path.remove(&path);
|
||||
self.excerpts_by_path.remove(&path);
|
||||
} else {
|
||||
self.buffers_by_path.insert(path, new_excerpt_ids);
|
||||
for excerpt_id in &new_excerpt_ids {
|
||||
self.paths_by_excerpt.insert(*excerpt_id, path.clone());
|
||||
}
|
||||
self.excerpts_by_path.insert(path, new_excerpt_ids);
|
||||
}
|
||||
|
||||
added_a_new_excerpt
|
||||
}
|
||||
|
||||
pub fn paths(&self) -> impl Iterator<Item = PathKey> + '_ {
|
||||
self.buffers_by_path.keys().cloned()
|
||||
self.excerpts_by_path.keys().cloned()
|
||||
}
|
||||
|
||||
pub fn remove_excerpts_for_path(&mut self, path: PathKey, cx: &mut Context<Self>) {
|
||||
if let Some(to_remove) = self.buffers_by_path.remove(&path) {
|
||||
if let Some(to_remove) = self.excerpts_by_path.remove(&path) {
|
||||
self.remove_excerpts(to_remove, cx)
|
||||
}
|
||||
}
|
||||
|
@ -2079,6 +2187,7 @@ impl MultiBuffer {
|
|||
let mut removed_buffer_ids = Vec::new();
|
||||
|
||||
while let Some(excerpt_id) = excerpt_ids.next() {
|
||||
self.paths_by_excerpt.remove(&excerpt_id);
|
||||
// Seek to the next excerpt to remove, preserving any preceding excerpts.
|
||||
let locator = snapshot.excerpt_locator_for_id(excerpt_id);
|
||||
new_excerpts.append(cursor.slice(&Some(locator), Bias::Left, &()), &());
|
||||
|
@ -2643,6 +2752,10 @@ impl MultiBuffer {
|
|||
return;
|
||||
}
|
||||
self.sync(cx);
|
||||
if !self.excerpts_by_path.is_empty() {
|
||||
self.expand_excerpts_with_paths(ids, line_count, direction, cx);
|
||||
return;
|
||||
}
|
||||
let mut snapshot = self.snapshot.borrow_mut();
|
||||
|
||||
let ids = ids.into_iter().collect::<Vec<_>>();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue