1use anyhow::{anyhow, bail, Result};
2use async_compression::futures::bufread::GzipDecoder;
3use async_tar::Archive;
4use async_trait::async_trait;
5use futures::{io::BufReader, StreamExt};
6use language::{LanguageServerName, LspAdapterDelegate};
7use lsp::LanguageServerBinary;
8use smol::fs;
9use std::{any::Any, env::consts, path::PathBuf};
10use util::{
11 async_maybe,
12 github::{latest_github_release, GitHubLspBinaryVersion},
13 ResultExt,
14};
15
16#[derive(Copy, Clone)]
17pub struct LuaLspAdapter;
18
19#[async_trait]
20impl super::LspAdapter for LuaLspAdapter {
21 fn name(&self) -> LanguageServerName {
22 LanguageServerName("lua-language-server".into())
23 }
24
25 async fn fetch_latest_server_version(
26 &self,
27 delegate: &dyn LspAdapterDelegate,
28 ) -> Result<Box<dyn 'static + Send + Any>> {
29 let os = match consts::OS {
30 "macos" => "darwin",
31 "linux" => "linux",
32 "windows" => "win32",
33 other => bail!("Running on unsupported os: {other}"),
34 };
35 let platform = match consts::ARCH {
36 "x86_64" => "x64",
37 "aarch64" => "arm64",
38 other => bail!("Running on unsupported platform: {other}"),
39 };
40 let release = latest_github_release(
41 "LuaLS/lua-language-server",
42 true,
43 false,
44 delegate.http_client(),
45 )
46 .await?;
47 let version = &release.tag_name;
48 let asset_name = format!("lua-language-server-{version}-{os}-{platform}.tar.gz");
49 let asset = release
50 .assets
51 .iter()
52 .find(|asset| asset.name == asset_name)
53 .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
54 let version = GitHubLspBinaryVersion {
55 name: release.tag_name,
56 url: asset.browser_download_url.clone(),
57 };
58 Ok(Box::new(version) as Box<_>)
59 }
60
61 async fn fetch_server_binary(
62 &self,
63 version: Box<dyn 'static + Send + Any>,
64 container_dir: PathBuf,
65 delegate: &dyn LspAdapterDelegate,
66 ) -> Result<LanguageServerBinary> {
67 let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
68
69 let binary_path = container_dir.join("bin/lua-language-server");
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 .map_err(|err| anyhow!("error downloading release: {}", err))?;
77 let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
78 let archive = Archive::new(decompressed_bytes);
79 archive.unpack(container_dir).await?;
80 }
81
82 // todo(windows)
83 #[cfg(not(windows))]
84 {
85 fs::set_permissions(
86 &binary_path,
87 <fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
88 )
89 .await?;
90 }
91 Ok(LanguageServerBinary {
92 path: binary_path,
93 env: None,
94 arguments: Vec::new(),
95 })
96 }
97
98 async fn cached_server_binary(
99 &self,
100 container_dir: PathBuf,
101 _: &dyn LspAdapterDelegate,
102 ) -> Option<LanguageServerBinary> {
103 get_cached_server_binary(container_dir).await
104 }
105
106 async fn installation_test_binary(
107 &self,
108 container_dir: PathBuf,
109 ) -> Option<LanguageServerBinary> {
110 get_cached_server_binary(container_dir)
111 .await
112 .map(|mut binary| {
113 binary.arguments = vec!["--version".into()];
114 binary
115 })
116 }
117}
118
119async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
120 async_maybe!({
121 let mut last_binary_path = None;
122 let mut entries = fs::read_dir(&container_dir).await?;
123 while let Some(entry) = entries.next().await {
124 let entry = entry?;
125 if entry.file_type().await?.is_file()
126 && entry
127 .file_name()
128 .to_str()
129 .map_or(false, |name| name == "lua-language-server")
130 {
131 last_binary_path = Some(entry.path());
132 }
133 }
134
135 if let Some(path) = last_binary_path {
136 Ok(LanguageServerBinary {
137 path,
138 env: None,
139 arguments: Vec::new(),
140 })
141 } else {
142 Err(anyhow!("no cached binary"))
143 }
144 })
145 .await
146 .log_err()
147}