Add failing test for buffer detecting on-disk changes

Max Brunsfeld created

Change summary

zed/src/editor/buffer/mod.rs | 97 ++++++++++++++++++++++++++++++++++++-
1 file changed, 94 insertions(+), 3 deletions(-)

Detailed changes

zed/src/editor/buffer/mod.rs 🔗

@@ -509,6 +509,10 @@ impl Buffer {
         self.version > self.saved_version || self.file.as_ref().map_or(false, |f| f.is_deleted())
     }
 
+    pub fn has_conflict(&self) -> bool {
+        false
+    }
+
     pub fn version(&self) -> time::Global {
         self.version.clone()
     }
@@ -2381,7 +2385,10 @@ impl ToPoint for usize {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{test::temp_tree, worktree::Worktree};
+    use crate::{
+        test::temp_tree,
+        worktree::{Worktree, WorktreeHandle},
+    };
     use cmp::Ordering;
     use gpui::App;
     use serde_json::json;
@@ -2989,8 +2996,6 @@ mod tests {
 
     #[test]
     fn test_is_dirty() {
-        use crate::worktree::WorktreeHandle;
-
         App::test_async((), |mut app| async move {
             let dir = temp_tree(json!({
                 "file1": "",
@@ -3105,6 +3110,92 @@ mod tests {
         });
     }
 
+    #[test]
+    fn test_file_changes_on_disk() {
+        App::test_async((), |mut app| async move {
+            let initial_contents = "aaa\nbbb\nccc\n";
+            let dir = temp_tree(json!({ "the-file": initial_contents }));
+            let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
+            app.read(|ctx| tree.read(ctx).scan_complete()).await;
+
+            let abs_path = dir.path().join("the-file");
+            let file = app.read(|ctx| tree.file("the-file", ctx));
+            let buffer = app.add_model(|ctx| {
+                Buffer::from_history(0, History::new(initial_contents.into()), Some(file), ctx)
+            });
+
+            // Add a cursor at the start of each row.
+            let (selection_set_id, _) = buffer.update(&mut app, |buffer, ctx| {
+                assert!(!buffer.is_dirty());
+                buffer.add_selection_set(
+                    (0..3)
+                        .map(|row| {
+                            let anchor = buffer
+                                .anchor_at(Point::new(row, 0), AnchorBias::Left)
+                                .unwrap();
+                            Selection {
+                                id: row as usize,
+                                start: anchor.clone(),
+                                end: anchor,
+                                reversed: false,
+                                goal: SelectionGoal::None,
+                            }
+                        })
+                        .collect::<Vec<_>>(),
+                    Some(ctx),
+                )
+            });
+
+            // Change the file on disk, adding a new line of text before each existing line.
+            buffer.update(&mut app, |buffer, _| {
+                assert!(!buffer.is_dirty());
+                assert!(!buffer.has_conflict());
+            });
+            tree.flush_fs_events(&app).await;
+            let new_contents = "AAA\naaa\nBBB\nbbb\nCCC\nccc\n";
+            fs::write(&abs_path, new_contents).unwrap();
+
+            // Because the buffer was not modified, it is reloaded from disk. Its
+            // contents are edited according to the diff between the old and new
+            // file contents.
+            buffer
+                .condition(&app, |buffer, _| buffer.text() == new_contents)
+                .await;
+            buffer.update(&mut app, |buffer, _| {
+                let selections = buffer.selections(selection_set_id).unwrap();
+                let cursor_positions = selections
+                    .iter()
+                    .map(|selection| {
+                        assert_eq!(selection.start, selection.end);
+                        selection.start.to_point(&buffer).unwrap()
+                    })
+                    .collect::<Vec<_>>();
+                assert_eq!(
+                    cursor_positions,
+                    &[Point::new(1, 0), Point::new(3, 0), Point::new(5, 0),]
+                );
+            });
+
+            // Modify the buffer
+            buffer.update(&mut app, |buffer, ctx| {
+                assert!(!buffer.is_dirty());
+                assert!(!buffer.has_conflict());
+
+                buffer.edit(vec![0..0], " ", Some(ctx)).unwrap();
+                assert!(buffer.is_dirty());
+            });
+
+            // Change the file on disk again, adding blank lines to the beginning.
+            fs::write(&abs_path, "\n\n\nAAA\naaa\nBBB\nbbb\nCCC\nccc\n").unwrap();
+
+            // Becaues the buffer is modified, it doesn't reload from disk, but is
+            // marked as having a conflict.
+            buffer
+                .condition(&app, |buffer, _| buffer.has_conflict())
+                .await;
+        });
+    }
+
     #[test]
     fn test_undo_redo() {
         App::test((), |app| {