Update buffer's saved mtime when file is reloaded after on-disk change

Max Brunsfeld , Antonio Scandurra , and Nathan Sobo created

Co-Authored-By: Antonio Scandurra <me@as-cii.com>
Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/language/src/lib.rs     | 71 +++++++++++------------------------
crates/project/src/worktree.rs | 36 ++++++------------
2 files changed, 35 insertions(+), 72 deletions(-)

Detailed changes

crates/language/src/lib.rs 🔗

@@ -300,45 +300,34 @@ impl Buffer {
         cx.emit(Event::Saved);
     }
 
+    pub fn file_renamed(&self, cx: &mut ModelContext<Self>) {
+        cx.emit(Event::FileHandleChanged);
+    }
+
     pub fn file_updated(
         &mut self,
-        path: Arc<Path>,
-        mtime: SystemTime,
-        new_text: Option<String>,
+        new_text: String,
         cx: &mut ModelContext<Self>,
-    ) {
-        let file = self.file.as_mut().unwrap();
-        let mut changed = false;
-        if path != *file.path() {
-            file.set_path(path);
-            changed = true;
-        }
-
-        if mtime != file.mtime() {
-            file.set_mtime(mtime);
-            changed = true;
-            if let Some(new_text) = new_text {
-                if self.version == self.saved_version {
-                    cx.spawn(|this, mut cx| async move {
-                        let diff = this
-                            .read_with(&cx, |this, cx| this.diff(new_text.into(), cx))
-                            .await;
-                        this.update(&mut cx, |this, cx| {
-                            if this.apply_diff(diff, cx) {
-                                this.saved_version = this.version.clone();
-                                this.saved_mtime = mtime;
-                                cx.emit(Event::Reloaded);
-                            }
-                        });
-                    })
-                    .detach();
-                }
-            }
-        }
-
-        if changed {
+    ) -> Option<Task<()>> {
+        if let Some(file) = self.file.as_ref() {
             cx.emit(Event::FileHandleChanged);
+            let mtime = file.mtime();
+            if self.version == self.saved_version {
+                return Some(cx.spawn(|this, mut cx| async move {
+                    let diff = this
+                        .read_with(&cx, |this, cx| this.diff(new_text.into(), cx))
+                        .await;
+                    this.update(&mut cx, |this, cx| {
+                        if this.apply_diff(diff, cx) {
+                            this.saved_version = this.version.clone();
+                            this.saved_mtime = mtime;
+                            cx.emit(Event::Reloaded);
+                        }
+                    });
+                }));
+            }
         }
+        None
     }
 
     pub fn file_deleted(&mut self, cx: &mut ModelContext<Self>) {
@@ -740,20 +729,6 @@ impl Buffer {
         })
     }
 
-    pub fn set_text_from_disk(&self, new_text: Arc<str>, cx: &mut ModelContext<Self>) -> Task<()> {
-        cx.spawn(|this, mut cx| async move {
-            let diff = this
-                .read_with(&cx, |this, cx| this.diff(new_text, cx))
-                .await;
-
-            this.update(&mut cx, |this, cx| {
-                if this.apply_diff(diff, cx) {
-                    this.saved_version = this.version.clone();
-                }
-            });
-        })
-    }
-
     fn apply_diff(&mut self, diff: Diff, cx: &mut ModelContext<Self>) -> bool {
         if self.version == diff.base_version {
             self.start_transaction(None).unwrap();

crates/project/src/worktree.rs 🔗

@@ -594,20 +594,12 @@ impl Worktree {
                     let buffer_is_clean = !buffer.is_dirty();
 
                     if let Some(file) = buffer.file_mut() {
-                        let mut file_changed = false;
-
                         if let Some(entry) = file
                             .entry_id()
                             .and_then(|entry_id| self.entry_for_id(entry_id))
                         {
-                            if entry.path != *file.path() {
-                                file.set_path(entry.path.clone());
-                                file_changed = true;
-                            }
-
                             if entry.mtime != file.mtime() {
                                 file.set_mtime(entry.mtime);
-                                file_changed = true;
                                 if let Some(worktree) = self.as_local() {
                                     if buffer_is_clean {
                                         let abs_path = worktree.absolutize(file.path().as_ref());
@@ -615,6 +607,11 @@ impl Worktree {
                                     }
                                 }
                             }
+
+                            if entry.path != *file.path() {
+                                file.set_path(entry.path.clone());
+                                buffer.file_renamed(cx);
+                            }
                         } else if let Some(entry) = self.entry_for_path(file.path().as_ref()) {
                             file.set_entry_id(Some(entry.id));
                             file.set_mtime(entry.mtime);
@@ -624,17 +621,9 @@ impl Worktree {
                                     refresh_buffer(abs_path, &worktree.fs, cx);
                                 }
                             }
-                            file_changed = true;
                         } else if !file.is_deleted() {
-                            if buffer_is_clean {
-                                cx.emit(language::Event::Dirtied);
-                            }
                             file.set_entry_id(None);
-                            file_changed = true;
-                        }
-
-                        if file_changed {
-                            cx.emit(language::Event::FileHandleChanged);
+                            buffer.file_deleted(cx);
                         }
                     }
                 });
@@ -1186,15 +1175,14 @@ fn build_gitignore(abs_path: &Path, fs: &dyn Fs) -> Result<Gitignore> {
 pub fn refresh_buffer(abs_path: PathBuf, fs: &Arc<dyn Fs>, cx: &mut ModelContext<Buffer>) {
     let fs = fs.clone();
     cx.spawn(|buffer, mut cx| async move {
-        let new_text = fs.load(&abs_path).await;
-        match new_text {
+        match fs.load(&abs_path).await {
             Err(error) => log::error!("error refreshing buffer after file changed: {}", error),
             Ok(new_text) => {
-                buffer
-                    .update(&mut cx, |buffer, cx| {
-                        buffer.set_text_from_disk(new_text.into(), cx)
-                    })
-                    .await;
+                if let Some(task) =
+                    buffer.update(&mut cx, |buffer, cx| buffer.file_updated(new_text, cx))
+                {
+                    task.await;
+                }
             }
         }
     })