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