lsp: Identify language servers by their configuration (#35270)

- **WIP: reorganize dispositions**
- **Introduce a LocalToolchainStore trait and use it for LspAdapter
methods**

Closes #35782
Closes #27331

Release Notes:

- Python: Improved propagation of a selected virtual environment into
the LSP configuration. This should the make all language-related
features such as Go to definition or Find all references more reliable.

---------

Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Lukas Wirth <lukas@zed.dev>
This commit is contained in:
Piotr Osiewicz 2025-08-18 11:43:52 +02:00 committed by GitHub
parent 42ffa8900a
commit b8a106632f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 1037 additions and 1085 deletions

View file

@ -28,7 +28,7 @@ impl super::LspAdapter for CLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;

View file

@ -2,7 +2,7 @@ use anyhow::{Context as _, Result};
use async_trait::async_trait;
use futures::StreamExt;
use gpui::AsyncApp;
use language::{LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
use language::{LspAdapter, LspAdapterDelegate, Toolchain};
use lsp::{LanguageServerBinary, LanguageServerName};
use node_runtime::{NodeRuntime, VersionStrategy};
use project::{Fs, lsp_store::language_server_settings};
@ -43,7 +43,7 @@ impl LspAdapter for CssLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let path = delegate
@ -144,7 +144,7 @@ impl LspAdapter for CssLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<serde_json::Value> {
let mut default_config = json!({

View file

@ -75,7 +75,7 @@ impl super::LspAdapter for GoLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;

View file

@ -8,8 +8,8 @@ use futures::StreamExt;
use gpui::{App, AsyncApp, Task};
use http_client::github::{GitHubLspBinaryVersion, latest_github_release};
use language::{
ContextProvider, LanguageName, LanguageRegistry, LanguageToolchainStore, LocalFile as _,
LspAdapter, LspAdapterDelegate,
ContextProvider, LanguageName, LanguageRegistry, LocalFile as _, LspAdapter,
LspAdapterDelegate, Toolchain,
};
use lsp::{LanguageServerBinary, LanguageServerName};
use node_runtime::{NodeRuntime, VersionStrategy};
@ -303,7 +303,7 @@ impl LspAdapter for JsonLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let path = delegate
@ -404,7 +404,7 @@ impl LspAdapter for JsonLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let mut config = self.get_or_init_workspace_config(cx).await?;
@ -529,7 +529,7 @@ impl LspAdapter for NodeVersionAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;

View file

@ -1,6 +1,6 @@
use anyhow::Context as _;
use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};
use gpui::{App, UpdateGlobal};
use gpui::{App, SharedString, UpdateGlobal};
use node_runtime::NodeRuntime;
use python::PyprojectTomlManifestProvider;
use rust::CargoManifestProvider;
@ -177,11 +177,13 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
adapters: vec![python_lsp_adapter.clone(), py_lsp_adapter.clone()],
context: Some(python_context_provider),
toolchain: Some(python_toolchain_provider),
manifest_name: Some(SharedString::new_static("pyproject.toml").into()),
},
LanguageInfo {
name: "rust",
adapters: vec![rust_lsp_adapter],
context: Some(rust_context_provider),
manifest_name: Some(SharedString::new_static("Cargo.toml").into()),
..Default::default()
},
LanguageInfo {
@ -234,6 +236,7 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
registration.adapters,
registration.context,
registration.toolchain,
registration.manifest_name,
);
}
@ -340,7 +343,7 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
Arc::from(PyprojectTomlManifestProvider),
];
for provider in manifest_providers {
project::ManifestProviders::global(cx).register(provider);
project::ManifestProvidersStore::global(cx).register(provider);
}
}
@ -350,6 +353,7 @@ struct LanguageInfo {
adapters: Vec<Arc<dyn LspAdapter>>,
context: Option<Arc<dyn ContextProvider>>,
toolchain: Option<Arc<dyn ToolchainLister>>,
manifest_name: Option<ManifestName>,
}
fn register_language(
@ -358,6 +362,7 @@ fn register_language(
adapters: Vec<Arc<dyn LspAdapter>>,
context: Option<Arc<dyn ContextProvider>>,
toolchain: Option<Arc<dyn ToolchainLister>>,
manifest_name: Option<ManifestName>,
) {
let config = load_config(name);
for adapter in adapters {
@ -368,12 +373,14 @@ fn register_language(
config.grammar.clone(),
config.matcher.clone(),
config.hidden,
manifest_name.clone(),
Arc::new(move || {
Ok(LoadedLanguage {
config: config.clone(),
queries: load_queries(name),
context_provider: context.clone(),
toolchain_provider: toolchain.clone(),
manifest_name: manifest_name.clone(),
})
}),
);

View file

@ -127,7 +127,7 @@ impl LspAdapter for PythonLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
if let Some(pyright_bin) = delegate.which("pyright-langserver".as_ref()).await {
@ -319,17 +319,9 @@ impl LspAdapter for PythonLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
adapter: &Arc<dyn LspAdapterDelegate>,
toolchains: Arc<dyn LanguageToolchainStore>,
toolchain: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let toolchain = toolchains
.active_toolchain(
adapter.worktree_id(),
Arc::from("".as_ref()),
LanguageName::new("Python"),
cx,
)
.await;
cx.update(move |cx| {
let mut user_settings =
language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx)
@ -397,9 +389,7 @@ impl LspAdapter for PythonLspAdapter {
user_settings
})
}
fn manifest_name(&self) -> Option<ManifestName> {
Some(SharedString::new_static("pyproject.toml").into())
}
fn workspace_folders_content(&self) -> WorkspaceFoldersContent {
WorkspaceFoldersContent::WorktreeRoot
}
@ -1046,8 +1036,8 @@ impl LspAdapter for PyLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
toolchains: Arc<dyn LanguageToolchainStore>,
cx: &AsyncApp,
toolchain: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
if let Some(pylsp_bin) = delegate.which(Self::SERVER_NAME.as_ref()).await {
let env = delegate.shell_env().await;
@ -1057,14 +1047,7 @@ impl LspAdapter for PyLspAdapter {
arguments: vec![],
})
} else {
let venv = toolchains
.active_toolchain(
delegate.worktree_id(),
Arc::from("".as_ref()),
LanguageName::new("Python"),
&mut cx.clone(),
)
.await?;
let venv = toolchain?;
let pylsp_path = Path::new(venv.path.as_ref()).parent()?.join("pylsp");
pylsp_path.exists().then(|| LanguageServerBinary {
path: venv.path.to_string().into(),
@ -1211,17 +1194,9 @@ impl LspAdapter for PyLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
adapter: &Arc<dyn LspAdapterDelegate>,
toolchains: Arc<dyn LanguageToolchainStore>,
toolchain: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let toolchain = toolchains
.active_toolchain(
adapter.worktree_id(),
Arc::from("".as_ref()),
LanguageName::new("Python"),
cx,
)
.await;
cx.update(move |cx| {
let mut user_settings =
language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx)
@ -1282,9 +1257,6 @@ impl LspAdapter for PyLspAdapter {
user_settings
})
}
fn manifest_name(&self) -> Option<ManifestName> {
Some(SharedString::new_static("pyproject.toml").into())
}
fn workspace_folders_content(&self) -> WorkspaceFoldersContent {
WorkspaceFoldersContent::WorktreeRoot
}
@ -1377,8 +1349,8 @@ impl LspAdapter for BasedPyrightLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
toolchains: Arc<dyn LanguageToolchainStore>,
cx: &AsyncApp,
toolchain: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
if let Some(bin) = delegate.which(Self::BINARY_NAME.as_ref()).await {
let env = delegate.shell_env().await;
@ -1388,15 +1360,7 @@ impl LspAdapter for BasedPyrightLspAdapter {
arguments: vec!["--stdio".into()],
})
} else {
let venv = toolchains
.active_toolchain(
delegate.worktree_id(),
Arc::from("".as_ref()),
LanguageName::new("Python"),
&mut cx.clone(),
)
.await?;
let path = Path::new(venv.path.as_ref())
let path = Path::new(toolchain?.path.as_ref())
.parent()?
.join(Self::BINARY_NAME);
path.exists().then(|| LanguageServerBinary {
@ -1543,17 +1507,9 @@ impl LspAdapter for BasedPyrightLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
adapter: &Arc<dyn LspAdapterDelegate>,
toolchains: Arc<dyn LanguageToolchainStore>,
toolchain: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let toolchain = toolchains
.active_toolchain(
adapter.worktree_id(),
Arc::from("".as_ref()),
LanguageName::new("Python"),
cx,
)
.await;
cx.update(move |cx| {
let mut user_settings =
language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx)
@ -1622,10 +1578,6 @@ impl LspAdapter for BasedPyrightLspAdapter {
})
}
fn manifest_name(&self) -> Option<ManifestName> {
Some(SharedString::new_static("pyproject.toml").into())
}
fn workspace_folders_content(&self) -> WorkspaceFoldersContent {
WorkspaceFoldersContent::WorktreeRoot
}

View file

@ -109,14 +109,10 @@ impl LspAdapter for RustLspAdapter {
SERVER_NAME.clone()
}
fn manifest_name(&self) -> Option<ManifestName> {
Some(SharedString::new_static("Cargo.toml").into())
}
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let path = delegate.which("rust-analyzer".as_ref()).await?;

View file

@ -3,7 +3,7 @@ use async_trait::async_trait;
use collections::HashMap;
use futures::StreamExt;
use gpui::AsyncApp;
use language::{LanguageName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
use language::{LanguageName, LspAdapter, LspAdapterDelegate, Toolchain};
use lsp::{LanguageServerBinary, LanguageServerName};
use node_runtime::{NodeRuntime, VersionStrategy};
use project::{Fs, lsp_store::language_server_settings};
@ -50,7 +50,7 @@ impl LspAdapter for TailwindLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
@ -155,7 +155,7 @@ impl LspAdapter for TailwindLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let mut tailwind_user_settings = cx.update(|cx| {

View file

@ -7,7 +7,7 @@ use gpui::{App, AppContext, AsyncApp, Task};
use http_client::github::{AssetKind, GitHubLspBinaryVersion, build_asset_url};
use language::{
ContextLocation, ContextProvider, File, LanguageName, LanguageToolchainStore, LspAdapter,
LspAdapterDelegate,
LspAdapterDelegate, Toolchain,
};
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName};
use node_runtime::{NodeRuntime, VersionStrategy};
@ -722,7 +722,7 @@ impl LspAdapter for TypeScriptLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let override_options = cx.update(|cx| {
@ -822,7 +822,7 @@ impl LspAdapter for EsLintLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let workspace_root = delegate.worktree_root_path();

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use async_trait::async_trait;
use collections::HashMap;
use gpui::AsyncApp;
use language::{LanguageName, LanguageToolchainStore, LspAdapter, LspAdapterDelegate};
use language::{LanguageName, LspAdapter, LspAdapterDelegate, Toolchain};
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName};
use node_runtime::{NodeRuntime, VersionStrategy};
use project::{Fs, lsp_store::language_server_settings};
@ -86,7 +86,7 @@ impl LspAdapter for VtslsLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let env = delegate.shell_env().await;
@ -211,7 +211,7 @@ impl LspAdapter for VtslsLspAdapter {
self: Arc<Self>,
fs: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let tsdk_path = Self::tsdk_path(fs, delegate).await;

View file

@ -2,9 +2,7 @@ use anyhow::{Context as _, Result};
use async_trait::async_trait;
use futures::StreamExt;
use gpui::AsyncApp;
use language::{
LanguageToolchainStore, LspAdapter, LspAdapterDelegate, language_settings::AllLanguageSettings,
};
use language::{LspAdapter, LspAdapterDelegate, Toolchain, language_settings::AllLanguageSettings};
use lsp::{LanguageServerBinary, LanguageServerName};
use node_runtime::{NodeRuntime, VersionStrategy};
use project::{Fs, lsp_store::language_server_settings};
@ -57,7 +55,7 @@ impl LspAdapter for YamlLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
let path = delegate.which(Self::SERVER_NAME.as_ref()).await?;
@ -135,7 +133,7 @@ impl LspAdapter for YamlLspAdapter {
self: Arc<Self>,
_: &dyn Fs,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Arc<dyn LanguageToolchainStore>,
_: Option<Toolchain>,
cx: &mut AsyncApp,
) -> Result<Value> {
let location = SettingsLocation {