clojure.rs

  1use anyhow::{anyhow, bail, Context, Result};
  2use async_trait::async_trait;
  3pub use language::*;
  4use lsp::LanguageServerBinary;
  5use smol::fs::{self, File};
  6use std::{any::Any, env::consts, path::PathBuf};
  7use util::{
  8    fs::remove_matching,
  9    github::{latest_github_release, GitHubLspBinaryVersion},
 10};
 11
 12#[derive(Copy, Clone)]
 13pub struct ClojureLspAdapter;
 14
 15#[async_trait(?Send)]
 16impl super::LspAdapter for ClojureLspAdapter {
 17    fn name(&self) -> LanguageServerName {
 18        LanguageServerName("clojure-lsp".into())
 19    }
 20
 21    async fn fetch_latest_server_version(
 22        &self,
 23        delegate: &dyn LspAdapterDelegate,
 24    ) -> Result<Box<dyn 'static + Send + Any>> {
 25        let release = latest_github_release(
 26            "clojure-lsp/clojure-lsp",
 27            true,
 28            false,
 29            delegate.http_client(),
 30        )
 31        .await?;
 32        let os = match consts::OS {
 33            "macos" => "macos",
 34            "linux" => "linux",
 35            "windows" => "windows",
 36            other => bail!("Running on unsupported os: {other}"),
 37        };
 38        let platform = match consts::ARCH {
 39            "x86_64" => "amd64",
 40            "aarch64" => "aarch64",
 41            other => bail!("Running on unsupported platform: {other}"),
 42        };
 43        let asset_name = format!("clojure-lsp-native-{os}-{platform}.zip");
 44        let asset = release
 45            .assets
 46            .iter()
 47            .find(|asset| asset.name == asset_name)
 48            .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
 49        let version = GitHubLspBinaryVersion {
 50            name: release.tag_name.clone(),
 51            url: asset.browser_download_url.clone(),
 52        };
 53        Ok(Box::new(version) as Box<_>)
 54    }
 55
 56    async fn fetch_server_binary(
 57        &self,
 58        version: Box<dyn 'static + Send + Any>,
 59        container_dir: PathBuf,
 60        delegate: &dyn LspAdapterDelegate,
 61    ) -> Result<LanguageServerBinary> {
 62        let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
 63        let zip_path = container_dir.join(format!("clojure-lsp_{}.zip", version.name));
 64        let folder_path = container_dir.join("bin");
 65        let binary_path = folder_path.join("clojure-lsp");
 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                .context("error downloading release")?;
 73            let mut file = File::create(&zip_path)
 74                .await
 75                .with_context(|| format!("failed to create file {}", zip_path.display()))?;
 76            if !response.status().is_success() {
 77                return Err(anyhow!(
 78                    "download failed with status {}",
 79                    response.status().to_string()
 80                ))?;
 81            }
 82            futures::io::copy(response.body_mut(), &mut file).await?;
 83
 84            fs::create_dir_all(&folder_path)
 85                .await
 86                .with_context(|| format!("failed to create directory {}", folder_path.display()))?;
 87
 88            let unzip_status = smol::process::Command::new("unzip")
 89                .arg(&zip_path)
 90                .arg("-d")
 91                .arg(&folder_path)
 92                .output()
 93                .await?
 94                .status;
 95            if !unzip_status.success() {
 96                return Err(anyhow!("failed to unzip elixir-ls archive"))?;
 97            }
 98
 99            remove_matching(&container_dir, |entry| entry != folder_path).await;
100        }
101
102        Ok(LanguageServerBinary {
103            path: binary_path,
104            env: None,
105            arguments: vec![],
106        })
107    }
108
109    async fn cached_server_binary(
110        &self,
111        container_dir: PathBuf,
112        _: &dyn LspAdapterDelegate,
113    ) -> Option<LanguageServerBinary> {
114        let binary_path = container_dir.join("bin").join("clojure-lsp");
115        if binary_path.exists() {
116            Some(LanguageServerBinary {
117                path: binary_path,
118                env: None,
119                arguments: vec![],
120            })
121        } else {
122            None
123        }
124    }
125
126    async fn installation_test_binary(
127        &self,
128        container_dir: PathBuf,
129    ) -> Option<LanguageServerBinary> {
130        let binary_path = container_dir.join("bin").join("clojure-lsp");
131        if binary_path.exists() {
132            Some(LanguageServerBinary {
133                path: binary_path,
134                env: None,
135                arguments: vec!["--version".into()],
136            })
137        } else {
138            None
139        }
140    }
141}