Properly open worktrees when cmd-clicking in terminal or on inlay hints (#19280)

Kirill Bulatov created

* uses the state that's synced, to fetch the language server name
* uses proper, canonicalized path when creating a remote ssh worktree,
otherwise `~/foo/something` stays unexpanded

Release Notes:

- N/A

Change summary

crates/editor/src/editor.rs                  | 18 +++---
crates/project/src/lsp_store.rs              | 55 ++++++++++++----------
crates/project/src/project.rs                | 11 ----
crates/project/src/worktree_store.rs         |  2 
crates/proto/proto/zed.proto                 |  1 
crates/remote_server/src/headless_project.rs |  3 
6 files changed, 43 insertions(+), 47 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -96,7 +96,9 @@ use language::{
     CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt,
     Point, Selection, SelectionGoal, TransactionId,
 };
-use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
+use language::{
+    point_to_lsp, BufferRow, CharClassifier, LanguageServerName, Runnable, RunnableRange,
+};
 use linked_editing_ranges::refresh_linked_ranges;
 pub use proposed_changes_editor::{
     ProposedChangesBuffer, ProposedChangesEditor, ProposedChangesEditorToolbar,
@@ -9893,21 +9895,19 @@ impl Editor {
         &self,
         lsp_location: lsp::Location,
         server_id: LanguageServerId,
-        cx: &mut ViewContext<Editor>,
+        cx: &mut ViewContext<Self>,
     ) -> Task<anyhow::Result<Option<Location>>> {
         let Some(project) = self.project.clone() else {
             return Task::Ready(Some(Ok(None)));
         };
 
         cx.spawn(move |editor, mut cx| async move {
-            let location_task = editor.update(&mut cx, |editor, cx| {
+            let location_task = editor.update(&mut cx, |_, cx| {
                 project.update(cx, |project, cx| {
-                    let language_server_name =
-                        editor.buffer.read(cx).as_singleton().and_then(|buffer| {
-                            project
-                                .language_server_for_buffer(buffer.read(cx), server_id, cx)
-                                .map(|(lsp_adapter, _)| lsp_adapter.name.clone())
-                        });
+                    let language_server_name = project
+                        .language_server_statuses(cx)
+                        .find(|(id, _)| server_id == *id)
+                        .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
                     language_server_name.map(|language_server_name| {
                         project.open_local_buffer_via_lsp(
                             lsp_location.uri.clone(),

crates/project/src/lsp_store.rs 🔗

@@ -3503,7 +3503,7 @@ impl LspStore {
         language_server_name: LanguageServerName,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Model<Buffer>>> {
-        cx.spawn(move |this, mut cx| async move {
+        cx.spawn(move |lsp_store, mut cx| async move {
             // Escape percent-encoded string.
             let current_scheme = abs_path.scheme().to_owned();
             let _ = abs_path.set_scheme("file");
@@ -3512,9 +3512,9 @@ impl LspStore {
                 .to_file_path()
                 .map_err(|_| anyhow!("can't convert URI to path"))?;
             let p = abs_path.clone();
-            let yarn_worktree = this
-                .update(&mut cx, move |this, cx| {
-                    this.as_local().unwrap().yarn.update(cx, |_, cx| {
+            let yarn_worktree = lsp_store
+                .update(&mut cx, move |lsp_store, cx| match lsp_store.as_local() {
+                    Some(local_lsp_store) => local_lsp_store.yarn.update(cx, |_, cx| {
                         cx.spawn(|this, mut cx| async move {
                             let t = this
                                 .update(&mut cx, |this, cx| {
@@ -3523,7 +3523,8 @@ impl LspStore {
                                 .ok()?;
                             t.await
                         })
-                    })
+                    }),
+                    None => Task::ready(None),
                 })?
                 .await;
             let (worktree_root_target, known_relative_path) =
@@ -3533,8 +3534,8 @@ impl LspStore {
                     (Arc::<Path>::from(abs_path.as_path()), None)
                 };
             let (worktree, relative_path) = if let Some(result) =
-                this.update(&mut cx, |this, cx| {
-                    this.worktree_store.update(cx, |worktree_store, cx| {
+                lsp_store.update(&mut cx, |lsp_store, cx| {
+                    lsp_store.worktree_store.update(cx, |worktree_store, cx| {
                         worktree_store.find_worktree(&worktree_root_target, cx)
                     })
                 })? {
@@ -3542,22 +3543,25 @@ impl LspStore {
                     known_relative_path.unwrap_or_else(|| Arc::<Path>::from(result.1));
                 (result.0, relative_path)
             } else {
-                let worktree = this
-                    .update(&mut cx, |this, cx| {
-                        this.worktree_store.update(cx, |worktree_store, cx| {
+                let worktree = lsp_store
+                    .update(&mut cx, |lsp_store, cx| {
+                        lsp_store.worktree_store.update(cx, |worktree_store, cx| {
                             worktree_store.create_worktree(&worktree_root_target, false, cx)
                         })
                     })?
                     .await?;
-                this.update(&mut cx, |this, cx| {
-                    this.register_language_server(
-                        worktree.read(cx).id(),
-                        language_server_name,
-                        language_server_id,
-                    )
-                })
-                .ok();
-                let worktree_root = worktree.update(&mut cx, |this, _| this.abs_path())?;
+                if worktree.update(&mut cx, |worktree, _| worktree.is_local())? {
+                    lsp_store
+                        .update(&mut cx, |lsp_store, cx| {
+                            lsp_store.register_language_server(
+                                worktree.read(cx).id(),
+                                language_server_name,
+                                language_server_id,
+                            )
+                        })
+                        .ok();
+                }
+                let worktree_root = worktree.update(&mut cx, |worktree, _| worktree.abs_path())?;
                 let relative_path = if let Some(known_path) = known_relative_path {
                     known_path
                 } else {
@@ -3569,12 +3573,13 @@ impl LspStore {
                 worktree_id: worktree.update(&mut cx, |worktree, _| worktree.id())?,
                 path: relative_path,
             };
-            this.update(&mut cx, |this, cx| {
-                this.buffer_store().update(cx, |buffer_store, cx| {
-                    buffer_store.open_buffer(project_path, cx)
-                })
-            })?
-            .await
+            lsp_store
+                .update(&mut cx, |lsp_store, cx| {
+                    lsp_store.buffer_store().update(cx, |buffer_store, cx| {
+                        buffer_store.open_buffer(project_path, cx)
+                    })
+                })?
+                .await
         })
     }
 

crates/project/src/project.rs 🔗

@@ -3979,17 +3979,6 @@ impl Project {
             .read(cx)
             .language_servers_for_buffer(buffer, cx)
     }
-
-    pub fn language_server_for_buffer<'a>(
-        &'a self,
-        buffer: &'a Buffer,
-        server_id: LanguageServerId,
-        cx: &'a AppContext,
-    ) -> Option<(&'a Arc<CachedLspAdapter>, &'a Arc<LanguageServer>)> {
-        self.lsp_store
-            .read(cx)
-            .language_server_for_buffer(buffer, server_id, cx)
-    }
 }
 
 fn deserialize_code_actions(code_actions: &HashMap<String, bool>) -> Vec<lsp::CodeActionKind> {

crates/proto/proto/zed.proto 🔗

@@ -2464,6 +2464,7 @@ message AddWorktree {
 
 message AddWorktreeResponse {
     uint64 worktree_id = 1;
+    string canonicalized_path = 2;
 }
 
 message UpdateUserSettings {

crates/remote_server/src/headless_project.rs 🔗

@@ -275,7 +275,7 @@ impl HeadlessProject {
         let worktree = this
             .update(&mut cx.clone(), |this, _| {
                 Worktree::local(
-                    Arc::from(canonicalized),
+                    Arc::from(canonicalized.as_path()),
                     message.payload.visible,
                     this.fs.clone(),
                     this.next_entry_id.clone(),
@@ -287,6 +287,7 @@ impl HeadlessProject {
         let response = this.update(&mut cx, |_, cx| {
             worktree.update(cx, |worktree, _| proto::AddWorktreeResponse {
                 worktree_id: worktree.id().to_proto(),
+                canonicalized_path: canonicalized.to_string_lossy().to_string(),
             })
         })?;