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