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