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