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

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 workspaceFolders 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 https://github.com/zed-industries/zed/issues/5108
Re-lands #22182 which I had to revert due to merging it into todays
Preview.

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 21:19:02 +01:00 committed by GitHub
parent 2c2a3ef13d
commit 08b3c03241
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 2151 additions and 943 deletions

View file

@ -9,6 +9,7 @@ pub mod lsp_ext_command;
pub mod lsp_store;
pub mod prettier_store;
pub mod project_settings;
mod project_tree;
pub mod search;
mod task_inventory;
pub mod task_store;
@ -474,6 +475,7 @@ pub struct DocumentHighlight {
pub struct Symbol {
pub language_server_name: LanguageServerName,
pub source_worktree_id: WorktreeId,
pub source_language_server_id: LanguageServerId,
pub path: ProjectPath,
pub label: CodeLabel,
pub name: String,
@ -1890,7 +1892,7 @@ impl Project {
pub fn open_buffer(
&mut self,
path: impl Into<ProjectPath>,
cx: &mut ModelContext<Self>,
cx: &mut AppContext,
) -> Task<Result<Model<Buffer>>> {
if self.is_disconnected(cx) {
return Task::ready(Err(anyhow!(ErrorCode::Disconnected)));
@ -1905,11 +1907,11 @@ impl Project {
pub fn open_buffer_with_lsp(
&mut self,
path: impl Into<ProjectPath>,
cx: &mut ModelContext<Self>,
cx: &mut AppContext,
) -> Task<Result<(Model<Buffer>, lsp_store::OpenLspBufferHandle)>> {
let buffer = self.open_buffer(path, cx);
let lsp_store = self.lsp_store().clone();
cx.spawn(|_, mut cx| async move {
cx.spawn(|mut cx| async move {
let buffer = buffer.await?;
let handle = lsp_store.update(&mut cx, |lsp_store, cx| {
lsp_store.register_buffer_with_language_servers(&buffer, cx)
@ -4145,14 +4147,25 @@ impl Project {
self.lsp_store.read(cx).supplementary_language_servers()
}
pub fn language_servers_for_local_buffer<'a>(
&'a self,
buffer: &'a Buffer,
cx: &'a AppContext,
) -> impl Iterator<Item = (&'a Arc<CachedLspAdapter>, &'a Arc<LanguageServer>)> {
self.lsp_store
.read(cx)
.language_servers_for_local_buffer(buffer, cx)
pub fn language_server_for_id(
&self,
id: LanguageServerId,
cx: &AppContext,
) -> Option<Arc<LanguageServer>> {
self.lsp_store.read(cx).language_server_for_id(id)
}
pub fn for_language_servers_for_local_buffer<R: 'static>(
&self,
buffer: &Buffer,
callback: impl FnOnce(
Box<dyn Iterator<Item = (&Arc<CachedLspAdapter>, &Arc<LanguageServer>)> + '_>,
) -> R,
cx: &mut AppContext,
) -> R {
self.lsp_store.update(cx, |this, cx| {
callback(Box::new(this.language_servers_for_local_buffer(buffer, cx)))
})
}
pub fn buffer_store(&self) -> &Model<BufferStore> {