project: Bring back language servers in detached worktrees (#23530)

Closes #ISSUE

Release Notes:

- N/A
This commit is contained in:
Piotr Osiewicz 2025-01-23 17:03:53 +01:00 committed by GitHub
parent bb937b6cee
commit 9e6b10018a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 75 additions and 22 deletions

View file

@ -6839,7 +6839,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_file("/file.rs", Default::default()).await; fs.insert_file("/file.rs", Default::default()).await;
let project = Project::test(fs, ["/".as_ref()], cx).await; let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang()); language_registry.add(rust_lang());
@ -7193,7 +7193,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_file("/file.rs", Default::default()).await; fs.insert_file("/file.rs", Default::default()).await;
let project = Project::test(fs, ["/".as_ref()], cx).await; let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang()); language_registry.add(rust_lang());
@ -7327,7 +7327,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_file("/file.rs", Default::default()).await; fs.insert_file("/file.rs", Default::default()).await;
let project = Project::test(fs, ["/".as_ref()], cx).await; let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(Arc::new(Language::new( language_registry.add(Arc::new(Language::new(

View file

@ -6,7 +6,7 @@ use crate::{
lsp_ext_command, lsp_ext_command,
prettier_store::{self, PrettierStore, PrettierStoreEvent}, prettier_store::{self, PrettierStore, PrettierStoreEvent},
project_settings::{LspSettings, ProjectSettings}, project_settings::{LspSettings, ProjectSettings},
project_tree::{LanguageServerTree, LaunchDisposition, ProjectTree}, 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},
@ -983,9 +983,11 @@ impl LocalLspStore {
if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) { if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) {
let worktree_id = file.worktree_id(cx); let worktree_id = file.worktree_id(cx);
let Some(path): Option<Arc<Path>> = file.path().parent().map(Arc::from) else { let path: Arc<Path> = file
return vec![]; .path()
}; .parent()
.map(Arc::from)
.unwrap_or_else(|| file.path().clone());
let worktree_path = ProjectPath { worktree_id, path }; let worktree_path = ProjectPath { worktree_id, path };
let Some(worktree) = self let Some(worktree) = self
.worktree_store .worktree_store
@ -996,9 +998,14 @@ impl LocalLspStore {
}; };
let delegate = LocalLspAdapterDelegate::from_local_lsp(self, &worktree, cx); let delegate = LocalLspAdapterDelegate::from_local_lsp(self, &worktree, cx);
let root = self.lsp_tree.update(cx, |this, cx| { let root = self.lsp_tree.update(cx, |this, cx| {
this.get(worktree_path, &language.name(), delegate, cx) this.get(
.filter_map(|node| node.server_id()) worktree_path,
.collect::<Vec<_>>() AdapterQuery::Language(&language.name()),
delegate,
cx,
)
.filter_map(|node| node.server_id())
.collect::<Vec<_>>()
}); });
root root
@ -1740,7 +1747,7 @@ impl LocalLspStore {
let nodes = self.lsp_tree.update(cx, |this, cx| { let nodes = self.lsp_tree.update(cx, |this, cx| {
this.get( this.get(
ProjectPath { worktree_id, path }, ProjectPath { worktree_id, path },
&language.name(), AdapterQuery::Language(&language.name()),
delegate, delegate,
cx, cx,
) )
@ -1860,9 +1867,11 @@ impl LocalLspStore {
let Some(language) = buffer.language().cloned() else { let Some(language) = buffer.language().cloned() else {
return; return;
}; };
let Some(path): Option<Arc<Path>> = file.path().parent().map(Arc::from) else { let path: Arc<Path> = file
return; .path()
}; .parent()
.map(Arc::from)
.unwrap_or_else(|| file.path().clone());
let Some(worktree) = self let Some(worktree) = self
.worktree_store .worktree_store
.read(cx) .read(cx)
@ -1874,7 +1883,7 @@ impl LocalLspStore {
let servers = self.lsp_tree.clone().update(cx, |this, cx| { let servers = self.lsp_tree.clone().update(cx, |this, cx| {
this.get( this.get(
ProjectPath { worktree_id, path }, ProjectPath { worktree_id, path },
&language.name(), AdapterQuery::Language(&language.name()),
delegate.clone(), delegate.clone(),
cx, cx,
) )
@ -5361,12 +5370,35 @@ impl LspStore {
fn register_local_language_server( fn register_local_language_server(
&mut self, &mut self,
worktree_id: WorktreeId, worktree: Model<Worktree>,
language_server_name: LanguageServerName, language_server_name: LanguageServerName,
language_server_id: LanguageServerId, language_server_id: LanguageServerId,
cx: &mut AppContext,
) { ) {
self.as_local_mut() let Some(local) = self.as_local_mut() else {
.unwrap() 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
});
}
});
local
.language_server_ids .language_server_ids
.entry((worktree_id, language_server_name)) .entry((worktree_id, language_server_name))
.or_default() .or_default()
@ -5609,9 +5641,10 @@ impl LspStore {
lsp_store lsp_store
.update(&mut cx, |lsp_store, cx| { .update(&mut cx, |lsp_store, cx| {
lsp_store.register_local_language_server( lsp_store.register_local_language_server(
worktree.read(cx).id(), worktree.clone(),
language_server_name, language_server_name,
language_server_id, language_server_id,
cx,
) )
}) })
.ok(); .ok();

View file

@ -25,7 +25,7 @@ use crate::{
ProjectPath, ProjectPath,
}; };
pub(crate) use server_tree::{LanguageServerTree, LaunchDisposition}; pub(crate) use server_tree::{AdapterQuery, LanguageServerTree, LaunchDisposition};
struct WorktreeRoots { struct WorktreeRoots {
roots: RootPathTrie<LanguageServerName>, roots: RootPathTrie<LanguageServerName>,

View file

@ -128,6 +128,14 @@ impl InnerTreeNode {
} }
} }
/// Determines how the list of adapters to query should be constructed.
pub(crate) enum AdapterQuery<'a> {
/// Search for roots of all adapters associated with a given language name.
Language(&'a LanguageName),
/// Search for roots of adapter with a given name.
Adapter(&'a LanguageServerName),
}
impl LanguageServerTree { impl LanguageServerTree {
pub(crate) fn new( pub(crate) fn new(
project_tree: Model<ProjectTree>, project_tree: Model<ProjectTree>,
@ -159,7 +167,7 @@ impl LanguageServerTree {
pub(crate) fn get<'a>( pub(crate) fn get<'a>(
&'a mut self, &'a mut self,
path: ProjectPath, path: ProjectPath,
language_name: &LanguageName, query: AdapterQuery<'_>,
delegate: Arc<dyn LspAdapterDelegate>, delegate: Arc<dyn LspAdapterDelegate>,
cx: &mut AppContext, cx: &mut AppContext,
) -> impl Iterator<Item = LanguageServerTreeNode> + 'a { ) -> impl Iterator<Item = LanguageServerTreeNode> + 'a {
@ -167,7 +175,15 @@ impl LanguageServerTree {
worktree_id: path.worktree_id, worktree_id: path.worktree_id,
path: &path.path, path: &path.path,
}; };
let adapters = self.adapters_for_language(settings_location, language_name, cx); let adapters = match query {
AdapterQuery::Language(language_name) => {
self.adapters_for_language(settings_location, language_name, cx)
}
AdapterQuery::Adapter(language_server_name) => IndexMap::from_iter(
self.adapter_for_name(language_server_name)
.map(|adapter| (adapter, (LspSettings::default(), BTreeSet::new()))),
),
};
self.get_with_adapters(path, adapters, delegate, cx) self.get_with_adapters(path, adapters, delegate, cx)
} }
@ -231,6 +247,10 @@ impl LanguageServerTree {
}) })
} }
fn adapter_for_name(&self, name: &LanguageServerName) -> Option<AdapterWrapper> {
self.languages.adapter_for_name(name).map(AdapterWrapper)
}
fn adapters_for_language( fn adapters_for_language(
&self, &self,
settings_location: SettingsLocation, settings_location: SettingsLocation,