git: More rigorously test excerpt syncing for split diff, and fix a couple of bugs (#49907)

This PR adds a more rigorous test for the excerpt syncing logic in
`SplittableEditor`, in preparation for refactoring that code, since
we've had some bugs there.

The new test covers
- edits within the RHS multibuffer
- edits to the individual main buffers, not necessarily within the
excerpt bounds of the split diff
- excerpt expansion
- excerpt removal
- excerpt recalculation based on diff hunk ranges

Bugs fixed:
- incorrect edit merging logic in `patches_for_range`
- `merge_excerpts` leaving stale excerpt IDs in `excerpts_by_path`

Before you mark this PR as ready for review, make sure that you have:
- [x] Added a solid test coverage and/or screenshots from doing manual
testing
- [x] Done a self-review taking into account security and performance
aspects
- [ ] Aligned any UI changes with the [UI
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)

Release Notes:

- N/A
This commit is contained in:
Cole Miller authored and GitHub committed 2026-02-24 03:33:33 +00:00
1 parent 7ffd4c0a95
commit 00b9127309
4 files changed
+355 -153

No files matched your search

+30 -11
View File
@@ -595,14 +595,12 @@ impl DiffState {
base_text_changed_range,
extended_range: _,
}) => {
if let Some(base_text_changed_range) = base_text_changed_range.clone() {
this.inverted_buffer_diff_changed(
diff,
main_buffer,
base_text_changed_range,
cx,
)
}
this.inverted_buffer_diff_changed(
diff,
main_buffer,
base_text_changed_range.clone(),
cx,
);
cx.emit(Event::BufferDiffChanged);
}
BufferDiffEvent::LanguageChanged => {
@@ -2202,7 +2200,19 @@ impl MultiBuffer {
drop(snapshot);
self.resize_excerpt(excerpt_ids[0], union_range, cx);
self.remove_excerpts(excerpt_ids[1..].iter().copied(), cx);
let removed = &excerpt_ids[1..];
for &excerpt_id in removed {
if let Some(path) = self.paths_by_excerpt.get(&excerpt_id) {
if let Some(excerpt_list) = self.excerpts_by_path.get_mut(path) {
excerpt_list.retain(|id| *id != excerpt_id);
if excerpt_list.is_empty() {
let path = path.clone();
self.excerpts_by_path.remove(&path);
}
}
}
}
self.remove_excerpts(removed.iter().copied(), cx);
excerpt_ids[0]
}
@@ -2489,7 +2499,7 @@ impl MultiBuffer {
&mut self,
diff: Entity<BufferDiff>,
main_buffer: Entity<language::Buffer>,
diff_change_range: Range<usize>,
diff_change_range: Option<Range<usize>>,
cx: &mut Context<Self>,
) {
self.sync_mut(cx);
@@ -2510,6 +2520,10 @@ impl MultiBuffer {
.diffs
.insert_or_replace(base_text_buffer_id, new_diff);
let Some(diff_change_range) = diff_change_range else {
return;
};
let excerpt_edits = snapshot.excerpt_edits_for_diff_change(buffer_state, diff_change_range);
let edits = Self::sync_diff_transforms(
&mut snapshot,
@@ -2705,7 +2719,12 @@ impl MultiBuffer {
let base_text_buffer_id = snapshot.remote_id();
let diff_change_range = 0..snapshot.len();
self.snapshot.get_mut().has_inverted_diff = true;
self.inverted_buffer_diff_changed(diff.clone(), main_buffer.clone(), diff_change_range, cx);
self.inverted_buffer_diff_changed(
diff.clone(),
main_buffer.clone(),
Some(diff_change_range),
cx,
);
self.diffs.insert(
base_text_buffer_id,
DiffState::new_inverted(diff, main_buffer, cx),