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