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::{
 24        Completion, CompletionKind, CompletionLabelDetails, InsertTextFormat, Symbol, SymbolKind,
 25    },
 26    zed::extension::slash_command::{SlashCommandArgumentCompletion, SlashCommandOutput},
 27    CodeLabel, CodeLabelSpan, Command, 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        // Note: The release channel can be used to stage a new version of the extension API.
 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 =
 89                latest::Extension::instantiate_async(store, component, latest::linker())
 90                    .await
 91                    .context("failed to instantiate wasm extension")?;
 92            Ok(Self::V020(extension))
 93        } else if version >= since_v0_1_0::MIN_VERSION {
 94            let extension = 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))
102        } else if version >= since_v0_0_6::MIN_VERSION {
103            let extension = 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))
111        } else if version >= since_v0_0_4::MIN_VERSION {
112            let extension = 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))
120        } else {
121            let extension = 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))
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(
268                    store,
269                    &language_server_id.0,
270                    &completions.into_iter().map(Into::into).collect::<Vec<_>>(),
271                )
272                .await?
273                .map(|labels| {
274                    labels
275                        .into_iter()
276                        .map(|label| label.map(Into::into))
277                        .collect()
278                })),
279            Extension::V006(ext) => Ok(ext
280                .call_labels_for_completions(
281                    store,
282                    &language_server_id.0,
283                    &completions.into_iter().map(Into::into).collect::<Vec<_>>(),
284                )
285                .await?
286                .map(|labels| {
287                    labels
288                        .into_iter()
289                        .map(|label| label.map(Into::into))
290                        .collect()
291                })),
292            Extension::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
293        }
294    }
295
296    pub async fn call_labels_for_symbols(
297        &self,
298        store: &mut Store<WasmState>,
299        language_server_id: &LanguageServerName,
300        symbols: Vec<latest::Symbol>,
301    ) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
302        match self {
303            Extension::V020(ext) => {
304                ext.call_labels_for_symbols(store, &language_server_id.0, &symbols)
305                    .await
306            }
307            Extension::V010(ext) => Ok(ext
308                .call_labels_for_symbols(
309                    store,
310                    &language_server_id.0,
311                    &symbols.into_iter().map(Into::into).collect::<Vec<_>>(),
312                )
313                .await?
314                .map(|labels| {
315                    labels
316                        .into_iter()
317                        .map(|label| label.map(Into::into))
318                        .collect()
319                })),
320            Extension::V006(ext) => Ok(ext
321                .call_labels_for_symbols(
322                    store,
323                    &language_server_id.0,
324                    &symbols.into_iter().map(Into::into).collect::<Vec<_>>(),
325                )
326                .await?
327                .map(|labels| {
328                    labels
329                        .into_iter()
330                        .map(|label| label.map(Into::into))
331                        .collect()
332                })),
333            Extension::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
334        }
335    }
336
337    pub async fn call_complete_slash_command_argument(
338        &self,
339        store: &mut Store<WasmState>,
340        command: &SlashCommand,
341        arguments: &[String],
342    ) -> Result<Result<Vec<SlashCommandArgumentCompletion>, String>> {
343        match self {
344            Extension::V020(ext) => {
345                ext.call_complete_slash_command_argument(store, command, arguments)
346                    .await
347            }
348            Extension::V010(ext) => {
349                ext.call_complete_slash_command_argument(store, command, arguments)
350                    .await
351            }
352            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Ok(Ok(Vec::new())),
353        }
354    }
355
356    pub async fn call_run_slash_command(
357        &self,
358        store: &mut Store<WasmState>,
359        command: &SlashCommand,
360        arguments: &[String],
361        resource: Option<Resource<Arc<dyn LspAdapterDelegate>>>,
362    ) -> Result<Result<SlashCommandOutput, String>> {
363        match self {
364            Extension::V020(ext) => {
365                ext.call_run_slash_command(store, command, arguments, resource)
366                    .await
367            }
368            Extension::V010(ext) => {
369                ext.call_run_slash_command(store, command, arguments, resource)
370                    .await
371            }
372            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
373                Err(anyhow!("`run_slash_command` not available prior to v0.1.0"))
374            }
375        }
376    }
377
378    pub async fn call_suggest_docs_packages(
379        &self,
380        store: &mut Store<WasmState>,
381        provider: &str,
382    ) -> Result<Result<Vec<String>, String>> {
383        match self {
384            Extension::V020(ext) => ext.call_suggest_docs_packages(store, provider).await,
385            Extension::V010(ext) => ext.call_suggest_docs_packages(store, provider).await,
386            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Err(anyhow!(
387                "`suggest_docs_packages` not available prior to v0.1.0"
388            )),
389        }
390    }
391
392    pub async fn call_index_docs(
393        &self,
394        store: &mut Store<WasmState>,
395        provider: &str,
396        package_name: &str,
397        database: Resource<Arc<IndexedDocsDatabase>>,
398    ) -> Result<Result<(), String>> {
399        match self {
400            Extension::V020(ext) => {
401                ext.call_index_docs(store, provider, package_name, database)
402                    .await
403            }
404            Extension::V010(ext) => {
405                ext.call_index_docs(store, provider, package_name, database)
406                    .await
407            }
408            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
409                Err(anyhow!("`index_docs` not available prior to v0.1.0"))
410            }
411        }
412    }
413}
414
415trait ToWasmtimeResult<T> {
416    fn to_wasmtime_result(self) -> wasmtime::Result<Result<T, String>>;
417}
418
419impl<T> ToWasmtimeResult<T> for Result<T> {
420    fn to_wasmtime_result(self) -> wasmtime::Result<Result<T, String>> {
421        Ok(self.map_err(|error| error.to_string()))
422    }
423}