Reuse existing language servers for invisible worktrees (#30707)
Closes https://github.com/zed-industries/zed/issues/20767 Before: https://github.com/user-attachments/assets/6438eb26-796a-4586-9b20-f49d9a133624 After: https://github.com/user-attachments/assets/b3fc2f8b-2873-443f-8d80-ab4a35cf0c09 Release Notes: - Fixed external files spawning extra language servers
This commit is contained in:
parent
ef511976be
commit
fcfe4e2c14
6 changed files with 400 additions and 107 deletions
|
@ -9,7 +9,9 @@ use crate::{
|
|||
environment::ProjectEnvironment,
|
||||
lsp_command::{self, *},
|
||||
lsp_store,
|
||||
manifest_tree::{AdapterQuery, LanguageServerTree, LaunchDisposition, ManifestTree},
|
||||
manifest_tree::{
|
||||
AdapterQuery, LanguageServerTree, LanguageServerTreeNode, LaunchDisposition, ManifestTree,
|
||||
},
|
||||
prettier_store::{self, PrettierStore, PrettierStoreEvent},
|
||||
project_settings::{LspSettings, ProjectSettings},
|
||||
relativize_path, resolve_path,
|
||||
|
@ -36,7 +38,7 @@ use http_client::HttpClient;
|
|||
use itertools::Itertools as _;
|
||||
use language::{
|
||||
Bias, BinaryStatus, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic,
|
||||
DiagnosticEntry, DiagnosticSet, Diff, File as _, Language, LanguageRegistry,
|
||||
DiagnosticEntry, DiagnosticSet, Diff, File as _, Language, LanguageName, LanguageRegistry,
|
||||
LanguageToolchainStore, LocalFile, LspAdapter, LspAdapterDelegate, Patch, PointUtf16,
|
||||
TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||
language_settings::{
|
||||
|
@ -73,7 +75,7 @@ use std::{
|
|||
any::Any,
|
||||
borrow::Cow,
|
||||
cell::RefCell,
|
||||
cmp::Ordering,
|
||||
cmp::{Ordering, Reverse},
|
||||
convert::TryInto,
|
||||
ffi::OsStr,
|
||||
iter, mem,
|
||||
|
@ -1032,7 +1034,7 @@ impl LocalLspStore {
|
|||
.read(cx)
|
||||
.worktree_for_id(project_path.worktree_id, cx)
|
||||
else {
|
||||
return vec![];
|
||||
return Vec::new();
|
||||
};
|
||||
let delegate = LocalLspAdapterDelegate::from_local_lsp(self, &worktree, cx);
|
||||
let root = self.lsp_tree.update(cx, |this, cx| {
|
||||
|
@ -2284,19 +2286,37 @@ impl LocalLspStore {
|
|||
else {
|
||||
return;
|
||||
};
|
||||
let delegate = LocalLspAdapterDelegate::from_local_lsp(self, &worktree, cx);
|
||||
let servers = self.lsp_tree.clone().update(cx, |this, cx| {
|
||||
this.get(
|
||||
ProjectPath { worktree_id, path },
|
||||
AdapterQuery::Language(&language.name()),
|
||||
delegate.clone(),
|
||||
cx,
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
let language_name = language.name();
|
||||
let (reused, delegate, servers) = self
|
||||
.lsp_tree
|
||||
.update(cx, |lsp_tree, cx| {
|
||||
self.reuse_existing_language_server(lsp_tree, &worktree, &language_name, cx)
|
||||
})
|
||||
.map(|(delegate, servers)| (true, delegate, servers))
|
||||
.unwrap_or_else(|| {
|
||||
let delegate = LocalLspAdapterDelegate::from_local_lsp(self, &worktree, cx);
|
||||
let servers = self
|
||||
.lsp_tree
|
||||
.clone()
|
||||
.update(cx, |language_server_tree, cx| {
|
||||
language_server_tree
|
||||
.get(
|
||||
ProjectPath { worktree_id, path },
|
||||
AdapterQuery::Language(&language.name()),
|
||||
delegate.clone(),
|
||||
cx,
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
(false, delegate, servers)
|
||||
});
|
||||
let servers = servers
|
||||
.into_iter()
|
||||
.filter_map(|server_node| {
|
||||
if reused && server_node.server_id().is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let server_id = server_node.server_id_or_init(
|
||||
|LaunchDisposition {
|
||||
server_name,
|
||||
|
@ -2435,6 +2455,63 @@ impl LocalLspStore {
|
|||
}
|
||||
}
|
||||
|
||||
fn reuse_existing_language_server(
|
||||
&self,
|
||||
server_tree: &mut LanguageServerTree,
|
||||
worktree: &Entity<Worktree>,
|
||||
language_name: &LanguageName,
|
||||
cx: &mut App,
|
||||
) -> Option<(Arc<LocalLspAdapterDelegate>, Vec<LanguageServerTreeNode>)> {
|
||||
if worktree.read(cx).is_visible() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let worktree_store = self.worktree_store.read(cx);
|
||||
let servers = server_tree
|
||||
.instances
|
||||
.iter()
|
||||
.filter(|(worktree_id, _)| {
|
||||
worktree_store
|
||||
.worktree_for_id(**worktree_id, cx)
|
||||
.is_some_and(|worktree| worktree.read(cx).is_visible())
|
||||
})
|
||||
.flat_map(|(worktree_id, servers)| {
|
||||
servers
|
||||
.roots
|
||||
.iter()
|
||||
.flat_map(|(_, language_servers)| language_servers)
|
||||
.map(move |(_, (server_node, server_languages))| {
|
||||
(worktree_id, server_node, server_languages)
|
||||
})
|
||||
.filter(|(_, _, server_languages)| server_languages.contains(language_name))
|
||||
.map(|(worktree_id, server_node, _)| {
|
||||
(
|
||||
*worktree_id,
|
||||
LanguageServerTreeNode::from(Arc::downgrade(server_node)),
|
||||
)
|
||||
})
|
||||
})
|
||||
.fold(HashMap::default(), |mut acc, (worktree_id, server_node)| {
|
||||
acc.entry(worktree_id)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(server_node);
|
||||
acc
|
||||
})
|
||||
.into_values()
|
||||
.max_by_key(|servers| servers.len())?;
|
||||
|
||||
for server_node in &servers {
|
||||
server_tree.register_reused(
|
||||
worktree.read(cx).id(),
|
||||
language_name.clone(),
|
||||
server_node.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
let delegate = LocalLspAdapterDelegate::from_local_lsp(self, worktree, cx);
|
||||
Some((delegate, servers))
|
||||
}
|
||||
|
||||
pub(crate) fn unregister_old_buffer_from_language_servers(
|
||||
&mut self,
|
||||
buffer: &Entity<Buffer>,
|
||||
|
@ -4018,6 +4095,16 @@ impl LspStore {
|
|||
buffers_with_unknown_injections.push(handle);
|
||||
}
|
||||
}
|
||||
|
||||
// Deprioritize the invisible worktrees so main worktrees' language servers can be started first,
|
||||
// and reused later in the invisible worktrees.
|
||||
plain_text_buffers.sort_by_key(|buffer| {
|
||||
Reverse(
|
||||
crate::File::from_dyn(buffer.read(cx).file())
|
||||
.map(|file| file.worktree.read(cx).is_visible()),
|
||||
)
|
||||
});
|
||||
|
||||
for buffer in plain_text_buffers {
|
||||
this.detect_language_for_buffer(&buffer, cx);
|
||||
if let Some(local) = this.as_local_mut() {
|
||||
|
@ -4355,8 +4442,13 @@ impl LspStore {
|
|||
};
|
||||
|
||||
let mut rebase = lsp_tree.rebase();
|
||||
for buffer in buffer_store.read(cx).buffers().collect::<Vec<_>>() {
|
||||
let buffer = buffer.read(cx);
|
||||
for buffer_handle in buffer_store.read(cx).buffers().sorted_by_key(|buffer| {
|
||||
Reverse(
|
||||
crate::File::from_dyn(buffer.read(cx).file())
|
||||
.map(|file| file.worktree.read(cx).is_visible()),
|
||||
)
|
||||
}) {
|
||||
let buffer = buffer_handle.read(cx);
|
||||
if !local.registered_buffers.contains_key(&buffer.remote_id()) {
|
||||
continue;
|
||||
}
|
||||
|
@ -4372,43 +4464,81 @@ impl LspStore {
|
|||
else {
|
||||
continue;
|
||||
};
|
||||
let path: Arc<Path> = file
|
||||
.path()
|
||||
.parent()
|
||||
.map(Arc::from)
|
||||
.unwrap_or_else(|| file.path().clone());
|
||||
let worktree_path = ProjectPath { worktree_id, path };
|
||||
|
||||
let Some(delegate) = adapters
|
||||
.entry(worktree_id)
|
||||
.or_insert_with(|| get_adapter(worktree_id, cx))
|
||||
.clone()
|
||||
let Some((reused, delegate, nodes)) = local
|
||||
.reuse_existing_language_server(
|
||||
rebase.server_tree(),
|
||||
&worktree,
|
||||
&language,
|
||||
cx,
|
||||
)
|
||||
.map(|(delegate, servers)| (true, delegate, servers))
|
||||
.or_else(|| {
|
||||
let delegate = adapters
|
||||
.entry(worktree_id)
|
||||
.or_insert_with(|| get_adapter(worktree_id, cx))
|
||||
.clone()?;
|
||||
let path = file
|
||||
.path()
|
||||
.parent()
|
||||
.map(Arc::from)
|
||||
.unwrap_or_else(|| file.path().clone());
|
||||
let worktree_path = ProjectPath { worktree_id, path };
|
||||
|
||||
let nodes = rebase.get(
|
||||
worktree_path,
|
||||
AdapterQuery::Language(&language),
|
||||
delegate.clone(),
|
||||
cx,
|
||||
);
|
||||
|
||||
Some((false, delegate, nodes.collect()))
|
||||
})
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let nodes = rebase.get(
|
||||
worktree_path,
|
||||
AdapterQuery::Language(&language),
|
||||
delegate.clone(),
|
||||
cx,
|
||||
);
|
||||
|
||||
for node in nodes {
|
||||
node.server_id_or_init(
|
||||
|LaunchDisposition {
|
||||
server_name,
|
||||
attach,
|
||||
path,
|
||||
settings,
|
||||
}| match attach {
|
||||
language::Attach::InstancePerRoot => {
|
||||
// todo: handle instance per root proper.
|
||||
if let Some(server_ids) = local
|
||||
.language_server_ids
|
||||
.get(&(worktree_id, server_name.clone()))
|
||||
{
|
||||
server_ids.iter().cloned().next().unwrap()
|
||||
} else {
|
||||
local.start_language_server(
|
||||
if !reused {
|
||||
node.server_id_or_init(
|
||||
|LaunchDisposition {
|
||||
server_name,
|
||||
attach,
|
||||
path,
|
||||
settings,
|
||||
}| match attach {
|
||||
language::Attach::InstancePerRoot => {
|
||||
// todo: handle instance per root proper.
|
||||
if let Some(server_ids) = local
|
||||
.language_server_ids
|
||||
.get(&(worktree_id, server_name.clone()))
|
||||
{
|
||||
server_ids.iter().cloned().next().unwrap()
|
||||
} else {
|
||||
local.start_language_server(
|
||||
&worktree,
|
||||
delegate.clone(),
|
||||
local
|
||||
.languages
|
||||
.lsp_adapters(&language)
|
||||
.into_iter()
|
||||
.find(|adapter| {
|
||||
&adapter.name() == server_name
|
||||
})
|
||||
.expect("To find LSP adapter"),
|
||||
settings,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}
|
||||
language::Attach::Shared => {
|
||||
let uri = Url::from_file_path(
|
||||
worktree.read(cx).abs_path().join(&path.path),
|
||||
);
|
||||
let key = (worktree_id, server_name.clone());
|
||||
local.language_server_ids.remove(&key);
|
||||
|
||||
let server_id = local.start_language_server(
|
||||
&worktree,
|
||||
delegate.clone(),
|
||||
local
|
||||
|
@ -4419,38 +4549,19 @@ impl LspStore {
|
|||
.expect("To find LSP adapter"),
|
||||
settings,
|
||||
cx,
|
||||
)
|
||||
);
|
||||
if let Some(state) =
|
||||
local.language_servers.get(&server_id)
|
||||
{
|
||||
if let Ok(uri) = uri {
|
||||
state.add_workspace_folder(uri);
|
||||
};
|
||||
}
|
||||
server_id
|
||||
}
|
||||
}
|
||||
language::Attach::Shared => {
|
||||
let uri = Url::from_file_path(
|
||||
worktree.read(cx).abs_path().join(&path.path),
|
||||
);
|
||||
let key = (worktree_id, server_name.clone());
|
||||
local.language_server_ids.remove(&key);
|
||||
|
||||
let server_id = local.start_language_server(
|
||||
&worktree,
|
||||
delegate.clone(),
|
||||
local
|
||||
.languages
|
||||
.lsp_adapters(&language)
|
||||
.into_iter()
|
||||
.find(|adapter| &adapter.name() == server_name)
|
||||
.expect("To find LSP adapter"),
|
||||
settings,
|
||||
cx,
|
||||
);
|
||||
if let Some(state) = local.language_servers.get(&server_id)
|
||||
{
|
||||
if let Ok(uri) = uri {
|
||||
state.add_workspace_folder(uri);
|
||||
};
|
||||
}
|
||||
server_id
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6365,26 +6476,30 @@ impl LspStore {
|
|||
let Some(local) = self.as_local_mut() else {
|
||||
return;
|
||||
};
|
||||
let worktree_id = worktree.read(cx).id();
|
||||
let path = ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from("".as_ref()),
|
||||
};
|
||||
let delegate = LocalLspAdapterDelegate::from_local_lsp(local, &worktree, cx);
|
||||
local.lsp_tree.update(cx, |this, cx| {
|
||||
for node in this.get(
|
||||
path,
|
||||
AdapterQuery::Adapter(&language_server_name),
|
||||
delegate,
|
||||
cx,
|
||||
) {
|
||||
node.server_id_or_init(|disposition| {
|
||||
assert_eq!(disposition.server_name, &language_server_name);
|
||||
|
||||
language_server_id
|
||||
});
|
||||
}
|
||||
});
|
||||
let worktree_id = worktree.read(cx).id();
|
||||
if worktree.read(cx).is_visible() {
|
||||
let path = ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::from("".as_ref()),
|
||||
};
|
||||
let delegate = LocalLspAdapterDelegate::from_local_lsp(local, &worktree, cx);
|
||||
local.lsp_tree.update(cx, |language_server_tree, cx| {
|
||||
for node in language_server_tree.get(
|
||||
path,
|
||||
AdapterQuery::Adapter(&language_server_name),
|
||||
delegate,
|
||||
cx,
|
||||
) {
|
||||
node.server_id_or_init(|disposition| {
|
||||
assert_eq!(disposition.server_name, &language_server_name);
|
||||
|
||||
language_server_id
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
local
|
||||
.language_server_ids
|
||||
.entry((worktree_id, language_server_name))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue