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 initialization options to pass to the other language server.
104 fn language_server_additional_initialization_options(
105 &mut self,
106 _language_server_id: &LanguageServerId,
107 _target_language_server_id: &LanguageServerId,
108 _worktree: &Worktree,
109 ) -> Result<Option<serde_json::Value>> {
110 Ok(None)
111 }
112
113 /// Returns the workspace configuration options to pass to the other language server.
114 fn language_server_additional_workspace_configuration(
115 &mut self,
116 _language_server_id: &LanguageServerId,
117 _target_language_server_id: &LanguageServerId,
118 _worktree: &Worktree,
119 ) -> Result<Option<serde_json::Value>> {
120 Ok(None)
121 }
122
123 /// Returns the label for the given completion.
124 fn label_for_completion(
125 &self,
126 _language_server_id: &LanguageServerId,
127 _completion: Completion,
128 ) -> Option<CodeLabel> {
129 None
130 }
131
132 /// Returns the label for the given symbol.
133 fn label_for_symbol(
134 &self,
135 _language_server_id: &LanguageServerId,
136 _symbol: Symbol,
137 ) -> Option<CodeLabel> {
138 None
139 }
140
141 /// Returns the completions that should be shown when completing the provided slash command with the given query.
142 fn complete_slash_command_argument(
143 &self,
144 _command: SlashCommand,
145 _args: Vec<String>,
146 ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
147 Ok(Vec::new())
148 }
149
150 /// Returns the output from running the provided slash command.
151 fn run_slash_command(
152 &self,
153 _command: SlashCommand,
154 _args: Vec<String>,
155 _worktree: Option<&Worktree>,
156 ) -> Result<SlashCommandOutput, String> {
157 Err("`run_slash_command` not implemented".to_string())
158 }
159
160 /// Returns the command used to start a context server.
161 fn context_server_command(
162 &mut self,
163 _context_server_id: &ContextServerId,
164 _project: &Project,
165 ) -> Result<Command> {
166 Err("`context_server_command` not implemented".to_string())
167 }
168
169 /// Returns the configuration options for the specified context server.
170 fn context_server_configuration(
171 &mut self,
172 _context_server_id: &ContextServerId,
173 _project: &Project,
174 ) -> Result<Option<ContextServerConfiguration>> {
175 Ok(None)
176 }
177
178 /// Returns a list of package names as suggestions to be included in the
179 /// search results of the `/docs` slash command.
180 ///
181 /// This can be used to provide completions for known packages (e.g., from the
182 /// local project or a registry) before a package has been indexed.
183 fn suggest_docs_packages(&self, _provider: String) -> Result<Vec<String>, String> {
184 Ok(Vec::new())
185 }
186
187 /// Indexes the docs for the specified package.
188 fn index_docs(
189 &self,
190 _provider: String,
191 _package: String,
192 _database: &KeyValueStore,
193 ) -> Result<(), String> {
194 Err("`index_docs` not implemented".to_string())
195 }
196
197 /// Returns the debug adapter binary for the specified adapter name and configuration.
198 fn get_dap_binary(
199 &mut self,
200 _adapter_name: String,
201 _config: DebugTaskDefinition,
202 _user_provided_debug_adapter_path: Option<String>,
203 _worktree: &Worktree,
204 ) -> Result<DebugAdapterBinary, String> {
205 Err("`get_dap_binary` not implemented".to_string())
206 }
207
208 /// Determines whether the specified adapter configuration should *launch* a new debuggee process
209 /// or *attach* to an existing one. This function should not perform any further validation (outside of determining the kind of a request).
210 /// This function should return an error when the kind cannot be determined (rather than fall back to a known default).
211 fn dap_request_kind(
212 &mut self,
213 _adapter_name: String,
214 _config: serde_json::Value,
215 ) -> Result<StartDebuggingRequestArgumentsRequest, String> {
216 Err("`dap_request_kind` not implemented".to_string())
217 }
218 /// 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.
219 ///
220 /// In layman's terms: given a program, list of arguments, current working directory and environment variables,
221 /// create a configuration that can be used to start a debug session.
222 fn dap_config_to_scenario(&mut self, _config: DebugConfig) -> Result<DebugScenario, String> {
223 Err("`dap_config_to_scenario` not implemented".to_string())
224 }
225
226 /// Locators are entities that convert a Zed task into a debug scenario.
227 ///
228 /// They can be provided even by extensions that don't provide a debug adapter.
229 /// 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.
230 /// A converted debug scenario can include a build task (it shouldn't contain any configuration in such case); a build task result will later
231 /// be resolved with [`Extension::run_dap_locator`].
232 ///
233 /// To work through a real-world example, take a `cargo run` task and a hypothetical `cargo` locator:
234 /// 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
235 /// `cargo build` task. This is the decision we make at `dap_locator_create_scenario` scope.
236 /// 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
237 /// 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
238 /// found the artifact path by themselves.
239 ///
240 /// 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
241 /// `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.
242 /// This might be of particular relevance to interpreted languages.
243 fn dap_locator_create_scenario(
244 &mut self,
245 _locator_name: String,
246 _build_task: TaskTemplate,
247 _resolved_label: String,
248 _debug_adapter_name: String,
249 ) -> Option<DebugScenario> {
250 None
251 }
252
253 /// Runs the second phase of locator resolution.
254 /// See [`Extension::dap_locator_create_scenario`] for a hefty comment on locators.
255 fn run_dap_locator(
256 &mut self,
257 _locator_name: String,
258 _build_task: TaskTemplate,
259 ) -> Result<DebugRequest, String> {
260 Err("`run_dap_locator` not implemented".to_string())
261 }
262}
263
264/// Registers the provided type as a Zed extension.
265///
266/// The type must implement the [`Extension`] trait.
267#[macro_export]
268macro_rules! register_extension {
269 ($extension_type:ty) => {
270 #[unsafe(export_name = "init-extension")]
271 pub extern "C" fn __init_extension() {
272 std::env::set_current_dir(std::env::var("PWD").unwrap()).unwrap();
273 zed_extension_api::register_extension(|| {
274 Box::new(<$extension_type as zed_extension_api::Extension>::new())
275 });
276 }
277 };
278}
279
280#[doc(hidden)]
281pub fn register_extension(build_extension: fn() -> Box<dyn Extension>) {
282 unsafe { EXTENSION = Some((build_extension)()) }
283}
284
285fn extension() -> &'static mut dyn Extension {
286 #[expect(static_mut_refs)]
287 unsafe {
288 EXTENSION.as_deref_mut().unwrap()
289 }
290}
291
292static mut EXTENSION: Option<Box<dyn Extension>> = None;
293
294#[cfg(target_arch = "wasm32")]
295#[unsafe(link_section = "zed:api-version")]
296#[doc(hidden)]
297pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "/version_bytes"));
298
299mod wit {
300
301 wit_bindgen::generate!({
302 skip: ["init-extension"],
303 path: "./wit/since_v0.6.0",
304 });
305}
306
307wit::export!(Component);
308
309struct Component;
310
311impl wit::Guest for Component {
312 fn language_server_command(
313 language_server_id: String,
314 worktree: &wit::Worktree,
315 ) -> Result<wit::Command> {
316 let language_server_id = LanguageServerId(language_server_id);
317 extension().language_server_command(&language_server_id, worktree)
318 }
319
320 fn language_server_initialization_options(
321 language_server_id: String,
322 worktree: &Worktree,
323 ) -> Result<Option<String>, String> {
324 let language_server_id = LanguageServerId(language_server_id);
325 Ok(extension()
326 .language_server_initialization_options(&language_server_id, worktree)?
327 .and_then(|value| serde_json::to_string(&value).ok()))
328 }
329
330 fn language_server_workspace_configuration(
331 language_server_id: String,
332 worktree: &Worktree,
333 ) -> Result<Option<String>, String> {
334 let language_server_id = LanguageServerId(language_server_id);
335 Ok(extension()
336 .language_server_workspace_configuration(&language_server_id, worktree)?
337 .and_then(|value| serde_json::to_string(&value).ok()))
338 }
339
340 fn language_server_additional_initialization_options(
341 language_server_id: String,
342 target_language_server_id: String,
343 worktree: &Worktree,
344 ) -> Result<Option<String>, String> {
345 let language_server_id = LanguageServerId(language_server_id);
346 let target_language_server_id = LanguageServerId(target_language_server_id);
347 Ok(extension()
348 .language_server_additional_initialization_options(
349 &language_server_id,
350 &target_language_server_id,
351 worktree,
352 )?
353 .and_then(|value| serde_json::to_string(&value).ok()))
354 }
355
356 fn language_server_additional_workspace_configuration(
357 language_server_id: String,
358 target_language_server_id: String,
359 worktree: &Worktree,
360 ) -> Result<Option<String>, String> {
361 let language_server_id = LanguageServerId(language_server_id);
362 let target_language_server_id = LanguageServerId(target_language_server_id);
363 Ok(extension()
364 .language_server_additional_workspace_configuration(
365 &language_server_id,
366 &target_language_server_id,
367 worktree,
368 )?
369 .and_then(|value| serde_json::to_string(&value).ok()))
370 }
371
372 fn labels_for_completions(
373 language_server_id: String,
374 completions: Vec<Completion>,
375 ) -> Result<Vec<Option<CodeLabel>>, String> {
376 let language_server_id = LanguageServerId(language_server_id);
377 let mut labels = Vec::new();
378 for (ix, completion) in completions.into_iter().enumerate() {
379 let label = extension().label_for_completion(&language_server_id, completion);
380 if let Some(label) = label {
381 labels.resize(ix + 1, None);
382 *labels.last_mut().unwrap() = Some(label);
383 }
384 }
385 Ok(labels)
386 }
387
388 fn labels_for_symbols(
389 language_server_id: String,
390 symbols: Vec<Symbol>,
391 ) -> Result<Vec<Option<CodeLabel>>, String> {
392 let language_server_id = LanguageServerId(language_server_id);
393 let mut labels = Vec::new();
394 for (ix, symbol) in symbols.into_iter().enumerate() {
395 let label = extension().label_for_symbol(&language_server_id, symbol);
396 if let Some(label) = label {
397 labels.resize(ix + 1, None);
398 *labels.last_mut().unwrap() = Some(label);
399 }
400 }
401 Ok(labels)
402 }
403
404 fn complete_slash_command_argument(
405 command: SlashCommand,
406 args: Vec<String>,
407 ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
408 extension().complete_slash_command_argument(command, args)
409 }
410
411 fn run_slash_command(
412 command: SlashCommand,
413 args: Vec<String>,
414 worktree: Option<&Worktree>,
415 ) -> Result<SlashCommandOutput, String> {
416 extension().run_slash_command(command, args, worktree)
417 }
418
419 fn context_server_command(
420 context_server_id: String,
421 project: &Project,
422 ) -> Result<wit::Command> {
423 let context_server_id = ContextServerId(context_server_id);
424 extension().context_server_command(&context_server_id, project)
425 }
426
427 fn context_server_configuration(
428 context_server_id: String,
429 project: &Project,
430 ) -> Result<Option<ContextServerConfiguration>, String> {
431 let context_server_id = ContextServerId(context_server_id);
432 extension().context_server_configuration(&context_server_id, project)
433 }
434
435 fn suggest_docs_packages(provider: String) -> Result<Vec<String>, String> {
436 extension().suggest_docs_packages(provider)
437 }
438
439 fn index_docs(
440 provider: String,
441 package: String,
442 database: &KeyValueStore,
443 ) -> Result<(), String> {
444 extension().index_docs(provider, package, database)
445 }
446
447 fn get_dap_binary(
448 adapter_name: String,
449 config: DebugTaskDefinition,
450 user_installed_path: Option<String>,
451 worktree: &Worktree,
452 ) -> Result<wit::DebugAdapterBinary, String> {
453 extension().get_dap_binary(adapter_name, config, user_installed_path, worktree)
454 }
455
456 fn dap_request_kind(
457 adapter_name: String,
458 config: String,
459 ) -> Result<StartDebuggingRequestArgumentsRequest, String> {
460 extension().dap_request_kind(
461 adapter_name,
462 serde_json::from_str(&config).map_err(|e| format!("Failed to parse config: {e}"))?,
463 )
464 }
465 fn dap_config_to_scenario(config: DebugConfig) -> Result<DebugScenario, String> {
466 extension().dap_config_to_scenario(config)
467 }
468 fn dap_locator_create_scenario(
469 locator_name: String,
470 build_task: TaskTemplate,
471 resolved_label: String,
472 debug_adapter_name: String,
473 ) -> Option<DebugScenario> {
474 extension().dap_locator_create_scenario(
475 locator_name,
476 build_task,
477 resolved_label,
478 debug_adapter_name,
479 )
480 }
481 fn run_dap_locator(
482 locator_name: String,
483 build_task: TaskTemplate,
484 ) -> Result<DebugRequest, String> {
485 extension().run_dap_locator(locator_name, build_task)
486 }
487}
488
489/// The ID of a language server.
490#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
491pub struct LanguageServerId(String);
492
493impl AsRef<str> for LanguageServerId {
494 fn as_ref(&self) -> &str {
495 &self.0
496 }
497}
498
499impl fmt::Display for LanguageServerId {
500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501 write!(f, "{}", self.0)
502 }
503}
504
505/// The ID of a context server.
506#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
507pub struct ContextServerId(String);
508
509impl AsRef<str> for ContextServerId {
510 fn as_ref(&self) -> &str {
511 &self.0
512 }
513}
514
515impl fmt::Display for ContextServerId {
516 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
517 write!(f, "{}", self.0)
518 }
519}
520
521impl CodeLabelSpan {
522 /// Returns a [`CodeLabelSpan::CodeRange`].
523 pub fn code_range(range: impl Into<wit::Range>) -> Self {
524 Self::CodeRange(range.into())
525 }
526
527 /// Returns a [`CodeLabelSpan::Literal`].
528 pub fn literal(text: impl Into<String>, highlight_name: Option<String>) -> Self {
529 Self::Literal(CodeLabelSpanLiteral {
530 text: text.into(),
531 highlight_name,
532 })
533 }
534}
535
536impl From<std::ops::Range<u32>> for wit::Range {
537 fn from(value: std::ops::Range<u32>) -> Self {
538 Self {
539 start: value.start,
540 end: value.end,
541 }
542 }
543}
544
545impl From<std::ops::Range<usize>> for wit::Range {
546 fn from(value: std::ops::Range<usize>) -> Self {
547 Self {
548 start: value.start as u32,
549 end: value.end as u32,
550 }
551 }
552}