diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index d42c3460cf..2d578c2c0b 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/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::>(), + 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::>(); + 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| {