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 AttachRequest, BuildTaskDefinition, BuildTaskDefinitionTemplatePayload, BuildTaskTemplate,
24 DebugAdapterBinary, DebugConfig, DebugRequest, DebugScenario, DebugTaskDefinition,
25 LaunchRequest, StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
26 TaskTemplate, TcpArguments, TcpArgumentsTemplate, resolve_tcp_template,
27 },
28 zed::extension::github::{
29 GithubRelease, GithubReleaseAsset, GithubReleaseOptions, github_release_by_tag_name,
30 latest_github_release,
31 },
32 zed::extension::nodejs::{
33 node_binary_path, npm_install_package, npm_package_installed_version,
34 npm_package_latest_version,
35 },
36 zed::extension::platform::{Architecture, Os, current_platform},
37 zed::extension::slash_command::{
38 SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, SlashCommandOutputSection,
39 },
40};
41
42// Undocumented WIT re-exports.
43//
44// These are symbols that need to be public for the purposes of implementing
45// the extension host, but aren't relevant to extension authors.
46#[doc(hidden)]
47pub use wit::Guest;
48
49/// Constructs for interacting with language servers over the
50/// Language Server Protocol (LSP).
51pub mod lsp {
52 pub use crate::wit::zed::extension::lsp::{
53 Completion, CompletionKind, InsertTextFormat, Symbol, SymbolKind,
54 };
55}
56
57/// A result returned from a Zed extension.
58pub type Result<T, E = String> = core::result::Result<T, E>;
59
60/// Updates the installation status for the given language server.
61pub fn set_language_server_installation_status(
62 language_server_id: &LanguageServerId,
63 status: &LanguageServerInstallationStatus,
64) {
65 wit::set_language_server_installation_status(&language_server_id.0, status)
66}
67
68/// A Zed extension.
69pub trait Extension: Send + Sync {
70 /// Returns a new instance of the extension.
71 fn new() -> Self
72 where
73 Self: Sized;
74
75 /// Returns the command used to start the language server for the specified
76 /// language.
77 fn language_server_command(
78 &mut self,
79 _language_server_id: &LanguageServerId,
80 _worktree: &Worktree,
81 ) -> Result<Command> {
82 Err("`language_server_command` not implemented".to_string())
83 }
84
85 /// Returns the initialization options to pass to the specified language server.
86 fn language_server_initialization_options(
87 &mut self,
88 _language_server_id: &LanguageServerId,
89 _worktree: &Worktree,
90 ) -> Result<Option<serde_json::Value>> {
91 Ok(None)
92 }
93
94 /// Returns the workspace configuration options to pass to the language server.
95 fn language_server_workspace_configuration(
96 &mut self,
97 _language_server_id: &LanguageServerId,
98 _worktree: &Worktree,
99 ) -> Result<Option<serde_json::Value>> {
100 Ok(None)
101 }
102
103 /// Returns the JSON schema for the initialization options.
104 ///
105 /// The schema must conform to the JSON Schema speification.
106 fn language_server_initialization_options_schema(
107 &mut self,
108 _language_server_id: &LanguageServerId,
109 _worktree: &Worktree,
110 ) -> Option<serde_json::Value> {
111 None
112 }
113
114 /// Returns the JSON schema for the workspace configuration.
115 ///
116 /// The schema must conform to the JSON Schema specification.
117 fn language_server_workspace_configuration_schema(
118 &mut self,
119 _language_server_id: &LanguageServerId,
120 _worktree: &Worktree,
121 ) -> Option<serde_json::Value> {
122 None
123 }
124
125 /// Returns the initialization options to pass to the other language server.
126 fn language_server_additional_initialization_options(
127 &mut self,
128 _language_server_id: &LanguageServerId,
129 _target_language_server_id: &LanguageServerId,
130 _worktree: &Worktree,
131 ) -> Result<Option<serde_json::Value>> {
132 Ok(None)
133 }
134
135 /// Returns the workspace configuration options to pass to the other language server.
136 fn language_server_additional_workspace_configuration(
137 &mut self,
138 _language_server_id: &LanguageServerId,
139 _target_language_server_id: &LanguageServerId,
140 _worktree: &Worktree,
141 ) -> Result<Option<serde_json::Value>> {
142 Ok(None)
143 }
144
145 /// Returns the label for the given completion.
146 fn label_for_completion(
147 &self,
148 _language_server_id: &LanguageServerId,
149 _completion: Completion,
150 ) -> Option<CodeLabel> {
151 None
152 }
153
154 /// Returns the label for the given symbol.
155 fn label_for_symbol(
156 &self,
157 _language_server_id: &LanguageServerId,
158 _symbol: Symbol,
159 ) -> Option<CodeLabel> {
160 None
161 }
162
163 /// Returns the completions that should be shown when completing the provided slash command with the given query.
164 fn complete_slash_command_argument(
165 &self,
166 _command: SlashCommand,
167 _args: Vec<String>,
168 ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
169 Ok(Vec::new())
170 }
171
172 /// Returns the output from running the provided slash command.
173 fn run_slash_command(
174 &self,
175 _command: SlashCommand,
176 _args: Vec<String>,
177 _worktree: Option<&Worktree>,
178 ) -> Result<SlashCommandOutput, String> {
179 Err("`run_slash_command` not implemented".to_string())
180 }
181
182 /// Returns the command used to start a context server.
183 fn context_server_command(
184 &mut self,
185 _context_server_id: &ContextServerId,
186 _project: &Project,
187 ) -> Result<Command> {
188 Err("`context_server_command` not implemented".to_string())
189 }
190
191 /// Returns the configuration options for the specified context server.
192 fn context_server_configuration(
193 &mut self,
194 _context_server_id: &ContextServerId,
195 _project: &Project,
196 ) -> Result<Option<ContextServerConfiguration>> {
197 Ok(None)
198 }
199
200 /// Returns a list of package names as suggestions to be included in the
201 /// search results of the `/docs` slash command.
202 ///
203 /// This can be used to provide completions for known packages (e.g., from the
204 /// local project or a registry) before a package has been indexed.
205 fn suggest_docs_packages(&self, _provider: String) -> Result<Vec<String>, String> {
206 Ok(Vec::new())
207 }
208
209 /// Indexes the docs for the specified package.
210 fn index_docs(
211 &self,
212 _provider: String,
213 _package: String,
214 _database: &KeyValueStore,
215 ) -> Result<(), String> {
216 Err("`index_docs` not implemented".to_string())
217 }
218
219 /// Returns the debug adapter binary for the specified adapter name and configuration.
220 fn get_dap_binary(
221 &mut self,
222 _adapter_name: String,
223 _config: DebugTaskDefinition,
224 _user_provided_debug_adapter_path: Option<String>,
225 _worktree: &Worktree,
226 ) -> Result<DebugAdapterBinary, String> {
227 Err("`get_dap_binary` not implemented".to_string())
228 }
229
230 /// Determines whether the specified adapter configuration should *launch* a new debuggee process
231 /// or *attach* to an existing one. This function should not perform any further validation (outside of determining the kind of a request).
232 /// This function should return an error when the kind cannot be determined (rather than fall back to a known default).
233 fn dap_request_kind(
234 &mut self,
235 _adapter_name: String,
236 _config: serde_json::Value,
237 ) -> Result<StartDebuggingRequestArgumentsRequest, String> {
238 Err("`dap_request_kind` not implemented".to_string())
239 }
240 /// Converts a high-level definition of a debug scenario (originating in a new session UI) to a "low-level" configuration suitable for a particular adapter.
241 ///
242 /// In layman's terms: given a program, list of arguments, current working directory and environment variables,
243 /// create a configuration that can be used to start a debug session.
244 fn dap_config_to_scenario(&mut self, _config: DebugConfig) -> Result<DebugScenario, String> {
245 Err("`dap_config_to_scenario` not implemented".to_string())
246 }
247
248 /// Locators are entities that convert a Zed task into a debug scenario.
249 ///
250 /// They can be provided even by extensions that don't provide a debug adapter.
251 /// For all tasks applicable to a given buffer, Zed will query all locators to find one that can turn the task into a debug scenario.
252 /// A converted debug scenario can include a build task (it shouldn't contain any configuration in such case); a build task result will later
253 /// be resolved with [`Extension::run_dap_locator`].
254 ///
255 /// To work through a real-world example, take a `cargo run` task and a hypothetical `cargo` locator:
256 /// 1. We may need to modify the task; in this case, it is problematic that `cargo run` spawns a binary. We should turn `cargo run` into a debug scenario with
257 /// `cargo build` task. This is the decision we make at `dap_locator_create_scenario` scope.
258 /// 2. Then, after the build task finishes, we will run `run_dap_locator` of the locator that produced the build task to find the program to be debugged. This function
259 /// should give us a debugger-agnostic configuration for launching a debug target (that we end up resolving with [`Extension::dap_config_to_scenario`]). It's almost as if the user
260 /// found the artifact path by themselves.
261 ///
262 /// Note that you're not obliged to use build tasks with locators. Specifically, it is sufficient to provide a debug configuration directly in the return value of
263 /// `dap_locator_create_scenario` if you're able to do that. Make sure to not fill out `build` field in that case, as that will prevent Zed from running second phase of resolution in such case.
264 /// This might be of particular relevance to interpreted languages.
265 fn dap_locator_create_scenario(
266 &mut self,
267 _locator_name: String,
268 _build_task: TaskTemplate,
269 _resolved_label: String,
270 _debug_adapter_name: String,
271 ) -> Option<DebugScenario> {
272 None
273 }
274
275 /// Runs the second phase of locator resolution.
276 /// See [`Extension::dap_locator_create_scenario`] for a hefty comment on locators.
277 fn run_dap_locator(
278 &mut self,
279 _locator_name: String,
280 _build_task: TaskTemplate,
281 ) -> Result<DebugRequest, String> {
282 Err("`run_dap_locator` not implemented".to_string())
283 }
284}
285
286/// Registers the provided type as a Zed extension.
287///
288/// The type must implement the [`Extension`] trait.
289#[macro_export]
290macro_rules! register_extension {
291 ($extension_type:ty) => {
292 #[cfg(target_os = "wasi")]
293 mod wasi_ext {
294 unsafe extern "C" {
295 static mut errno: i32;
296 pub static mut __wasilibc_cwd: *mut std::ffi::c_char;
297 }
298
299 pub fn init_cwd() {
300 unsafe {
301 // Ensure that our chdir function is linked, instead of the
302 // one from wasi-libc in the chdir.o translation unit. Otherwise
303 // we risk linking in `__wasilibc_find_relpath_alloc` which
304 // is a weak symbol and is being used by
305 // `__wasilibc_find_relpath`, which we do not want on
306 // Windows.
307 chdir(std::ptr::null());
308
309 __wasilibc_cwd = std::ffi::CString::new(std::env::var("PWD").unwrap())
310 .unwrap()
311 .into_raw()
312 .cast();
313 }
314 }
315
316 #[unsafe(no_mangle)]
317 pub unsafe extern "C" fn chdir(raw_path: *const std::ffi::c_char) -> i32 {
318 // Forbid extensions from changing CWD and so return an appropriate error code.
319 errno = 58; // NOTSUP
320 return -1;
321 }
322 }
323
324 #[unsafe(export_name = "init-extension")]
325 pub extern "C" fn __init_extension() {
326 #[cfg(target_os = "wasi")]
327 wasi_ext::init_cwd();
328
329 zed_extension_api::register_extension(|| {
330 Box::new(<$extension_type as zed_extension_api::Extension>::new())
331 });
332 }
333 };
334}
335
336#[doc(hidden)]
337pub fn register_extension(build_extension: fn() -> Box<dyn Extension>) {
338 unsafe { EXTENSION = Some((build_extension)()) }
339}
340
341fn extension() -> &'static mut dyn Extension {
342 #[expect(static_mut_refs)]
343 unsafe {
344 EXTENSION.as_deref_mut().unwrap()
345 }
346}
347
348static mut EXTENSION: Option<Box<dyn Extension>> = None;
349
350#[cfg(target_arch = "wasm32")]
351#[unsafe(link_section = "zed:api-version")]
352#[doc(hidden)]
353pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "/version_bytes"));
354
355mod wit {
356 wit_bindgen::generate!({
357 skip: ["init-extension"],
358 path: "./wit/since_v0.8.0",
359 });
360}
361
362wit::export!(Component);
363
364struct Component;
365
366impl wit::Guest for Component {
367 fn language_server_command(
368 language_server_id: String,
369 worktree: &wit::Worktree,
370 ) -> Result<wit::Command> {
371 let language_server_id = LanguageServerId(language_server_id);
372 extension().language_server_command(&language_server_id, worktree)
373 }
374
375 fn language_server_initialization_options(
376 language_server_id: String,
377 worktree: &Worktree,
378 ) -> Result<Option<String>, String> {
379 let language_server_id = LanguageServerId(language_server_id);
380 Ok(extension()
381 .language_server_initialization_options(&language_server_id, worktree)?
382 .and_then(|value| serde_json::to_string(&value).ok()))
383 }
384
385 fn language_server_workspace_configuration(
386 language_server_id: String,
387 worktree: &Worktree,
388 ) -> Result<Option<String>, String> {
389 let language_server_id = LanguageServerId(language_server_id);
390 Ok(extension()
391 .language_server_workspace_configuration(&language_server_id, worktree)?
392 .and_then(|value| serde_json::to_string(&value).ok()))
393 }
394
395 fn language_server_initialization_options_schema(
396 language_server_id: String,
397 worktree: &Worktree,
398 ) -> Option<String> {
399 let language_server_id = LanguageServerId(language_server_id);
400 extension()
401 .language_server_initialization_options_schema(&language_server_id, worktree)
402 .and_then(|value| serde_json::to_string(&value).ok())
403 }
404
405 fn language_server_workspace_configuration_schema(
406 language_server_id: String,
407 worktree: &Worktree,
408 ) -> Option<String> {
409 let language_server_id = LanguageServerId(language_server_id);
410 extension()
411 .language_server_workspace_configuration_schema(&language_server_id, worktree)
412 .and_then(|value| serde_json::to_string(&value).ok())
413 }
414
415 fn language_server_additional_initialization_options(
416 language_server_id: String,
417 target_language_server_id: String,
418 worktree: &Worktree,
419 ) -> Result<Option<String>, String> {
420 let language_server_id = LanguageServerId(language_server_id);
421 let target_language_server_id = LanguageServerId(target_language_server_id);
422 Ok(extension()
423 .language_server_additional_initialization_options(
424 &language_server_id,
425 &target_language_server_id,
426 worktree,
427 )?
428 .and_then(|value| serde_json::to_string(&value).ok()))
429 }
430
431 fn language_server_additional_workspace_configuration(
432 language_server_id: String,
433 target_language_server_id: String,
434 worktree: &Worktree,
435 ) -> Result<Option<String>, String> {
436 let language_server_id = LanguageServerId(language_server_id);
437 let target_language_server_id = LanguageServerId(target_language_server_id);
438 Ok(extension()
439 .language_server_additional_workspace_configuration(
440 &language_server_id,
441 &target_language_server_id,
442 worktree,
443 )?
444 .and_then(|value| serde_json::to_string(&value).ok()))
445 }
446
447 fn labels_for_completions(
448 language_server_id: String,
449 completions: Vec<Completion>,
450 ) -> Result<Vec<Option<CodeLabel>>, String> {
451 let language_server_id = LanguageServerId(language_server_id);
452 let mut labels = Vec::new();
453 for (ix, completion) in completions.into_iter().enumerate() {
454 let label = extension().label_for_completion(&language_server_id, completion);
455 if let Some(label) = label {
456 labels.resize(ix + 1, None);
457 *labels.last_mut().unwrap() = Some(label);
458 }
459 }
460 Ok(labels)
461 }
462
463 fn labels_for_symbols(
464 language_server_id: String,
465 symbols: Vec<Symbol>,
466 ) -> Result<Vec<Option<CodeLabel>>, String> {
467 let language_server_id = LanguageServerId(language_server_id);
468 let mut labels = Vec::new();
469 for (ix, symbol) in symbols.into_iter().enumerate() {
470 let label = extension().label_for_symbol(&language_server_id, symbol);
471 if let Some(label) = label {
472 labels.resize(ix + 1, None);
473 *labels.last_mut().unwrap() = Some(label);
474 }
475 }
476 Ok(labels)
477 }
478
479 fn complete_slash_command_argument(
480 command: SlashCommand,
481 args: Vec<String>,
482 ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
483 extension().complete_slash_command_argument(command, args)
484 }
485
486 fn run_slash_command(
487 command: SlashCommand,
488 args: Vec<String>,
489 worktree: Option<&Worktree>,
490 ) -> Result<SlashCommandOutput, String> {
491 extension().run_slash_command(command, args, worktree)
492 }
493
494 fn context_server_command(
495 context_server_id: String,
496 project: &Project,
497 ) -> Result<wit::Command> {
498 let context_server_id = ContextServerId(context_server_id);
499 extension().context_server_command(&context_server_id, project)
500 }
501
502 fn context_server_configuration(
503 context_server_id: String,
504 project: &Project,
505 ) -> Result<Option<ContextServerConfiguration>, String> {
506 let context_server_id = ContextServerId(context_server_id);
507 extension().context_server_configuration(&context_server_id, project)
508 }
509
510 fn suggest_docs_packages(provider: String) -> Result<Vec<String>, String> {
511 extension().suggest_docs_packages(provider)
512 }
513
514 fn index_docs(
515 provider: String,
516 package: String,
517 database: &KeyValueStore,
518 ) -> Result<(), String> {
519 extension().index_docs(provider, package, database)
520 }
521
522 fn get_dap_binary(
523 adapter_name: String,
524 config: DebugTaskDefinition,
525 user_installed_path: Option<String>,
526 worktree: &Worktree,
527 ) -> Result<wit::DebugAdapterBinary, String> {
528 extension().get_dap_binary(adapter_name, config, user_installed_path, worktree)
529 }
530
531 fn dap_request_kind(
532 adapter_name: String,
533 config: String,
534 ) -> Result<StartDebuggingRequestArgumentsRequest, String> {
535 extension().dap_request_kind(
536 adapter_name,
537 serde_json::from_str(&config).map_err(|e| format!("Failed to parse config: {e}"))?,
538 )
539 }
540 fn dap_config_to_scenario(config: DebugConfig) -> Result<DebugScenario, String> {
541 extension().dap_config_to_scenario(config)
542 }
543 fn dap_locator_create_scenario(
544 locator_name: String,
545 build_task: TaskTemplate,
546 resolved_label: String,
547 debug_adapter_name: String,
548 ) -> Option<DebugScenario> {
549 extension().dap_locator_create_scenario(
550 locator_name,
551 build_task,
552 resolved_label,
553 debug_adapter_name,
554 )
555 }
556 fn run_dap_locator(
557 locator_name: String,
558 build_task: TaskTemplate,
559 ) -> Result<DebugRequest, String> {
560 extension().run_dap_locator(locator_name, build_task)
561 }
562}
563
564/// The ID of a language server.
565#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
566pub struct LanguageServerId(String);
567
568impl LanguageServerId {
569 pub fn new(value: String) -> Self {
570 Self(value)
571 }
572}
573
574impl AsRef<str> for LanguageServerId {
575 fn as_ref(&self) -> &str {
576 &self.0
577 }
578}
579
580impl fmt::Display for LanguageServerId {
581 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582 write!(f, "{}", self.0)
583 }
584}
585
586/// The ID of a context server.
587#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
588pub struct ContextServerId(String);
589
590impl ContextServerId {
591 pub fn new(value: String) -> Self {
592 Self(value)
593 }
594}
595
596impl AsRef<str> for ContextServerId {
597 fn as_ref(&self) -> &str {
598 &self.0
599 }
600}
601
602impl fmt::Display for ContextServerId {
603 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
604 write!(f, "{}", self.0)
605 }
606}
607
608impl CodeLabelSpan {
609 /// Returns a [`CodeLabelSpan::CodeRange`].
610 pub fn code_range(range: impl Into<wit::Range>) -> Self {
611 Self::CodeRange(range.into())
612 }
613
614 /// Returns a [`CodeLabelSpan::Literal`].
615 pub fn literal(text: impl Into<String>, highlight_name: Option<String>) -> Self {
616 Self::Literal(CodeLabelSpanLiteral {
617 text: text.into(),
618 highlight_name,
619 })
620 }
621}
622
623impl From<std::ops::Range<u32>> for wit::Range {
624 fn from(value: std::ops::Range<u32>) -> Self {
625 Self {
626 start: value.start,
627 end: value.end,
628 }
629 }
630}
631
632impl From<std::ops::Range<usize>> for wit::Range {
633 fn from(value: std::ops::Range<usize>) -> Self {
634 Self {
635 start: value.start as u32,
636 end: value.end as u32,
637 }
638 }
639}