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, WorktreeDelegate};
7use language::LanguageName;
8use lsp::LanguageServerName;
9use release_channel::ReleaseChannel;
10use since_v0_2_0 as latest;
11
12use super::{wasm_engine, WasmState};
13use anyhow::{anyhow, Context, Result};
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 => latest::MAX_VERSION,
62 };
63
64 since_v0_0_1::MIN_VERSION..=max_version
65}
66
67/// Authorizes access to use unreleased versions of the Wasm API, based on the provided [`ReleaseChannel`].
68///
69/// Note: If there isn't currently an unreleased Wasm API version this function may be unused. Don't delete it!
70pub fn authorize_access_to_unreleased_wasm_api_version(
71 release_channel: ReleaseChannel,
72) -> Result<()> {
73 let allow_unreleased_version = match release_channel {
74 ReleaseChannel::Dev | ReleaseChannel::Nightly => true,
75 ReleaseChannel::Stable | ReleaseChannel::Preview => {
76 // We always allow the latest in tests so that the extension tests pass on release branches.
77 cfg!(any(test, feature = "test-support"))
78 }
79 };
80
81 if !allow_unreleased_version {
82 Err(anyhow!(
83 "unreleased versions of the extension API can only be used on development builds of Zed"
84 ))?;
85 }
86
87 Ok(())
88}
89
90pub enum Extension {
91 V020(since_v0_2_0::Extension),
92 V010(since_v0_1_0::Extension),
93 V006(since_v0_0_6::Extension),
94 V004(since_v0_0_4::Extension),
95 V001(since_v0_0_1::Extension),
96}
97
98impl Extension {
99 pub async fn instantiate_async(
100 store: &mut Store<WasmState>,
101 release_channel: ReleaseChannel,
102 version: SemanticVersion,
103 component: &Component,
104 ) -> Result<Self> {
105 // Note: The release channel can be used to stage a new version of the extension API.
106 let _ = release_channel;
107
108 if version >= latest::MIN_VERSION {
109 let extension =
110 latest::Extension::instantiate_async(store, component, latest::linker())
111 .await
112 .context("failed to instantiate wasm extension")?;
113 Ok(Self::V020(extension))
114 } else if version >= since_v0_1_0::MIN_VERSION {
115 let extension = since_v0_1_0::Extension::instantiate_async(
116 store,
117 component,
118 since_v0_1_0::linker(),
119 )
120 .await
121 .context("failed to instantiate wasm extension")?;
122 Ok(Self::V010(extension))
123 } else if version >= since_v0_0_6::MIN_VERSION {
124 let extension = since_v0_0_6::Extension::instantiate_async(
125 store,
126 component,
127 since_v0_0_6::linker(),
128 )
129 .await
130 .context("failed to instantiate wasm extension")?;
131 Ok(Self::V006(extension))
132 } else if version >= since_v0_0_4::MIN_VERSION {
133 let extension = since_v0_0_4::Extension::instantiate_async(
134 store,
135 component,
136 since_v0_0_4::linker(),
137 )
138 .await
139 .context("failed to instantiate wasm extension")?;
140 Ok(Self::V004(extension))
141 } else {
142 let extension = since_v0_0_1::Extension::instantiate_async(
143 store,
144 component,
145 since_v0_0_1::linker(),
146 )
147 .await
148 .context("failed to instantiate wasm extension")?;
149 Ok(Self::V001(extension))
150 }
151 }
152
153 pub async fn call_init_extension(&self, store: &mut Store<WasmState>) -> Result<()> {
154 match self {
155 Extension::V020(ext) => ext.call_init_extension(store).await,
156 Extension::V010(ext) => ext.call_init_extension(store).await,
157 Extension::V006(ext) => ext.call_init_extension(store).await,
158 Extension::V004(ext) => ext.call_init_extension(store).await,
159 Extension::V001(ext) => ext.call_init_extension(store).await,
160 }
161 }
162
163 pub async fn call_language_server_command(
164 &self,
165 store: &mut Store<WasmState>,
166 language_server_id: &LanguageServerName,
167 language_name: &LanguageName,
168 resource: Resource<Arc<dyn WorktreeDelegate>>,
169 ) -> Result<Result<Command, String>> {
170 match self {
171 Extension::V020(ext) => {
172 ext.call_language_server_command(store, &language_server_id.0, resource)
173 .await
174 }
175 Extension::V010(ext) => Ok(ext
176 .call_language_server_command(store, &language_server_id.0, resource)
177 .await?
178 .map(|command| command.into())),
179 Extension::V006(ext) => Ok(ext
180 .call_language_server_command(store, &language_server_id.0, resource)
181 .await?
182 .map(|command| command.into())),
183 Extension::V004(ext) => Ok(ext
184 .call_language_server_command(
185 store,
186 &LanguageServerConfig {
187 name: language_server_id.0.to_string(),
188 language_name: language_name.to_string(),
189 },
190 resource,
191 )
192 .await?
193 .map(|command| command.into())),
194 Extension::V001(ext) => Ok(ext
195 .call_language_server_command(
196 store,
197 &LanguageServerConfig {
198 name: language_server_id.0.to_string(),
199 language_name: language_name.to_string(),
200 }
201 .into(),
202 resource,
203 )
204 .await?
205 .map(|command| command.into())),
206 }
207 }
208
209 pub async fn call_language_server_initialization_options(
210 &self,
211 store: &mut Store<WasmState>,
212 language_server_id: &LanguageServerName,
213 language_name: &LanguageName,
214 resource: Resource<Arc<dyn WorktreeDelegate>>,
215 ) -> Result<Result<Option<String>, String>> {
216 match self {
217 Extension::V020(ext) => {
218 ext.call_language_server_initialization_options(
219 store,
220 &language_server_id.0,
221 resource,
222 )
223 .await
224 }
225 Extension::V010(ext) => {
226 ext.call_language_server_initialization_options(
227 store,
228 &language_server_id.0,
229 resource,
230 )
231 .await
232 }
233 Extension::V006(ext) => {
234 ext.call_language_server_initialization_options(
235 store,
236 &language_server_id.0,
237 resource,
238 )
239 .await
240 }
241 Extension::V004(ext) => {
242 ext.call_language_server_initialization_options(
243 store,
244 &LanguageServerConfig {
245 name: language_server_id.0.to_string(),
246 language_name: language_name.to_string(),
247 },
248 resource,
249 )
250 .await
251 }
252 Extension::V001(ext) => {
253 ext.call_language_server_initialization_options(
254 store,
255 &LanguageServerConfig {
256 name: language_server_id.0.to_string(),
257 language_name: language_name.to_string(),
258 }
259 .into(),
260 resource,
261 )
262 .await
263 }
264 }
265 }
266
267 pub async fn call_language_server_workspace_configuration(
268 &self,
269 store: &mut Store<WasmState>,
270 language_server_id: &LanguageServerName,
271 resource: Resource<Arc<dyn WorktreeDelegate>>,
272 ) -> Result<Result<Option<String>, String>> {
273 match self {
274 Extension::V020(ext) => {
275 ext.call_language_server_workspace_configuration(
276 store,
277 &language_server_id.0,
278 resource,
279 )
280 .await
281 }
282 Extension::V010(ext) => {
283 ext.call_language_server_workspace_configuration(
284 store,
285 &language_server_id.0,
286 resource,
287 )
288 .await
289 }
290 Extension::V006(ext) => {
291 ext.call_language_server_workspace_configuration(
292 store,
293 &language_server_id.0,
294 resource,
295 )
296 .await
297 }
298 Extension::V004(_) | Extension::V001(_) => Ok(Ok(None)),
299 }
300 }
301
302 pub async fn call_labels_for_completions(
303 &self,
304 store: &mut Store<WasmState>,
305 language_server_id: &LanguageServerName,
306 completions: Vec<latest::Completion>,
307 ) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
308 match self {
309 Extension::V020(ext) => {
310 ext.call_labels_for_completions(store, &language_server_id.0, &completions)
311 .await
312 }
313 Extension::V010(ext) => Ok(ext
314 .call_labels_for_completions(
315 store,
316 &language_server_id.0,
317 &completions.into_iter().map(Into::into).collect::<Vec<_>>(),
318 )
319 .await?
320 .map(|labels| {
321 labels
322 .into_iter()
323 .map(|label| label.map(Into::into))
324 .collect()
325 })),
326 Extension::V006(ext) => Ok(ext
327 .call_labels_for_completions(
328 store,
329 &language_server_id.0,
330 &completions.into_iter().map(Into::into).collect::<Vec<_>>(),
331 )
332 .await?
333 .map(|labels| {
334 labels
335 .into_iter()
336 .map(|label| label.map(Into::into))
337 .collect()
338 })),
339 Extension::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
340 }
341 }
342
343 pub async fn call_labels_for_symbols(
344 &self,
345 store: &mut Store<WasmState>,
346 language_server_id: &LanguageServerName,
347 symbols: Vec<latest::Symbol>,
348 ) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
349 match self {
350 Extension::V020(ext) => {
351 ext.call_labels_for_symbols(store, &language_server_id.0, &symbols)
352 .await
353 }
354 Extension::V010(ext) => Ok(ext
355 .call_labels_for_symbols(
356 store,
357 &language_server_id.0,
358 &symbols.into_iter().map(Into::into).collect::<Vec<_>>(),
359 )
360 .await?
361 .map(|labels| {
362 labels
363 .into_iter()
364 .map(|label| label.map(Into::into))
365 .collect()
366 })),
367 Extension::V006(ext) => Ok(ext
368 .call_labels_for_symbols(
369 store,
370 &language_server_id.0,
371 &symbols.into_iter().map(Into::into).collect::<Vec<_>>(),
372 )
373 .await?
374 .map(|labels| {
375 labels
376 .into_iter()
377 .map(|label| label.map(Into::into))
378 .collect()
379 })),
380 Extension::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
381 }
382 }
383
384 pub async fn call_complete_slash_command_argument(
385 &self,
386 store: &mut Store<WasmState>,
387 command: &SlashCommand,
388 arguments: &[String],
389 ) -> Result<Result<Vec<SlashCommandArgumentCompletion>, String>> {
390 match self {
391 Extension::V020(ext) => {
392 ext.call_complete_slash_command_argument(store, command, arguments)
393 .await
394 }
395 Extension::V010(ext) => {
396 ext.call_complete_slash_command_argument(store, command, arguments)
397 .await
398 }
399 Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Ok(Ok(Vec::new())),
400 }
401 }
402
403 pub async fn call_run_slash_command(
404 &self,
405 store: &mut Store<WasmState>,
406 command: &SlashCommand,
407 arguments: &[String],
408 resource: Option<Resource<Arc<dyn WorktreeDelegate>>>,
409 ) -> Result<Result<SlashCommandOutput, String>> {
410 match self {
411 Extension::V020(ext) => {
412 ext.call_run_slash_command(store, command, arguments, resource)
413 .await
414 }
415 Extension::V010(ext) => {
416 ext.call_run_slash_command(store, command, arguments, resource)
417 .await
418 }
419 Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
420 Err(anyhow!("`run_slash_command` not available prior to v0.1.0"))
421 }
422 }
423 }
424
425 pub async fn call_context_server_command(
426 &self,
427 store: &mut Store<WasmState>,
428 context_server_id: Arc<str>,
429 project: Resource<ExtensionProject>,
430 ) -> Result<Result<Command, String>> {
431 match self {
432 Extension::V020(ext) => {
433 ext.call_context_server_command(store, &context_server_id, project)
434 .await
435 }
436 Extension::V001(_) | Extension::V004(_) | Extension::V006(_) | Extension::V010(_) => {
437 Err(anyhow!(
438 "`context_server_command` not available prior to v0.2.0"
439 ))
440 }
441 }
442 }
443
444 pub async fn call_suggest_docs_packages(
445 &self,
446 store: &mut Store<WasmState>,
447 provider: &str,
448 ) -> Result<Result<Vec<String>, String>> {
449 match self {
450 Extension::V020(ext) => ext.call_suggest_docs_packages(store, provider).await,
451 Extension::V010(ext) => ext.call_suggest_docs_packages(store, provider).await,
452 Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Err(anyhow!(
453 "`suggest_docs_packages` not available prior to v0.1.0"
454 )),
455 }
456 }
457
458 pub async fn call_index_docs(
459 &self,
460 store: &mut Store<WasmState>,
461 provider: &str,
462 package_name: &str,
463 kv_store: Resource<Arc<dyn KeyValueStoreDelegate>>,
464 ) -> Result<Result<(), String>> {
465 match self {
466 Extension::V020(ext) => {
467 ext.call_index_docs(store, provider, package_name, kv_store)
468 .await
469 }
470 Extension::V010(ext) => {
471 ext.call_index_docs(store, provider, package_name, kv_store)
472 .await
473 }
474 Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
475 Err(anyhow!("`index_docs` not available prior to v0.1.0"))
476 }
477 }
478 }
479}
480
481trait ToWasmtimeResult<T> {
482 fn to_wasmtime_result(self) -> wasmtime::Result<Result<T, String>>;
483}
484
485impl<T> ToWasmtimeResult<T> for Result<T> {
486 fn to_wasmtime_result(self) -> wasmtime::Result<Result<T, String>> {
487 Ok(self.map_err(|error| error.to_string()))
488 }
489}