Fix gopls langserver downloads (#7571)

Kirill Bulatov created

Fixes https://github.com/zed-industries/zed/issues/7534 by not requiring
assets for gopls and vscode-eslint langservers — those two are the only
ones in Zed that do not use assets directly when determining langserver
version and retrieving those.
All other servers deal with assets, hence require those to be present.

The problem with https://github.com/tamasfe/taplo/releases is that they
host multiple binary releases in the same release list, so for now the
code works because only the langserver has assets — but as soon as
another release there gets assets, it will break again.
We could filter out those by names also, but they also tend to change
(and can be edited manually), so keeping it as is for now.

Release Notes:

- Fixed gopls language server downloads
([7534](https://github.com/zed-industries/zed/issues/7534))

Change summary

crates/copilot/src/copilot.rs          |  5 +++--
crates/util/src/github.rs              |  4 +++-
crates/zed/src/languages/c.rs          |  3 ++-
crates/zed/src/languages/csharp.rs     | 11 +++++++----
crates/zed/src/languages/deno.rs       |  3 ++-
crates/zed/src/languages/elixir.rs     | 17 +++++++++--------
crates/zed/src/languages/gleam.rs      |  2 +-
crates/zed/src/languages/go.rs         |  3 ++-
crates/zed/src/languages/lua.rs        | 14 +++++++++-----
crates/zed/src/languages/rust.rs       |  9 +++++++--
crates/zed/src/languages/toml.rs       |  3 ++-
crates/zed/src/languages/typescript.rs |  9 +++++++--
crates/zed/src/languages/zig.rs        |  5 +++--
13 files changed, 57 insertions(+), 31 deletions(-)

Detailed changes

crates/copilot/src/copilot.rs 🔗

@@ -976,7 +976,8 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
 
     ///Check for the latest copilot language server and download it if we haven't already
     async fn fetch_latest(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
-        let release = latest_github_release("zed-industries/copilot", false, http.clone()).await?;
+        let release =
+            latest_github_release("zed-industries/copilot", true, false, http.clone()).await?;
 
         let version_dir = &*paths::COPILOT_DIR.join(format!("copilot-{}", release.name));
 
@@ -997,7 +998,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
             let mut response = http
                 .get(url, Default::default(), true)
                 .await
-                .map_err(|err| anyhow!("error downloading copilot release: {}", err))?;
+                .context("error downloading copilot release")?;
             let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
             let archive = Archive::new(decompressed_bytes);
             archive.unpack(dist_dir).await?;

crates/util/src/github.rs 🔗

@@ -27,6 +27,7 @@ pub struct GithubReleaseAsset {
 
 pub async fn latest_github_release(
     repo_name_with_owner: &str,
+    require_assets: bool,
     pre_release: bool,
     http: Arc<dyn HttpClient>,
 ) -> Result<GithubRelease, anyhow::Error> {
@@ -68,6 +69,7 @@ pub async fn latest_github_release(
 
     releases
         .into_iter()
-        .find(|release| !release.assets.is_empty() && release.pre_release == pre_release)
+        .filter(|release| !require_assets || !release.assets.is_empty())
+        .find(|release| release.pre_release == pre_release)
         .ok_or(anyhow!("Failed to find a release"))
 }

crates/zed/src/languages/c.rs 🔗

@@ -28,7 +28,8 @@ impl super::LspAdapter for CLspAdapter {
         &self,
         delegate: &dyn LspAdapterDelegate,
     ) -> Result<Box<dyn 'static + Send + Any>> {
-        let release = latest_github_release("clangd/clangd", false, delegate.http_client()).await?;
+        let release =
+            latest_github_release("clangd/clangd", true, false, delegate.http_client()).await?;
         let asset_name = format!("clangd-mac-{}.zip", release.name);
         let asset = release
             .assets

crates/zed/src/languages/csharp.rs 🔗

@@ -29,10 +29,6 @@ impl super::LspAdapter for OmniSharpAdapter {
         &self,
         delegate: &dyn LspAdapterDelegate,
     ) -> Result<Box<dyn 'static + Send + Any>> {
-        let release =
-            latest_github_release("OmniSharp/omnisharp-roslyn", false, delegate.http_client())
-                .await?;
-
         let mapped_arch = match ARCH {
             "aarch64" => Some("arm64"),
             "x86_64" => Some("x64"),
@@ -42,6 +38,13 @@ impl super::LspAdapter for OmniSharpAdapter {
         match mapped_arch {
             None => Ok(Box::new(())),
             Some(arch) => {
+                let release = latest_github_release(
+                    "OmniSharp/omnisharp-roslyn",
+                    true,
+                    false,
+                    delegate.http_client(),
+                )
+                .await?;
                 let asset_name = format!("omnisharp-osx-{}-net6.0.tar.gz", arch);
                 let asset = release
                     .assets

crates/zed/src/languages/deno.rs 🔗

@@ -70,7 +70,8 @@ impl LspAdapter for DenoLspAdapter {
         &self,
         delegate: &dyn LspAdapterDelegate,
     ) -> Result<Box<dyn 'static + Send + Any>> {
-        let release = latest_github_release("denoland/deno", false, delegate.http_client()).await?;
+        let release =
+            latest_github_release("denoland/deno", true, false, delegate.http_client()).await?;
         let asset_name = format!("deno-{}-apple-darwin.zip", consts::ARCH);
         let asset = release
             .assets

crates/zed/src/languages/elixir.rs 🔗

@@ -111,19 +111,19 @@ impl LspAdapter for ElixirLspAdapter {
         delegate: &dyn LspAdapterDelegate,
     ) -> Result<Box<dyn 'static + Send + Any>> {
         let http = delegate.http_client();
-        let release = latest_github_release("elixir-lsp/elixir-ls", false, http).await?;
+        let release = latest_github_release("elixir-lsp/elixir-ls", true, false, http).await?;
         let version_name = release
             .name
             .strip_prefix("Release ")
             .context("Elixir-ls release name does not start with prefix")?
             .to_owned();
 
-        let asset_name = format!("elixir-ls-{}.zip", &version_name);
+        let asset_name = format!("elixir-ls-{version_name}.zip");
         let asset = release
             .assets
             .iter()
             .find(|asset| asset.name == asset_name)
-            .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
+            .ok_or_else(|| anyhow!("no asset found matching {asset_name:?}"))?;
 
         let version = GitHubLspBinaryVersion {
             name: version_name,
@@ -313,20 +313,21 @@ impl LspAdapter for NextLspAdapter {
         &self,
         delegate: &dyn LspAdapterDelegate,
     ) -> Result<Box<dyn 'static + Send + Any>> {
-        let release =
-            latest_github_release("elixir-tools/next-ls", false, delegate.http_client()).await?;
-        let version = release.name.clone();
         let platform = match consts::ARCH {
             "x86_64" => "darwin_amd64",
             "aarch64" => "darwin_arm64",
             other => bail!("Running on unsupported platform: {other}"),
         };
-        let asset_name = format!("next_ls_{}", platform);
+        let release =
+            latest_github_release("elixir-tools/next-ls", true, false, delegate.http_client())
+                .await?;
+        let version = release.name;
+        let asset_name = format!("next_ls_{platform}");
         let asset = release
             .assets
             .iter()
             .find(|asset| asset.name == asset_name)
-            .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
+            .with_context(|| format!("no asset found matching {asset_name:?}"))?;
         let version = GitHubLspBinaryVersion {
             name: version,
             url: asset.browser_download_url.clone(),

crates/zed/src/languages/gleam.rs 🔗

@@ -35,7 +35,7 @@ impl LspAdapter for GleamLspAdapter {
         delegate: &dyn LspAdapterDelegate,
     ) -> Result<Box<dyn 'static + Send + Any>> {
         let release =
-            latest_github_release("gleam-lang/gleam", false, delegate.http_client()).await?;
+            latest_github_release("gleam-lang/gleam", true, false, delegate.http_client()).await?;
 
         let asset_name = format!(
             "gleam-{version}-{arch}-apple-darwin.tar.gz",

crates/zed/src/languages/go.rs 🔗

@@ -45,7 +45,8 @@ impl super::LspAdapter for GoLspAdapter {
         &self,
         delegate: &dyn LspAdapterDelegate,
     ) -> Result<Box<dyn 'static + Send + Any>> {
-        let release = latest_github_release("golang/tools", false, delegate.http_client()).await?;
+        let release =
+            latest_github_release("golang/tools", false, false, delegate.http_client()).await?;
         let version: Option<String> = release.name.strip_prefix("gopls/v").map(str::to_string);
         if version.is_none() {
             log::warn!(

crates/zed/src/languages/lua.rs 🔗

@@ -30,15 +30,19 @@ impl super::LspAdapter for LuaLspAdapter {
         &self,
         delegate: &dyn LspAdapterDelegate,
     ) -> Result<Box<dyn 'static + Send + Any>> {
-        let release =
-            latest_github_release("LuaLS/lua-language-server", false, delegate.http_client())
-                .await?;
-        let version = release.name.clone();
         let platform = match consts::ARCH {
             "x86_64" => "x64",
             "aarch64" => "arm64",
             other => bail!("Running on unsupported platform: {other}"),
         };
+        let release = latest_github_release(
+            "LuaLS/lua-language-server",
+            true,
+            false,
+            delegate.http_client(),
+        )
+        .await?;
+        let version = &release.name;
         let asset_name = format!("lua-language-server-{version}-darwin-{platform}.tar.gz");
         let asset = release
             .assets
@@ -46,7 +50,7 @@ impl super::LspAdapter for LuaLspAdapter {
             .find(|asset| asset.name == asset_name)
             .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
         let version = GitHubLspBinaryVersion {
-            name: release.name.clone(),
+            name: release.name,
             url: asset.browser_download_url.clone(),
         };
         Ok(Box::new(version) as Box<_>)

crates/zed/src/languages/rust.rs 🔗

@@ -31,8 +31,13 @@ impl LspAdapter for RustLspAdapter {
         &self,
         delegate: &dyn LspAdapterDelegate,
     ) -> Result<Box<dyn 'static + Send + Any>> {
-        let release =
-            latest_github_release("rust-lang/rust-analyzer", false, delegate.http_client()).await?;
+        let release = latest_github_release(
+            "rust-lang/rust-analyzer",
+            true,
+            false,
+            delegate.http_client(),
+        )
+        .await?;
         let asset_name = format!("rust-analyzer-{}-apple-darwin.gz", consts::ARCH);
         let asset = release
             .assets

crates/zed/src/languages/toml.rs 🔗

@@ -26,7 +26,8 @@ impl LspAdapter for TaploLspAdapter {
         &self,
         delegate: &dyn LspAdapterDelegate,
     ) -> Result<Box<dyn 'static + Send + Any>> {
-        let release = latest_github_release("tamasfe/taplo", false, delegate.http_client()).await?;
+        let release =
+            latest_github_release("tamasfe/taplo", true, false, delegate.http_client()).await?;
         let asset_name = format!("taplo-full-darwin-{arch}.gz", arch = std::env::consts::ARCH);
 
         let asset = release

crates/zed/src/languages/typescript.rs 🔗

@@ -246,8 +246,13 @@ impl LspAdapter for EsLintLspAdapter {
         // At the time of writing the latest vscode-eslint release was released in 2020 and requires
         // special custom LSP protocol extensions be handled to fully initialize. Download the latest
         // prerelease instead to sidestep this issue
-        let release =
-            latest_github_release("microsoft/vscode-eslint", true, delegate.http_client()).await?;
+        let release = latest_github_release(
+            "microsoft/vscode-eslint",
+            false,
+            false,
+            delegate.http_client(),
+        )
+        .await?;
         Ok(Box::new(GitHubLspBinaryVersion {
             name: release.name,
             url: release.tarball_url,

crates/zed/src/languages/zig.rs 🔗

@@ -28,8 +28,9 @@ impl LspAdapter for ZlsAdapter {
         &self,
         delegate: &dyn LspAdapterDelegate,
     ) -> Result<Box<dyn 'static + Send + Any>> {
-        let release = latest_github_release("zigtools/zls", false, delegate.http_client()).await?;
-        let asset_name = format!("zls-{}-macos.tar.gz", ARCH);
+        let release =
+            latest_github_release("zigtools/zls", true, false, delegate.http_client()).await?;
+        let asset_name = format!("zls-{ARCH}-macos.tar.gz");
         let asset = release
             .assets
             .iter()