1mod since_v0_0_1;
2mod since_v0_0_4;
3mod since_v0_0_6;
4mod since_v0_1_0;
5mod since_v0_2_0;
6use extension::{KeyValueStoreDelegate, WorktreeDelegate};
7use lsp::LanguageServerName;
8use release_channel::ReleaseChannel;
9use since_v0_2_0 as latest;
10
11use super::{wasm_engine, WasmState};
12use anyhow::{anyhow, Context, Result};
13use semantic_version::SemanticVersion;
14use std::{ops::RangeInclusive, sync::Arc};
15use wasmtime::{
16 component::{Component, Linker, Resource},
17 Store,
18};
19
20#[cfg(test)]
21pub use latest::CodeLabelSpanLiteral;
22pub use latest::{
23 zed::extension::lsp::{
24 Completion, CompletionKind, CompletionLabelDetails, InsertTextFormat, Symbol, SymbolKind,
25 },
26 zed::extension::slash_command::{SlashCommandArgumentCompletion, SlashCommandOutput},
27 CodeLabel, CodeLabelSpan, Command, ExtensionProject, Range, SlashCommand,
28};
29pub use since_v0_0_4::LanguageServerConfig;
30
31pub fn new_linker(
32 f: impl Fn(&mut Linker<WasmState>, fn(&mut WasmState) -> &mut WasmState) -> Result<()>,
33) -> Linker<WasmState> {
34 let mut linker = Linker::new(&wasm_engine());
35 wasmtime_wasi::add_to_linker_async(&mut linker).unwrap();
36 f(&mut linker, wasi_view).unwrap();
37 linker
38}
39
40fn wasi_view(state: &mut WasmState) -> &mut WasmState {
41 state
42}
43
44/// Returns whether the given Wasm API version is supported by the Wasm host.
45pub fn is_supported_wasm_api_version(
46 release_channel: ReleaseChannel,
47 version: SemanticVersion,
48) -> bool {
49 wasm_api_version_range(release_channel).contains(&version)
50}
51
52/// Returns the Wasm API version range that is supported by the Wasm host.
53#[inline(always)]
54pub fn wasm_api_version_range(release_channel: ReleaseChannel) -> RangeInclusive<SemanticVersion> {
55 // Note: The release channel can be used to stage a new version of the extension API.
56 let _ = release_channel;
57
58 let max_version = match release_channel {
59 ReleaseChannel::Dev | ReleaseChannel::Nightly => latest::MAX_VERSION,
60 ReleaseChannel::Stable | ReleaseChannel::Preview => latest::MAX_VERSION,
61 };
62
63 since_v0_0_1::MIN_VERSION..=max_version
64}
65
66/// Authorizes access to use unreleased versions of the Wasm API, based on the provided [`ReleaseChannel`].
67///
68/// Note: If there isn't currently an unreleased Wasm API version this function may be unused. Don't delete it!
69pub fn authorize_access_to_unreleased_wasm_api_version(
70 release_channel: ReleaseChannel,
71) -> Result<()> {
72 let allow_unreleased_version = match release_channel {
73 ReleaseChannel::Dev | ReleaseChannel::Nightly => true,
74 ReleaseChannel::Stable | ReleaseChannel::Preview => {
75 // We always allow the latest in tests so that the extension tests pass on release branches.
76 cfg!(any(test, feature = "test-support"))
77 }
78 };
79
80 if !allow_unreleased_version {
81 Err(anyhow!(
82 "unreleased versions of the extension API can only be used on development builds of Zed"
83 ))?;
84 }
85
86 Ok(())
87}
88
89pub enum Extension {
90 V020(since_v0_2_0::Extension),
91 V010(since_v0_1_0::Extension),
92 V006(since_v0_0_6::Extension),
93 V004(since_v0_0_4::Extension),
94 V001(since_v0_0_1::Extension),
95}
96
97impl Extension {
98 pub async fn instantiate_async(
99 store: &mut Store<WasmState>,
100 release_channel: ReleaseChannel,
101 version: SemanticVersion,
102 component: &Component,
103 ) -> Result<Self> {
104 // Note: The release channel can be used to stage a new version of the extension API.
105 let _ = release_channel;
106
107 if version >= latest::MIN_VERSION {
108 let extension =
109 latest::Extension::instantiate_async(store, component, latest::linker())
110 .await
111 .context("failed to instantiate wasm extension")?;
112 Ok(Self::V020(extension))
113 } else if version >= since_v0_1_0::MIN_VERSION {
114 let extension = since_v0_1_0::Extension::instantiate_async(
115 store,
116 component,
117 since_v0_1_0::linker(),
118 )
119 .await
120 .context("failed to instantiate wasm extension")?;
121 Ok(Self::V010(extension))
122 } else if version >= since_v0_0_6::MIN_VERSION {
123 let extension = since_v0_0_6::Extension::instantiate_async(
124 store,
125 component,
126 since_v0_0_6::linker(),
127 )
128 .await
129 .context("failed to instantiate wasm extension")?;
130 Ok(Self::V006(extension))
131 } else if version >= since_v0_0_4::MIN_VERSION {
132 let extension = since_v0_0_4::Extension::instantiate_async(
133 store,
134 component,
135 since_v0_0_4::linker(),
136 )
137 .await
138 .context("failed to instantiate wasm extension")?;
139 Ok(Self::V004(extension))
140 } else {
141 let extension = since_v0_0_1::Extension::instantiate_async(
142 store,
143 component,
144 since_v0_0_1::linker(),
145 )
146 .await
147 .context("failed to instantiate wasm extension")?;
148 Ok(Self::V001(extension))
149 }
150 }
151
152 pub async fn call_init_extension(&self, store: &mut Store<WasmState>) -> Result<()> {
153 match self {
154 Extension::V020(ext) => ext.call_init_extension(store).await,
155 Extension::V010(ext) => ext.call_init_extension(store).await,
156 Extension::V006(ext) => ext.call_init_extension(store).await,
157 Extension::V004(ext) => ext.call_init_extension(store).await,
158 Extension::V001(ext) => ext.call_init_extension(store).await,
159 }
160 }
161
162 pub async fn call_language_server_command(
163 &self,
164 store: &mut Store<WasmState>,
165 language_server_id: &LanguageServerName,
166 config: &LanguageServerConfig,
167 resource: Resource<Arc<dyn WorktreeDelegate>>,
168 ) -> Result<Result<Command, String>> {
169 match self {
170 Extension::V020(ext) => {
171 ext.call_language_server_command(store, &language_server_id.0, resource)
172 .await
173 }
174 Extension::V010(ext) => Ok(ext
175 .call_language_server_command(store, &language_server_id.0, resource)
176 .await?
177 .map(|command| command.into())),
178 Extension::V006(ext) => Ok(ext
179 .call_language_server_command(store, &language_server_id.0, resource)
180 .await?
181 .map(|command| command.into())),
182 Extension::V004(ext) => Ok(ext
183 .call_language_server_command(store, config, resource)
184 .await?
185 .map(|command| command.into())),
186 Extension::V001(ext) => Ok(ext
187 .call_language_server_command(store, &config.clone().into(), resource)
188 .await?
189 .map(|command| command.into())),
190 }
191 }
192
193 pub async fn call_language_server_initialization_options(
194 &self,
195 store: &mut Store<WasmState>,
196 language_server_id: &LanguageServerName,
197 config: &LanguageServerConfig,
198 resource: Resource<Arc<dyn WorktreeDelegate>>,
199 ) -> Result<Result<Option<String>, String>> {
200 match self {
201 Extension::V020(ext) => {
202 ext.call_language_server_initialization_options(
203 store,
204 &language_server_id.0,
205 resource,
206 )
207 .await
208 }
209 Extension::V010(ext) => {
210 ext.call_language_server_initialization_options(
211 store,
212 &language_server_id.0,
213 resource,
214 )
215 .await
216 }
217 Extension::V006(ext) => {
218 ext.call_language_server_initialization_options(
219 store,
220 &language_server_id.0,
221 resource,
222 )
223 .await
224 }
225 Extension::V004(ext) => {
226 ext.call_language_server_initialization_options(store, config, resource)
227 .await
228 }
229 Extension::V001(ext) => {
230 ext.call_language_server_initialization_options(
231 store,
232 &config.clone().into(),
233 resource,
234 )
235 .await
236 }
237 }
238 }
239
240 pub async fn call_language_server_workspace_configuration(
241 &self,
242 store: &mut Store<WasmState>,
243 language_server_id: &LanguageServerName,
244 resource: Resource<Arc<dyn WorktreeDelegate>>,
245 ) -> Result<Result<Option<String>, String>> {
246 match self {
247 Extension::V020(ext) => {
248 ext.call_language_server_workspace_configuration(
249 store,
250 &language_server_id.0,
251 resource,
252 )
253 .await
254 }
255 Extension::V010(ext) => {
256 ext.call_language_server_workspace_configuration(
257 store,
258 &language_server_id.0,
259 resource,
260 )
261 .await
262 }
263 Extension::V006(ext) => {
264 ext.call_language_server_workspace_configuration(
265 store,
266 &language_server_id.0,
267 resource,
268 )
269 .await
270 }
271 Extension::V004(_) | Extension::V001(_) => Ok(Ok(None)),
272 }
273 }
274
275 pub async fn call_labels_for_completions(
276 &self,
277 store: &mut Store<WasmState>,
278 language_server_id: &LanguageServerName,
279 completions: Vec<latest::Completion>,
280 ) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
281 match self {
282 Extension::V020(ext) => {
283 ext.call_labels_for_completions(store, &language_server_id.0, &completions)
284 .await
285 }
286 Extension::V010(ext) => Ok(ext
287 .call_labels_for_completions(
288 store,
289 &language_server_id.0,
290 &completions.into_iter().map(Into::into).collect::<Vec<_>>(),
291 )
292 .await?
293 .map(|labels| {
294 labels
295 .into_iter()
296 .map(|label| label.map(Into::into))
297 .collect()
298 })),
299 Extension::V006(ext) => Ok(ext
300 .call_labels_for_completions(
301 store,
302 &language_server_id.0,
303 &completions.into_iter().map(Into::into).collect::<Vec<_>>(),
304 )
305 .await?
306 .map(|labels| {
307 labels
308 .into_iter()
309 .map(|label| label.map(Into::into))
310 .collect()
311 })),
312 Extension::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
313 }
314 }
315
316 pub async fn call_labels_for_symbols(
317 &self,
318 store: &mut Store<WasmState>,
319 language_server_id: &LanguageServerName,
320 symbols: Vec<latest::Symbol>,
321 ) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
322 match self {
323 Extension::V020(ext) => {
324 ext.call_labels_for_symbols(store, &language_server_id.0, &symbols)
325 .await
326 }
327 Extension::V010(ext) => Ok(ext
328 .call_labels_for_symbols(
329 store,
330 &language_server_id.0,
331 &symbols.into_iter().map(Into::into).collect::<Vec<_>>(),
332 )
333 .await?
334 .map(|labels| {
335 labels
336 .into_iter()
337 .map(|label| label.map(Into::into))
338 .collect()
339 })),
340 Extension::V006(ext) => Ok(ext
341 .call_labels_for_symbols(
342 store,
343 &language_server_id.0,
344 &symbols.into_iter().map(Into::into).collect::<Vec<_>>(),
345 )
346 .await?
347 .map(|labels| {
348 labels
349 .into_iter()
350 .map(|label| label.map(Into::into))
351 .collect()
352 })),
353 Extension::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
354 }
355 }
356
357 pub async fn call_complete_slash_command_argument(
358 &self,
359 store: &mut Store<WasmState>,
360 command: &SlashCommand,
361 arguments: &[String],
362 ) -> Result<Result<Vec<SlashCommandArgumentCompletion>, String>> {
363 match self {
364 Extension::V020(ext) => {
365 ext.call_complete_slash_command_argument(store, command, arguments)
366 .await
367 }
368 Extension::V010(ext) => {
369 ext.call_complete_slash_command_argument(store, command, arguments)
370 .await
371 }
372 Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Ok(Ok(Vec::new())),
373 }
374 }
375
376 pub async fn call_run_slash_command(
377 &self,
378 store: &mut Store<WasmState>,
379 command: &SlashCommand,
380 arguments: &[String],
381 resource: Option<Resource<Arc<dyn WorktreeDelegate>>>,
382 ) -> Result<Result<SlashCommandOutput, String>> {
383 match self {
384 Extension::V020(ext) => {
385 ext.call_run_slash_command(store, command, arguments, resource)
386 .await
387 }
388 Extension::V010(ext) => {
389 ext.call_run_slash_command(store, command, arguments, resource)
390 .await
391 }
392 Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
393 Err(anyhow!("`run_slash_command` not available prior to v0.1.0"))
394 }
395 }
396 }
397
398 pub async fn call_context_server_command(
399 &self,
400 store: &mut Store<WasmState>,
401 context_server_id: Arc<str>,
402 project: Resource<ExtensionProject>,
403 ) -> Result<Result<Command, String>> {
404 match self {
405 Extension::V020(ext) => {
406 ext.call_context_server_command(store, &context_server_id, project)
407 .await
408 }
409 Extension::V001(_) | Extension::V004(_) | Extension::V006(_) | Extension::V010(_) => {
410 Err(anyhow!(
411 "`context_server_command` not available prior to v0.2.0"
412 ))
413 }
414 }
415 }
416
417 pub async fn call_suggest_docs_packages(
418 &self,
419 store: &mut Store<WasmState>,
420 provider: &str,
421 ) -> Result<Result<Vec<String>, String>> {
422 match self {
423 Extension::V020(ext) => ext.call_suggest_docs_packages(store, provider).await,
424 Extension::V010(ext) => ext.call_suggest_docs_packages(store, provider).await,
425 Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Err(anyhow!(
426 "`suggest_docs_packages` not available prior to v0.1.0"
427 )),
428 }
429 }
430
431 pub async fn call_index_docs(
432 &self,
433 store: &mut Store<WasmState>,
434 provider: &str,
435 package_name: &str,
436 kv_store: Resource<Arc<dyn KeyValueStoreDelegate>>,
437 ) -> Result<Result<(), String>> {
438 match self {
439 Extension::V020(ext) => {
440 ext.call_index_docs(store, provider, package_name, kv_store)
441 .await
442 }
443 Extension::V010(ext) => {
444 ext.call_index_docs(store, provider, package_name, kv_store)
445 .await
446 }
447 Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
448 Err(anyhow!("`index_docs` not available prior to v0.1.0"))
449 }
450 }
451 }
452}
453
454trait ToWasmtimeResult<T> {
455 fn to_wasmtime_result(self) -> wasmtime::Result<Result<T, String>>;
456}
457
458impl<T> ToWasmtimeResult<T> for Result<T> {
459 fn to_wasmtime_result(self) -> wasmtime::Result<Result<T, String>> {
460 Ok(self.map_err(|error| error.to_string()))
461 }
462}