gleam.rs

  1use std::any::Any;
  2use std::env::consts;
  3use std::ffi::OsString;
  4use std::path::PathBuf;
  5
  6use anyhow::{anyhow, bail, Result};
  7use async_compression::futures::bufread::GzipDecoder;
  8use async_tar::Archive;
  9use async_trait::async_trait;
 10use futures::io::BufReader;
 11use futures::StreamExt;
 12use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
 13use lsp::LanguageServerBinary;
 14use smol::fs;
 15use util::github::{latest_github_release, GitHubLspBinaryVersion};
 16use util::{async_maybe, ResultExt};
 17
 18fn server_binary_arguments() -> Vec<OsString> {
 19    vec!["lsp".into()]
 20}
 21
 22pub struct GleamLspAdapter;
 23
 24#[async_trait(?Send)]
 25impl LspAdapter for GleamLspAdapter {
 26    fn name(&self) -> LanguageServerName {
 27        LanguageServerName("gleam".into())
 28    }
 29
 30    async fn fetch_latest_server_version(
 31        &self,
 32        delegate: &dyn LspAdapterDelegate,
 33    ) -> Result<Box<dyn 'static + Send + Any>> {
 34        let release =
 35            latest_github_release("gleam-lang/gleam", true, false, delegate.http_client()).await?;
 36        let asset_name = format!(
 37            "gleam-{version}-{arch}-{os}.tar.gz",
 38            version = release.tag_name,
 39            arch = std::env::consts::ARCH,
 40            os = match consts::OS {
 41                "macos" => "apple-darwin",
 42                "linux" => "unknown-linux-musl",
 43                "windows" => "pc-windows-msvc",
 44                other => bail!("Running on unsupported os: {other}"),
 45            },
 46        );
 47        let asset = release
 48            .assets
 49            .iter()
 50            .find(|asset| asset.name == asset_name)
 51            .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
 52        Ok(Box::new(GitHubLspBinaryVersion {
 53            name: release.tag_name,
 54            url: asset.browser_download_url.clone(),
 55        }))
 56    }
 57
 58    async fn fetch_server_binary(
 59        &self,
 60        version: Box<dyn 'static + Send + Any>,
 61        container_dir: PathBuf,
 62        delegate: &dyn LspAdapterDelegate,
 63    ) -> Result<LanguageServerBinary> {
 64        let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
 65        let binary_path = container_dir.join("gleam");
 66
 67        if fs::metadata(&binary_path).await.is_err() {
 68            let mut response = delegate
 69                .http_client()
 70                .get(&version.url, Default::default(), true)
 71                .await
 72                .map_err(|err| anyhow!("error downloading release: {}", err))?;
 73            let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
 74            let archive = Archive::new(decompressed_bytes);
 75            archive.unpack(container_dir).await?;
 76        }
 77
 78        Ok(LanguageServerBinary {
 79            path: binary_path,
 80            env: None,
 81            arguments: server_binary_arguments(),
 82        })
 83    }
 84
 85    async fn cached_server_binary(
 86        &self,
 87        container_dir: PathBuf,
 88        _: &dyn LspAdapterDelegate,
 89    ) -> Option<LanguageServerBinary> {
 90        get_cached_server_binary(container_dir).await
 91    }
 92
 93    async fn installation_test_binary(
 94        &self,
 95        container_dir: PathBuf,
 96    ) -> Option<LanguageServerBinary> {
 97        get_cached_server_binary(container_dir)
 98            .await
 99            .map(|mut binary| {
100                binary.arguments = vec!["--version".into()];
101                binary
102            })
103    }
104}
105
106async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
107    async_maybe!({
108        let mut last = None;
109        let mut entries = fs::read_dir(&container_dir).await?;
110        while let Some(entry) = entries.next().await {
111            last = Some(entry?.path());
112        }
113
114        anyhow::Ok(LanguageServerBinary {
115            path: last.ok_or_else(|| anyhow!("no cached binary"))?,
116            env: None,
117            arguments: server_binary_arguments(),
118        })
119    })
120    .await
121    .log_err()
122}