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;
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 fn short_name(&self) -> &'static str {
24 "zls"
25 }
26
27 async fn fetch_latest_server_version(
28 &self,
29 delegate: &dyn LspAdapterDelegate,
30 ) -> Result<Box<dyn 'static + Send + Any>> {
31 let release = latest_github_release("zigtools/zls", false, delegate.http_client()).await?;
32 let asset_name = format!("zls-{}-macos.tar.gz", ARCH);
33 let asset = release
34 .assets
35 .iter()
36 .find(|asset| asset.name == asset_name)
37 .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
38 let version = GitHubLspBinaryVersion {
39 name: release.name,
40 url: asset.browser_download_url.clone(),
41 };
42
43 Ok(Box::new(version) as Box<_>)
44 }
45
46 async fn fetch_server_binary(
47 &self,
48 version: Box<dyn 'static + Send + Any>,
49 container_dir: PathBuf,
50 delegate: &dyn LspAdapterDelegate,
51 ) -> Result<LanguageServerBinary> {
52 let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
53 let binary_path = container_dir.join("bin/zls");
54
55 if fs::metadata(&binary_path).await.is_err() {
56 let mut response = delegate
57 .http_client()
58 .get(&version.url, Default::default(), true)
59 .await
60 .context("error downloading release")?;
61 let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
62 let archive = Archive::new(decompressed_bytes);
63 archive.unpack(container_dir).await?;
64 }
65
66 fs::set_permissions(
67 &binary_path,
68 <fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
69 )
70 .await?;
71 Ok(LanguageServerBinary {
72 path: binary_path,
73 arguments: vec![],
74 })
75 }
76
77 async fn cached_server_binary(
78 &self,
79 container_dir: PathBuf,
80 _: &dyn LspAdapterDelegate,
81 ) -> Option<LanguageServerBinary> {
82 get_cached_server_binary(container_dir).await
83 }
84
85 async fn installation_test_binary(
86 &self,
87 container_dir: PathBuf,
88 ) -> Option<LanguageServerBinary> {
89 get_cached_server_binary(container_dir)
90 .await
91 .map(|mut binary| {
92 binary.arguments = vec!["--help".into()];
93 binary
94 })
95 }
96}
97
98async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
99 async_maybe!({
100 let mut last_binary_path = None;
101 let mut entries = fs::read_dir(&container_dir).await?;
102 while let Some(entry) = entries.next().await {
103 let entry = entry?;
104 if entry.file_type().await?.is_file()
105 && entry
106 .file_name()
107 .to_str()
108 .map_or(false, |name| name == "zls")
109 {
110 last_binary_path = Some(entry.path());
111 }
112 }
113
114 if let Some(path) = last_binary_path {
115 Ok(LanguageServerBinary {
116 path,
117 arguments: Vec::new(),
118 })
119 } else {
120 Err(anyhow!("no cached binary"))
121 }
122 })
123 .await
124 .log_err()
125}