1//! The Zed Rust Extension API allows you write extensions for [Zed](https://zed.dev/) in Rust.
2
3pub mod http_client;
4pub mod process;
5pub mod settings;
6
7use core::fmt;
8
9use wit::*;
10
11pub use serde_json;
12
13// WIT re-exports.
14//
15// We explicitly enumerate the symbols we want to re-export, as there are some
16// that we may want to shadow to provide a cleaner Rust API.
17pub use wit::{
18 CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
19 KeyValueStore, LanguageServerInstallationStatus, Project, Range, Worktree, download_file,
20 make_file_executable,
21 zed::extension::context_server::ContextServerConfiguration,
22 zed::extension::github::{
23 GithubRelease, GithubReleaseAsset, GithubReleaseOptions, github_release_by_tag_name,
24 latest_github_release,
25 },
26 zed::extension::nodejs::{
27 node_binary_path, npm_install_package, npm_package_installed_version,
28 npm_package_latest_version,
29 },
30 zed::extension::platform::{Architecture, Os, current_platform},
31 zed::extension::slash_command::{
32 SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, SlashCommandOutputSection,
33 },
34};
35
36// Undocumented WIT re-exports.
37//
38// These are symbols that need to be public for the purposes of implementing
39// the extension host, but aren't relevant to extension authors.
40#[doc(hidden)]
41pub use wit::Guest;
42
43/// Constructs for interacting with language servers over the
44/// Language Server Protocol (LSP).
45pub mod lsp {
46 pub use crate::wit::zed::extension::lsp::{
47 Completion, CompletionKind, InsertTextFormat, Symbol, SymbolKind,
48 };
49}
50
51/// A result returned from a Zed extension.
52pub type Result<T, E = String> = core::result::Result<T, E>;
53
54/// Updates the installation status for the given language server.
55pub fn set_language_server_installation_status(
56 language_server_id: &LanguageServerId,
57 status: &LanguageServerInstallationStatus,
58) {
59 wit::set_language_server_installation_status(&language_server_id.0, status)
60}
61
62/// A Zed extension.
63pub trait Extension: Send + Sync {
64 /// Returns a new instance of the extension.
65 fn new() -> Self
66 where
67 Self: Sized;
68
69 /// Returns the command used to start the language server for the specified
70 /// language.
71 fn language_server_command(
72 &mut self,
73 _language_server_id: &LanguageServerId,
74 _worktree: &Worktree,
75 ) -> Result<Command> {
76 Err("`language_server_command` not implemented".to_string())
77 }
78
79 /// Returns the initialization options to pass to the specified language server.
80 fn language_server_initialization_options(
81 &mut self,
82 _language_server_id: &LanguageServerId,
83 _worktree: &Worktree,
84 ) -> Result<Option<serde_json::Value>> {
85 Ok(None)
86 }
87
88 /// Returns the workspace configuration options to pass to the language server.
89 fn language_server_workspace_configuration(
90 &mut self,
91 _language_server_id: &LanguageServerId,
92 _worktree: &Worktree,
93 ) -> Result<Option<serde_json::Value>> {
94 Ok(None)
95 }
96
97 /// Returns the initialization options to pass to the other language server.
98 fn language_server_additional_initialization_options(
99 &mut self,
100 _language_server_id: &LanguageServerId,
101 _target_language_server_id: &LanguageServerId,
102 _worktree: &Worktree,
103 ) -> Result<Option<serde_json::Value>> {
104 Ok(None)
105 }
106
107 /// Returns the workspace configuration options to pass to the other language server.
108 fn language_server_additional_workspace_configuration(
109 &mut self,
110 _language_server_id: &LanguageServerId,
111 _target_language_server_id: &LanguageServerId,
112 _worktree: &Worktree,
113 ) -> Result<Option<serde_json::Value>> {
114 Ok(None)
115 }
116
117 /// Returns the label for the given completion.
118 fn label_for_completion(
119 &self,
120 _language_server_id: &LanguageServerId,
121 _completion: Completion,
122 ) -> Option<CodeLabel> {
123 None
124 }
125
126 /// Returns the label for the given symbol.
127 fn label_for_symbol(
128 &self,
129 _language_server_id: &LanguageServerId,
130 _symbol: Symbol,
131 ) -> Option<CodeLabel> {
132 None
133 }
134
135 /// Returns the completions that should be shown when completing the provided slash command with the given query.
136 fn complete_slash_command_argument(
137 &self,
138 _command: SlashCommand,
139 _args: Vec<String>,
140 ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
141 Ok(Vec::new())
142 }
143
144 /// Returns the output from running the provided slash command.
145 fn run_slash_command(
146 &self,
147 _command: SlashCommand,
148 _args: Vec<String>,
149 _worktree: Option<&Worktree>,
150 ) -> Result<SlashCommandOutput, String> {
151 Err("`run_slash_command` not implemented".to_string())
152 }
153
154 /// Returns the command used to start a context server.
155 fn context_server_command(
156 &mut self,
157 _context_server_id: &ContextServerId,
158 _project: &Project,
159 ) -> Result<Command> {
160 Err("`context_server_command` not implemented".to_string())
161 }
162
163 /// Returns the configuration options for the specified context server.
164 fn context_server_configuration(
165 &mut self,
166 _context_server_id: &ContextServerId,
167 _project: &Project,
168 ) -> Result<Option<ContextServerConfiguration>> {
169 Ok(None)
170 }
171
172 /// Returns a list of package names as suggestions to be included in the
173 /// search results of the `/docs` slash command.
174 ///
175 /// This can be used to provide completions for known packages (e.g., from the
176 /// local project or a registry) before a package has been indexed.
177 fn suggest_docs_packages(&self, _provider: String) -> Result<Vec<String>, String> {
178 Ok(Vec::new())
179 }
180
181 /// Indexes the docs for the specified package.
182 fn index_docs(
183 &self,
184 _provider: String,
185 _package: String,
186 _database: &KeyValueStore,
187 ) -> Result<(), String> {
188 Err("`index_docs` not implemented".to_string())
189 }
190
191 /// Returns the debug adapter binary for the specified adapter name and configuration.
192 fn get_dap_binary(
193 &mut self,
194 _adapter_name: String,
195 _config: DebugTaskDefinition,
196 _user_provided_path: Option<String>,
197 ) -> Result<DebugAdapterBinary, String> {
198 Err("`get_dap_binary` not implemented".to_string())
199 }
200}
201
202/// Registers the provided type as a Zed extension.
203///
204/// The type must implement the [`Extension`] trait.
205#[macro_export]
206macro_rules! register_extension {
207 ($extension_type:ty) => {
208 #[unsafe(export_name = "init-extension")]
209 pub extern "C" fn __init_extension() {
210 std::env::set_current_dir(std::env::var("PWD").unwrap()).unwrap();
211 zed_extension_api::register_extension(|| {
212 Box::new(<$extension_type as zed_extension_api::Extension>::new())
213 });
214 }
215 };
216}
217
218#[doc(hidden)]
219pub fn register_extension(build_extension: fn() -> Box<dyn Extension>) {
220 unsafe { EXTENSION = Some((build_extension)()) }
221}
222
223fn extension() -> &'static mut dyn Extension {
224 #[expect(static_mut_refs)]
225 unsafe {
226 EXTENSION.as_deref_mut().unwrap()
227 }
228}
229
230static mut EXTENSION: Option<Box<dyn Extension>> = None;
231
232#[cfg(target_arch = "wasm32")]
233#[unsafe(link_section = "zed:api-version")]
234#[doc(hidden)]
235pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "/version_bytes"));
236
237mod wit {
238
239 wit_bindgen::generate!({
240 skip: ["init-extension"],
241 path: "./wit/since_v0.6.0",
242 });
243}
244
245wit::export!(Component);
246
247struct Component;
248
249impl wit::Guest for Component {
250 fn language_server_command(
251 language_server_id: String,
252 worktree: &wit::Worktree,
253 ) -> Result<wit::Command> {
254 let language_server_id = LanguageServerId(language_server_id);
255 extension().language_server_command(&language_server_id, worktree)
256 }
257
258 fn language_server_initialization_options(
259 language_server_id: String,
260 worktree: &Worktree,
261 ) -> Result<Option<String>, String> {
262 let language_server_id = LanguageServerId(language_server_id);
263 Ok(extension()
264 .language_server_initialization_options(&language_server_id, worktree)?
265 .and_then(|value| serde_json::to_string(&value).ok()))
266 }
267
268 fn language_server_workspace_configuration(
269 language_server_id: String,
270 worktree: &Worktree,
271 ) -> Result<Option<String>, String> {
272 let language_server_id = LanguageServerId(language_server_id);
273 Ok(extension()
274 .language_server_workspace_configuration(&language_server_id, worktree)?
275 .and_then(|value| serde_json::to_string(&value).ok()))
276 }
277
278 fn language_server_additional_initialization_options(
279 language_server_id: String,
280 target_language_server_id: String,
281 worktree: &Worktree,
282 ) -> Result<Option<String>, String> {
283 let language_server_id = LanguageServerId(language_server_id);
284 let target_language_server_id = LanguageServerId(target_language_server_id);
285 Ok(extension()
286 .language_server_additional_initialization_options(
287 &language_server_id,
288 &target_language_server_id,
289 worktree,
290 )?
291 .and_then(|value| serde_json::to_string(&value).ok()))
292 }
293
294 fn language_server_additional_workspace_configuration(
295 language_server_id: String,
296 target_language_server_id: String,
297 worktree: &Worktree,
298 ) -> Result<Option<String>, String> {
299 let language_server_id = LanguageServerId(language_server_id);
300 let target_language_server_id = LanguageServerId(target_language_server_id);
301 Ok(extension()
302 .language_server_additional_workspace_configuration(
303 &language_server_id,
304 &target_language_server_id,
305 worktree,
306 )?
307 .and_then(|value| serde_json::to_string(&value).ok()))
308 }
309
310 fn labels_for_completions(
311 language_server_id: String,
312 completions: Vec<Completion>,
313 ) -> Result<Vec<Option<CodeLabel>>, String> {
314 let language_server_id = LanguageServerId(language_server_id);
315 let mut labels = Vec::new();
316 for (ix, completion) in completions.into_iter().enumerate() {
317 let label = extension().label_for_completion(&language_server_id, completion);
318 if let Some(label) = label {
319 labels.resize(ix + 1, None);
320 *labels.last_mut().unwrap() = Some(label);
321 }
322 }
323 Ok(labels)
324 }
325
326 fn labels_for_symbols(
327 language_server_id: String,
328 symbols: Vec<Symbol>,
329 ) -> Result<Vec<Option<CodeLabel>>, String> {
330 let language_server_id = LanguageServerId(language_server_id);
331 let mut labels = Vec::new();
332 for (ix, symbol) in symbols.into_iter().enumerate() {
333 let label = extension().label_for_symbol(&language_server_id, symbol);
334 if let Some(label) = label {
335 labels.resize(ix + 1, None);
336 *labels.last_mut().unwrap() = Some(label);
337 }
338 }
339 Ok(labels)
340 }
341
342 fn complete_slash_command_argument(
343 command: SlashCommand,
344 args: Vec<String>,
345 ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
346 extension().complete_slash_command_argument(command, args)
347 }
348
349 fn run_slash_command(
350 command: SlashCommand,
351 args: Vec<String>,
352 worktree: Option<&Worktree>,
353 ) -> Result<SlashCommandOutput, String> {
354 extension().run_slash_command(command, args, worktree)
355 }
356
357 fn context_server_command(
358 context_server_id: String,
359 project: &Project,
360 ) -> Result<wit::Command> {
361 let context_server_id = ContextServerId(context_server_id);
362 extension().context_server_command(&context_server_id, project)
363 }
364
365 fn context_server_configuration(
366 context_server_id: String,
367 project: &Project,
368 ) -> Result<Option<ContextServerConfiguration>, String> {
369 let context_server_id = ContextServerId(context_server_id);
370 extension().context_server_configuration(&context_server_id, project)
371 }
372
373 fn suggest_docs_packages(provider: String) -> Result<Vec<String>, String> {
374 extension().suggest_docs_packages(provider)
375 }
376
377 fn index_docs(
378 provider: String,
379 package: String,
380 database: &KeyValueStore,
381 ) -> Result<(), String> {
382 extension().index_docs(provider, package, database)
383 }
384
385 fn get_dap_binary(
386 adapter_name: String,
387 config: DebugTaskDefinition,
388 user_installed_path: Option<String>,
389 ) -> Result<DebugAdapterBinary, String> {
390 extension().get_dap_binary(adapter_name, config, user_installed_path)
391 }
392}
393
394/// The ID of a language server.
395#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
396pub struct LanguageServerId(String);
397
398impl AsRef<str> for LanguageServerId {
399 fn as_ref(&self) -> &str {
400 &self.0
401 }
402}
403
404impl fmt::Display for LanguageServerId {
405 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
406 write!(f, "{}", self.0)
407 }
408}
409
410/// The ID of a context server.
411#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
412pub struct ContextServerId(String);
413
414impl AsRef<str> for ContextServerId {
415 fn as_ref(&self) -> &str {
416 &self.0
417 }
418}
419
420impl fmt::Display for ContextServerId {
421 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422 write!(f, "{}", self.0)
423 }
424}
425
426impl CodeLabelSpan {
427 /// Returns a [`CodeLabelSpan::CodeRange`].
428 pub fn code_range(range: impl Into<wit::Range>) -> Self {
429 Self::CodeRange(range.into())
430 }
431
432 /// Returns a [`CodeLabelSpan::Literal`].
433 pub fn literal(text: impl Into<String>, highlight_name: Option<String>) -> Self {
434 Self::Literal(CodeLabelSpanLiteral {
435 text: text.into(),
436 highlight_name,
437 })
438 }
439}
440
441impl From<std::ops::Range<u32>> for wit::Range {
442 fn from(value: std::ops::Range<u32>) -> Self {
443 Self {
444 start: value.start,
445 end: value.end,
446 }
447 }
448}
449
450impl From<std::ops::Range<usize>> for wit::Range {
451 fn from(value: std::ops::Range<usize>) -> Self {
452 Self {
453 start: value.start as u32,
454 end: value.end as u32,
455 }
456 }
457}