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