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