Handle empty diff excerpts (#24168)
Release Notes: - Fix display, revert and undo of deleted hunks when the file is empty.
This commit is contained in:
parent
8bce896395
commit
cf4539ec79
4 changed files with 98 additions and 1318 deletions
File diff suppressed because it is too large
Load diff
|
@ -80,6 +80,20 @@ impl BufferDiff {
|
||||||
let buffer_text = buffer.as_rope().to_string();
|
let buffer_text = buffer.as_rope().to_string();
|
||||||
let patch = Self::diff(diff_base, &buffer_text);
|
let patch = Self::diff(diff_base, &buffer_text);
|
||||||
|
|
||||||
|
// A common case in Zed is that the empty buffer is represented as just a newline,
|
||||||
|
// but if we just compute a naive diff you get a "preserved" line in the middle,
|
||||||
|
// which is a bit odd.
|
||||||
|
if buffer_text == "\n" && diff_base.ends_with("\n") && diff_base.len() > 1 {
|
||||||
|
tree.push(
|
||||||
|
InternalDiffHunk {
|
||||||
|
buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0),
|
||||||
|
diff_base_byte_range: 0..diff_base.len() - 1,
|
||||||
|
},
|
||||||
|
buffer,
|
||||||
|
);
|
||||||
|
return Self { tree };
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(patch) = patch {
|
if let Some(patch) = patch {
|
||||||
let mut divergence = 0;
|
let mut divergence = 0;
|
||||||
for hunk_index in 0..patch.num_hunks() {
|
for hunk_index in 0..patch.num_hunks() {
|
||||||
|
|
|
@ -2875,15 +2875,6 @@ impl MultiBuffer {
|
||||||
// Visit each excerpt that intersects the edit.
|
// Visit each excerpt that intersects the edit.
|
||||||
let mut did_expand_hunks = false;
|
let mut did_expand_hunks = false;
|
||||||
while let Some(excerpt) = excerpts.item() {
|
while let Some(excerpt) = excerpts.item() {
|
||||||
if excerpt.text_summary.len == 0 {
|
|
||||||
if excerpts.end(&()) <= edit.new.end {
|
|
||||||
excerpts.next(&());
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recompute the expanded hunks in the portion of the excerpt that
|
// Recompute the expanded hunks in the portion of the excerpt that
|
||||||
// intersects the edit.
|
// intersects the edit.
|
||||||
if let Some(diff_state) = snapshot.diffs.get(&excerpt.buffer_id) {
|
if let Some(diff_state) = snapshot.diffs.get(&excerpt.buffer_id) {
|
||||||
|
@ -3602,8 +3593,10 @@ impl MultiBufferSnapshot {
|
||||||
value: None,
|
value: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
if cursor.region().is_some_and(|region| !region.is_main_buffer) {
|
if let Some(region) = cursor.region().filter(|region| !region.is_main_buffer) {
|
||||||
cursor.prev();
|
if region.range.start.key > 0 {
|
||||||
|
cursor.prev()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
iter::from_fn(move || loop {
|
iter::from_fn(move || loop {
|
||||||
|
@ -6044,17 +6037,13 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_buffer_position(&self) -> Option<D> {
|
fn main_buffer_position(&self) -> Option<D> {
|
||||||
if let DiffTransform::BufferContent { .. } = self.diff_transforms.next_item()? {
|
let excerpt = self.excerpts.item()?;
|
||||||
let excerpt = self.excerpts.item()?;
|
let buffer = &excerpt.buffer;
|
||||||
let buffer = &excerpt.buffer;
|
let buffer_context_start = excerpt.range.context.start.summary::<D>(buffer);
|
||||||
let buffer_context_start = excerpt.range.context.start.summary::<D>(buffer);
|
let mut buffer_start = buffer_context_start;
|
||||||
let mut buffer_start = buffer_context_start;
|
let overshoot = self.diff_transforms.end(&()).1 .0 - self.excerpts.start().0;
|
||||||
let overshoot = self.diff_transforms.end(&()).1 .0 - self.excerpts.start().0;
|
buffer_start.add_assign(&overshoot);
|
||||||
buffer_start.add_assign(&overshoot);
|
Some(buffer_start)
|
||||||
Some(buffer_start)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_region(&self) -> Option<MultiBufferRegion<'a, D>> {
|
fn build_region(&self) -> Option<MultiBufferRegion<'a, D>> {
|
||||||
|
|
|
@ -989,6 +989,79 @@ fn test_empty_multibuffer(cx: &mut App) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn test_empty_diff_excerpt(cx: &mut TestAppContext) {
|
||||||
|
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||||
|
let buffer = cx.new(|cx| Buffer::local("", cx));
|
||||||
|
let base_text = "a\nb\nc";
|
||||||
|
|
||||||
|
let change_set = cx.new(|cx| {
|
||||||
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
|
let mut change_set = BufferChangeSet::new(&buffer, cx);
|
||||||
|
let _ = change_set.set_base_text(base_text.into(), snapshot.text, cx);
|
||||||
|
change_set
|
||||||
|
});
|
||||||
|
multibuffer.update(cx, |multibuffer, cx| {
|
||||||
|
multibuffer.set_all_diff_hunks_expanded(cx);
|
||||||
|
multibuffer.add_change_set(change_set.clone(), cx);
|
||||||
|
multibuffer.push_excerpts(
|
||||||
|
buffer.clone(),
|
||||||
|
[ExcerptRange {
|
||||||
|
context: 0..0,
|
||||||
|
primary: None,
|
||||||
|
}],
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
|
||||||
|
assert_eq!(snapshot.text(), "a\nb\nc\n");
|
||||||
|
|
||||||
|
let hunk = snapshot
|
||||||
|
.diff_hunks_in_range(Point::new(1, 1)..Point::new(1, 1))
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(hunk.diff_base_byte_range.start, 0);
|
||||||
|
|
||||||
|
let buf2 = cx.new(|cx| Buffer::local("X", cx));
|
||||||
|
multibuffer.update(cx, |multibuffer, cx| {
|
||||||
|
multibuffer.push_excerpts(
|
||||||
|
buf2,
|
||||||
|
[ExcerptRange {
|
||||||
|
context: 0..1,
|
||||||
|
primary: None,
|
||||||
|
}],
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.edit([(0..0, "a\nb\nc")], None, cx);
|
||||||
|
change_set.update(cx, |change_set, cx| {
|
||||||
|
let _ = change_set.recalculate_diff(buffer.snapshot().text, cx);
|
||||||
|
});
|
||||||
|
assert_eq!(buffer.text(), "a\nb\nc")
|
||||||
|
});
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
|
||||||
|
assert_eq!(snapshot.text(), "a\nb\nc\nX");
|
||||||
|
|
||||||
|
buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.undo(cx);
|
||||||
|
change_set.update(cx, |change_set, cx| {
|
||||||
|
let _ = change_set.recalculate_diff(buffer.snapshot().text, cx);
|
||||||
|
});
|
||||||
|
assert_eq!(buffer.text(), "")
|
||||||
|
});
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx));
|
||||||
|
assert_eq!(snapshot.text(), "a\nb\nc\n\nX");
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_singleton_multibuffer_anchors(cx: &mut App) {
|
fn test_singleton_multibuffer_anchors(cx: &mut App) {
|
||||||
let buffer = cx.new(|cx| Buffer::local("abcd", cx));
|
let buffer = cx.new(|cx| Buffer::local("abcd", cx));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue