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