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