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