1use std::fs;
2use zed::LanguageServerId;
3use zed_extension_api::settings::LspSettings;
4use zed_extension_api::{self as zed, Result};
5
6struct TaploBinary {
7 path: String,
8 args: Option<Vec<String>>,
9}
10
11struct TomlExtension {
12 cached_binary_path: Option<String>,
13}
14
15impl TomlExtension {
16 fn language_server_binary(
17 &mut self,
18 language_server_id: &LanguageServerId,
19 worktree: &zed::Worktree,
20 ) -> Result<TaploBinary> {
21 let binary_settings = LspSettings::for_worktree("taplo", worktree)
22 .ok()
23 .and_then(|lsp_settings| lsp_settings.binary);
24 let binary_args = binary_settings
25 .as_ref()
26 .and_then(|binary_settings| binary_settings.arguments.clone());
27
28 if let Some(path) = binary_settings.and_then(|binary_settings| binary_settings.path) {
29 return Ok(TaploBinary {
30 path,
31 args: binary_args,
32 });
33 }
34
35 if let Some(path) = worktree.which("taplo") {
36 return Ok(TaploBinary {
37 path,
38 args: binary_args,
39 });
40 }
41
42 if let Some(path) = &self.cached_binary_path {
43 if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
44 return Ok(TaploBinary {
45 path: path.clone(),
46 args: binary_args,
47 });
48 }
49 }
50
51 zed::set_language_server_installation_status(
52 language_server_id,
53 &zed::LanguageServerInstallationStatus::CheckingForUpdate,
54 );
55 let release = zed::latest_github_release(
56 "tamasfe/taplo",
57 zed::GithubReleaseOptions {
58 require_assets: true,
59 pre_release: false,
60 },
61 )?;
62
63 let (platform, arch) = zed::current_platform();
64 let asset_name = format!(
65 "taplo-{os}-{arch}.gz",
66 arch = match arch {
67 zed::Architecture::Aarch64 => "aarch64",
68 zed::Architecture::X86 => "x86",
69 zed::Architecture::X8664 => "x86_64",
70 },
71 os = match platform {
72 zed::Os::Mac => "darwin",
73 zed::Os::Linux => "linux",
74 zed::Os::Windows => "windows",
75 },
76 );
77
78 let asset = release
79 .assets
80 .iter()
81 .find(|asset| asset.name == asset_name)
82 .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?;
83
84 let version_dir = format!("taplo-{}", release.version);
85 fs::create_dir_all(&version_dir)
86 .map_err(|err| format!("failed to create directory '{version_dir}': {err}"))?;
87
88 let binary_path = format!(
89 "{version_dir}/{bin_name}",
90 bin_name = match platform {
91 zed::Os::Windows => "taplo.exe",
92 zed::Os::Mac | zed::Os::Linux => "taplo",
93 }
94 );
95
96 if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) {
97 zed::set_language_server_installation_status(
98 language_server_id,
99 &zed::LanguageServerInstallationStatus::Downloading,
100 );
101
102 zed::download_file(
103 &asset.download_url,
104 &binary_path,
105 zed::DownloadedFileType::Gzip,
106 )
107 .map_err(|err| format!("failed to download file: {err}"))?;
108
109 zed::make_file_executable(&binary_path)?;
110
111 let entries: Vec<_> = fs::read_dir(".")
112 .map_err(|err| format!("failed to list working directory {err}"))?
113 .collect();
114 for entry in entries {
115 let entry = entry.map_err(|err| format!("failed to load directory entry {err}"))?;
116 if entry.file_name().to_str() != Some(&version_dir) {
117 fs::remove_dir_all(entry.path()).ok();
118 }
119 }
120 }
121
122 self.cached_binary_path = Some(binary_path.clone());
123 Ok(TaploBinary {
124 path: binary_path,
125 args: binary_args,
126 })
127 }
128}
129
130impl zed::Extension for TomlExtension {
131 fn new() -> Self {
132 Self {
133 cached_binary_path: None,
134 }
135 }
136
137 fn language_server_command(
138 &mut self,
139 language_server_id: &LanguageServerId,
140 worktree: &zed::Worktree,
141 ) -> Result<zed::Command> {
142 let taplo_binary = self.language_server_binary(language_server_id, worktree)?;
143 Ok(zed::Command {
144 command: taplo_binary.path,
145 args: taplo_binary
146 .args
147 .unwrap_or_else(|| vec!["lsp".to_string(), "stdio".to_string()]),
148 env: Default::default(),
149 })
150 }
151}
152
153zed::register_extension!(TomlExtension);