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:
parent
d85fec5f13
commit
bed917b0b1
29 changed files with 2151 additions and 943 deletions
|
@ -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>(
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue