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