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