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 => since_v0_1_0::MAX_VERSION,
 61    };
 62
 63    since_v0_0_1::MIN_VERSION..=max_version
 64}
 65
 66pub enum Extension {
 67    V020(since_v0_2_0::Extension),
 68    V010(since_v0_1_0::Extension),
 69    V006(since_v0_0_6::Extension),
 70    V004(since_v0_0_4::Extension),
 71    V001(since_v0_0_1::Extension),
 72}
 73
 74impl Extension {
 75    pub async fn instantiate_async(
 76        store: &mut Store<WasmState>,
 77        release_channel: ReleaseChannel,
 78        version: SemanticVersion,
 79        component: &Component,
 80    ) -> Result<Self> {
 81        if version >= latest::MIN_VERSION {
 82            // Note: The release channel can be used to stage a new version of the extension API.
 83            // We always allow the latest in tests so that the extension tests pass on release branches.
 84            let allow_latest_version = match release_channel {
 85                ReleaseChannel::Dev | ReleaseChannel::Nightly => true,
 86                ReleaseChannel::Stable | ReleaseChannel::Preview => {
 87                    cfg!(any(test, feature = "test-support"))
 88                }
 89            };
 90            if !allow_latest_version {
 91                Err(anyhow!(
 92                    "unreleased versions of the extension API can only be used on development builds of Zed"
 93                ))?;
 94            }
 95            let extension =
 96                latest::Extension::instantiate_async(store, component, latest::linker())
 97                    .await
 98                    .context("failed to instantiate wasm extension")?;
 99            Ok(Self::V020(extension))
100        } else if version >= since_v0_1_0::MIN_VERSION {
101            let extension = since_v0_1_0::Extension::instantiate_async(
102                store,
103                component,
104                since_v0_1_0::linker(),
105            )
106            .await
107            .context("failed to instantiate wasm extension")?;
108            Ok(Self::V010(extension))
109        } else if version >= since_v0_0_6::MIN_VERSION {
110            let extension = since_v0_0_6::Extension::instantiate_async(
111                store,
112                component,
113                since_v0_0_6::linker(),
114            )
115            .await
116            .context("failed to instantiate wasm extension")?;
117            Ok(Self::V006(extension))
118        } else if version >= since_v0_0_4::MIN_VERSION {
119            let extension = since_v0_0_4::Extension::instantiate_async(
120                store,
121                component,
122                since_v0_0_4::linker(),
123            )
124            .await
125            .context("failed to instantiate wasm extension")?;
126            Ok(Self::V004(extension))
127        } else {
128            let extension = since_v0_0_1::Extension::instantiate_async(
129                store,
130                component,
131                since_v0_0_1::linker(),
132            )
133            .await
134            .context("failed to instantiate wasm extension")?;
135            Ok(Self::V001(extension))
136        }
137    }
138
139    pub async fn call_init_extension(&self, store: &mut Store<WasmState>) -> Result<()> {
140        match self {
141            Extension::V020(ext) => ext.call_init_extension(store).await,
142            Extension::V010(ext) => ext.call_init_extension(store).await,
143            Extension::V006(ext) => ext.call_init_extension(store).await,
144            Extension::V004(ext) => ext.call_init_extension(store).await,
145            Extension::V001(ext) => ext.call_init_extension(store).await,
146        }
147    }
148
149    pub async fn call_language_server_command(
150        &self,
151        store: &mut Store<WasmState>,
152        language_server_id: &LanguageServerName,
153        config: &LanguageServerConfig,
154        resource: Resource<Arc<dyn WorktreeDelegate>>,
155    ) -> Result<Result<Command, String>> {
156        match self {
157            Extension::V020(ext) => {
158                ext.call_language_server_command(store, &language_server_id.0, resource)
159                    .await
160            }
161            Extension::V010(ext) => Ok(ext
162                .call_language_server_command(store, &language_server_id.0, resource)
163                .await?
164                .map(|command| command.into())),
165            Extension::V006(ext) => Ok(ext
166                .call_language_server_command(store, &language_server_id.0, resource)
167                .await?
168                .map(|command| command.into())),
169            Extension::V004(ext) => Ok(ext
170                .call_language_server_command(store, config, resource)
171                .await?
172                .map(|command| command.into())),
173            Extension::V001(ext) => Ok(ext
174                .call_language_server_command(store, &config.clone().into(), resource)
175                .await?
176                .map(|command| command.into())),
177        }
178    }
179
180    pub async fn call_language_server_initialization_options(
181        &self,
182        store: &mut Store<WasmState>,
183        language_server_id: &LanguageServerName,
184        config: &LanguageServerConfig,
185        resource: Resource<Arc<dyn WorktreeDelegate>>,
186    ) -> Result<Result<Option<String>, String>> {
187        match self {
188            Extension::V020(ext) => {
189                ext.call_language_server_initialization_options(
190                    store,
191                    &language_server_id.0,
192                    resource,
193                )
194                .await
195            }
196            Extension::V010(ext) => {
197                ext.call_language_server_initialization_options(
198                    store,
199                    &language_server_id.0,
200                    resource,
201                )
202                .await
203            }
204            Extension::V006(ext) => {
205                ext.call_language_server_initialization_options(
206                    store,
207                    &language_server_id.0,
208                    resource,
209                )
210                .await
211            }
212            Extension::V004(ext) => {
213                ext.call_language_server_initialization_options(store, config, resource)
214                    .await
215            }
216            Extension::V001(ext) => {
217                ext.call_language_server_initialization_options(
218                    store,
219                    &config.clone().into(),
220                    resource,
221                )
222                .await
223            }
224        }
225    }
226
227    pub async fn call_language_server_workspace_configuration(
228        &self,
229        store: &mut Store<WasmState>,
230        language_server_id: &LanguageServerName,
231        resource: Resource<Arc<dyn WorktreeDelegate>>,
232    ) -> Result<Result<Option<String>, String>> {
233        match self {
234            Extension::V020(ext) => {
235                ext.call_language_server_workspace_configuration(
236                    store,
237                    &language_server_id.0,
238                    resource,
239                )
240                .await
241            }
242            Extension::V010(ext) => {
243                ext.call_language_server_workspace_configuration(
244                    store,
245                    &language_server_id.0,
246                    resource,
247                )
248                .await
249            }
250            Extension::V006(ext) => {
251                ext.call_language_server_workspace_configuration(
252                    store,
253                    &language_server_id.0,
254                    resource,
255                )
256                .await
257            }
258            Extension::V004(_) | Extension::V001(_) => Ok(Ok(None)),
259        }
260    }
261
262    pub async fn call_labels_for_completions(
263        &self,
264        store: &mut Store<WasmState>,
265        language_server_id: &LanguageServerName,
266        completions: Vec<latest::Completion>,
267    ) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
268        match self {
269            Extension::V020(ext) => {
270                ext.call_labels_for_completions(store, &language_server_id.0, &completions)
271                    .await
272            }
273            Extension::V010(ext) => Ok(ext
274                .call_labels_for_completions(
275                    store,
276                    &language_server_id.0,
277                    &completions.into_iter().map(Into::into).collect::<Vec<_>>(),
278                )
279                .await?
280                .map(|labels| {
281                    labels
282                        .into_iter()
283                        .map(|label| label.map(Into::into))
284                        .collect()
285                })),
286            Extension::V006(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::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
300        }
301    }
302
303    pub async fn call_labels_for_symbols(
304        &self,
305        store: &mut Store<WasmState>,
306        language_server_id: &LanguageServerName,
307        symbols: Vec<latest::Symbol>,
308    ) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
309        match self {
310            Extension::V020(ext) => {
311                ext.call_labels_for_symbols(store, &language_server_id.0, &symbols)
312                    .await
313            }
314            Extension::V010(ext) => Ok(ext
315                .call_labels_for_symbols(
316                    store,
317                    &language_server_id.0,
318                    &symbols.into_iter().map(Into::into).collect::<Vec<_>>(),
319                )
320                .await?
321                .map(|labels| {
322                    labels
323                        .into_iter()
324                        .map(|label| label.map(Into::into))
325                        .collect()
326                })),
327            Extension::V006(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::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
341        }
342    }
343
344    pub async fn call_complete_slash_command_argument(
345        &self,
346        store: &mut Store<WasmState>,
347        command: &SlashCommand,
348        arguments: &[String],
349    ) -> Result<Result<Vec<SlashCommandArgumentCompletion>, String>> {
350        match self {
351            Extension::V020(ext) => {
352                ext.call_complete_slash_command_argument(store, command, arguments)
353                    .await
354            }
355            Extension::V010(ext) => {
356                ext.call_complete_slash_command_argument(store, command, arguments)
357                    .await
358            }
359            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Ok(Ok(Vec::new())),
360        }
361    }
362
363    pub async fn call_run_slash_command(
364        &self,
365        store: &mut Store<WasmState>,
366        command: &SlashCommand,
367        arguments: &[String],
368        resource: Option<Resource<Arc<dyn WorktreeDelegate>>>,
369    ) -> Result<Result<SlashCommandOutput, String>> {
370        match self {
371            Extension::V020(ext) => {
372                ext.call_run_slash_command(store, command, arguments, resource)
373                    .await
374            }
375            Extension::V010(ext) => {
376                ext.call_run_slash_command(store, command, arguments, resource)
377                    .await
378            }
379            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
380                Err(anyhow!("`run_slash_command` not available prior to v0.1.0"))
381            }
382        }
383    }
384
385    pub async fn call_context_server_command(
386        &self,
387        store: &mut Store<WasmState>,
388        context_server_id: Arc<str>,
389        project: Resource<ExtensionProject>,
390    ) -> Result<Result<Command, String>> {
391        match self {
392            Extension::V020(ext) => {
393                ext.call_context_server_command(store, &context_server_id, project)
394                    .await
395            }
396            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) | Extension::V010(_) => {
397                Err(anyhow!(
398                    "`context_server_command` not available prior to v0.2.0"
399                ))
400            }
401        }
402    }
403
404    pub async fn call_suggest_docs_packages(
405        &self,
406        store: &mut Store<WasmState>,
407        provider: &str,
408    ) -> Result<Result<Vec<String>, String>> {
409        match self {
410            Extension::V020(ext) => ext.call_suggest_docs_packages(store, provider).await,
411            Extension::V010(ext) => ext.call_suggest_docs_packages(store, provider).await,
412            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Err(anyhow!(
413                "`suggest_docs_packages` not available prior to v0.1.0"
414            )),
415        }
416    }
417
418    pub async fn call_index_docs(
419        &self,
420        store: &mut Store<WasmState>,
421        provider: &str,
422        package_name: &str,
423        kv_store: Resource<Arc<dyn KeyValueStoreDelegate>>,
424    ) -> Result<Result<(), String>> {
425        match self {
426            Extension::V020(ext) => {
427                ext.call_index_docs(store, provider, package_name, kv_store)
428                    .await
429            }
430            Extension::V010(ext) => {
431                ext.call_index_docs(store, provider, package_name, kv_store)
432                    .await
433            }
434            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
435                Err(anyhow!("`index_docs` not available prior to v0.1.0"))
436            }
437        }
438    }
439}
440
441trait ToWasmtimeResult<T> {
442    fn to_wasmtime_result(self) -> wasmtime::Result<Result<T, String>>;
443}
444
445impl<T> ToWasmtimeResult<T> for Result<T> {
446    fn to_wasmtime_result(self) -> wasmtime::Result<Result<T, String>> {
447        Ok(self.map_err(|error| error.to_string()))
448    }
449}