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, DebugConfig, DebugRequest, DebugScenario, DebugTaskDefinition,
24 StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest, TaskTemplate,
25 TcpArguments, TcpArgumentsTemplate, 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 ) -> Result<DebugScenario, String> {
218 Err("`dap_config_to_scenario` not implemented".to_string())
219 }
220 fn dap_locator_create_scenario(
221 &mut self,
222 _locator_name: String,
223 _build_task: TaskTemplate,
224 _resolved_label: String,
225 _debug_adapter_name: String,
226 ) -> Option<DebugScenario> {
227 None
228 }
229 fn run_dap_locator(
230 &mut self,
231 _locator_name: String,
232 _build_task: TaskTemplate,
233 ) -> Result<DebugRequest, String> {
234 Err("`run_dap_locator` not implemented".to_string())
235 }
236}
237
238/// Registers the provided type as a Zed extension.
239///
240/// The type must implement the [`Extension`] trait.
241#[macro_export]
242macro_rules! register_extension {
243 ($extension_type:ty) => {
244 #[unsafe(export_name = "init-extension")]
245 pub extern "C" fn __init_extension() {
246 std::env::set_current_dir(std::env::var("PWD").unwrap()).unwrap();
247 zed_extension_api::register_extension(|| {
248 Box::new(<$extension_type as zed_extension_api::Extension>::new())
249 });
250 }
251 };
252}
253
254#[doc(hidden)]
255pub fn register_extension(build_extension: fn() -> Box<dyn Extension>) {
256 unsafe { EXTENSION = Some((build_extension)()) }
257}
258
259fn extension() -> &'static mut dyn Extension {
260 #[expect(static_mut_refs)]
261 unsafe {
262 EXTENSION.as_deref_mut().unwrap()
263 }
264}
265
266static mut EXTENSION: Option<Box<dyn Extension>> = None;
267
268#[cfg(target_arch = "wasm32")]
269#[unsafe(link_section = "zed:api-version")]
270#[doc(hidden)]
271pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "/version_bytes"));
272
273mod wit {
274
275 wit_bindgen::generate!({
276 skip: ["init-extension"],
277 path: "./wit/since_v0.6.0",
278 });
279}
280
281wit::export!(Component);
282
283struct Component;
284
285impl wit::Guest for Component {
286 fn language_server_command(
287 language_server_id: String,
288 worktree: &wit::Worktree,
289 ) -> Result<wit::Command> {
290 let language_server_id = LanguageServerId(language_server_id);
291 extension().language_server_command(&language_server_id, worktree)
292 }
293
294 fn language_server_initialization_options(
295 language_server_id: String,
296 worktree: &Worktree,
297 ) -> Result<Option<String>, String> {
298 let language_server_id = LanguageServerId(language_server_id);
299 Ok(extension()
300 .language_server_initialization_options(&language_server_id, worktree)?
301 .and_then(|value| serde_json::to_string(&value).ok()))
302 }
303
304 fn language_server_workspace_configuration(
305 language_server_id: String,
306 worktree: &Worktree,
307 ) -> Result<Option<String>, String> {
308 let language_server_id = LanguageServerId(language_server_id);
309 Ok(extension()
310 .language_server_workspace_configuration(&language_server_id, worktree)?
311 .and_then(|value| serde_json::to_string(&value).ok()))
312 }
313
314 fn language_server_additional_initialization_options(
315 language_server_id: String,
316 target_language_server_id: String,
317 worktree: &Worktree,
318 ) -> Result<Option<String>, String> {
319 let language_server_id = LanguageServerId(language_server_id);
320 let target_language_server_id = LanguageServerId(target_language_server_id);
321 Ok(extension()
322 .language_server_additional_initialization_options(
323 &language_server_id,
324 &target_language_server_id,
325 worktree,
326 )?
327 .and_then(|value| serde_json::to_string(&value).ok()))
328 }
329
330 fn language_server_additional_workspace_configuration(
331 language_server_id: String,
332 target_language_server_id: String,
333 worktree: &Worktree,
334 ) -> Result<Option<String>, String> {
335 let language_server_id = LanguageServerId(language_server_id);
336 let target_language_server_id = LanguageServerId(target_language_server_id);
337 Ok(extension()
338 .language_server_additional_workspace_configuration(
339 &language_server_id,
340 &target_language_server_id,
341 worktree,
342 )?
343 .and_then(|value| serde_json::to_string(&value).ok()))
344 }
345
346 fn labels_for_completions(
347 language_server_id: String,
348 completions: Vec<Completion>,
349 ) -> Result<Vec<Option<CodeLabel>>, String> {
350 let language_server_id = LanguageServerId(language_server_id);
351 let mut labels = Vec::new();
352 for (ix, completion) in completions.into_iter().enumerate() {
353 let label = extension().label_for_completion(&language_server_id, completion);
354 if let Some(label) = label {
355 labels.resize(ix + 1, None);
356 *labels.last_mut().unwrap() = Some(label);
357 }
358 }
359 Ok(labels)
360 }
361
362 fn labels_for_symbols(
363 language_server_id: String,
364 symbols: Vec<Symbol>,
365 ) -> Result<Vec<Option<CodeLabel>>, String> {
366 let language_server_id = LanguageServerId(language_server_id);
367 let mut labels = Vec::new();
368 for (ix, symbol) in symbols.into_iter().enumerate() {
369 let label = extension().label_for_symbol(&language_server_id, symbol);
370 if let Some(label) = label {
371 labels.resize(ix + 1, None);
372 *labels.last_mut().unwrap() = Some(label);
373 }
374 }
375 Ok(labels)
376 }
377
378 fn complete_slash_command_argument(
379 command: SlashCommand,
380 args: Vec<String>,
381 ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
382 extension().complete_slash_command_argument(command, args)
383 }
384
385 fn run_slash_command(
386 command: SlashCommand,
387 args: Vec<String>,
388 worktree: Option<&Worktree>,
389 ) -> Result<SlashCommandOutput, String> {
390 extension().run_slash_command(command, args, worktree)
391 }
392
393 fn context_server_command(
394 context_server_id: String,
395 project: &Project,
396 ) -> Result<wit::Command> {
397 let context_server_id = ContextServerId(context_server_id);
398 extension().context_server_command(&context_server_id, project)
399 }
400
401 fn context_server_configuration(
402 context_server_id: String,
403 project: &Project,
404 ) -> Result<Option<ContextServerConfiguration>, String> {
405 let context_server_id = ContextServerId(context_server_id);
406 extension().context_server_configuration(&context_server_id, project)
407 }
408
409 fn suggest_docs_packages(provider: String) -> Result<Vec<String>, String> {
410 extension().suggest_docs_packages(provider)
411 }
412
413 fn index_docs(
414 provider: String,
415 package: String,
416 database: &KeyValueStore,
417 ) -> Result<(), String> {
418 extension().index_docs(provider, package, database)
419 }
420
421 fn get_dap_binary(
422 adapter_name: String,
423 config: DebugTaskDefinition,
424 user_installed_path: Option<String>,
425 worktree: &Worktree,
426 ) -> Result<wit::DebugAdapterBinary, String> {
427 extension().get_dap_binary(adapter_name, config, user_installed_path, worktree)
428 }
429
430 fn dap_request_kind(
431 adapter_name: String,
432 config: String,
433 ) -> Result<StartDebuggingRequestArgumentsRequest, String> {
434 extension().dap_request_kind(
435 adapter_name,
436 serde_json::from_str(&config).map_err(|e| format!("Failed to parse config: {e}"))?,
437 )
438 }
439 fn dap_config_to_scenario(config: DebugConfig) -> Result<DebugScenario, String> {
440 extension().dap_config_to_scenario(config)
441 }
442 fn dap_locator_create_scenario(
443 locator_name: String,
444 build_task: TaskTemplate,
445 resolved_label: String,
446 debug_adapter_name: String,
447 ) -> Option<DebugScenario> {
448 extension().dap_locator_create_scenario(
449 locator_name,
450 build_task,
451 resolved_label,
452 debug_adapter_name,
453 )
454 }
455 fn run_dap_locator(
456 locator_name: String,
457 build_task: TaskTemplate,
458 ) -> Result<DebugRequest, String> {
459 extension().run_dap_locator(locator_name, build_task)
460 }
461}
462
463/// The ID of a language server.
464#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
465pub struct LanguageServerId(String);
466
467impl AsRef<str> for LanguageServerId {
468 fn as_ref(&self) -> &str {
469 &self.0
470 }
471}
472
473impl fmt::Display for LanguageServerId {
474 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
475 write!(f, "{}", self.0)
476 }
477}
478
479/// The ID of a context server.
480#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
481pub struct ContextServerId(String);
482
483impl AsRef<str> for ContextServerId {
484 fn as_ref(&self) -> &str {
485 &self.0
486 }
487}
488
489impl fmt::Display for ContextServerId {
490 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
491 write!(f, "{}", self.0)
492 }
493}
494
495impl CodeLabelSpan {
496 /// Returns a [`CodeLabelSpan::CodeRange`].
497 pub fn code_range(range: impl Into<wit::Range>) -> Self {
498 Self::CodeRange(range.into())
499 }
500
501 /// Returns a [`CodeLabelSpan::Literal`].
502 pub fn literal(text: impl Into<String>, highlight_name: Option<String>) -> Self {
503 Self::Literal(CodeLabelSpanLiteral {
504 text: text.into(),
505 highlight_name,
506 })
507 }
508}
509
510impl From<std::ops::Range<u32>> for wit::Range {
511 fn from(value: std::ops::Range<u32>) -> Self {
512 Self {
513 start: value.start,
514 end: value.end,
515 }
516 }
517}
518
519impl From<std::ops::Range<usize>> for wit::Range {
520 fn from(value: std::ops::Range<usize>) -> Self {
521 Self {
522 start: value.start as u32,
523 end: value.end as u32,
524 }
525 }
526}