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, SlashCommandOutput, SlashCommandOutputSection},
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 /// Returns the completions that should be shown when completing the provided slash command with the given query.
112 fn complete_slash_command_argument(
113 &self,
114 _command: SlashCommand,
115 _query: String,
116 ) -> Result<Vec<String>, String> {
117 Ok(Vec::new())
118 }
119
120 /// Returns the output from running the provided slash command.
121 fn run_slash_command(
122 &self,
123 _command: SlashCommand,
124 _argument: Option<String>,
125 _worktree: &Worktree,
126 ) -> Result<SlashCommandOutput, String> {
127 Err("`run_slash_command` not implemented".to_string())
128 }
129}
130
131/// Registers the provided type as a Zed extension.
132///
133/// The type must implement the [`Extension`] trait.
134#[macro_export]
135macro_rules! register_extension {
136 ($extension_type:ty) => {
137 #[export_name = "init-extension"]
138 pub extern "C" fn __init_extension() {
139 std::env::set_current_dir(std::env::var("PWD").unwrap()).unwrap();
140 zed_extension_api::register_extension(|| {
141 Box::new(<$extension_type as zed_extension_api::Extension>::new())
142 });
143 }
144 };
145}
146
147#[doc(hidden)]
148pub fn register_extension(build_extension: fn() -> Box<dyn Extension>) {
149 unsafe { EXTENSION = Some((build_extension)()) }
150}
151
152fn extension() -> &'static mut dyn Extension {
153 unsafe { EXTENSION.as_deref_mut().unwrap() }
154}
155
156static mut EXTENSION: Option<Box<dyn Extension>> = None;
157
158#[cfg(target_arch = "wasm32")]
159#[link_section = "zed:api-version"]
160#[doc(hidden)]
161pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "/version_bytes"));
162
163mod wit {
164 #![allow(clippy::too_many_arguments)]
165
166 wit_bindgen::generate!({
167 skip: ["init-extension"],
168 path: "./wit/since_v0.0.7",
169 });
170}
171
172wit::export!(Component);
173
174struct Component;
175
176impl wit::Guest for Component {
177 fn language_server_command(
178 language_server_id: String,
179 worktree: &wit::Worktree,
180 ) -> Result<wit::Command> {
181 let language_server_id = LanguageServerId(language_server_id);
182 extension().language_server_command(&language_server_id, worktree)
183 }
184
185 fn language_server_initialization_options(
186 language_server_id: String,
187 worktree: &Worktree,
188 ) -> Result<Option<String>, String> {
189 let language_server_id = LanguageServerId(language_server_id);
190 Ok(extension()
191 .language_server_initialization_options(&language_server_id, worktree)?
192 .and_then(|value| serde_json::to_string(&value).ok()))
193 }
194
195 fn language_server_workspace_configuration(
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_workspace_configuration(&language_server_id, worktree)?
202 .and_then(|value| serde_json::to_string(&value).ok()))
203 }
204
205 fn labels_for_completions(
206 language_server_id: String,
207 completions: Vec<Completion>,
208 ) -> Result<Vec<Option<CodeLabel>>, String> {
209 let language_server_id = LanguageServerId(language_server_id);
210 let mut labels = Vec::new();
211 for (ix, completion) in completions.into_iter().enumerate() {
212 let label = extension().label_for_completion(&language_server_id, completion);
213 if let Some(label) = label {
214 labels.resize(ix + 1, None);
215 *labels.last_mut().unwrap() = Some(label);
216 }
217 }
218 Ok(labels)
219 }
220
221 fn labels_for_symbols(
222 language_server_id: String,
223 symbols: Vec<Symbol>,
224 ) -> Result<Vec<Option<CodeLabel>>, String> {
225 let language_server_id = LanguageServerId(language_server_id);
226 let mut labels = Vec::new();
227 for (ix, symbol) in symbols.into_iter().enumerate() {
228 let label = extension().label_for_symbol(&language_server_id, symbol);
229 if let Some(label) = label {
230 labels.resize(ix + 1, None);
231 *labels.last_mut().unwrap() = Some(label);
232 }
233 }
234 Ok(labels)
235 }
236
237 fn complete_slash_command_argument(
238 command: SlashCommand,
239 query: String,
240 ) -> Result<Vec<String>, String> {
241 extension().complete_slash_command_argument(command, query)
242 }
243
244 fn run_slash_command(
245 command: SlashCommand,
246 argument: Option<String>,
247 worktree: &Worktree,
248 ) -> Result<SlashCommandOutput, String> {
249 extension().run_slash_command(command, argument, worktree)
250 }
251}
252
253/// The ID of a language server.
254#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
255pub struct LanguageServerId(String);
256
257impl AsRef<str> for LanguageServerId {
258 fn as_ref(&self) -> &str {
259 &self.0
260 }
261}
262
263impl fmt::Display for LanguageServerId {
264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265 write!(f, "{}", self.0)
266 }
267}
268
269impl CodeLabelSpan {
270 /// Returns a [`CodeLabelSpan::CodeRange`].
271 pub fn code_range(range: impl Into<wit::Range>) -> Self {
272 Self::CodeRange(range.into())
273 }
274
275 /// Returns a [`CodeLabelSpan::Literal`].
276 pub fn literal(text: impl Into<String>, highlight_name: Option<String>) -> Self {
277 Self::Literal(CodeLabelSpanLiteral {
278 text: text.into(),
279 highlight_name,
280 })
281 }
282}
283
284impl From<std::ops::Range<u32>> for wit::Range {
285 fn from(value: std::ops::Range<u32>) -> Self {
286 Self {
287 start: value.start,
288 end: value.end,
289 }
290 }
291}
292
293impl From<std::ops::Range<usize>> for wit::Range {
294 fn from(value: std::ops::Range<usize>) -> Self {
295 Self {
296 start: value.start as u32,
297 end: value.end as u32,
298 }
299 }
300}