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