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 let entries =
78 fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?;
79 for entry in entries {
80 let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?;
81 if entry.file_name().to_str() != Some(&version_dir) {
82 fs::remove_dir_all(entry.path()).ok();
83 }
84 }
85 }
86
87 self.cached_binary_path = Some(binary_path.clone());
88 Ok(binary_path)
89 }
90}
91
92impl zed::Extension for DenoExtension {
93 fn new() -> Self {
94 Self {
95 cached_binary_path: None,
96 }
97 }
98
99 fn language_server_command(
100 &mut self,
101 language_server_id: &LanguageServerId,
102 worktree: &zed::Worktree,
103 ) -> Result<zed::Command> {
104 Ok(zed::Command {
105 command: self.language_server_binary_path(language_server_id, worktree)?,
106 args: vec!["lsp".to_string()],
107 env: Default::default(),
108 })
109 }
110
111 fn language_server_initialization_options(
112 &mut self,
113 _language_server_id: &zed::LanguageServerId,
114 _worktree: &zed::Worktree,
115 ) -> Result<Option<serde_json::Value>> {
116 Ok(Some(serde_json::json!({
117 "provideFormatter": true,
118 })))
119 }
120
121 fn language_server_workspace_configuration(
122 &mut self,
123 _language_server_id: &zed::LanguageServerId,
124 worktree: &zed::Worktree,
125 ) -> Result<Option<serde_json::Value>> {
126 let settings = LspSettings::for_worktree("deno", worktree)
127 .ok()
128 .and_then(|lsp_settings| lsp_settings.settings.clone())
129 .unwrap_or_default();
130 Ok(Some(settings))
131 }
132
133 fn label_for_completion(
134 &self,
135 _language_server_id: &LanguageServerId,
136 completion: zed::lsp::Completion,
137 ) -> Option<CodeLabel> {
138 let highlight_name = match completion.kind? {
139 CompletionKind::Class | CompletionKind::Interface | CompletionKind::Constructor => {
140 "type"
141 }
142 CompletionKind::Constant => "constant",
143 CompletionKind::Function | CompletionKind::Method => "function",
144 CompletionKind::Property | CompletionKind::Field => "property",
145 _ => return None,
146 };
147
148 let len = completion.label.len();
149 let name_span = CodeLabelSpan::literal(completion.label, Some(highlight_name.to_string()));
150
151 Some(zed::CodeLabel {
152 code: Default::default(),
153 spans: if let Some(detail) = completion.detail {
154 vec![
155 name_span,
156 CodeLabelSpan::literal(" ", None),
157 CodeLabelSpan::literal(detail, None),
158 ]
159 } else {
160 vec![name_span]
161 },
162 filter_range: (0..len).into(),
163 })
164 }
165}
166
167zed::register_extension!(DenoExtension);