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 indexed_docs::IndexedDocsDatabase;
  7use release_channel::ReleaseChannel;
  8use since_v0_2_0 as latest;
  9
 10use super::{wasm_engine, WasmState};
 11use anyhow::{anyhow, Context, Result};
 12use language::{LanguageServerName, LspAdapterDelegate};
 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::{Completion, CompletionKind, InsertTextFormat, Symbol, SymbolKind},
 24    zed::extension::slash_command::{SlashCommandArgumentCompletion, SlashCommandOutput},
 25    CodeLabel, CodeLabelSpan, Command, Range, SlashCommand,
 26};
 27pub use since_v0_0_4::LanguageServerConfig;
 28
 29pub fn new_linker(
 30    f: impl Fn(&mut Linker<WasmState>, fn(&mut WasmState) -> &mut WasmState) -> Result<()>,
 31) -> Linker<WasmState> {
 32    let mut linker = Linker::new(&wasm_engine());
 33    wasmtime_wasi::add_to_linker_async(&mut linker).unwrap();
 34    f(&mut linker, wasi_view).unwrap();
 35    linker
 36}
 37
 38fn wasi_view(state: &mut WasmState) -> &mut WasmState {
 39    state
 40}
 41
 42/// Returns whether the given Wasm API version is supported by the Wasm host.
 43pub fn is_supported_wasm_api_version(
 44    release_channel: ReleaseChannel,
 45    version: SemanticVersion,
 46) -> bool {
 47    wasm_api_version_range(release_channel).contains(&version)
 48}
 49
 50/// Returns the Wasm API version range that is supported by the Wasm host.
 51#[inline(always)]
 52pub fn wasm_api_version_range(release_channel: ReleaseChannel) -> RangeInclusive<SemanticVersion> {
 53    // Note: The release channel can be used to stage a new version of the extension API.
 54    let _ = release_channel;
 55
 56    let max_version = match release_channel {
 57        ReleaseChannel::Dev | ReleaseChannel::Nightly => latest::MAX_VERSION,
 58        ReleaseChannel::Stable | ReleaseChannel::Preview => since_v0_1_0::MAX_VERSION,
 59    };
 60
 61    since_v0_0_1::MIN_VERSION..=max_version
 62}
 63
 64pub enum Extension {
 65    V020(since_v0_2_0::Extension),
 66    V010(since_v0_1_0::Extension),
 67    V006(since_v0_0_6::Extension),
 68    V004(since_v0_0_4::Extension),
 69    V001(since_v0_0_1::Extension),
 70}
 71
 72impl Extension {
 73    pub async fn instantiate_async(
 74        store: &mut Store<WasmState>,
 75        release_channel: ReleaseChannel,
 76        version: SemanticVersion,
 77        component: &Component,
 78    ) -> Result<Self> {
 79        // Note: The release channel can be used to stage a new version of the extension API.
 80        let allow_latest_version = match release_channel {
 81            ReleaseChannel::Dev | ReleaseChannel::Nightly => true,
 82            ReleaseChannel::Stable | ReleaseChannel::Preview => false,
 83        };
 84
 85        if allow_latest_version && version >= latest::MIN_VERSION {
 86            let extension =
 87                latest::Extension::instantiate_async(store, component, latest::linker())
 88                    .await
 89                    .context("failed to instantiate wasm extension")?;
 90            Ok(Self::V020(extension))
 91        } else if version >= since_v0_1_0::MIN_VERSION {
 92            let extension = since_v0_1_0::Extension::instantiate_async(
 93                store,
 94                component,
 95                since_v0_1_0::linker(),
 96            )
 97            .await
 98            .context("failed to instantiate wasm extension")?;
 99            Ok(Self::V010(extension))
100        } else if version >= since_v0_0_6::MIN_VERSION {
101            let extension = since_v0_0_6::Extension::instantiate_async(
102                store,
103                component,
104                since_v0_0_6::linker(),
105            )
106            .await
107            .context("failed to instantiate wasm extension")?;
108            Ok(Self::V006(extension))
109        } else if version >= since_v0_0_4::MIN_VERSION {
110            let extension = since_v0_0_4::Extension::instantiate_async(
111                store,
112                component,
113                since_v0_0_4::linker(),
114            )
115            .await
116            .context("failed to instantiate wasm extension")?;
117            Ok(Self::V004(extension))
118        } else {
119            let extension = since_v0_0_1::Extension::instantiate_async(
120                store,
121                component,
122                since_v0_0_1::linker(),
123            )
124            .await
125            .context("failed to instantiate wasm extension")?;
126            Ok(Self::V001(extension))
127        }
128    }
129
130    pub async fn call_init_extension(&self, store: &mut Store<WasmState>) -> Result<()> {
131        match self {
132            Extension::V020(ext) => ext.call_init_extension(store).await,
133            Extension::V010(ext) => ext.call_init_extension(store).await,
134            Extension::V006(ext) => ext.call_init_extension(store).await,
135            Extension::V004(ext) => ext.call_init_extension(store).await,
136            Extension::V001(ext) => ext.call_init_extension(store).await,
137        }
138    }
139
140    pub async fn call_language_server_command(
141        &self,
142        store: &mut Store<WasmState>,
143        language_server_id: &LanguageServerName,
144        config: &LanguageServerConfig,
145        resource: Resource<Arc<dyn LspAdapterDelegate>>,
146    ) -> Result<Result<Command, String>> {
147        match self {
148            Extension::V020(ext) => {
149                ext.call_language_server_command(store, &language_server_id.0, resource)
150                    .await
151            }
152            Extension::V010(ext) => Ok(ext
153                .call_language_server_command(store, &language_server_id.0, resource)
154                .await?
155                .map(|command| command.into())),
156            Extension::V006(ext) => Ok(ext
157                .call_language_server_command(store, &language_server_id.0, resource)
158                .await?
159                .map(|command| command.into())),
160            Extension::V004(ext) => Ok(ext
161                .call_language_server_command(store, config, resource)
162                .await?
163                .map(|command| command.into())),
164            Extension::V001(ext) => Ok(ext
165                .call_language_server_command(store, &config.clone().into(), resource)
166                .await?
167                .map(|command| command.into())),
168        }
169    }
170
171    pub async fn call_language_server_initialization_options(
172        &self,
173        store: &mut Store<WasmState>,
174        language_server_id: &LanguageServerName,
175        config: &LanguageServerConfig,
176        resource: Resource<Arc<dyn LspAdapterDelegate>>,
177    ) -> Result<Result<Option<String>, String>> {
178        match self {
179            Extension::V020(ext) => {
180                ext.call_language_server_initialization_options(
181                    store,
182                    &language_server_id.0,
183                    resource,
184                )
185                .await
186            }
187            Extension::V010(ext) => {
188                ext.call_language_server_initialization_options(
189                    store,
190                    &language_server_id.0,
191                    resource,
192                )
193                .await
194            }
195            Extension::V006(ext) => {
196                ext.call_language_server_initialization_options(
197                    store,
198                    &language_server_id.0,
199                    resource,
200                )
201                .await
202            }
203            Extension::V004(ext) => {
204                ext.call_language_server_initialization_options(store, config, resource)
205                    .await
206            }
207            Extension::V001(ext) => {
208                ext.call_language_server_initialization_options(
209                    store,
210                    &config.clone().into(),
211                    resource,
212                )
213                .await
214            }
215        }
216    }
217
218    pub async fn call_language_server_workspace_configuration(
219        &self,
220        store: &mut Store<WasmState>,
221        language_server_id: &LanguageServerName,
222        resource: Resource<Arc<dyn LspAdapterDelegate>>,
223    ) -> Result<Result<Option<String>, String>> {
224        match self {
225            Extension::V020(ext) => {
226                ext.call_language_server_workspace_configuration(
227                    store,
228                    &language_server_id.0,
229                    resource,
230                )
231                .await
232            }
233            Extension::V010(ext) => {
234                ext.call_language_server_workspace_configuration(
235                    store,
236                    &language_server_id.0,
237                    resource,
238                )
239                .await
240            }
241            Extension::V006(ext) => {
242                ext.call_language_server_workspace_configuration(
243                    store,
244                    &language_server_id.0,
245                    resource,
246                )
247                .await
248            }
249            Extension::V004(_) | Extension::V001(_) => Ok(Ok(None)),
250        }
251    }
252
253    pub async fn call_labels_for_completions(
254        &self,
255        store: &mut Store<WasmState>,
256        language_server_id: &LanguageServerName,
257        completions: Vec<latest::Completion>,
258    ) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
259        match self {
260            Extension::V020(ext) => {
261                ext.call_labels_for_completions(store, &language_server_id.0, &completions)
262                    .await
263            }
264            Extension::V010(ext) => Ok(ext
265                .call_labels_for_completions(store, &language_server_id.0, &completions)
266                .await?
267                .map(|labels| {
268                    labels
269                        .into_iter()
270                        .map(|label| label.map(Into::into))
271                        .collect()
272                })),
273            Extension::V006(ext) => Ok(ext
274                .call_labels_for_completions(store, &language_server_id.0, &completions)
275                .await?
276                .map(|labels| {
277                    labels
278                        .into_iter()
279                        .map(|label| label.map(Into::into))
280                        .collect()
281                })),
282            Extension::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
283        }
284    }
285
286    pub async fn call_labels_for_symbols(
287        &self,
288        store: &mut Store<WasmState>,
289        language_server_id: &LanguageServerName,
290        symbols: Vec<latest::Symbol>,
291    ) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
292        match self {
293            Extension::V020(ext) => {
294                ext.call_labels_for_symbols(store, &language_server_id.0, &symbols)
295                    .await
296            }
297            Extension::V010(ext) => Ok(ext
298                .call_labels_for_symbols(store, &language_server_id.0, &symbols)
299                .await?
300                .map(|labels| {
301                    labels
302                        .into_iter()
303                        .map(|label| label.map(Into::into))
304                        .collect()
305                })),
306            Extension::V006(ext) => Ok(ext
307                .call_labels_for_symbols(store, &language_server_id.0, &symbols)
308                .await?
309                .map(|labels| {
310                    labels
311                        .into_iter()
312                        .map(|label| label.map(Into::into))
313                        .collect()
314                })),
315            Extension::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
316        }
317    }
318
319    pub async fn call_complete_slash_command_argument(
320        &self,
321        store: &mut Store<WasmState>,
322        command: &SlashCommand,
323        arguments: &[String],
324    ) -> Result<Result<Vec<SlashCommandArgumentCompletion>, String>> {
325        match self {
326            Extension::V020(ext) => {
327                ext.call_complete_slash_command_argument(store, command, arguments)
328                    .await
329            }
330            Extension::V010(ext) => {
331                ext.call_complete_slash_command_argument(store, command, arguments)
332                    .await
333            }
334            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Ok(Ok(Vec::new())),
335        }
336    }
337
338    pub async fn call_run_slash_command(
339        &self,
340        store: &mut Store<WasmState>,
341        command: &SlashCommand,
342        arguments: &[String],
343        resource: Option<Resource<Arc<dyn LspAdapterDelegate>>>,
344    ) -> Result<Result<SlashCommandOutput, String>> {
345        match self {
346            Extension::V020(ext) => {
347                ext.call_run_slash_command(store, command, arguments, resource)
348                    .await
349            }
350            Extension::V010(ext) => {
351                ext.call_run_slash_command(store, command, arguments, resource)
352                    .await
353            }
354            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
355                Err(anyhow!("`run_slash_command` not available prior to v0.1.0"))
356            }
357        }
358    }
359
360    pub async fn call_suggest_docs_packages(
361        &self,
362        store: &mut Store<WasmState>,
363        provider: &str,
364    ) -> Result<Result<Vec<String>, String>> {
365        match self {
366            Extension::V020(ext) => ext.call_suggest_docs_packages(store, provider).await,
367            Extension::V010(ext) => ext.call_suggest_docs_packages(store, provider).await,
368            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Err(anyhow!(
369                "`suggest_docs_packages` not available prior to v0.1.0"
370            )),
371        }
372    }
373
374    pub async fn call_index_docs(
375        &self,
376        store: &mut Store<WasmState>,
377        provider: &str,
378        package_name: &str,
379        database: Resource<Arc<IndexedDocsDatabase>>,
380    ) -> Result<Result<(), String>> {
381        match self {
382            Extension::V020(ext) => {
383                ext.call_index_docs(store, provider, package_name, database)
384                    .await
385            }
386            Extension::V010(ext) => {
387                ext.call_index_docs(store, provider, package_name, database)
388                    .await
389            }
390            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
391                Err(anyhow!("`index_docs` not available prior to v0.1.0"))
392            }
393        }
394    }
395}
396
397trait ToWasmtimeResult<T> {
398    fn to_wasmtime_result(self) -> wasmtime::Result<Result<T, String>>;
399}
400
401impl<T> ToWasmtimeResult<T> for Result<T> {
402    fn to_wasmtime_result(self) -> wasmtime::Result<Result<T, String>> {
403        Ok(self.map_err(|error| error.to_string()))
404    }
405}