Fix project entry rename in Remote Development (#23382)

Andrew Borg (Kashin) created

Closes #22883

To fix the problem, we move `handle_rename_project_entry` from
`Worktree` to `LspStore` and register it there. This way it becomes
available both in local and headless projects and this avoids the
duplication.

Release Notes:

- Fixed renaming project entries in Remote Development

Change summary

crates/project/src/lsp_store.rs                  | 47 +++++++++++++++
crates/project/src/project.rs                    |  2 
crates/project/src/worktree_store.rs             | 55 -----------------
crates/remote_server/src/remote_editing_tests.rs | 40 +++++++++++++
4 files changed, 88 insertions(+), 56 deletions(-)

Detailed changes

crates/project/src/lsp_store.rs 🔗

@@ -2872,6 +2872,7 @@ impl LspStore {
         client.add_model_request_handler(Self::handle_on_type_formatting);
         client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion);
         client.add_model_request_handler(Self::handle_register_buffer_with_language_servers);
+        client.add_model_request_handler(Self::handle_rename_project_entry);
         client.add_model_request_handler(Self::handle_lsp_command::<GetCodeActions>);
         client.add_model_request_handler(Self::handle_lsp_command::<GetCompletions>);
         client.add_model_request_handler(Self::handle_lsp_command::<GetHover>);
@@ -5928,6 +5929,52 @@ impl LspStore {
         Ok(proto::Ack {})
     }
 
+    async fn handle_rename_project_entry(
+        this: Model<Self>,
+        envelope: TypedEnvelope<proto::RenameProjectEntry>,
+        mut cx: AsyncAppContext,
+    ) -> Result<proto::ProjectEntryResponse> {
+        let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
+        let (worktree_id, worktree, old_path, is_dir) = this
+            .update(&mut cx, |this, cx| {
+                this.worktree_store
+                    .read(cx)
+                    .worktree_and_entry_for_id(entry_id, cx)
+                    .map(|(worktree, entry)| {
+                        (
+                            worktree.read(cx).id(),
+                            worktree,
+                            entry.path.clone(),
+                            entry.is_dir(),
+                        )
+                    })
+            })?
+            .ok_or_else(|| anyhow!("worktree not found"))?;
+        let (old_abs_path, new_abs_path) = {
+            let root_path = worktree.update(&mut cx, |this, _| this.abs_path())?;
+            (
+                root_path.join(&old_path),
+                root_path.join(&envelope.payload.new_path),
+            )
+        };
+
+        Self::will_rename_entry(
+            this.downgrade(),
+            worktree_id,
+            &old_abs_path,
+            &new_abs_path,
+            is_dir,
+            cx.clone(),
+        )
+        .await;
+        let response = Worktree::handle_rename_entry(worktree, envelope.payload, cx.clone()).await;
+        this.update(&mut cx, |this, _| {
+            this.did_rename_entry(worktree_id, &old_abs_path, &new_abs_path, is_dir);
+        })
+        .ok();
+        response
+    }
+
     async fn handle_update_diagnostic_summary(
         this: Model<Self>,
         envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,

crates/project/src/project.rs 🔗

@@ -602,8 +602,6 @@ impl Project {
         client.add_model_request_handler(Self::handle_open_new_buffer);
         client.add_model_message_handler(Self::handle_create_buffer_for_peer);
 
-        client.add_model_request_handler(WorktreeStore::handle_rename_project_entry);
-
         WorktreeStore::init(&client);
         BufferStore::init(&client);
         LspStore::init(&client);

crates/project/src/worktree_store.rs 🔗

@@ -27,7 +27,7 @@ use text::ReplicaId;
 use util::{paths::SanitizedPath, ResultExt};
 use worktree::{Entry, ProjectEntryId, Worktree, WorktreeId, WorktreeSettings};
 
-use crate::{search::SearchQuery, LspStore, ProjectPath};
+use crate::{search::SearchQuery, ProjectPath};
 
 struct MatchingEntry {
     worktree_path: Arc<Path>,
@@ -1018,59 +1018,6 @@ impl WorktreeStore {
         Worktree::handle_create_entry(worktree, envelope.payload, cx).await
     }
 
-    pub async fn handle_rename_project_entry(
-        this: Model<super::Project>,
-        envelope: TypedEnvelope<proto::RenameProjectEntry>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::ProjectEntryResponse> {
-        let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
-        let (worktree_id, worktree, old_path, is_dir) = this
-            .update(&mut cx, |this, cx| {
-                this.worktree_store
-                    .read(cx)
-                    .worktree_and_entry_for_id(entry_id, cx)
-                    .map(|(worktree, entry)| {
-                        (
-                            worktree.read(cx).id(),
-                            worktree,
-                            entry.path.clone(),
-                            entry.is_dir(),
-                        )
-                    })
-            })?
-            .ok_or_else(|| anyhow!("worktree not found"))?;
-        let (old_abs_path, new_abs_path) = {
-            let root_path = worktree.update(&mut cx, |this, _| this.abs_path())?;
-            (
-                root_path.join(&old_path),
-                root_path.join(&envelope.payload.new_path),
-            )
-        };
-        let lsp_store = this
-            .update(&mut cx, |this, _| this.lsp_store())?
-            .downgrade();
-        LspStore::will_rename_entry(
-            lsp_store,
-            worktree_id,
-            &old_abs_path,
-            &new_abs_path,
-            is_dir,
-            cx.clone(),
-        )
-        .await;
-        let response = Worktree::handle_rename_entry(worktree, envelope.payload, cx.clone()).await;
-        this.update(&mut cx, |this, cx| {
-            this.lsp_store().read(cx).did_rename_entry(
-                worktree_id,
-                &old_abs_path,
-                &new_abs_path,
-                is_dir,
-            );
-        })
-        .ok();
-        response
-    }
-
     pub async fn handle_copy_project_entry(
         this: Model<Self>,
         envelope: TypedEnvelope<proto::CopyProjectEntry>,

crates/remote_server/src/remote_editing_tests.rs 🔗

@@ -1135,6 +1135,46 @@ async fn test_remote_root_rename(cx: &mut TestAppContext, server_cx: &mut TestAp
     })
 }
 
+#[gpui::test]
+async fn test_remote_rename_entry(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
+    let fs = FakeFs::new(server_cx.executor());
+    fs.insert_tree(
+        "/code",
+        json!({
+            "project1": {
+                ".git": {},
+                "README.md": "# project 1",
+            },
+        }),
+    )
+    .await;
+
+    let (project, _) = init_test(&fs, cx, server_cx).await;
+    let (worktree, _) = project
+        .update(cx, |project, cx| {
+            project.find_or_create_worktree("/code/project1", true, cx)
+        })
+        .await
+        .unwrap();
+
+    cx.run_until_parked();
+
+    let entry = worktree
+        .update(cx, |worktree, cx| {
+            let entry = worktree.entry_for_path("README.md").unwrap();
+            worktree.rename_entry(entry.id, Path::new("README.rst"), cx)
+        })
+        .await
+        .unwrap()
+        .to_included()
+        .unwrap();
+
+    cx.run_until_parked();
+
+    worktree.update(cx, |worktree, _| {
+        assert_eq!(worktree.entry_for_path("README.rst").unwrap().id, entry.id)
+    });
+}
 #[gpui::test]
 async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
     let fs = FakeFs::new(server_cx.executor());