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