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