1use std::fs;
2use zed::lsp::CompletionKind;
3use zed::{serde_json, CodeLabel, CodeLabelSpan, LanguageServerId};
4use zed_extension_api::settings::LspSettings;
5use zed_extension_api::{self as zed, Result};
6
7struct DenoExtension {
8 cached_binary_path: Option<String>,
9}
10
11impl DenoExtension {
12 fn language_server_binary_path(
13 &mut self,
14 language_server_id: &LanguageServerId,
15 worktree: &zed::Worktree,
16 ) -> Result<String> {
17 if let Some(path) = worktree.which("deno") {
18 return Ok(path);
19 }
20
21 if let Some(path) = &self.cached_binary_path {
22 if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
23 return Ok(path.clone());
24 }
25 }
26
27 zed::set_language_server_installation_status(
28 language_server_id,
29 &zed::LanguageServerInstallationStatus::CheckingForUpdate,
30 );
31 let release = zed::latest_github_release(
32 "denoland/deno",
33 zed::GithubReleaseOptions {
34 require_assets: true,
35 pre_release: false,
36 },
37 )?;
38
39 let (platform, arch) = zed::current_platform();
40 let asset_name = format!(
41 "deno-{arch}-{os}.zip",
42 arch = match arch {
43 zed::Architecture::Aarch64 => "aarch64",
44 zed::Architecture::X8664 => "x86_64",
45 zed::Architecture::X86 =>
46 return Err(format!("unsupported architecture: {arch:?}")),
47 },
48 os = match platform {
49 zed::Os::Mac => "apple-darwin",
50 zed::Os::Linux => "unknown-linux-gnu",
51 zed::Os::Windows => "pc-windows-msvc",
52 },
53 );
54
55 let asset = release
56 .assets
57 .iter()
58 .find(|asset| asset.name == asset_name)
59 .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?;
60
61 let version_dir = format!("deno-{}", release.version);
62 let binary_path = format!("{version_dir}/deno");
63
64 if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) {
65 zed::set_language_server_installation_status(
66 language_server_id,
67 &zed::LanguageServerInstallationStatus::Downloading,
68 );
69
70 zed::download_file(
71 &asset.download_url,
72 &version_dir,
73 zed::DownloadedFileType::Zip,
74 )
75 .map_err(|e| format!("failed to download file: {e}"))?;
76
77 zed::make_file_executable(&binary_path)?;
78
79 let entries =
80 fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?;
81 for entry in entries {
82 let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?;
83 if entry.file_name().to_str() != Some(&version_dir) {
84 fs::remove_dir_all(entry.path()).ok();
85 }
86 }
87 }
88
89 self.cached_binary_path = Some(binary_path.clone());
90 Ok(binary_path)
91 }
92}
93
94impl zed::Extension for DenoExtension {
95 fn new() -> Self {
96 Self {
97 cached_binary_path: None,
98 }
99 }
100
101 fn language_server_command(
102 &mut self,
103 language_server_id: &LanguageServerId,
104 worktree: &zed::Worktree,
105 ) -> Result<zed::Command> {
106 Ok(zed::Command {
107 command: self.language_server_binary_path(language_server_id, worktree)?,
108 args: vec!["lsp".to_string()],
109 env: Default::default(),
110 })
111 }
112
113 fn language_server_initialization_options(
114 &mut self,
115 _language_server_id: &zed::LanguageServerId,
116 _worktree: &zed::Worktree,
117 ) -> Result<Option<serde_json::Value>> {
118 Ok(Some(serde_json::json!({
119 "provideFormatter": true,
120 })))
121 }
122
123 fn language_server_workspace_configuration(
124 &mut self,
125 _language_server_id: &zed::LanguageServerId,
126 worktree: &zed::Worktree,
127 ) -> Result<Option<serde_json::Value>> {
128 let settings = LspSettings::for_worktree("deno", worktree)
129 .ok()
130 .and_then(|lsp_settings| lsp_settings.settings.clone())
131 .unwrap_or_default();
132 Ok(Some(settings))
133 }
134
135 fn label_for_completion(
136 &self,
137 _language_server_id: &LanguageServerId,
138 completion: zed::lsp::Completion,
139 ) -> Option<CodeLabel> {
140 let highlight_name = match completion.kind? {
141 CompletionKind::Class | CompletionKind::Interface | CompletionKind::Constructor => {
142 "type"
143 }
144 CompletionKind::Constant => "constant",
145 CompletionKind::Function | CompletionKind::Method => "function",
146 CompletionKind::Property | CompletionKind::Field => "property",
147 _ => return None,
148 };
149
150 let len = completion.label.len();
151 let name_span = CodeLabelSpan::literal(completion.label, Some(highlight_name.to_string()));
152
153 Some(zed::CodeLabel {
154 code: Default::default(),
155 spans: if let Some(detail) = completion.detail {
156 vec![
157 name_span,
158 CodeLabelSpan::literal(" ", None),
159 CodeLabelSpan::literal(detail, None),
160 ]
161 } else {
162 vec![name_span]
163 },
164 filter_range: (0..len).into(),
165 })
166 }
167}
168
169zed::register_extension!(DenoExtension);