Fix tracking newly saved buffers

Conrad Irwin and Mikayla Maki created

Co-Authored-By: Mikayla Maki <mikayla@zed.dev>

Change summary

crates/language/src/buffer.rs             |  2 
crates/project/src/project.rs             | 28 +++++++++
crates/project_panel/src/project_panel.rs | 68 ++++++++++++++++++++++++
3 files changed, 95 insertions(+), 3 deletions(-)

Detailed changes

crates/language/src/buffer.rs 🔗

@@ -660,12 +660,12 @@ impl Buffer {
             file_changed = true;
         };
 
+        self.file = Some(new_file);
         if file_changed {
             self.file_update_count += 1;
             cx.emit(Event::FileHandleChanged);
             cx.notify();
         }
-        self.file = Some(new_file);
         task
     }
 

crates/project/src/project.rs 🔗

@@ -1841,6 +1841,7 @@ impl Project {
                     Worktree::Remote(_) => panic!("cannot remote buffers as new files"),
                 })
                 .await?;
+
             this.update(&mut cx, |this, cx| {
                 this.detect_language_for_buffer(&buffer, cx);
                 this.register_buffer_with_language_servers(&buffer, cx);
@@ -2368,7 +2369,30 @@ impl Project {
                     }
                 }
             }
+            BufferEvent::FileHandleChanged => {
+                let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
+                    return None;
+                };
+
+                match self.local_buffer_ids_by_entry_id.get(&file.entry_id) {
+                    Some(_) => {
+                        return None;
+                    }
+                    None => {
+                        let remote_id = buffer.read(cx).remote_id();
+                        self.local_buffer_ids_by_entry_id
+                            .insert(file.entry_id, remote_id);
 
+                        self.local_buffer_ids_by_path.insert(
+                            ProjectPath {
+                                worktree_id: file.worktree_id(cx),
+                                path: file.path.clone(),
+                            },
+                            remote_id,
+                        );
+                    }
+                }
+            }
             _ => {}
         }
 
@@ -5906,7 +5930,9 @@ impl Project {
                 Some(&buffer_id) => buffer_id,
                 None => match self.local_buffer_ids_by_path.get(&project_path) {
                     Some(&buffer_id) => buffer_id,
-                    None => continue,
+                    None => {
+                        continue;
+                    }
                 },
             };
 

crates/project_panel/src/project_panel.rs 🔗

@@ -1737,7 +1737,7 @@ mod tests {
     use settings::SettingsStore;
     use std::{
         collections::HashSet,
-        path::Path,
+        path::{Path, PathBuf},
         sync::atomic::{self, AtomicUsize},
     };
     use workspace::{pane, AppState};
@@ -2759,6 +2759,71 @@ mod tests {
         );
     }
 
+    #[gpui::test]
+    async fn test_new_file_move(cx: &mut gpui::TestAppContext) {
+        init_test(cx);
+
+        let fs = FakeFs::new(cx.background());
+        fs.as_fake().insert_tree("/root", json!({})).await;
+        let project = Project::test(fs, ["/root".as_ref()], cx).await;
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project.clone(), cx))
+            .root(cx);
+        let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
+
+        // Make a new buffer with no backing file
+        workspace.update(cx, |workspace, cx| {
+            Editor::new_file(workspace, &Default::default(), cx)
+        });
+
+        // "Save as"" the buffer, creating a new backing file for it
+        let task = workspace.update(cx, |workspace, cx| {
+            workspace.save_active_item(workspace::SaveIntent::Save, cx)
+        });
+
+        cx.foreground().run_until_parked();
+        cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/new")));
+        task.await.unwrap();
+
+        // Rename the file
+        select_path(&panel, "root/new", cx);
+        assert_eq!(
+            visible_entries_as_strings(&panel, 0..10, cx),
+            &["v root", "      new  <== selected"]
+        );
+        panel.update(cx, |panel, cx| panel.rename(&Rename, cx));
+        panel.update(cx, |panel, cx| {
+            panel
+                .filename_editor
+                .update(cx, |editor, cx| editor.set_text("newer", cx));
+        });
+        panel
+            .update(cx, |panel, cx| panel.confirm(&Confirm, cx))
+            .unwrap()
+            .await
+            .unwrap();
+
+        cx.foreground().run_until_parked();
+        assert_eq!(
+            visible_entries_as_strings(&panel, 0..10, cx),
+            &["v root", "      newer  <== selected"]
+        );
+
+        workspace
+            .update(cx, |workspace, cx| {
+                workspace.save_active_item(workspace::SaveIntent::Save, cx)
+            })
+            .await
+            .unwrap();
+
+        cx.foreground().run_until_parked();
+        // assert that saving the file doesn't restore "new"
+        assert_eq!(
+            visible_entries_as_strings(&panel, 0..10, cx),
+            &["v root", "      newer  <== selected"]
+        );
+    }
+
     fn toggle_expand_dir(
         panel: &ViewHandle<ProjectPanel>,
         path: impl AsRef<Path>,
@@ -2862,6 +2927,7 @@ mod tests {
             editor::init_settings(cx);
             crate::init((), cx);
             workspace::init_settings(cx);
+            client::init_settings(cx);
             Project::init_settings(cx);
         });
     }