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

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>
This commit is contained in:
Piotr Osiewicz 2025-03-21 15:22:36 +01:00 committed by GitHub
parent 6bced3a834
commit 05aa8880a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 241 additions and 154 deletions

View file

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

View file

@ -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>>;
}

View file

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

View file

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

View file

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

View file

@ -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 path_trie;
mod server_tree; mod server_tree;
@ -14,8 +16,8 @@ use std::{
use collections::HashMap; use collections::HashMap;
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription}; use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription};
use language::{CachedLspAdapter, LspAdapterDelegate}; use language::{LspAdapterDelegate, ManifestName, ManifestQuery};
use lsp::LanguageServerName; pub use manifest_store::ManifestProviders;
use path_trie::{LabelPresence, RootPathTrie, TriePath}; use path_trie::{LabelPresence, RootPathTrie, TriePath};
use settings::{SettingsStore, WorktreeId}; use settings::{SettingsStore, WorktreeId};
use worktree::{Event as WorktreeEvent, Worktree}; use worktree::{Event as WorktreeEvent, Worktree};
@ -28,7 +30,7 @@ use crate::{
pub(crate) use server_tree::{AdapterQuery, LanguageServerTree, LaunchDisposition}; pub(crate) use server_tree::{AdapterQuery, LanguageServerTree, LaunchDisposition};
struct WorktreeRoots { struct WorktreeRoots {
roots: RootPathTrie<LanguageServerName>, roots: RootPathTrie<ManifestName>,
worktree_store: Entity<WorktreeStore>, worktree_store: Entity<WorktreeStore>,
_worktree_subscription: Subscription, _worktree_subscription: Subscription,
} }
@ -70,55 +72,21 @@ impl WorktreeRoots {
} }
} }
pub struct ProjectTree { pub struct ManifestTree {
root_points: HashMap<WorktreeId, Entity<WorktreeRoots>>, root_points: HashMap<WorktreeId, Entity<WorktreeRoots>>,
worktree_store: Entity<WorktreeStore>, worktree_store: Entity<WorktreeStore>,
_subscriptions: [Subscription; 2], _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)] #[derive(PartialEq)]
pub(crate) enum ProjectTreeEvent { pub(crate) enum ManifestTreeEvent {
WorktreeRemoved(WorktreeId), WorktreeRemoved(WorktreeId),
Cleared, 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> { pub(crate) fn new(worktree_store: Entity<WorktreeStore>, cx: &mut App) -> Entity<Self> {
cx.new(|cx| Self { cx.new(|cx| Self {
root_points: Default::default(), root_points: Default::default(),
@ -130,26 +98,22 @@ impl ProjectTree {
worktree_roots.roots = RootPathTrie::new(); worktree_roots.roots = RootPathTrie::new();
}) })
} }
cx.emit(ProjectTreeEvent::Cleared); cx.emit(ManifestTreeEvent::Cleared);
}), }),
], ],
worktree_store, worktree_store,
}) })
} }
#[allow(clippy::mutable_key_type)]
fn root_for_path( fn root_for_path(
&mut self, &mut self,
ProjectPath { worktree_id, path }: ProjectPath, ProjectPath { worktree_id, path }: ProjectPath,
adapters: Vec<Arc<CachedLspAdapter>>, manifests: &mut dyn Iterator<Item = ManifestName>,
delegate: Arc<dyn LspAdapterDelegate>, delegate: Arc<dyn LspAdapterDelegate>,
cx: &mut App, cx: &mut App,
) -> BTreeMap<AdapterWrapper, ProjectPath> { ) -> BTreeMap<ManifestName, ProjectPath> {
debug_assert_eq!(delegate.worktree_id(), worktree_id); debug_assert_eq!(delegate.worktree_id(), worktree_id);
#[allow(clippy::mutable_key_type)]
let mut roots = BTreeMap::from_iter( let mut roots = BTreeMap::from_iter(
adapters manifests.map(|manifest| (manifest, (None, LabelPresence::KnownAbsent))),
.into_iter()
.map(|adapter| (AdapterWrapper(adapter), (None, LabelPresence::KnownAbsent))),
); );
let worktree_roots = match self.root_points.entry(worktree_id) { let worktree_roots = match self.root_points.entry(worktree_id) {
Entry::Occupied(occupied_entry) => occupied_entry.get().clone(), Entry::Occupied(occupied_entry) => occupied_entry.get().clone(),
@ -182,7 +146,8 @@ impl ProjectTree {
ControlFlow::Continue(()) ControlFlow::Continue(())
}); });
}); });
for (adapter, (root_path, presence)) in &mut roots {
for (manifest_name, (root_path, presence)) in &mut roots {
if *presence == LabelPresence::Present { if *presence == LabelPresence::Present {
continue; continue;
} }
@ -198,12 +163,22 @@ impl ProjectTree {
.unwrap_or_else(|| path.components().count() + 1); .unwrap_or_else(|| path.components().count() + 1);
if depth > 0 { 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 { match root {
Some(known_root) => worktree_roots.update(cx, |this, _| { Some(known_root) => worktree_roots.update(cx, |this, _| {
let root = TriePath::from(&*known_root); let root = TriePath::from(&*known_root);
this.roots this.roots
.insert(&root, adapter.0.name(), LabelPresence::Present); .insert(&root, manifest_name.clone(), LabelPresence::Present);
*presence = LabelPresence::Present; *presence = LabelPresence::Present;
*root_path = Some(ProjectPath { *root_path = Some(ProjectPath {
worktree_id, worktree_id,
@ -212,7 +187,7 @@ impl ProjectTree {
}), }),
None => worktree_roots.update(cx, |this, _| { None => worktree_roots.update(cx, |this, _| {
this.roots this.roots
.insert(&key, adapter.0.name(), LabelPresence::KnownAbsent); .insert(&key, manifest_name.clone(), LabelPresence::KnownAbsent);
}), }),
} }
} }
@ -235,7 +210,7 @@ impl ProjectTree {
match evt { match evt {
WorktreeStoreEvent::WorktreeRemoved(_, worktree_id) => { WorktreeStoreEvent::WorktreeRemoved(_, worktree_id) => {
self.root_points.remove(&worktree_id); self.root_points.remove(&worktree_id);
cx.emit(ProjectTreeEvent::WorktreeRemoved(*worktree_id)); cx.emit(ManifestTreeEvent::WorktreeRemoved(*worktree_id));
} }
_ => {} _ => {}
} }

View file

@ -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()
}
}

View file

@ -6,7 +6,7 @@ use std::{
sync::Arc, 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. /// 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. /// Conceptually, it allows one to annotate Worktree entries with arbitrary extra metadata and run closest-ancestor searches.
/// ///

View file

@ -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 //! 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 //! 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. //! 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::{ use std::{
collections::{BTreeMap, BTreeSet}, collections::{BTreeMap, BTreeSet},
@ -16,10 +15,9 @@ use std::{
use collections::{HashMap, IndexMap}; use collections::{HashMap, IndexMap};
use gpui::{App, AppContext as _, Entity, Subscription}; use gpui::{App, AppContext as _, Entity, Subscription};
use itertools::Itertools;
use language::{ use language::{
language_settings::AllLanguageSettings, Attach, LanguageName, LanguageRegistry, language_settings::AllLanguageSettings, Attach, CachedLspAdapter, LanguageName,
LspAdapterDelegate, LanguageRegistry, LspAdapterDelegate,
}; };
use lsp::LanguageServerName; use lsp::LanguageServerName;
use settings::{Settings, SettingsLocation, WorktreeId}; use settings::{Settings, SettingsLocation, WorktreeId};
@ -27,7 +25,7 @@ use std::sync::OnceLock;
use crate::{project_settings::LspSettings, LanguageServerId, ProjectPath}; use crate::{project_settings::LspSettings, LanguageServerId, ProjectPath};
use super::{AdapterWrapper, ProjectTree, ProjectTreeEvent}; use super::{ManifestTree, ManifestTreeEvent};
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct ServersForWorktree { struct ServersForWorktree {
@ -38,7 +36,7 @@ struct ServersForWorktree {
} }
pub struct LanguageServerTree { pub struct LanguageServerTree {
project_tree: Entity<ProjectTree>, manifest_tree: Entity<ManifestTree>,
instances: BTreeMap<WorktreeId, ServersForWorktree>, instances: BTreeMap<WorktreeId, ServersForWorktree>,
attach_kind_cache: HashMap<LanguageServerName, Attach>, attach_kind_cache: HashMap<LanguageServerName, Attach>,
languages: Arc<LanguageRegistry>, languages: Arc<LanguageRegistry>,
@ -133,30 +131,20 @@ pub(crate) enum AdapterQuery<'a> {
impl LanguageServerTree { impl LanguageServerTree {
pub(crate) fn new( pub(crate) fn new(
project_tree: Entity<ProjectTree>, manifest_tree: Entity<ManifestTree>,
languages: Arc<LanguageRegistry>, languages: Arc<LanguageRegistry>,
cx: &mut App, cx: &mut App,
) -> Entity<Self> { ) -> Entity<Self> {
cx.new(|cx| Self { cx.new(|cx| Self {
_subscriptions: cx.subscribe( _subscriptions: cx.subscribe(&manifest_tree, |_: &mut Self, _, event, _| {
&project_tree, if event == &ManifestTreeEvent::Cleared {}
|_: &mut Self, _, event, _| { }),
if event == &ProjectTreeEvent::Cleared {} manifest_tree,
},
),
project_tree,
instances: Default::default(), instances: Default::default(),
attach_kind_cache: Default::default(), attach_kind_cache: Default::default(),
languages, 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. /// 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>( pub(crate) fn get<'a>(
@ -174,10 +162,14 @@ impl LanguageServerTree {
AdapterQuery::Language(language_name) => { AdapterQuery::Language(language_name) => {
self.adapters_for_language(settings_location, language_name, cx) self.adapters_for_language(settings_location, language_name, cx)
} }
AdapterQuery::Adapter(language_server_name) => IndexMap::from_iter( AdapterQuery::Adapter(language_server_name) => {
self.adapter_for_name(language_server_name) IndexMap::from_iter(self.adapter_for_name(language_server_name).map(|adapter| {
.map(|adapter| (adapter, (LspSettings::default(), BTreeSet::new()))), (
), adapter.name(),
(LspSettings::default(), BTreeSet::new(), adapter),
)
}))
}
}; };
self.get_with_adapters(path, adapters, delegate, cx) self.get_with_adapters(path, adapters, delegate, cx)
} }
@ -185,41 +177,48 @@ impl LanguageServerTree {
fn get_with_adapters<'a>( fn get_with_adapters<'a>(
&'a mut self, &'a mut self,
path: ProjectPath, path: ProjectPath,
adapters: IndexMap<AdapterWrapper, (LspSettings, BTreeSet<LanguageName>)>, adapters: IndexMap<
LanguageServerName,
(LspSettings, BTreeSet<LanguageName>, Arc<CachedLspAdapter>),
>,
delegate: Arc<dyn LspAdapterDelegate>, delegate: Arc<dyn LspAdapterDelegate>,
cx: &mut App, cx: &mut App,
) -> impl Iterator<Item = LanguageServerTreeNode> + 'a { ) -> impl Iterator<Item = LanguageServerTreeNode> + 'a {
let worktree_id = path.worktree_id; 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( this.root_for_path(
path, path,
adapters &mut manifest_to_adapters.keys().cloned(),
.iter()
.map(|(adapter, _)| adapter.0.clone())
.collect(),
delegate, delegate,
cx, cx,
) )
}); });
let mut root_path = None; let root_path = std::cell::LazyCell::new(move || ProjectPath {
// 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. worktree_id,
for (adapter, _) in adapters.iter() { path: Arc::from("".as_ref()),
roots.entry(adapter.clone()).or_insert_with(|| { });
root_path adapters
.get_or_insert_with(|| ProjectPath {
worktree_id,
path: Arc::from("".as_ref()),
})
.clone()
});
}
roots
.into_iter() .into_iter()
.filter_map(move |(adapter, root_path)| { .map(move |(_, (settings, new_languages, adapter))| {
let attach = self.attach_kind(&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 (index, _, (settings, new_languages)) = adapters.get_full(&adapter)?; 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 let inner_node = self
.instances .instances
.entry(root_path.worktree_id) .entry(root_path.worktree_id)
@ -227,27 +226,25 @@ impl LanguageServerTree {
.roots .roots
.entry(root_path.path.clone()) .entry(root_path.path.clone())
.or_default() .or_default()
.entry(adapter.0.name.clone()); .entry(adapter.name());
let (node, languages) = inner_node.or_insert_with(move || { let (node, languages) = inner_node.or_insert_with(|| {
( (
Arc::new(InnerTreeNode::new( Arc::new(InnerTreeNode::new(
adapter.0.name(), adapter.name(),
attach, attach,
root_path, root_path.clone(),
settings.clone(), settings.clone(),
)), )),
Default::default(), Default::default(),
) )
}); });
languages.extend(new_languages.iter().cloned()); 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> { fn adapter_for_name(&self, name: &LanguageServerName) -> Option<Arc<CachedLspAdapter>> {
self.languages.adapter_for_name(name).map(AdapterWrapper) self.languages.adapter_for_name(name)
} }
fn adapters_for_language( fn adapters_for_language(
@ -255,7 +252,8 @@ impl LanguageServerTree {
settings_location: SettingsLocation, settings_location: SettingsLocation,
language_name: &LanguageName, language_name: &LanguageName,
cx: &App, cx: &App,
) -> IndexMap<AdapterWrapper, (LspSettings, BTreeSet<LanguageName>)> { ) -> IndexMap<LanguageServerName, (LspSettings, BTreeSet<LanguageName>, Arc<CachedLspAdapter>)>
{
let settings = AllLanguageSettings::get(Some(settings_location), cx).language( let settings = AllLanguageSettings::get(Some(settings_location), cx).language(
Some(settings_location), Some(settings_location),
Some(language_name), Some(language_name),
@ -297,10 +295,11 @@ impl LanguageServerTree {
.cloned() .cloned()
.unwrap_or_default(); .unwrap_or_default();
Some(( Some((
AdapterWrapper(adapter), adapter.name(),
( (
adapter_settings, adapter_settings,
BTreeSet::from_iter([language_name.clone()]), BTreeSet::from_iter([language_name.clone()]),
adapter,
), ),
)) ))
}) })
@ -314,8 +313,8 @@ impl LanguageServerTree {
self.languages.reorder_language_servers( self.languages.reorder_language_servers(
&language_name, &language_name,
adapters_with_settings adapters_with_settings
.keys() .values()
.map(|wrapper| wrapper.0.clone()) .map(|(_, _, adapter)| adapter.clone())
.collect(), .collect(),
); );
@ -392,11 +391,16 @@ impl<'tree> ServerTreeRebase<'tree> {
self.new_tree self.new_tree
.adapters_for_language(settings_location, language_name, cx) .adapters_for_language(settings_location, language_name, cx)
} }
AdapterQuery::Adapter(language_server_name) => IndexMap::from_iter( AdapterQuery::Adapter(language_server_name) => {
self.new_tree IndexMap::from_iter(self.new_tree.adapter_for_name(language_server_name).map(
.adapter_for_name(language_server_name) |adapter| {
.map(|adapter| (adapter, (LspSettings::default(), BTreeSet::new()))), (
), adapter.name(),
(LspSettings::default(), BTreeSet::new(), adapter),
)
},
))
}
}; };
self.new_tree self.new_tree

View file

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