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