languages: Use rustup rust-analyzer when toolchain override is present (#57696)

rust-analyzer does not really attempt to be backwards compatible, so
when users opt-into the component a toolchain override, we should prefer
that over any other rust-analyzer (especially our own) for better
toolchain compatibility in case people have a more out of date install.
This mirrors the VSCode extension behavior

Release Notes:

- When a worktree contains a Rust toolchain file with a rust analyzer
component specified, Zed will now spawn the given toolchain's
rust-analyzer for toolchain compatability
This commit is contained in:
Lukas Wirth 2026-05-26 16:58:09 +02:00 committed by GitHub
parent a8966695ee
commit b20cd411ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 188 additions and 71 deletions

View file

@ -608,7 +608,7 @@ pub trait LspInstaller {
type BinaryVersion;
fn check_if_user_installed(
&self,
_: &dyn LspAdapterDelegate,
_: &Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> impl Future<Output = Option<LanguageServerBinary>> {
@ -617,7 +617,7 @@ pub trait LspInstaller {
fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
pre_release: bool,
cx: &mut AsyncApp,
) -> impl Future<Output = Result<Self::BinaryVersion>>;
@ -684,7 +684,7 @@ where
delegate.update_status(name.clone(), BinaryStatus::CheckingForUpdate);
let latest_version = self
.fetch_latest_server_version(delegate.as_ref(), pre_release, cx)
.fetch_latest_server_version(delegate, pre_release, cx)
.await?;
if let Some(binary) = cx
@ -730,7 +730,7 @@ where
// for each worktree we might have open.
if binary_options.allow_path_lookup
&& let Some(binary) = self
.check_if_user_installed(delegate.as_ref(), toolchain, &mut cx)
.check_if_user_installed(&delegate, toolchain, &mut cx)
.await
{
log::info!(
@ -1400,7 +1400,7 @@ impl LspInstaller for FakeLspAdapter {
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
_: &Arc<dyn LspAdapterDelegate>,
_: bool,
_: &mut AsyncApp,
) -> Result<Self::BinaryVersion> {
@ -1409,7 +1409,7 @@ impl LspInstaller for FakeLspAdapter {
async fn check_if_user_installed(
&self,
_: &dyn LspAdapterDelegate,
_: &Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {

View file

@ -76,7 +76,7 @@ impl LspInstaller for BashLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: &gpui::AsyncApp,
) -> Option<lsp::LanguageServerBinary> {
@ -130,7 +130,7 @@ impl LspInstaller for BashLspAdapter {
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
_: &Arc<dyn LspAdapterDelegate>,
_: bool,
_: &mut gpui::AsyncApp,
) -> Result<Self::BinaryVersion> {

View file

@ -23,7 +23,7 @@ impl LspInstaller for CLspAdapter {
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
pre_release: bool,
_: &mut AsyncApp,
) -> Result<GitHubLspBinaryVersion> {
@ -54,7 +54,7 @@ impl LspInstaller for CLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {

View file

@ -38,7 +38,7 @@ impl LspInstaller for CssLspAdapter {
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
_: &Arc<dyn LspAdapterDelegate>,
_: bool,
_: &mut AsyncApp,
) -> Result<Self::BinaryVersion> {
@ -49,7 +49,7 @@ impl LspInstaller for CssLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {

View file

@ -83,7 +83,7 @@ impl LspInstaller for EsLintLspAdapter {
async fn fetch_latest_server_version(
&self,
_delegate: &dyn LspAdapterDelegate,
_delegate: &Arc<dyn LspAdapterDelegate>,
_: bool,
_: &mut AsyncApp,
) -> Result<GitHubLspBinaryVersion> {

View file

@ -69,7 +69,7 @@ impl LspInstaller for GoLspAdapter {
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
_: bool,
cx: &mut AsyncApp,
) -> Result<Option<String>> {
@ -106,7 +106,7 @@ impl LspInstaller for GoLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {

View file

@ -150,7 +150,7 @@ impl LspInstaller for JsonLspAdapter {
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
_: &Arc<dyn LspAdapterDelegate>,
_: bool,
_: &mut AsyncApp,
) -> Result<Self::BinaryVersion> {
@ -161,7 +161,7 @@ impl LspInstaller for JsonLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
@ -434,7 +434,7 @@ impl LspInstaller for NodeVersionAdapter {
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
_: bool,
_: &mut AsyncApp,
) -> Result<GitHubLspBinaryVersion> {
@ -471,7 +471,7 @@ impl LspInstaller for NodeVersionAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {

View file

@ -399,7 +399,7 @@ impl LspInstaller for TyLspAdapter {
type BinaryVersion = GitHubLspBinaryVersion;
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
_: bool,
_: &mut AsyncApp,
) -> Result<Self::BinaryVersion> {
@ -420,7 +420,7 @@ impl LspInstaller for TyLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
toolchain: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
@ -744,7 +744,7 @@ impl LspInstaller for PyrightLspAdapter {
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
_: &Arc<dyn LspAdapterDelegate>,
_: bool,
_: &mut AsyncApp,
) -> Result<Self::BinaryVersion> {
@ -755,7 +755,7 @@ impl LspInstaller for PyrightLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
@ -1905,7 +1905,7 @@ impl LspInstaller for PyLspAdapter {
type BinaryVersion = ();
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
toolchain: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
@ -1954,7 +1954,7 @@ impl LspInstaller for PyLspAdapter {
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
_: &Arc<dyn LspAdapterDelegate>,
_: bool,
_: &mut AsyncApp,
) -> Result<()> {
@ -2204,7 +2204,7 @@ impl LspInstaller for BasedPyrightLspAdapter {
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
_: &Arc<dyn LspAdapterDelegate>,
_: bool,
_: &mut AsyncApp,
) -> Result<Self::BinaryVersion> {
@ -2215,7 +2215,7 @@ impl LspInstaller for BasedPyrightLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
@ -2538,7 +2538,7 @@ impl LspInstaller for RuffLspAdapter {
type BinaryVersion = GitHubLspBinaryVersion;
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
toolchain: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
@ -2568,7 +2568,7 @@ impl LspInstaller for RuffLspAdapter {
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
_: bool,
_: &mut AsyncApp,
) -> Result<GitHubLspBinaryVersion> {

View file

@ -27,7 +27,7 @@ use std::{
sync::{Arc, LazyLock},
};
use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};
use util::command::Stdio;
use util::command::{Stdio, new_command};
use util::fs::{make_file_executable, remove_matching};
use util::merge_json_value_into;
use util::rel_path::RelPath;
@ -200,6 +200,58 @@ impl RustLspAdapter {
format!("{}-{}", Self::ARCH_SERVER_NAME, libc)
}
async fn rustup_rust_analyzer_for_worktree(
delegate: &dyn LspAdapterDelegate,
) -> Option<PathBuf> {
if !Self::workspace_has_rust_toolchain_override(delegate).await {
return None;
}
let rustup = delegate.which("rustup".as_ref()).await?;
let env = delegate.shell_env().await;
let worktree_root = delegate.worktree_root_path();
let output = new_command(rustup)
.args(["which", "rust-analyzer"])
.envs(env.iter())
.current_dir(worktree_root)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.await;
let output = match output {
Ok(output) if output.status.success() => output,
Ok(output) => {
log::debug!(
"failed to locate rust-analyzer through rustup in {worktree_root:?}: {}",
String::from_utf8_lossy(&output.stderr)
);
return None;
}
Err(err) => {
log::debug!(
"failed to run `rustup which rust-analyzer` in {worktree_root:?}: {err:#}"
);
return None;
}
};
let path = PathBuf::from(String::from_utf8_lossy(&output.stdout).trim());
Some(path).filter(|p| !p.as_os_str().is_empty())
}
async fn workspace_has_rust_toolchain_override(delegate: &dyn LspAdapterDelegate) -> bool {
for file_name in ["rust-toolchain.toml", "rust-toolchain"] {
if fs::metadata(delegate.resolve_relative_path(PathBuf::from(file_name)))
.await
.is_ok()
{
return true;
}
}
false
}
async fn build_asset_name() -> String {
let extension = match Self::GITHUB_ASSET_KIND {
AssetKind::TarGz => "tar.gz",
@ -671,42 +723,64 @@ impl LspInstaller for RustLspAdapter {
type BinaryVersion = GitHubLspBinaryVersion;
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: &AsyncApp,
cx: &AsyncApp,
) -> Option<LanguageServerBinary> {
let path = delegate.which("rust-analyzer".as_ref()).await?;
let env = delegate.shell_env().await;
let delegate = delegate.clone();
cx.background_spawn(async move {
let env = delegate.shell_env().await;
if let Some(path) = Self::rustup_rust_analyzer_for_worktree(delegate.as_ref()).await {
let result = delegate
.try_exec(LanguageServerBinary {
path: path.clone(),
arguments: vec!["--help".into()],
env: Some(env.clone()),
})
.await;
if result.is_ok() {
log::debug!("found rust-analyzer in rustup toolchain override");
return Some(LanguageServerBinary {
path,
env: Some(env),
arguments: vec![],
});
}
}
// It is surprisingly common for ~/.cargo/bin/rust-analyzer to be a symlink to
// /usr/bin/rust-analyzer that fails when you run it; so we need to test it.
log::debug!("found rust-analyzer in PATH. trying to run `rust-analyzer --help`");
let result = delegate
.try_exec(LanguageServerBinary {
path: path.clone(),
arguments: vec!["--help".into()],
env: Some(env.clone()),
})
.await;
if let Err(err) = result {
log::debug!(
"failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {}",
let path = delegate.which("rust-analyzer".as_ref()).await?;
// It is surprisingly common for ~/.cargo/bin/rust-analyzer to be a symlink to
// /usr/bin/rust-analyzer that fails when you run it; so we need to test it.
log::debug!("found rust-analyzer in PATH. trying to run `rust-analyzer --help`");
let result = delegate
.try_exec(LanguageServerBinary {
path: path.clone(),
arguments: vec!["--help".into()],
env: Some(env.clone()),
})
.await;
if let Err(err) = result {
log::debug!(
"failed to run rust-analyzer after detecting it in PATH: binary: {:?}: {}",
path,
err
);
return None;
}
Some(LanguageServerBinary {
path,
err
);
return None;
}
Some(LanguageServerBinary {
path,
env: Some(env),
arguments: vec![],
env: Some(env),
arguments: vec![],
})
})
.await
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
pre_release: bool,
_: &mut AsyncApp,
) -> Result<GitHubLspBinaryVersion> {

View file

@ -45,7 +45,7 @@ impl LspInstaller for TailwindLspAdapter {
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
_: &Arc<dyn LspAdapterDelegate>,
_: bool,
_: &mut AsyncApp,
) -> Result<Self::BinaryVersion> {
@ -56,7 +56,7 @@ impl LspInstaller for TailwindLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {

View file

@ -41,7 +41,7 @@ impl LspInstaller for TailwindCssLspAdapter {
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
_: &Arc<dyn LspAdapterDelegate>,
_: bool,
_: &mut AsyncApp,
) -> Result<Self::BinaryVersion> {
@ -52,7 +52,7 @@ impl LspInstaller for TailwindCssLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {

View file

@ -654,7 +654,7 @@ impl LspInstaller for TypeScriptLspAdapter {
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
_: &Arc<dyn LspAdapterDelegate>,
_: bool,
_: &mut AsyncApp,
) -> Result<Self::BinaryVersion> {

View file

@ -96,7 +96,7 @@ impl LspInstaller for VtslsLspAdapter {
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
_: &Arc<dyn LspAdapterDelegate>,
_: bool,
_: &mut AsyncApp,
) -> Result<Self::BinaryVersion> {
@ -111,7 +111,7 @@ impl LspInstaller for VtslsLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {

View file

@ -41,7 +41,7 @@ impl LspInstaller for YamlLspAdapter {
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
_: &Arc<dyn LspAdapterDelegate>,
_: bool,
_: &mut AsyncApp,
) -> Result<Self::BinaryVersion> {
@ -52,7 +52,7 @@ impl LspInstaller for YamlLspAdapter {
async fn check_if_user_installed(
&self,
delegate: &dyn LspAdapterDelegate,
delegate: &Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {

View file

@ -735,6 +735,48 @@ impl LocalLspStore {
})
});
}
#[cfg(any(test, feature = "test-support"))]
if !adapter.adapter.is_extension() && self.languages.has_fake_lsp_server(&adapter.name) {
let language_server_name = adapter.name.clone();
let languages = self.languages.clone();
return cx.spawn(async move |_| {
if let Some(mut wait_until_worktree_trust) = wait_until_worktree_trust {
let already_trusted = *wait_until_worktree_trust.borrow();
if !already_trusted {
log::info!(
"Waiting for worktree {worktree_abs_path:?} to be trusted, before starting language server {language_server_name}",
);
while let Some(worktree_trusted) = wait_until_worktree_trust.recv().await {
if worktree_trusted {
break;
}
}
log::info!(
"Worktree {worktree_abs_path:?} is trusted, starting language server {language_server_name}",
);
}
languages.update_lsp_binary_status(
language_server_name.clone(),
BinaryStatus::Starting,
);
}
Ok(LanguageServerBinary {
path: PathBuf::from(format!("/fake/lsp/{language_server_name}")),
arguments: Vec::new(),
env: None,
})
});
}
if cfg!(any(test, feature = "test-support")) && !adapter.adapter.is_extension() {
return Task::ready(Err(anyhow!(
"language server binary lookup for {:?} is disabled in tests; register a fake language server or configure an explicit binary",
adapter.name
)));
}
let lsp_binary_options = LanguageServerBinaryOptions {
allow_path_lookup: !settings
.binary
@ -751,10 +793,11 @@ impl LocalLspStore {
cx.spawn(async move |cx| {
if let Some(mut wait_until_worktree_trust) = wait_until_worktree_trust {
let already_trusted = *wait_until_worktree_trust.borrow();
let already_trusted = *wait_until_worktree_trust.borrow();
if !already_trusted {
log::info!(
"Waiting for worktree {worktree_abs_path:?} to be trusted, before starting language server {}",
"Waiting for worktree {worktree_abs_path:?} to be trusted, \
before starting language server {}",
adapter.name(),
);
while let Some(worktree_trusted) = wait_until_worktree_trust.recv().await {
@ -764,7 +807,7 @@ impl LocalLspStore {
}
log::info!(
"Worktree {worktree_abs_path:?} is trusted, starting language server {}",
adapter.name(),
adapter.name(),
);
}
}
@ -14501,7 +14544,7 @@ impl LspInstaller for SshLspAdapter {
type BinaryVersion = ();
async fn check_if_user_installed(
&self,
_: &dyn LspAdapterDelegate,
_: &Arc<dyn LspAdapterDelegate>,
_: Option<Toolchain>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
@ -14518,7 +14561,7 @@ impl LspInstaller for SshLspAdapter {
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
_: &Arc<dyn LspAdapterDelegate>,
_: bool,
_: &mut AsyncApp,
) -> Result<()> {