project: Allow running multiple instances of a single language server within a single worktree (#22182)

This PR introduces a new entity called Project Tree which is responsible
for finding subprojects within a worktree;
a subproject is a language-specific subset of a worktree which should be
accurately tracked on the language server side. We'll have an ability to
set multiple disjoint `workspaceFolder`s on language server side OR
spawn multiple instances of a single language server (which will be the
case with e.g. Python language servers, as they need to interact with
multiple disjoint virtual environments).
Project Tree assumes that projects of the same LspAdapter kind cannot
overlap. Additionally **project nesting** is not allowed within the
scope of a single LspAdapter.

Closes #5108

Release Notes:

- Language servers now track their working directory more accurately.

---------

Co-authored-by: João <joao@zed.dev>
This commit is contained in:
Piotr Osiewicz 2025-01-22 17:31:14 +01:00 committed by GitHub
parent d85fec5f13
commit bed917b0b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 2151 additions and 943 deletions

View file

@ -45,7 +45,6 @@ use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use settings::WorktreeId;
use smol::future::FutureExt as _;
use std::num::NonZeroU32;
use std::{
any::Any,
ffi::OsStr,
@ -61,6 +60,7 @@ use std::{
Arc, LazyLock,
},
};
use std::{num::NonZeroU32, sync::OnceLock};
use syntax_map::{QueryCursorHandle, SyntaxSnapshot};
use task::RunnableTag;
pub use task_context::{ContextProvider, RunnableRange};
@ -163,6 +163,7 @@ pub struct CachedLspAdapter {
pub adapter: Arc<dyn LspAdapter>,
pub reinstall_attempt_count: AtomicU64,
cached_binary: futures::lock::Mutex<Option<LanguageServerBinary>>,
attach_kind: OnceLock<Attach>,
}
impl Debug for CachedLspAdapter {
@ -198,6 +199,7 @@ impl CachedLspAdapter {
adapter,
cached_binary: Default::default(),
reinstall_attempt_count: AtomicU64::new(0),
attach_kind: Default::default(),
})
}
@ -259,6 +261,38 @@ 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 attach_kind(&self) -> Attach {
*self.attach_kind.get_or_init(|| self.adapter.attach_kind())
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Attach {
/// Create a single language server instance per subproject root.
InstancePerRoot,
/// Use one shared language server instance for all subprojects within a project.
Shared,
}
impl Attach {
pub fn root_path(
&self,
root_subproject_path: (WorktreeId, Arc<Path>),
) -> (WorktreeId, Arc<Path>) {
match self {
Attach::InstancePerRoot => root_subproject_path,
Attach::Shared => (root_subproject_path.0, Arc::from(Path::new(""))),
}
}
}
/// [`LspAdapterDelegate`] allows [`LspAdapter]` implementations to interface with the application
@ -505,6 +539,19 @@ 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()))
}
}
async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized>(

View file

@ -96,6 +96,7 @@ struct LanguageRegistryState {
available_languages: Vec<AvailableLanguage>,
grammars: HashMap<Arc<str>, AvailableGrammar>,
lsp_adapters: HashMap<LanguageName, Vec<Arc<CachedLspAdapter>>>,
all_lsp_adapters: HashMap<LanguageServerName, Arc<CachedLspAdapter>>,
available_lsp_adapters:
HashMap<LanguageServerName, Arc<dyn Fn() -> Arc<CachedLspAdapter> + 'static + Send + Sync>>,
loading_languages: HashMap<LanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
@ -222,6 +223,7 @@ impl LanguageRegistry {
language_settings: Default::default(),
loading_languages: Default::default(),
lsp_adapters: Default::default(),
all_lsp_adapters: Default::default(),
available_lsp_adapters: HashMap::default(),
subscription: watch::channel(),
theme: Default::default(),
@ -344,12 +346,16 @@ impl LanguageRegistry {
adapter: Arc<dyn LspAdapter>,
) -> Arc<CachedLspAdapter> {
let cached = CachedLspAdapter::new(adapter);
self.state
.write()
let mut state = self.state.write();
state
.lsp_adapters
.entry(language_name)
.or_default()
.push(cached.clone());
state
.all_lsp_adapters
.insert(cached.name.clone(), cached.clone());
cached
}
@ -389,12 +395,17 @@ impl LanguageRegistry {
let adapter_name = LanguageServerName(adapter.name.into());
let capabilities = adapter.capabilities.clone();
let initializer = adapter.initializer.take();
self.state
.write()
.lsp_adapters
.entry(language_name.clone())
.or_default()
.push(CachedLspAdapter::new(Arc::new(adapter)));
let adapter = CachedLspAdapter::new(Arc::new(adapter));
{
let mut state = self.state.write();
state
.lsp_adapters
.entry(language_name.clone())
.or_default()
.push(adapter.clone());
state.all_lsp_adapters.insert(adapter.name(), adapter);
}
self.register_fake_language_server(adapter_name, capabilities, initializer)
}
@ -407,12 +418,16 @@ impl LanguageRegistry {
adapter: crate::FakeLspAdapter,
) {
let language_name = language_name.into();
self.state
.write()
let mut state = self.state.write();
let cached_adapter = CachedLspAdapter::new(Arc::new(adapter));
state
.lsp_adapters
.entry(language_name.clone())
.or_default()
.push(CachedLspAdapter::new(Arc::new(adapter)));
.push(cached_adapter.clone());
state
.all_lsp_adapters
.insert(cached_adapter.name(), cached_adapter);
}
/// Register a fake language server (without the adapter)
@ -880,6 +895,10 @@ impl LanguageRegistry {
.unwrap_or_default()
}
pub fn adapter_for_name(&self, name: &LanguageServerName) -> Option<Arc<CachedLspAdapter>> {
self.state.read().all_lsp_adapters.get(name).cloned()
}
pub fn update_lsp_status(
&self,
server_name: LanguageServerName,