Compute diffs based on characters rather than lines

Previously, a change on a given line would cause that whole line to be
replaced. In turn, this caused anchors on that line to go to the start
of that line because they would lie inside of a deleted region after applying
the diff.

By switching to a character-wise diff, we perform smaller edits to the buffer
which stabilizes anchor positions.
This commit is contained in:
Antonio Scandurra 2022-08-02 16:52:37 +02:00
parent f7a3141576
commit 71128d2ee6
2 changed files with 6 additions and 3 deletions

View file

@ -1006,7 +1006,7 @@ impl Buffer {
let old_text = old_text.to_string();
let line_ending = LineEnding::detect(&new_text);
LineEnding::normalize(&mut new_text);
let changes = TextDiff::from_lines(old_text.as_str(), new_text.as_str())
let changes = TextDiff::from_chars(old_text.as_str(), new_text.as_str())
.iter_all_changes()
.map(|c| (c.tag(), c.value().len()))
.collect::<Vec<_>>();

View file

@ -183,20 +183,23 @@ fn test_edit_events(cx: &mut gpui::MutableAppContext) {
async fn test_apply_diff(cx: &mut gpui::TestAppContext) {
let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
let anchor = buffer.read_with(cx, |buffer, _| buffer.anchor_before(Point::new(3, 3)));
let text = "a\nccc\ndddd\nffffff\n";
let diff = buffer.read_with(cx, |b, cx| b.diff(text.into(), cx)).await;
buffer.update(cx, |buffer, cx| {
buffer.apply_diff(diff, cx).unwrap();
assert_eq!(buffer.text(), text);
assert_eq!(anchor.to_point(&buffer), Point::new(2, 3));
});
cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
let diff = buffer.read_with(cx, |b, cx| b.diff(text.into(), cx)).await;
buffer.update(cx, |buffer, cx| {
buffer.apply_diff(diff, cx).unwrap();
assert_eq!(buffer.text(), text);
assert_eq!(anchor.to_point(&buffer), Point::new(4, 4));
});
cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
}
#[gpui::test]