project: Track manifest locations per unique manifest locator (#27194)

Piotr Osiewicz and Anthony created

This pull request paves way for exposing manifest tracking to
extensions.
- Project tree was renamed to manifest tree to better reflect it's
intent (and avoid confusion).
- Language server adapters now provide a name of their *manifest
locator*. If multiple language servers refer to the same locator, the
locating code will run just once for a given path.

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: Anthony <anthony@zed.dev>

Change summary

crates/language/src/language.rs                    |  27 +-
crates/language/src/manifest.rs                    |  48 +++++
crates/languages/src/lib.rs                        |   2 
crates/languages/src/rust.rs                       |  34 ++-
crates/project/src/lsp_store.rs                    |   6 
crates/project/src/manifest_tree.rs                |  87 +++------
crates/project/src/manifest_tree/manifest_store.rs |  48 +++++
crates/project/src/manifest_tree/path_trie.rs      |   2 
crates/project/src/manifest_tree/server_tree.rs    | 138 ++++++++-------
crates/project/src/project.rs                      |   3 
10 files changed, 241 insertions(+), 154 deletions(-)

Detailed changes

crates/language/src/language.rs 🔗

@@ -11,6 +11,7 @@ mod diagnostic_set;
 mod highlight_map;
 mod language_registry;
 pub mod language_settings;
+mod manifest;
 mod outline;
 pub mod proto;
 mod syntax_map;
@@ -33,6 +34,7 @@ pub use highlight_map::HighlightMap;
 use http_client::HttpClient;
 pub use language_registry::{LanguageName, LoadedLanguage};
 use lsp::{CodeActionKind, InitializeParams, LanguageServerBinary, LanguageServerBinaryOptions};
+pub use manifest::{ManifestName, ManifestProvider, ManifestQuery};
 use parking_lot::Mutex;
 use regex::Regex;
 use schemars::{
@@ -163,6 +165,7 @@ pub struct CachedLspAdapter {
     pub adapter: Arc<dyn LspAdapter>,
     pub reinstall_attempt_count: AtomicU64,
     cached_binary: futures::lock::Mutex<Option<LanguageServerBinary>>,
+    manifest_name: OnceLock<Option<ManifestName>>,
     attach_kind: OnceLock<Attach>,
 }
 
@@ -200,6 +203,7 @@ impl CachedLspAdapter {
             cached_binary: Default::default(),
             reinstall_attempt_count: AtomicU64::new(0),
             attach_kind: Default::default(),
+            manifest_name: Default::default(),
         })
     }
 
@@ -261,14 +265,10 @@ impl CachedLspAdapter {
             .cloned()
             .unwrap_or_else(|| language_name.lsp_id())
     }
-    pub fn find_project_root(
-        &self,
-        path: &Path,
-        ancestor_depth: usize,
-        delegate: &Arc<dyn LspAdapterDelegate>,
-    ) -> Option<Arc<Path>> {
-        self.adapter
-            .find_project_root(path, ancestor_depth, delegate)
+    pub fn manifest_name(&self) -> Option<ManifestName> {
+        self.manifest_name
+            .get_or_init(|| self.adapter.manifest_name())
+            .clone()
     }
     pub fn attach_kind(&self) -> Attach {
         *self.attach_kind.get_or_init(|| self.adapter.attach_kind())
@@ -542,18 +542,13 @@ pub trait LspAdapter: 'static + Send + Sync {
     fn prepare_initialize_params(&self, original: InitializeParams) -> Result<InitializeParams> {
         Ok(original)
     }
+
     fn attach_kind(&self) -> Attach {
         Attach::Shared
     }
-    fn find_project_root(
-        &self,
 
-        _path: &Path,
-        _ancestor_depth: usize,
-        _: &Arc<dyn LspAdapterDelegate>,
-    ) -> Option<Arc<Path>> {
-        // By default all language servers are rooted at the root of the worktree.
-        Some(Arc::from("".as_ref()))
+    fn manifest_name(&self) -> Option<ManifestName> {
+        None
     }
 
     /// Method only implemented by the default JSON language server adapter.

crates/language/src/manifest.rs 🔗

@@ -0,0 +1,48 @@
+use std::{borrow::Borrow, path::Path, sync::Arc};
+
+use gpui::SharedString;
+
+use crate::LspAdapterDelegate;
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct ManifestName(SharedString);
+
+impl Borrow<SharedString> for ManifestName {
+    fn borrow(&self) -> &SharedString {
+        &self.0
+    }
+}
+
+impl From<SharedString> for ManifestName {
+    fn from(value: SharedString) -> Self {
+        Self(value)
+    }
+}
+
+impl From<ManifestName> for SharedString {
+    fn from(value: ManifestName) -> Self {
+        value.0
+    }
+}
+
+impl AsRef<SharedString> for ManifestName {
+    fn as_ref(&self) -> &SharedString {
+        &self.0
+    }
+}
+
+/// Represents a manifest query; given a path to a file, [ManifestSearcher] is tasked with finding a path to the directory containing the manifest for that file.
+///
+/// Since parts of the path might have already been explored, there's an additional `depth` parameter that indicates to what ancestry level a given path should be explored.
+/// For example, given a path like `foo/bar/baz`, a depth of 2 would explore `foo/bar/baz` and `foo/bar`, but not `foo`.
+pub struct ManifestQuery {
+    /// Path to the file, relative to worktree root.
+    pub path: Arc<Path>,
+    pub depth: usize,
+    pub delegate: Arc<dyn LspAdapterDelegate>,
+}
+
+pub trait ManifestProvider {
+    fn name(&self) -> ManifestName;
+    fn search(&self, query: ManifestQuery) -> Option<Arc<Path>>;
+}

crates/languages/src/lib.rs 🔗

@@ -2,6 +2,7 @@ use anyhow::Context as _;
 use gpui::{App, UpdateGlobal};
 use json::json_task_context;
 use node_runtime::NodeRuntime;
+use rust::CargoManifestProvider;
 use rust_embed::RustEmbed;
 use settings::SettingsStore;
 use smol::stream::StreamExt;
@@ -301,6 +302,7 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
         anyhow::Ok(())
     })
     .detach();
+    project::ManifestProviders::global(cx).register(Arc::from(CargoManifestProvider));
 }
 
 #[derive(Default)]

crates/languages/src/rust.rs 🔗

@@ -3,7 +3,7 @@ use async_compression::futures::bufread::GzipDecoder;
 use async_trait::async_trait;
 use collections::HashMap;
 use futures::{io::BufReader, StreamExt};
-use gpui::{App, AsyncApp, Task};
+use gpui::{App, AsyncApp, SharedString, Task};
 use http_client::github::AssetKind;
 use http_client::github::{latest_github_release, GitHubLspBinaryVersion};
 pub use language::*;
@@ -68,20 +68,23 @@ impl RustLspAdapter {
     }
 }
 
-#[async_trait(?Send)]
-impl LspAdapter for RustLspAdapter {
-    fn name(&self) -> LanguageServerName {
-        Self::SERVER_NAME.clone()
+pub(crate) struct CargoManifestProvider;
+
+impl ManifestProvider for CargoManifestProvider {
+    fn name(&self) -> ManifestName {
+        SharedString::new_static("Cargo.toml").into()
     }
 
-    fn find_project_root(
+    fn search(
         &self,
-        path: &Path,
-        ancestor_depth: usize,
-        delegate: &Arc<dyn LspAdapterDelegate>,
+        ManifestQuery {
+            path,
+            depth,
+            delegate,
+        }: ManifestQuery,
     ) -> Option<Arc<Path>> {
         let mut outermost_cargo_toml = None;
-        for path in path.ancestors().take(ancestor_depth) {
+        for path in path.ancestors().take(depth) {
             let p = path.join("Cargo.toml");
             if delegate.exists(&p, Some(false)) {
                 outermost_cargo_toml = Some(Arc::from(path));
@@ -90,6 +93,17 @@ impl LspAdapter for RustLspAdapter {
 
         outermost_cargo_toml
     }
+}
+
+#[async_trait(?Send)]
+impl LspAdapter for RustLspAdapter {
+    fn name(&self) -> LanguageServerName {
+        Self::SERVER_NAME.clone()
+    }
+
+    fn manifest_name(&self) -> Option<ManifestName> {
+        Some(SharedString::new_static("Cargo.toml").into())
+    }
 
     async fn check_if_user_installed(
         &self,

crates/project/src/lsp_store.rs 🔗

@@ -6,9 +6,9 @@ use crate::{
     buffer_store::{BufferStore, BufferStoreEvent},
     environment::ProjectEnvironment,
     lsp_command::{self, *},
+    manifest_tree::{AdapterQuery, LanguageServerTree, LaunchDisposition, ManifestTree},
     prettier_store::{self, PrettierStore, PrettierStoreEvent},
     project_settings::{LspSettings, ProjectSettings},
-    project_tree::{AdapterQuery, LanguageServerTree, LaunchDisposition, ProjectTree},
     relativize_path, resolve_path,
     toolchain_store::{EmptyToolchainStore, ToolchainStoreEvent},
     worktree_store::{WorktreeStore, WorktreeStoreEvent},
@@ -3349,7 +3349,7 @@ impl LspStore {
                 sender,
             )
         };
-        let project_tree = ProjectTree::new(worktree_store.clone(), cx);
+        let manifest_tree = ManifestTree::new(worktree_store.clone(), cx);
         Self {
             mode: LspStoreMode::Local(LocalLspStore {
                 weak: cx.weak_entity(),
@@ -3375,7 +3375,7 @@ impl LspStore {
                 _subscription: cx.on_app_quit(|this, cx| {
                     this.as_local_mut().unwrap().shutdown_language_servers(cx)
                 }),
-                lsp_tree: LanguageServerTree::new(project_tree, languages.clone(), cx),
+                lsp_tree: LanguageServerTree::new(manifest_tree, languages.clone(), cx),
                 registered_buffers: Default::default(),
             }),
             last_formatting_failure: None,

crates/project/src/project_tree.rs → crates/project/src/manifest_tree.rs 🔗

@@ -1,7 +1,9 @@
-//! This module defines a Project Tree.
+//! This module defines a Manifest Tree.
 //!
-//! A Project Tree is responsible for determining where the roots of subprojects are located in a project.
+//! A Manifest Tree is responsible for determining where the manifests for subprojects are located in a project.
+//! This then is used to provide those locations to language servers & determine locations eligible for toolchain selection.
 
+mod manifest_store;
 mod path_trie;
 mod server_tree;
 
@@ -14,8 +16,8 @@ use std::{
 
 use collections::HashMap;
 use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription};
-use language::{CachedLspAdapter, LspAdapterDelegate};
-use lsp::LanguageServerName;
+use language::{LspAdapterDelegate, ManifestName, ManifestQuery};
+pub use manifest_store::ManifestProviders;
 use path_trie::{LabelPresence, RootPathTrie, TriePath};
 use settings::{SettingsStore, WorktreeId};
 use worktree::{Event as WorktreeEvent, Worktree};
@@ -28,7 +30,7 @@ use crate::{
 pub(crate) use server_tree::{AdapterQuery, LanguageServerTree, LaunchDisposition};
 
 struct WorktreeRoots {
-    roots: RootPathTrie<LanguageServerName>,
+    roots: RootPathTrie<ManifestName>,
     worktree_store: Entity<WorktreeStore>,
     _worktree_subscription: Subscription,
 }
@@ -70,55 +72,21 @@ impl WorktreeRoots {
     }
 }
 
-pub struct ProjectTree {
+pub struct ManifestTree {
     root_points: HashMap<WorktreeId, Entity<WorktreeRoots>>,
     worktree_store: Entity<WorktreeStore>,
     _subscriptions: [Subscription; 2],
 }
 
-#[derive(Debug, Clone)]
-struct AdapterWrapper(Arc<CachedLspAdapter>);
-impl PartialEq for AdapterWrapper {
-    fn eq(&self, other: &Self) -> bool {
-        self.0.name.eq(&other.0.name)
-    }
-}
-
-impl Eq for AdapterWrapper {}
-
-impl std::hash::Hash for AdapterWrapper {
-    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
-        self.0.name.hash(state);
-    }
-}
-
-impl PartialOrd for AdapterWrapper {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        Some(self.0.name.cmp(&other.0.name))
-    }
-}
-
-impl Ord for AdapterWrapper {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        self.0.name.cmp(&other.0.name)
-    }
-}
-
-impl Borrow<LanguageServerName> for AdapterWrapper {
-    fn borrow(&self) -> &LanguageServerName {
-        &self.0.name
-    }
-}
-
 #[derive(PartialEq)]
-pub(crate) enum ProjectTreeEvent {
+pub(crate) enum ManifestTreeEvent {
     WorktreeRemoved(WorktreeId),
     Cleared,
 }
 
-impl EventEmitter<ProjectTreeEvent> for ProjectTree {}
+impl EventEmitter<ManifestTreeEvent> for ManifestTree {}
 
-impl ProjectTree {
+impl ManifestTree {
     pub(crate) fn new(worktree_store: Entity<WorktreeStore>, cx: &mut App) -> Entity<Self> {
         cx.new(|cx| Self {
             root_points: Default::default(),
@@ -130,26 +98,22 @@ impl ProjectTree {
                             worktree_roots.roots = RootPathTrie::new();
                         })
                     }
-                    cx.emit(ProjectTreeEvent::Cleared);
+                    cx.emit(ManifestTreeEvent::Cleared);
                 }),
             ],
             worktree_store,
         })
     }
-    #[allow(clippy::mutable_key_type)]
     fn root_for_path(
         &mut self,
         ProjectPath { worktree_id, path }: ProjectPath,
-        adapters: Vec<Arc<CachedLspAdapter>>,
+        manifests: &mut dyn Iterator<Item = ManifestName>,
         delegate: Arc<dyn LspAdapterDelegate>,
         cx: &mut App,
-    ) -> BTreeMap<AdapterWrapper, ProjectPath> {
+    ) -> BTreeMap<ManifestName, ProjectPath> {
         debug_assert_eq!(delegate.worktree_id(), worktree_id);
-        #[allow(clippy::mutable_key_type)]
         let mut roots = BTreeMap::from_iter(
-            adapters
-                .into_iter()
-                .map(|adapter| (AdapterWrapper(adapter), (None, LabelPresence::KnownAbsent))),
+            manifests.map(|manifest| (manifest, (None, LabelPresence::KnownAbsent))),
         );
         let worktree_roots = match self.root_points.entry(worktree_id) {
             Entry::Occupied(occupied_entry) => occupied_entry.get().clone(),
@@ -182,7 +146,8 @@ impl ProjectTree {
                 ControlFlow::Continue(())
             });
         });
-        for (adapter, (root_path, presence)) in &mut roots {
+
+        for (manifest_name, (root_path, presence)) in &mut roots {
             if *presence == LabelPresence::Present {
                 continue;
             }
@@ -198,12 +163,22 @@ impl ProjectTree {
                 .unwrap_or_else(|| path.components().count() + 1);
 
             if depth > 0 {
-                let root = adapter.0.find_project_root(&path, depth, &delegate);
+                let Some(provider) = ManifestProviders::global(cx).get(manifest_name.borrow())
+                else {
+                    log::warn!("Manifest provider `{}` not found", manifest_name.as_ref());
+                    continue;
+                };
+
+                let root = provider.search(ManifestQuery {
+                    path: path.clone(),
+                    depth,
+                    delegate: delegate.clone(),
+                });
                 match root {
                     Some(known_root) => worktree_roots.update(cx, |this, _| {
                         let root = TriePath::from(&*known_root);
                         this.roots
-                            .insert(&root, adapter.0.name(), LabelPresence::Present);
+                            .insert(&root, manifest_name.clone(), LabelPresence::Present);
                         *presence = LabelPresence::Present;
                         *root_path = Some(ProjectPath {
                             worktree_id,
@@ -212,7 +187,7 @@ impl ProjectTree {
                     }),
                     None => worktree_roots.update(cx, |this, _| {
                         this.roots
-                            .insert(&key, adapter.0.name(), LabelPresence::KnownAbsent);
+                            .insert(&key, manifest_name.clone(), LabelPresence::KnownAbsent);
                     }),
                 }
             }
@@ -235,7 +210,7 @@ impl ProjectTree {
         match evt {
             WorktreeStoreEvent::WorktreeRemoved(_, worktree_id) => {
                 self.root_points.remove(&worktree_id);
-                cx.emit(ProjectTreeEvent::WorktreeRemoved(*worktree_id));
+                cx.emit(ManifestTreeEvent::WorktreeRemoved(*worktree_id));
             }
             _ => {}
         }

crates/project/src/manifest_tree/manifest_store.rs 🔗

@@ -0,0 +1,48 @@
+use collections::HashMap;
+use gpui::{App, Global, SharedString};
+use parking_lot::RwLock;
+use std::{ops::Deref, sync::Arc};
+
+use language::{ManifestName, ManifestProvider};
+
+#[derive(Default)]
+struct ManifestProvidersState {
+    providers: HashMap<ManifestName, Arc<dyn ManifestProvider>>,
+}
+
+#[derive(Clone, Default)]
+pub struct ManifestProviders(Arc<RwLock<ManifestProvidersState>>);
+
+#[derive(Default)]
+struct GlobalManifestProvider(ManifestProviders);
+
+impl Deref for GlobalManifestProvider {
+    type Target = ManifestProviders;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl Global for GlobalManifestProvider {}
+
+impl ManifestProviders {
+    /// Returns the global [`ManifestStore`].
+    ///
+    /// Inserts a default [`ManifestStore`] if one does not yet exist.
+    pub fn global(cx: &mut App) -> Self {
+        cx.default_global::<GlobalManifestProvider>().0.clone()
+    }
+
+    pub fn register(&self, provider: Arc<dyn ManifestProvider>) {
+        self.0.write().providers.insert(provider.name(), provider);
+    }
+
+    pub fn unregister(&self, name: &SharedString) {
+        self.0.write().providers.remove(name);
+    }
+
+    pub(super) fn get(&self, name: &SharedString) -> Option<Arc<dyn ManifestProvider>> {
+        self.0.read().providers.get(name).cloned()
+    }
+}

crates/project/src/project_tree/path_trie.rs → crates/project/src/manifest_tree/path_trie.rs 🔗

@@ -6,7 +6,7 @@ use std::{
     sync::Arc,
 };
 
-/// [RootPathTrie] is a workhorse of [super::ProjectTree]. It is responsible for determining the closest known project root for a given path.
+/// [RootPathTrie] is a workhorse of [super::ManifestTree]. It is responsible for determining the closest known project root for a given path.
 /// It also determines how much of a given path is unexplored, thus letting callers fill in that gap if needed.
 /// Conceptually, it allows one to annotate Worktree entries with arbitrary extra metadata and run closest-ancestor searches.
 ///

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

@@ -6,7 +6,6 @@
 //! LSP Tree is transparent to RPC peers; when clients ask host to spawn a new language server, the host will perform LSP Tree lookup for provided path; it may decide
 //! to reuse existing language server. The client maintains it's own LSP Tree that is a subset of host LSP Tree. Done this way, the client does not need to
 //! ask about suitable language server for each path it interacts with; it can resolve most of the queries locally.
-//! This module defines a Project Tree.
 
 use std::{
     collections::{BTreeMap, BTreeSet},
@@ -16,10 +15,9 @@ use std::{
 
 use collections::{HashMap, IndexMap};
 use gpui::{App, AppContext as _, Entity, Subscription};
-use itertools::Itertools;
 use language::{
-    language_settings::AllLanguageSettings, Attach, LanguageName, LanguageRegistry,
-    LspAdapterDelegate,
+    language_settings::AllLanguageSettings, Attach, CachedLspAdapter, LanguageName,
+    LanguageRegistry, LspAdapterDelegate,
 };
 use lsp::LanguageServerName;
 use settings::{Settings, SettingsLocation, WorktreeId};
@@ -27,7 +25,7 @@ use std::sync::OnceLock;
 
 use crate::{project_settings::LspSettings, LanguageServerId, ProjectPath};
 
-use super::{AdapterWrapper, ProjectTree, ProjectTreeEvent};
+use super::{ManifestTree, ManifestTreeEvent};
 
 #[derive(Debug, Default)]
 struct ServersForWorktree {
@@ -38,7 +36,7 @@ struct ServersForWorktree {
 }
 
 pub struct LanguageServerTree {
-    project_tree: Entity<ProjectTree>,
+    manifest_tree: Entity<ManifestTree>,
     instances: BTreeMap<WorktreeId, ServersForWorktree>,
     attach_kind_cache: HashMap<LanguageServerName, Attach>,
     languages: Arc<LanguageRegistry>,
@@ -133,30 +131,20 @@ pub(crate) enum AdapterQuery<'a> {
 
 impl LanguageServerTree {
     pub(crate) fn new(
-        project_tree: Entity<ProjectTree>,
+        manifest_tree: Entity<ManifestTree>,
         languages: Arc<LanguageRegistry>,
         cx: &mut App,
     ) -> Entity<Self> {
         cx.new(|cx| Self {
-            _subscriptions: cx.subscribe(
-                &project_tree,
-                |_: &mut Self, _, event, _| {
-                    if event == &ProjectTreeEvent::Cleared {}
-                },
-            ),
-            project_tree,
+            _subscriptions: cx.subscribe(&manifest_tree, |_: &mut Self, _, event, _| {
+                if event == &ManifestTreeEvent::Cleared {}
+            }),
+            manifest_tree,
             instances: Default::default(),
             attach_kind_cache: Default::default(),
             languages,
         })
     }
-    /// Memoize calls to attach_kind on LspAdapter (which might be a WASM extension, thus ~expensive to call).
-    fn attach_kind(&mut self, adapter: &AdapterWrapper) -> Attach {
-        *self
-            .attach_kind_cache
-            .entry(adapter.0.name.clone())
-            .or_insert_with(|| adapter.0.attach_kind())
-    }
 
     /// Get all language server root points for a given path and language; the language servers might already be initialized at a given path.
     pub(crate) fn get<'a>(
@@ -174,10 +162,14 @@ impl LanguageServerTree {
             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()))),
-            ),
+            AdapterQuery::Adapter(language_server_name) => {
+                IndexMap::from_iter(self.adapter_for_name(language_server_name).map(|adapter| {
+                    (
+                        adapter.name(),
+                        (LspSettings::default(), BTreeSet::new(), adapter),
+                    )
+                }))
+            }
         };
         self.get_with_adapters(path, adapters, delegate, cx)
     }
@@ -185,41 +177,48 @@ impl LanguageServerTree {
     fn get_with_adapters<'a>(
         &'a mut self,
         path: ProjectPath,
-        adapters: IndexMap<AdapterWrapper, (LspSettings, BTreeSet<LanguageName>)>,
+        adapters: IndexMap<
+            LanguageServerName,
+            (LspSettings, BTreeSet<LanguageName>, Arc<CachedLspAdapter>),
+        >,
         delegate: Arc<dyn LspAdapterDelegate>,
         cx: &mut App,
     ) -> impl Iterator<Item = LanguageServerTreeNode> + 'a {
         let worktree_id = path.worktree_id;
-        #[allow(clippy::mutable_key_type)]
-        let mut roots = self.project_tree.update(cx, |this, cx| {
+
+        let mut manifest_to_adapters = BTreeMap::default();
+        for (_, _, adapter) in adapters.values() {
+            if let Some(manifest_name) = adapter.manifest_name() {
+                manifest_to_adapters
+                    .entry(manifest_name)
+                    .or_insert_with(Vec::default)
+                    .push(adapter.clone());
+            }
+        }
+
+        let roots = self.manifest_tree.update(cx, |this, cx| {
             this.root_for_path(
                 path,
-                adapters
-                    .iter()
-                    .map(|(adapter, _)| adapter.0.clone())
-                    .collect(),
+                &mut manifest_to_adapters.keys().cloned(),
                 delegate,
                 cx,
             )
         });
-        let mut root_path = None;
-        // Backwards-compat: Fill in any adapters for which we did not detect the root as having the project root at the root of a worktree.
-        for (adapter, _) in adapters.iter() {
-            roots.entry(adapter.clone()).or_insert_with(|| {
-                root_path
-                    .get_or_insert_with(|| ProjectPath {
-                        worktree_id,
-                        path: Arc::from("".as_ref()),
-                    })
-                    .clone()
-            });
-        }
-
-        roots
+        let root_path = std::cell::LazyCell::new(move || ProjectPath {
+            worktree_id,
+            path: Arc::from("".as_ref()),
+        });
+        adapters
             .into_iter()
-            .filter_map(move |(adapter, root_path)| {
-                let attach = self.attach_kind(&adapter);
-                let (index, _, (settings, new_languages)) = adapters.get_full(&adapter)?;
+            .map(move |(_, (settings, new_languages, adapter))| {
+                // Backwards-compat: Fill in any adapters for which we did not detect the root as having the project root at the root of a worktree.
+                let root_path = adapter
+                    .manifest_name()
+                    .and_then(|name| roots.get(&name))
+                    .cloned()
+                    .unwrap_or_else(|| root_path.clone());
+                let attach = adapter.attach_kind();
+
                 let inner_node = self
                     .instances
                     .entry(root_path.worktree_id)
@@ -227,27 +226,25 @@ impl LanguageServerTree {
                     .roots
                     .entry(root_path.path.clone())
                     .or_default()
-                    .entry(adapter.0.name.clone());
-                let (node, languages) = inner_node.or_insert_with(move || {
+                    .entry(adapter.name());
+                let (node, languages) = inner_node.or_insert_with(|| {
                     (
                         Arc::new(InnerTreeNode::new(
-                            adapter.0.name(),
+                            adapter.name(),
                             attach,
-                            root_path,
+                            root_path.clone(),
                             settings.clone(),
                         )),
                         Default::default(),
                     )
                 });
                 languages.extend(new_languages.iter().cloned());
-                Some((index, Arc::downgrade(&node).into()))
+                Arc::downgrade(&node).into()
             })
-            .sorted_by_key(|(index, _)| *index)
-            .map(|(_, node)| node)
     }
 
-    fn adapter_for_name(&self, name: &LanguageServerName) -> Option<AdapterWrapper> {
-        self.languages.adapter_for_name(name).map(AdapterWrapper)
+    fn adapter_for_name(&self, name: &LanguageServerName) -> Option<Arc<CachedLspAdapter>> {
+        self.languages.adapter_for_name(name)
     }
 
     fn adapters_for_language(
@@ -255,7 +252,8 @@ impl LanguageServerTree {
         settings_location: SettingsLocation,
         language_name: &LanguageName,
         cx: &App,
-    ) -> IndexMap<AdapterWrapper, (LspSettings, BTreeSet<LanguageName>)> {
+    ) -> IndexMap<LanguageServerName, (LspSettings, BTreeSet<LanguageName>, Arc<CachedLspAdapter>)>
+    {
         let settings = AllLanguageSettings::get(Some(settings_location), cx).language(
             Some(settings_location),
             Some(language_name),
@@ -297,10 +295,11 @@ impl LanguageServerTree {
                 .cloned()
                 .unwrap_or_default();
                 Some((
-                    AdapterWrapper(adapter),
+                    adapter.name(),
                     (
                         adapter_settings,
                         BTreeSet::from_iter([language_name.clone()]),
+                        adapter,
                     ),
                 ))
             })
@@ -314,8 +313,8 @@ impl LanguageServerTree {
         self.languages.reorder_language_servers(
             &language_name,
             adapters_with_settings
-                .keys()
-                .map(|wrapper| wrapper.0.clone())
+                .values()
+                .map(|(_, _, adapter)| adapter.clone())
                 .collect(),
         );
 
@@ -392,11 +391,16 @@ impl<'tree> ServerTreeRebase<'tree> {
                 self.new_tree
                     .adapters_for_language(settings_location, language_name, cx)
             }
-            AdapterQuery::Adapter(language_server_name) => IndexMap::from_iter(
-                self.new_tree
-                    .adapter_for_name(language_server_name)
-                    .map(|adapter| (adapter, (LspSettings::default(), BTreeSet::new()))),
-            ),
+            AdapterQuery::Adapter(language_server_name) => {
+                IndexMap::from_iter(self.new_tree.adapter_for_name(language_server_name).map(
+                    |adapter| {
+                        (
+                            adapter.name(),
+                            (LspSettings::default(), BTreeSet::new(), adapter),
+                        )
+                    },
+                ))
+            }
         };
 
         self.new_tree

crates/project/src/project.rs 🔗

@@ -7,9 +7,9 @@ pub mod git_store;
 pub mod image_store;
 pub mod lsp_command;
 pub mod lsp_store;
+mod manifest_tree;
 pub mod prettier_store;
 pub mod project_settings;
-mod project_tree;
 pub mod search;
 mod task_inventory;
 pub mod task_store;
@@ -73,6 +73,7 @@ use lsp::{
 };
 use lsp_command::*;
 use lsp_store::{CompletionDocumentation, LspFormatTarget, OpenLspBufferHandle};
+pub use manifest_tree::ManifestProviders;
 use node_runtime::NodeRuntime;
 use parking_lot::Mutex;
 pub use prettier_store::PrettierStore;