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