1use std::fs;
2use zed_extension_api::{self as zed, serde_json, settings::LspSettings, LanguageServerId, Result};
3
4struct ZigExtension {
5 cached_binary_path: Option<String>,
6}
7
8#[derive(Clone)]
9struct ZlsBinary {
10 path: String,
11 args: Option<Vec<String>>,
12 environment: Option<Vec<(String, String)>>,
13}
14
15impl ZigExtension {
16 fn language_server_binary(
17 &mut self,
18 language_server_id: &LanguageServerId,
19 worktree: &zed::Worktree,
20 ) -> Result<ZlsBinary> {
21 let mut args: Option<Vec<String>> = None;
22
23 let (platform, arch) = zed::current_platform();
24 let environment = match platform {
25 zed::Os::Mac | zed::Os::Linux => Some(worktree.shell_env()),
26 zed::Os::Windows => None,
27 };
28
29 if let Ok(lsp_settings) = LspSettings::for_worktree("zls", worktree) {
30 if let Some(binary) = lsp_settings.binary {
31 args = binary.arguments;
32 if let Some(path) = binary.path {
33 return Ok(ZlsBinary {
34 path: path.clone(),
35 args,
36 environment,
37 });
38 }
39 }
40 }
41
42 if let Some(path) = worktree.which("zls") {
43 return Ok(ZlsBinary {
44 path,
45 args,
46 environment,
47 });
48 }
49
50 if let Some(path) = &self.cached_binary_path {
51 if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
52 return Ok(ZlsBinary {
53 path: path.clone(),
54 args,
55 environment,
56 });
57 }
58 }
59
60 zed::set_language_server_installation_status(
61 language_server_id,
62 &zed::LanguageServerInstallationStatus::CheckingForUpdate,
63 );
64
65 // Note that in github releases and on zlstools.org the tar.gz asset is not shown
66 // but is available at https://builds.zigtools.org/zls-{os}-{arch}-{version}.tar.gz
67 let release = zed::latest_github_release(
68 "zigtools/zls",
69 zed::GithubReleaseOptions {
70 require_assets: true,
71 pre_release: false,
72 },
73 )?;
74
75 let arch: &str = match arch {
76 zed::Architecture::Aarch64 => "aarch64",
77 zed::Architecture::X86 => "x86",
78 zed::Architecture::X8664 => "x86_64",
79 };
80
81 let os: &str = match platform {
82 zed::Os::Mac => "macos",
83 zed::Os::Linux => "linux",
84 zed::Os::Windows => "windows",
85 };
86
87 let extension: &str = match platform {
88 zed::Os::Mac | zed::Os::Linux => "tar.gz",
89 zed::Os::Windows => "zip",
90 };
91
92 let asset_name: String = format!("zls-{}-{}-{}.{}", os, arch, release.version, extension);
93 let download_url = format!("https://builds.zigtools.org/{}", asset_name);
94
95 let version_dir = format!("zls-{}", release.version);
96 let binary_path = match platform {
97 zed::Os::Mac | zed::Os::Linux => format!("{version_dir}/zls"),
98 zed::Os::Windows => format!("{version_dir}/zls.exe"),
99 };
100
101 if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) {
102 zed::set_language_server_installation_status(
103 language_server_id,
104 &zed::LanguageServerInstallationStatus::Downloading,
105 );
106
107 zed::download_file(
108 &download_url,
109 &version_dir,
110 match platform {
111 zed::Os::Mac | zed::Os::Linux => zed::DownloadedFileType::GzipTar,
112 zed::Os::Windows => zed::DownloadedFileType::Zip,
113 },
114 )
115 .map_err(|e| format!("failed to download file: {e}"))?;
116
117 zed::make_file_executable(&binary_path)?;
118
119 let entries =
120 fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?;
121 for entry in entries {
122 let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?;
123 if entry.file_name().to_str() != Some(&version_dir) {
124 fs::remove_dir_all(entry.path()).ok();
125 }
126 }
127 }
128
129 self.cached_binary_path = Some(binary_path.clone());
130 Ok(ZlsBinary {
131 path: binary_path,
132 args,
133 environment,
134 })
135 }
136}
137
138impl zed::Extension for ZigExtension {
139 fn new() -> Self {
140 Self {
141 cached_binary_path: None,
142 }
143 }
144
145 fn language_server_command(
146 &mut self,
147 language_server_id: &LanguageServerId,
148 worktree: &zed::Worktree,
149 ) -> Result<zed::Command> {
150 let zls_binary = self.language_server_binary(language_server_id, worktree)?;
151 Ok(zed::Command {
152 command: zls_binary.path,
153 args: zls_binary.args.unwrap_or_default(),
154 env: zls_binary.environment.unwrap_or_default(),
155 })
156 }
157
158 fn language_server_workspace_configuration(
159 &mut self,
160 _language_server_id: &zed::LanguageServerId,
161 worktree: &zed::Worktree,
162 ) -> Result<Option<serde_json::Value>> {
163 let settings = LspSettings::for_worktree("zls", worktree)
164 .ok()
165 .and_then(|lsp_settings| lsp_settings.settings.clone())
166 .unwrap_or_default();
167 Ok(Some(settings))
168 }
169}
170
171zed::register_extension!(ZigExtension);