Add File.disk_state enum to clarify filesystem states (#20776)

Motivation for this is to make things more understandable while figuring
out #20775.

This is intended to be a refactoring that does not affect behavior, but
there are a few tricky spots:

* Previously `File.mtime()` (now `File.disk_state().mtime()`) would
return last known modification time for deleted files. Looking at uses,
I believe this will not affect anything. If there are behavior changes
here I believe they would be improvements.

* `BufferEvent::DirtyChanged` is now only emitted if dirtiness actually
changed, rather than if it may have changed. This should only be an
efficiency improvement.

Release Notes:

- N/A

Co-authored-by: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
Michael Sloan 2024-11-18 10:30:08 -08:00 committed by GitHub
parent df1d0dec0a
commit d99f5fe83e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 161 additions and 134 deletions

View file

@ -104,6 +104,7 @@ pub struct Buffer {
text: TextBuffer,
diff_base: Option<BufferDiffBase>,
git_diff: git::diff::BufferDiff,
/// Filesystem state, `None` when there is no path.
file: Option<Arc<dyn File>>,
/// The mtime of the file when this buffer was last loaded from
/// or saved to disk.
@ -371,8 +372,9 @@ pub trait File: Send + Sync {
self.as_local().is_some()
}
/// Returns the file's mtime.
fn mtime(&self) -> Option<SystemTime>;
/// Returns whether the file is new, exists in storage, or has been deleted. Includes metadata
/// only available in some states, such as modification time.
fn disk_state(&self) -> DiskState;
/// Returns the path of this file relative to the worktree's root directory.
fn path(&self) -> &Arc<Path>;
@ -390,14 +392,6 @@ pub trait File: Send + Sync {
/// This is needed for looking up project-specific settings.
fn worktree_id(&self, cx: &AppContext) -> WorktreeId;
/// Returns whether the file has been deleted.
fn is_deleted(&self) -> bool;
/// Returns whether the file existed on disk at one point
fn is_created(&self) -> bool {
self.mtime().is_some()
}
/// Converts this file into an [`Any`] trait object.
fn as_any(&self) -> &dyn Any;
@ -408,6 +402,34 @@ pub trait File: Send + Sync {
fn is_private(&self) -> bool;
}
/// The file's storage status - whether it's stored (`Present`), and if so when it was last
/// modified. In the case where the file is not stored, it can be either `New` or `Deleted`. In the
/// UI these two states are distinguished. For example, the buffer tab does not display a deletion
/// indicator for new files.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DiskState {
/// File created in Zed that has not been saved.
New,
/// File present on the filesystem.
Present {
/// Last known mtime (modification time).
mtime: SystemTime,
},
/// Deleted file that was previously present.
Deleted,
}
impl DiskState {
/// Returns the file's last known modification time on disk.
pub fn mtime(self) -> Option<SystemTime> {
match self {
DiskState::New => None,
DiskState::Present { mtime } => Some(mtime),
DiskState::Deleted => None,
}
}
}
/// The file associated with a buffer, in the case where the file is on the local disk.
pub trait LocalFile: File {
/// Returns the absolute path of this file
@ -750,7 +772,7 @@ impl Buffer {
file: Option<Arc<dyn File>>,
capability: Capability,
) -> Self {
let saved_mtime = file.as_ref().and_then(|file| file.mtime());
let saved_mtime = file.as_ref().and_then(|file| file.disk_state().mtime());
let snapshot = buffer.snapshot();
let git_diff = git::diff::BufferDiff::new(&snapshot);
let syntax_map = Mutex::new(SyntaxMap::new(&snapshot));
@ -1014,7 +1036,7 @@ impl Buffer {
self.reload_task = Some(cx.spawn(|this, mut cx| async move {
let Some((new_mtime, new_text)) = this.update(&mut cx, |this, cx| {
let file = this.file.as_ref()?.as_local()?;
Some((file.mtime(), file.load(cx)))
Some((file.disk_state().mtime(), file.load(cx)))
})?
else {
return Ok(());
@ -1070,6 +1092,7 @@ impl Buffer {
/// Updates the [`File`] backing this buffer. This should be called when
/// the file has changed or has been deleted.
pub fn file_updated(&mut self, new_file: Arc<dyn File>, cx: &mut ModelContext<Self>) {
let was_dirty = self.is_dirty();
let mut file_changed = false;
if let Some(old_file) = self.file.as_ref() {
@ -1077,21 +1100,12 @@ impl Buffer {
file_changed = true;
}
if new_file.is_deleted() {
if !old_file.is_deleted() {
file_changed = true;
if !self.is_dirty() {
cx.emit(BufferEvent::DirtyChanged);
}
}
} else {
let new_mtime = new_file.mtime();
if new_mtime != old_file.mtime() {
file_changed = true;
if !self.is_dirty() {
cx.emit(BufferEvent::ReloadNeeded);
}
let old_state = old_file.disk_state();
let new_state = new_file.disk_state();
if old_state != new_state {
file_changed = true;
if !was_dirty && matches!(new_state, DiskState::Present { .. }) {
cx.emit(BufferEvent::ReloadNeeded)
}
}
} else {
@ -1101,6 +1115,9 @@ impl Buffer {
self.file = Some(new_file);
if file_changed {
self.non_text_state_update_count += 1;
if was_dirty != self.is_dirty() {
cx.emit(BufferEvent::DirtyChanged);
}
cx.emit(BufferEvent::FileHandleChanged);
cx.notify();
}
@ -1742,15 +1759,10 @@ impl Buffer {
pub fn is_dirty(&self) -> bool {
self.capability != Capability::ReadOnly
&& (self.has_conflict
|| self.has_unsaved_edits()
|| self
.file
.as_ref()
.map_or(false, |file| file.is_deleted() || !file.is_created()))
}
pub fn is_deleted(&self) -> bool {
self.file.as_ref().map_or(false, |file| file.is_deleted())
|| self.file.as_ref().map_or(false, |file| {
matches!(file.disk_state(), DiskState::New | DiskState::Deleted)
})
|| self.has_unsaved_edits())
}
/// Checks if the buffer and its file have both changed since the buffer
@ -1762,7 +1774,13 @@ impl Buffer {
let Some(file) = self.file.as_ref() else {
return false;
};
file.is_deleted() || (file.mtime() > self.saved_mtime && self.has_unsaved_edits())
match file.disk_state() {
DiskState::New | DiskState::Deleted => true,
DiskState::Present { mtime } => match self.saved_mtime {
Some(saved_mtime) => mtime > saved_mtime && self.has_unsaved_edits(),
None => true,
},
}
}
/// Gets a [`Subscription`] that tracks all of the changes to the buffer's text.
@ -4403,7 +4421,7 @@ impl File for TestFile {
None
}
fn mtime(&self) -> Option<SystemTime> {
fn disk_state(&self) -> DiskState {
unimplemented!()
}
@ -4415,10 +4433,6 @@ impl File for TestFile {
WorktreeId::from_usize(0)
}
fn is_deleted(&self) -> bool {
unimplemented!()
}
fn as_any(&self) -> &dyn std::any::Any {
unimplemented!()
}