Fix diff_hunk_before in a multibuffer (#26059)

Also simplify it to avoid doing a bunch of unnecessary work.

Co-Authored-By: Cole <cole@zed.dev>

Closes #ISSUE

Release Notes:

- git: Fix jumping to the previous diff hunk

---------

Co-authored-by: Cole <cole@zed.dev>
This commit is contained in:
Conrad Irwin 2025-03-04 20:07:19 -07:00 committed by GitHub
parent 3e64f38ba0
commit d7b90f4204
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 162 additions and 138 deletions

View file

@ -3818,104 +3818,57 @@ impl MultiBufferSnapshot {
})
}
pub fn diff_hunk_before<T: ToOffset>(&self, position: T) -> Option<MultiBufferDiffHunk> {
pub fn diff_hunk_before<T: ToOffset>(&self, position: T) -> Option<MultiBufferRow> {
let offset = position.to_offset(self);
// Go to the region containing the given offset.
let mut cursor = self.cursor::<DimensionPair<usize, Point>>();
cursor.seek(&DimensionPair {
key: offset,
value: None,
});
let mut region = cursor.region()?;
if region.range.start.key == offset || !region.is_main_buffer {
cursor.prev();
region = cursor.region()?;
cursor.seek_to_start_of_current_excerpt();
let excerpt = cursor.excerpt()?;
let excerpt_end = excerpt.range.context.end.to_offset(&excerpt.buffer);
let current_position = self
.anchor_before(offset)
.text_anchor
.to_offset(&excerpt.buffer);
let excerpt_end = excerpt
.buffer
.anchor_before(excerpt_end.min(current_position));
if let Some(diff) = self.diffs.get(&excerpt.buffer_id) {
for hunk in diff.hunks_intersecting_range_rev(
excerpt.range.context.start..excerpt_end,
&excerpt.buffer,
) {
let hunk_end = hunk.buffer_range.end.to_offset(&excerpt.buffer);
if hunk_end >= current_position {
continue;
}
let start =
Anchor::in_buffer(excerpt.id, excerpt.buffer_id, hunk.buffer_range.start)
.to_point(&self);
return Some(MultiBufferRow(start.row));
}
}
// Find the corresponding buffer offset.
let overshoot = if region.is_main_buffer {
offset - region.range.start.key
} else {
0
};
let mut max_buffer_offset = region
.buffer
.clip_offset(region.buffer_range.start.key + overshoot, Bias::Right);
loop {
let excerpt = cursor.excerpt()?;
let excerpt_end = excerpt.range.context.end.to_offset(&excerpt.buffer);
let buffer_offset = excerpt_end.min(max_buffer_offset);
let buffer_end = excerpt.buffer.anchor_before(buffer_offset);
let buffer_end_row = buffer_end.to_point(&excerpt.buffer).row;
if let Some(diff) = self.diffs.get(&excerpt.buffer_id) {
for hunk in diff.hunks_intersecting_range_rev(
excerpt.range.context.start..buffer_end,
&excerpt.buffer,
) {
let hunk_range = hunk.buffer_range.to_offset(&excerpt.buffer);
if hunk.range.end >= Point::new(buffer_end_row, 0) {
continue;
}
let hunk_start = hunk.range.start;
let hunk_end = hunk.range.end;
cursor.seek_to_buffer_position_in_current_excerpt(&DimensionPair {
key: hunk_range.start,
value: None,
});
let mut region = cursor.region()?;
while !region.is_main_buffer || region.buffer_range.start.key >= hunk_range.end
{
cursor.prev();
region = cursor.region()?;
}
let overshoot = if region.is_main_buffer {
hunk_start.saturating_sub(region.buffer_range.start.value.unwrap())
} else {
Point::zero()
};
let start = region.range.start.value.unwrap() + overshoot;
while let Some(region) = cursor.region() {
if !region.is_main_buffer
|| region.buffer_range.end.value.unwrap() <= hunk_end
{
cursor.next();
} else {
break;
}
}
let end = if let Some(region) = cursor.region() {
let overshoot = if region.is_main_buffer {
hunk_end.saturating_sub(region.buffer_range.start.value.unwrap())
} else {
Point::zero()
};
region.range.start.value.unwrap() + overshoot
} else {
self.max_point()
};
return Some(MultiBufferDiffHunk {
row_range: MultiBufferRow(start.row)..MultiBufferRow(end.row),
buffer_id: excerpt.buffer_id,
excerpt_id: excerpt.id,
buffer_range: hunk.buffer_range.clone(),
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
secondary_status: hunk.secondary_status,
});
}
}
cursor.prev_excerpt();
max_buffer_offset = usize::MAX;
let excerpt = cursor.excerpt()?;
let Some(diff) = self.diffs.get(&excerpt.buffer_id) else {
continue;
};
let mut hunks =
diff.hunks_intersecting_range_rev(excerpt.range.context.clone(), &excerpt.buffer);
let Some(hunk) = hunks.next() else {
continue;
};
let start = Anchor::in_buffer(excerpt.id, excerpt.buffer_id, hunk.buffer_range.start)
.to_point(&self);
return Some(MultiBufferRow(start.row));
}
}
@ -6132,21 +6085,6 @@ where
}
}
fn seek_to_buffer_position_in_current_excerpt(&mut self, position: &D) {
self.cached_region.take();
if let Some(excerpt) = self.excerpts.item() {
let excerpt_start = excerpt.range.context.start.summary::<D>(&excerpt.buffer);
let position_in_excerpt = *position - excerpt_start;
let mut excerpt_position = self.excerpts.start().0;
excerpt_position.add_assign(&position_in_excerpt);
self.diff_transforms
.seek(&ExcerptDimension(excerpt_position), Bias::Left, &());
if self.diff_transforms.item().is_none() {
self.diff_transforms.next(&());
}
}
}
fn next_excerpt(&mut self) {
self.excerpts.next(&());
self.seek_to_start_of_current_excerpt();

View file

@ -440,23 +440,14 @@ fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
vec![1..3, 4..6, 7..8]
);
assert_eq!(snapshot.diff_hunk_before(Point::new(1, 1)), None,);
assert_eq!(
snapshot
.diff_hunk_before(Point::new(1, 1))
.map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
None,
snapshot.diff_hunk_before(Point::new(7, 0)),
Some(MultiBufferRow(4))
);
assert_eq!(
snapshot
.diff_hunk_before(Point::new(7, 0))
.map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
Some(4..6)
);
assert_eq!(
snapshot
.diff_hunk_before(Point::new(4, 0))
.map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
Some(1..3)
snapshot.diff_hunk_before(Point::new(4, 0)),
Some(MultiBufferRow(1))
);
multibuffer.update(cx, |multibuffer, cx| {
@ -478,16 +469,12 @@ fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
);
assert_eq!(
snapshot
.diff_hunk_before(Point::new(2, 0))
.map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
Some(1..1),
snapshot.diff_hunk_before(Point::new(2, 0)),
Some(MultiBufferRow(1)),
);
assert_eq!(
snapshot
.diff_hunk_before(Point::new(4, 0))
.map(|hunk| hunk.row_range.start.0..hunk.row_range.end.0),
Some(2..2)
snapshot.diff_hunk_before(Point::new(4, 0)),
Some(MultiBufferRow(2))
);
}