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