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}