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