From b20cd411ec792dc441705bbbeb4cc3ac88664ca0 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 26 May 2026 16:58:09 +0200 Subject: [PATCH] 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 --- crates/language/src/language.rs | 12 +-- crates/languages/src/bash.rs | 4 +- crates/languages/src/c.rs | 4 +- crates/languages/src/css.rs | 4 +- crates/languages/src/eslint.rs | 2 +- crates/languages/src/go.rs | 4 +- crates/languages/src/json.rs | 8 +- crates/languages/src/python.rs | 20 ++--- crates/languages/src/rust.rs | 130 ++++++++++++++++++++++------ crates/languages/src/tailwind.rs | 4 +- crates/languages/src/tailwindcss.rs | 4 +- crates/languages/src/typescript.rs | 2 +- crates/languages/src/vtsls.rs | 4 +- crates/languages/src/yaml.rs | 4 +- crates/project/src/lsp_store.rs | 53 ++++++++++-- 15 files changed, 188 insertions(+), 71 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index cb19b5e6dbb..7182a0cdfe9 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -608,7 +608,7 @@ pub trait LspInstaller { type BinaryVersion; fn check_if_user_installed( &self, - _: &dyn LspAdapterDelegate, + _: &Arc, _: Option, _: &AsyncApp, ) -> impl Future> { @@ -617,7 +617,7 @@ pub trait LspInstaller { fn fetch_latest_server_version( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, pre_release: bool, cx: &mut AsyncApp, ) -> impl Future>; @@ -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, _: bool, _: &mut AsyncApp, ) -> Result { @@ -1409,7 +1409,7 @@ impl LspInstaller for FakeLspAdapter { async fn check_if_user_installed( &self, - _: &dyn LspAdapterDelegate, + _: &Arc, _: Option, _: &AsyncApp, ) -> Option { diff --git a/crates/languages/src/bash.rs b/crates/languages/src/bash.rs index 2f550e87c7f..2335b0a1700 100644 --- a/crates/languages/src/bash.rs +++ b/crates/languages/src/bash.rs @@ -76,7 +76,7 @@ impl LspInstaller for BashLspAdapter { async fn check_if_user_installed( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, _: Option, _: &gpui::AsyncApp, ) -> Option { @@ -130,7 +130,7 @@ impl LspInstaller for BashLspAdapter { async fn fetch_latest_server_version( &self, - _: &dyn LspAdapterDelegate, + _: &Arc, _: bool, _: &mut gpui::AsyncApp, ) -> Result { diff --git a/crates/languages/src/c.rs b/crates/languages/src/c.rs index d2e92904c6d..80d794cd617 100644 --- a/crates/languages/src/c.rs +++ b/crates/languages/src/c.rs @@ -23,7 +23,7 @@ impl LspInstaller for CLspAdapter { async fn fetch_latest_server_version( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, pre_release: bool, _: &mut AsyncApp, ) -> Result { @@ -54,7 +54,7 @@ impl LspInstaller for CLspAdapter { async fn check_if_user_installed( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, _: Option, _: &AsyncApp, ) -> Option { diff --git a/crates/languages/src/css.rs b/crates/languages/src/css.rs index 4506481a17b..5f796ed78ef 100644 --- a/crates/languages/src/css.rs +++ b/crates/languages/src/css.rs @@ -38,7 +38,7 @@ impl LspInstaller for CssLspAdapter { async fn fetch_latest_server_version( &self, - _: &dyn LspAdapterDelegate, + _: &Arc, _: bool, _: &mut AsyncApp, ) -> Result { @@ -49,7 +49,7 @@ impl LspInstaller for CssLspAdapter { async fn check_if_user_installed( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, _: Option, _: &AsyncApp, ) -> Option { diff --git a/crates/languages/src/eslint.rs b/crates/languages/src/eslint.rs index 063cf85affd..2d0c88bd016 100644 --- a/crates/languages/src/eslint.rs +++ b/crates/languages/src/eslint.rs @@ -83,7 +83,7 @@ impl LspInstaller for EsLintLspAdapter { async fn fetch_latest_server_version( &self, - _delegate: &dyn LspAdapterDelegate, + _delegate: &Arc, _: bool, _: &mut AsyncApp, ) -> Result { diff --git a/crates/languages/src/go.rs b/crates/languages/src/go.rs index 3bedd62b8e6..75aaf8048ad 100644 --- a/crates/languages/src/go.rs +++ b/crates/languages/src/go.rs @@ -69,7 +69,7 @@ impl LspInstaller for GoLspAdapter { async fn fetch_latest_server_version( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, _: bool, cx: &mut AsyncApp, ) -> Result> { @@ -106,7 +106,7 @@ impl LspInstaller for GoLspAdapter { async fn check_if_user_installed( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, _: Option, _: &AsyncApp, ) -> Option { diff --git a/crates/languages/src/json.rs b/crates/languages/src/json.rs index 8389fd65f65..b97429c685b 100644 --- a/crates/languages/src/json.rs +++ b/crates/languages/src/json.rs @@ -150,7 +150,7 @@ impl LspInstaller for JsonLspAdapter { async fn fetch_latest_server_version( &self, - _: &dyn LspAdapterDelegate, + _: &Arc, _: bool, _: &mut AsyncApp, ) -> Result { @@ -161,7 +161,7 @@ impl LspInstaller for JsonLspAdapter { async fn check_if_user_installed( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, _: Option, _: &AsyncApp, ) -> Option { @@ -434,7 +434,7 @@ impl LspInstaller for NodeVersionAdapter { async fn fetch_latest_server_version( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, _: bool, _: &mut AsyncApp, ) -> Result { @@ -471,7 +471,7 @@ impl LspInstaller for NodeVersionAdapter { async fn check_if_user_installed( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, _: Option, _: &AsyncApp, ) -> Option { diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 5d2024d3b8d..5a3f6939830 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -399,7 +399,7 @@ impl LspInstaller for TyLspAdapter { type BinaryVersion = GitHubLspBinaryVersion; async fn fetch_latest_server_version( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, _: bool, _: &mut AsyncApp, ) -> Result { @@ -420,7 +420,7 @@ impl LspInstaller for TyLspAdapter { async fn check_if_user_installed( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, toolchain: Option, _: &AsyncApp, ) -> Option { @@ -744,7 +744,7 @@ impl LspInstaller for PyrightLspAdapter { async fn fetch_latest_server_version( &self, - _: &dyn LspAdapterDelegate, + _: &Arc, _: bool, _: &mut AsyncApp, ) -> Result { @@ -755,7 +755,7 @@ impl LspInstaller for PyrightLspAdapter { async fn check_if_user_installed( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, _: Option, _: &AsyncApp, ) -> Option { @@ -1905,7 +1905,7 @@ impl LspInstaller for PyLspAdapter { type BinaryVersion = (); async fn check_if_user_installed( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, toolchain: Option, _: &AsyncApp, ) -> Option { @@ -1954,7 +1954,7 @@ impl LspInstaller for PyLspAdapter { async fn fetch_latest_server_version( &self, - _: &dyn LspAdapterDelegate, + _: &Arc, _: bool, _: &mut AsyncApp, ) -> Result<()> { @@ -2204,7 +2204,7 @@ impl LspInstaller for BasedPyrightLspAdapter { async fn fetch_latest_server_version( &self, - _: &dyn LspAdapterDelegate, + _: &Arc, _: bool, _: &mut AsyncApp, ) -> Result { @@ -2215,7 +2215,7 @@ impl LspInstaller for BasedPyrightLspAdapter { async fn check_if_user_installed( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, _: Option, _: &AsyncApp, ) -> Option { @@ -2538,7 +2538,7 @@ impl LspInstaller for RuffLspAdapter { type BinaryVersion = GitHubLspBinaryVersion; async fn check_if_user_installed( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, toolchain: Option, _: &AsyncApp, ) -> Option { @@ -2568,7 +2568,7 @@ impl LspInstaller for RuffLspAdapter { async fn fetch_latest_server_version( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, _: bool, _: &mut AsyncApp, ) -> Result { diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index de219d30928..445bcfa5de3 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -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 { + 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, _: Option, - _: &AsyncApp, + cx: &AsyncApp, ) -> Option { - 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, pre_release: bool, _: &mut AsyncApp, ) -> Result { diff --git a/crates/languages/src/tailwind.rs b/crates/languages/src/tailwind.rs index 6d4211b58c8..0af7ae68b2c 100644 --- a/crates/languages/src/tailwind.rs +++ b/crates/languages/src/tailwind.rs @@ -45,7 +45,7 @@ impl LspInstaller for TailwindLspAdapter { async fn fetch_latest_server_version( &self, - _: &dyn LspAdapterDelegate, + _: &Arc, _: bool, _: &mut AsyncApp, ) -> Result { @@ -56,7 +56,7 @@ impl LspInstaller for TailwindLspAdapter { async fn check_if_user_installed( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, _: Option, _: &AsyncApp, ) -> Option { diff --git a/crates/languages/src/tailwindcss.rs b/crates/languages/src/tailwindcss.rs index 0e9ac9af40f..ac5e12e07ff 100644 --- a/crates/languages/src/tailwindcss.rs +++ b/crates/languages/src/tailwindcss.rs @@ -41,7 +41,7 @@ impl LspInstaller for TailwindCssLspAdapter { async fn fetch_latest_server_version( &self, - _: &dyn LspAdapterDelegate, + _: &Arc, _: bool, _: &mut AsyncApp, ) -> Result { @@ -52,7 +52,7 @@ impl LspInstaller for TailwindCssLspAdapter { async fn check_if_user_installed( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, _: Option, _: &AsyncApp, ) -> Option { diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index 4d37898eca1..5572e43f049 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -654,7 +654,7 @@ impl LspInstaller for TypeScriptLspAdapter { async fn fetch_latest_server_version( &self, - _: &dyn LspAdapterDelegate, + _: &Arc, _: bool, _: &mut AsyncApp, ) -> Result { diff --git a/crates/languages/src/vtsls.rs b/crates/languages/src/vtsls.rs index c46ea39a4f1..acf8aea5e59 100644 --- a/crates/languages/src/vtsls.rs +++ b/crates/languages/src/vtsls.rs @@ -96,7 +96,7 @@ impl LspInstaller for VtslsLspAdapter { async fn fetch_latest_server_version( &self, - _: &dyn LspAdapterDelegate, + _: &Arc, _: bool, _: &mut AsyncApp, ) -> Result { @@ -111,7 +111,7 @@ impl LspInstaller for VtslsLspAdapter { async fn check_if_user_installed( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, _: Option, _: &AsyncApp, ) -> Option { diff --git a/crates/languages/src/yaml.rs b/crates/languages/src/yaml.rs index de9b11b03dc..41586a469fd 100644 --- a/crates/languages/src/yaml.rs +++ b/crates/languages/src/yaml.rs @@ -41,7 +41,7 @@ impl LspInstaller for YamlLspAdapter { async fn fetch_latest_server_version( &self, - _: &dyn LspAdapterDelegate, + _: &Arc, _: bool, _: &mut AsyncApp, ) -> Result { @@ -52,7 +52,7 @@ impl LspInstaller for YamlLspAdapter { async fn check_if_user_installed( &self, - delegate: &dyn LspAdapterDelegate, + delegate: &Arc, _: Option, _: &AsyncApp, ) -> Option { diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index b33cd6286fe..f2bd07da59d 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -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, _: Option, _: &AsyncApp, ) -> Option { @@ -14518,7 +14561,7 @@ impl LspInstaller for SshLspAdapter { async fn fetch_latest_server_version( &self, - _: &dyn LspAdapterDelegate, + _: &Arc, _: bool, _: &mut AsyncApp, ) -> Result<()> {