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