Compute diffs based on characters rather than lines

Antonio Scandurra created

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.

Change summary

crates/language/src/buffer.rs | 2 +-
crates/language/src/tests.rs  | 7 +++++--
2 files changed, 6 insertions(+), 3 deletions(-)

Detailed changes

crates/language/src/buffer.rs 🔗

@@ -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<_>>();

crates/language/src/tests.rs 🔗

@@ -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]