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}