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;
  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}