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