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