project: Bring back language servers in detached worktrees (#23530)

Piotr Osiewicz created

Closes #ISSUE

Release Notes:

- N/A

Change summary

crates/editor/src/editor_tests.rs              |  6 
crates/project/src/lsp_store.rs                | 65 +++++++++++++++----
crates/project/src/project_tree.rs             |  2 
crates/project/src/project_tree/server_tree.rs | 24 ++++++
4 files changed, 75 insertions(+), 22 deletions(-)

Detailed changes

crates/editor/src/editor_tests.rs 🔗

@@ -6839,7 +6839,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
     let fs = FakeFs::new(cx.executor());
     fs.insert_file("/file.rs", Default::default()).await;
 
-    let project = Project::test(fs, ["/".as_ref()], cx).await;
+    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 
     let language_registry = project.read_with(cx, |project, _| project.languages().clone());
     language_registry.add(rust_lang());
@@ -7193,7 +7193,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
     let fs = FakeFs::new(cx.executor());
     fs.insert_file("/file.rs", Default::default()).await;
 
-    let project = Project::test(fs, ["/".as_ref()], cx).await;
+    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 
     let language_registry = project.read_with(cx, |project, _| project.languages().clone());
     language_registry.add(rust_lang());
@@ -7327,7 +7327,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
     let fs = FakeFs::new(cx.executor());
     fs.insert_file("/file.rs", Default::default()).await;
 
-    let project = Project::test(fs, ["/".as_ref()], cx).await;
+    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 
     let language_registry = project.read_with(cx, |project, _| project.languages().clone());
     language_registry.add(Arc::new(Language::new(

crates/project/src/lsp_store.rs 🔗

@@ -6,7 +6,7 @@ use crate::{
     lsp_ext_command,
     prettier_store::{self, PrettierStore, PrettierStoreEvent},
     project_settings::{LspSettings, ProjectSettings},
-    project_tree::{LanguageServerTree, LaunchDisposition, ProjectTree},
+    project_tree::{AdapterQuery, LanguageServerTree, LaunchDisposition, ProjectTree},
     relativize_path, resolve_path,
     toolchain_store::{EmptyToolchainStore, ToolchainStoreEvent},
     worktree_store::{WorktreeStore, WorktreeStoreEvent},
@@ -983,9 +983,11 @@ impl LocalLspStore {
         if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) {
             let worktree_id = file.worktree_id(cx);
 
-            let Some(path): Option<Arc<Path>> = file.path().parent().map(Arc::from) else {
-                return vec![];
-            };
+            let path: Arc<Path> = file
+                .path()
+                .parent()
+                .map(Arc::from)
+                .unwrap_or_else(|| file.path().clone());
             let worktree_path = ProjectPath { worktree_id, path };
             let Some(worktree) = self
                 .worktree_store
@@ -996,9 +998,14 @@ impl LocalLspStore {
             };
             let delegate = LocalLspAdapterDelegate::from_local_lsp(self, &worktree, cx);
             let root = self.lsp_tree.update(cx, |this, cx| {
-                this.get(worktree_path, &language.name(), delegate, cx)
-                    .filter_map(|node| node.server_id())
-                    .collect::<Vec<_>>()
+                this.get(
+                    worktree_path,
+                    AdapterQuery::Language(&language.name()),
+                    delegate,
+                    cx,
+                )
+                .filter_map(|node| node.server_id())
+                .collect::<Vec<_>>()
             });
 
             root
@@ -1740,7 +1747,7 @@ impl LocalLspStore {
                 let nodes = self.lsp_tree.update(cx, |this, cx| {
                     this.get(
                         ProjectPath { worktree_id, path },
-                        &language.name(),
+                        AdapterQuery::Language(&language.name()),
                         delegate,
                         cx,
                     )
@@ -1860,9 +1867,11 @@ impl LocalLspStore {
         let Some(language) = buffer.language().cloned() else {
             return;
         };
-        let Some(path): Option<Arc<Path>> = file.path().parent().map(Arc::from) else {
-            return;
-        };
+        let path: Arc<Path> = file
+            .path()
+            .parent()
+            .map(Arc::from)
+            .unwrap_or_else(|| file.path().clone());
         let Some(worktree) = self
             .worktree_store
             .read(cx)
@@ -1874,7 +1883,7 @@ impl LocalLspStore {
         let servers = self.lsp_tree.clone().update(cx, |this, cx| {
             this.get(
                 ProjectPath { worktree_id, path },
-                &language.name(),
+                AdapterQuery::Language(&language.name()),
                 delegate.clone(),
                 cx,
             )
@@ -5361,12 +5370,35 @@ impl LspStore {
 
     fn register_local_language_server(
         &mut self,
-        worktree_id: WorktreeId,
+        worktree: Model<Worktree>,
         language_server_name: LanguageServerName,
         language_server_id: LanguageServerId,
+        cx: &mut AppContext,
     ) {
-        self.as_local_mut()
-            .unwrap()
+        let Some(local) = self.as_local_mut() else {
+            return;
+        };
+        let worktree_id = worktree.read(cx).id();
+        let path = ProjectPath {
+            worktree_id,
+            path: Arc::from("".as_ref()),
+        };
+        let delegate = LocalLspAdapterDelegate::from_local_lsp(local, &worktree, cx);
+        local.lsp_tree.update(cx, |this, cx| {
+            for node in this.get(
+                path,
+                AdapterQuery::Adapter(&language_server_name),
+                delegate,
+                cx,
+            ) {
+                node.server_id_or_init(|disposition| {
+                    assert_eq!(disposition.server_name, &language_server_name);
+
+                    language_server_id
+                });
+            }
+        });
+        local
             .language_server_ids
             .entry((worktree_id, language_server_name))
             .or_default()
@@ -5609,9 +5641,10 @@ impl LspStore {
                     lsp_store
                         .update(&mut cx, |lsp_store, cx| {
                             lsp_store.register_local_language_server(
-                                worktree.read(cx).id(),
+                                worktree.clone(),
                                 language_server_name,
                                 language_server_id,
+                                cx,
                             )
                         })
                         .ok();

crates/project/src/project_tree.rs 🔗

@@ -25,7 +25,7 @@ use crate::{
     ProjectPath,
 };
 
-pub(crate) use server_tree::{LanguageServerTree, LaunchDisposition};
+pub(crate) use server_tree::{AdapterQuery, LanguageServerTree, LaunchDisposition};
 
 struct WorktreeRoots {
     roots: RootPathTrie<LanguageServerName>,

crates/project/src/project_tree/server_tree.rs 🔗

@@ -128,6 +128,14 @@ impl InnerTreeNode {
     }
 }
 
+/// Determines how the list of adapters to query should be constructed.
+pub(crate) enum AdapterQuery<'a> {
+    /// Search for roots of all adapters associated with a given language name.
+    Language(&'a LanguageName),
+    /// Search for roots of adapter with a given name.
+    Adapter(&'a LanguageServerName),
+}
+
 impl LanguageServerTree {
     pub(crate) fn new(
         project_tree: Model<ProjectTree>,
@@ -159,7 +167,7 @@ impl LanguageServerTree {
     pub(crate) fn get<'a>(
         &'a mut self,
         path: ProjectPath,
-        language_name: &LanguageName,
+        query: AdapterQuery<'_>,
         delegate: Arc<dyn LspAdapterDelegate>,
         cx: &mut AppContext,
     ) -> impl Iterator<Item = LanguageServerTreeNode> + 'a {
@@ -167,7 +175,15 @@ impl LanguageServerTree {
             worktree_id: path.worktree_id,
             path: &path.path,
         };
-        let adapters = self.adapters_for_language(settings_location, language_name, cx);
+        let adapters = match query {
+            AdapterQuery::Language(language_name) => {
+                self.adapters_for_language(settings_location, language_name, cx)
+            }
+            AdapterQuery::Adapter(language_server_name) => IndexMap::from_iter(
+                self.adapter_for_name(language_server_name)
+                    .map(|adapter| (adapter, (LspSettings::default(), BTreeSet::new()))),
+            ),
+        };
         self.get_with_adapters(path, adapters, delegate, cx)
     }
 
@@ -231,6 +247,10 @@ impl LanguageServerTree {
         })
     }
 
+    fn adapter_for_name(&self, name: &LanguageServerName) -> Option<AdapterWrapper> {
+        self.languages.adapter_for_name(name).map(AdapterWrapper)
+    }
+
     fn adapters_for_language(
         &self,
         settings_location: SettingsLocation,