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 => latest::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            let extension =
112                latest::Extension::instantiate_async(store, component, latest::linker())
113                    .await
114                    .context("failed to instantiate wasm extension")?;
115            Ok(Self::V030(extension))
116        } else if version >= since_v0_2_0::MIN_VERSION {
117            let extension = since_v0_2_0::Extension::instantiate_async(
118                store,
119                component,
120                since_v0_2_0::linker(),
121            )
122            .await
123            .context("failed to instantiate wasm extension")?;
124            Ok(Self::V020(extension))
125        } else if version >= since_v0_1_0::MIN_VERSION {
126            let extension = since_v0_1_0::Extension::instantiate_async(
127                store,
128                component,
129                since_v0_1_0::linker(),
130            )
131            .await
132            .context("failed to instantiate wasm extension")?;
133            Ok(Self::V010(extension))
134        } else if version >= since_v0_0_6::MIN_VERSION {
135            let extension = since_v0_0_6::Extension::instantiate_async(
136                store,
137                component,
138                since_v0_0_6::linker(),
139            )
140            .await
141            .context("failed to instantiate wasm extension")?;
142            Ok(Self::V006(extension))
143        } else if version >= since_v0_0_4::MIN_VERSION {
144            let extension = since_v0_0_4::Extension::instantiate_async(
145                store,
146                component,
147                since_v0_0_4::linker(),
148            )
149            .await
150            .context("failed to instantiate wasm extension")?;
151            Ok(Self::V004(extension))
152        } else {
153            let extension = since_v0_0_1::Extension::instantiate_async(
154                store,
155                component,
156                since_v0_0_1::linker(),
157            )
158            .await
159            .context("failed to instantiate wasm extension")?;
160            Ok(Self::V001(extension))
161        }
162    }
163
164    pub async fn call_init_extension(&self, store: &mut Store<WasmState>) -> Result<()> {
165        match self {
166            Extension::V030(ext) => ext.call_init_extension(store).await,
167            Extension::V020(ext) => ext.call_init_extension(store).await,
168            Extension::V010(ext) => ext.call_init_extension(store).await,
169            Extension::V006(ext) => ext.call_init_extension(store).await,
170            Extension::V004(ext) => ext.call_init_extension(store).await,
171            Extension::V001(ext) => ext.call_init_extension(store).await,
172        }
173    }
174
175    pub async fn call_language_server_command(
176        &self,
177        store: &mut Store<WasmState>,
178        language_server_id: &LanguageServerName,
179        language_name: &LanguageName,
180        resource: Resource<Arc<dyn WorktreeDelegate>>,
181    ) -> Result<Result<Command, String>> {
182        match self {
183            Extension::V030(ext) => {
184                ext.call_language_server_command(store, &language_server_id.0, resource)
185                    .await
186            }
187            Extension::V020(ext) => Ok(ext
188                .call_language_server_command(store, &language_server_id.0, resource)
189                .await?
190                .map(|command| command.into())),
191            Extension::V010(ext) => Ok(ext
192                .call_language_server_command(store, &language_server_id.0, resource)
193                .await?
194                .map(|command| command.into())),
195            Extension::V006(ext) => Ok(ext
196                .call_language_server_command(store, &language_server_id.0, resource)
197                .await?
198                .map(|command| command.into())),
199            Extension::V004(ext) => Ok(ext
200                .call_language_server_command(
201                    store,
202                    &LanguageServerConfig {
203                        name: language_server_id.0.to_string(),
204                        language_name: language_name.to_string(),
205                    },
206                    resource,
207                )
208                .await?
209                .map(|command| command.into())),
210            Extension::V001(ext) => Ok(ext
211                .call_language_server_command(
212                    store,
213                    &LanguageServerConfig {
214                        name: language_server_id.0.to_string(),
215                        language_name: language_name.to_string(),
216                    }
217                    .into(),
218                    resource,
219                )
220                .await?
221                .map(|command| command.into())),
222        }
223    }
224
225    pub async fn call_language_server_initialization_options(
226        &self,
227        store: &mut Store<WasmState>,
228        language_server_id: &LanguageServerName,
229        language_name: &LanguageName,
230        resource: Resource<Arc<dyn WorktreeDelegate>>,
231    ) -> Result<Result<Option<String>, String>> {
232        match self {
233            Extension::V030(ext) => {
234                ext.call_language_server_initialization_options(
235                    store,
236                    &language_server_id.0,
237                    resource,
238                )
239                .await
240            }
241            Extension::V020(ext) => {
242                ext.call_language_server_initialization_options(
243                    store,
244                    &language_server_id.0,
245                    resource,
246                )
247                .await
248            }
249            Extension::V010(ext) => {
250                ext.call_language_server_initialization_options(
251                    store,
252                    &language_server_id.0,
253                    resource,
254                )
255                .await
256            }
257            Extension::V006(ext) => {
258                ext.call_language_server_initialization_options(
259                    store,
260                    &language_server_id.0,
261                    resource,
262                )
263                .await
264            }
265            Extension::V004(ext) => {
266                ext.call_language_server_initialization_options(
267                    store,
268                    &LanguageServerConfig {
269                        name: language_server_id.0.to_string(),
270                        language_name: language_name.to_string(),
271                    },
272                    resource,
273                )
274                .await
275            }
276            Extension::V001(ext) => {
277                ext.call_language_server_initialization_options(
278                    store,
279                    &LanguageServerConfig {
280                        name: language_server_id.0.to_string(),
281                        language_name: language_name.to_string(),
282                    }
283                    .into(),
284                    resource,
285                )
286                .await
287            }
288        }
289    }
290
291    pub async fn call_language_server_workspace_configuration(
292        &self,
293        store: &mut Store<WasmState>,
294        language_server_id: &LanguageServerName,
295        resource: Resource<Arc<dyn WorktreeDelegate>>,
296    ) -> Result<Result<Option<String>, String>> {
297        match self {
298            Extension::V030(ext) => {
299                ext.call_language_server_workspace_configuration(
300                    store,
301                    &language_server_id.0,
302                    resource,
303                )
304                .await
305            }
306            Extension::V020(ext) => {
307                ext.call_language_server_workspace_configuration(
308                    store,
309                    &language_server_id.0,
310                    resource,
311                )
312                .await
313            }
314            Extension::V010(ext) => {
315                ext.call_language_server_workspace_configuration(
316                    store,
317                    &language_server_id.0,
318                    resource,
319                )
320                .await
321            }
322            Extension::V006(ext) => {
323                ext.call_language_server_workspace_configuration(
324                    store,
325                    &language_server_id.0,
326                    resource,
327                )
328                .await
329            }
330            Extension::V004(_) | Extension::V001(_) => Ok(Ok(None)),
331        }
332    }
333
334    pub async fn call_labels_for_completions(
335        &self,
336        store: &mut Store<WasmState>,
337        language_server_id: &LanguageServerName,
338        completions: Vec<latest::Completion>,
339    ) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
340        match self {
341            Extension::V030(ext) => {
342                ext.call_labels_for_completions(store, &language_server_id.0, &completions)
343                    .await
344            }
345            Extension::V020(ext) => Ok(ext
346                .call_labels_for_completions(
347                    store,
348                    &language_server_id.0,
349                    &completions.into_iter().collect::<Vec<_>>(),
350                )
351                .await?
352                .map(|labels| {
353                    labels
354                        .into_iter()
355                        .map(|label| label.map(Into::into))
356                        .collect()
357                })),
358            Extension::V010(ext) => Ok(ext
359                .call_labels_for_completions(
360                    store,
361                    &language_server_id.0,
362                    &completions.into_iter().map(Into::into).collect::<Vec<_>>(),
363                )
364                .await?
365                .map(|labels| {
366                    labels
367                        .into_iter()
368                        .map(|label| label.map(Into::into))
369                        .collect()
370                })),
371            Extension::V006(ext) => Ok(ext
372                .call_labels_for_completions(
373                    store,
374                    &language_server_id.0,
375                    &completions.into_iter().map(Into::into).collect::<Vec<_>>(),
376                )
377                .await?
378                .map(|labels| {
379                    labels
380                        .into_iter()
381                        .map(|label| label.map(Into::into))
382                        .collect()
383                })),
384            Extension::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
385        }
386    }
387
388    pub async fn call_labels_for_symbols(
389        &self,
390        store: &mut Store<WasmState>,
391        language_server_id: &LanguageServerName,
392        symbols: Vec<latest::Symbol>,
393    ) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
394        match self {
395            Extension::V030(ext) => {
396                ext.call_labels_for_symbols(store, &language_server_id.0, &symbols)
397                    .await
398            }
399            Extension::V020(ext) => Ok(ext
400                .call_labels_for_symbols(
401                    store,
402                    &language_server_id.0,
403                    &symbols.into_iter().collect::<Vec<_>>(),
404                )
405                .await?
406                .map(|labels| {
407                    labels
408                        .into_iter()
409                        .map(|label| label.map(Into::into))
410                        .collect()
411                })),
412            Extension::V010(ext) => Ok(ext
413                .call_labels_for_symbols(
414                    store,
415                    &language_server_id.0,
416                    &symbols.into_iter().map(Into::into).collect::<Vec<_>>(),
417                )
418                .await?
419                .map(|labels| {
420                    labels
421                        .into_iter()
422                        .map(|label| label.map(Into::into))
423                        .collect()
424                })),
425            Extension::V006(ext) => Ok(ext
426                .call_labels_for_symbols(
427                    store,
428                    &language_server_id.0,
429                    &symbols.into_iter().map(Into::into).collect::<Vec<_>>(),
430                )
431                .await?
432                .map(|labels| {
433                    labels
434                        .into_iter()
435                        .map(|label| label.map(Into::into))
436                        .collect()
437                })),
438            Extension::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
439        }
440    }
441
442    pub async fn call_complete_slash_command_argument(
443        &self,
444        store: &mut Store<WasmState>,
445        command: &SlashCommand,
446        arguments: &[String],
447    ) -> Result<Result<Vec<SlashCommandArgumentCompletion>, String>> {
448        match self {
449            Extension::V030(ext) => {
450                ext.call_complete_slash_command_argument(store, command, arguments)
451                    .await
452            }
453            Extension::V020(ext) => {
454                ext.call_complete_slash_command_argument(store, command, arguments)
455                    .await
456            }
457            Extension::V010(ext) => {
458                ext.call_complete_slash_command_argument(store, command, arguments)
459                    .await
460            }
461            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Ok(Ok(Vec::new())),
462        }
463    }
464
465    pub async fn call_run_slash_command(
466        &self,
467        store: &mut Store<WasmState>,
468        command: &SlashCommand,
469        arguments: &[String],
470        resource: Option<Resource<Arc<dyn WorktreeDelegate>>>,
471    ) -> Result<Result<SlashCommandOutput, String>> {
472        match self {
473            Extension::V030(ext) => {
474                ext.call_run_slash_command(store, command, arguments, resource)
475                    .await
476            }
477            Extension::V020(ext) => {
478                ext.call_run_slash_command(store, command, arguments, resource)
479                    .await
480            }
481            Extension::V010(ext) => {
482                ext.call_run_slash_command(store, command, arguments, resource)
483                    .await
484            }
485            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
486                Err(anyhow!("`run_slash_command` not available prior to v0.1.0"))
487            }
488        }
489    }
490
491    pub async fn call_context_server_command(
492        &self,
493        store: &mut Store<WasmState>,
494        context_server_id: Arc<str>,
495        project: Resource<ExtensionProject>,
496    ) -> Result<Result<Command, String>> {
497        match self {
498            Extension::V030(ext) => {
499                ext.call_context_server_command(store, &context_server_id, project)
500                    .await
501            }
502            Extension::V020(ext) => Ok(ext
503                .call_context_server_command(store, &context_server_id, project)
504                .await?
505                .map(Into::into)),
506            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) | Extension::V010(_) => {
507                Err(anyhow!(
508                    "`context_server_command` not available prior to v0.2.0"
509                ))
510            }
511        }
512    }
513
514    pub async fn call_suggest_docs_packages(
515        &self,
516        store: &mut Store<WasmState>,
517        provider: &str,
518    ) -> Result<Result<Vec<String>, String>> {
519        match self {
520            Extension::V030(ext) => ext.call_suggest_docs_packages(store, provider).await,
521            Extension::V020(ext) => ext.call_suggest_docs_packages(store, provider).await,
522            Extension::V010(ext) => ext.call_suggest_docs_packages(store, provider).await,
523            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Err(anyhow!(
524                "`suggest_docs_packages` not available prior to v0.1.0"
525            )),
526        }
527    }
528
529    pub async fn call_index_docs(
530        &self,
531        store: &mut Store<WasmState>,
532        provider: &str,
533        package_name: &str,
534        kv_store: Resource<Arc<dyn KeyValueStoreDelegate>>,
535    ) -> Result<Result<(), String>> {
536        match self {
537            Extension::V030(ext) => {
538                ext.call_index_docs(store, provider, package_name, kv_store)
539                    .await
540            }
541            Extension::V020(ext) => {
542                ext.call_index_docs(store, provider, package_name, kv_store)
543                    .await
544            }
545            Extension::V010(ext) => {
546                ext.call_index_docs(store, provider, package_name, kv_store)
547                    .await
548            }
549            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
550                Err(anyhow!("`index_docs` not available prior to v0.1.0"))
551            }
552        }
553    }
554}
555
556trait ToWasmtimeResult<T> {
557    fn to_wasmtime_result(self) -> wasmtime::Result<Result<T, String>>;
558}
559
560impl<T> ToWasmtimeResult<T> for Result<T> {
561    fn to_wasmtime_result(self) -> wasmtime::Result<Result<T, String>> {
562        Ok(self.map_err(|error| error.to_string()))
563    }
564}