wit.rs

  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}