1mod since_v0_0_1;
2mod since_v0_0_4;
3mod since_v0_0_6;
4mod since_v0_0_7;
5use indexed_docs::IndexedDocsDatabase;
6use release_channel::ReleaseChannel;
7use since_v0_0_7 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 let max_version = match release_channel {
53 ReleaseChannel::Dev | ReleaseChannel::Nightly => latest::MAX_VERSION,
54 ReleaseChannel::Stable | ReleaseChannel::Preview => since_v0_0_6::MAX_VERSION,
55 };
56
57 since_v0_0_1::MIN_VERSION..=max_version
58}
59
60pub enum Extension {
61 V007(since_v0_0_7::Extension),
62 V006(since_v0_0_6::Extension),
63 V004(since_v0_0_4::Extension),
64 V001(since_v0_0_1::Extension),
65}
66
67impl Extension {
68 pub async fn instantiate_async(
69 store: &mut Store<WasmState>,
70 release_channel: ReleaseChannel,
71 version: SemanticVersion,
72 component: &Component,
73 ) -> Result<(Self, Instance)> {
74 if release_channel == ReleaseChannel::Dev && version >= latest::MIN_VERSION {
75 let (extension, instance) =
76 latest::Extension::instantiate_async(store, &component, latest::linker())
77 .await
78 .context("failed to instantiate wasm extension")?;
79 Ok((Self::V007(extension), instance))
80 } else if version >= since_v0_0_6::MIN_VERSION {
81 let (extension, instance) = since_v0_0_6::Extension::instantiate_async(
82 store,
83 &component,
84 since_v0_0_6::linker(),
85 )
86 .await
87 .context("failed to instantiate wasm extension")?;
88 Ok((Self::V006(extension), instance))
89 } else if version >= since_v0_0_4::MIN_VERSION {
90 let (extension, instance) = since_v0_0_4::Extension::instantiate_async(
91 store,
92 &component,
93 since_v0_0_4::linker(),
94 )
95 .await
96 .context("failed to instantiate wasm extension")?;
97 Ok((Self::V004(extension), instance))
98 } else {
99 let (extension, instance) = since_v0_0_1::Extension::instantiate_async(
100 store,
101 &component,
102 since_v0_0_1::linker(),
103 )
104 .await
105 .context("failed to instantiate wasm extension")?;
106 Ok((Self::V001(extension), instance))
107 }
108 }
109
110 pub async fn call_init_extension(&self, store: &mut Store<WasmState>) -> Result<()> {
111 match self {
112 Extension::V007(ext) => ext.call_init_extension(store).await,
113 Extension::V006(ext) => ext.call_init_extension(store).await,
114 Extension::V004(ext) => ext.call_init_extension(store).await,
115 Extension::V001(ext) => ext.call_init_extension(store).await,
116 }
117 }
118
119 pub async fn call_language_server_command(
120 &self,
121 store: &mut Store<WasmState>,
122 language_server_id: &LanguageServerName,
123 config: &LanguageServerConfig,
124 resource: Resource<Arc<dyn LspAdapterDelegate>>,
125 ) -> Result<Result<Command, String>> {
126 match self {
127 Extension::V007(ext) => {
128 ext.call_language_server_command(store, &language_server_id.0, resource)
129 .await
130 }
131 Extension::V006(ext) => Ok(ext
132 .call_language_server_command(store, &language_server_id.0, resource)
133 .await?
134 .map(|command| command.into())),
135 Extension::V004(ext) => Ok(ext
136 .call_language_server_command(store, config, resource)
137 .await?
138 .map(|command| command.into())),
139 Extension::V001(ext) => Ok(ext
140 .call_language_server_command(store, &config.clone().into(), resource)
141 .await?
142 .map(|command| command.into())),
143 }
144 }
145
146 pub async fn call_language_server_initialization_options(
147 &self,
148 store: &mut Store<WasmState>,
149 language_server_id: &LanguageServerName,
150 config: &LanguageServerConfig,
151 resource: Resource<Arc<dyn LspAdapterDelegate>>,
152 ) -> Result<Result<Option<String>, String>> {
153 match self {
154 Extension::V007(ext) => {
155 ext.call_language_server_initialization_options(
156 store,
157 &language_server_id.0,
158 resource,
159 )
160 .await
161 }
162 Extension::V006(ext) => {
163 ext.call_language_server_initialization_options(
164 store,
165 &language_server_id.0,
166 resource,
167 )
168 .await
169 }
170 Extension::V004(ext) => {
171 ext.call_language_server_initialization_options(store, config, resource)
172 .await
173 }
174 Extension::V001(ext) => {
175 ext.call_language_server_initialization_options(
176 store,
177 &config.clone().into(),
178 resource,
179 )
180 .await
181 }
182 }
183 }
184
185 pub async fn call_language_server_workspace_configuration(
186 &self,
187 store: &mut Store<WasmState>,
188 language_server_id: &LanguageServerName,
189 resource: Resource<Arc<dyn LspAdapterDelegate>>,
190 ) -> Result<Result<Option<String>, String>> {
191 match self {
192 Extension::V007(ext) => {
193 ext.call_language_server_workspace_configuration(
194 store,
195 &language_server_id.0,
196 resource,
197 )
198 .await
199 }
200 Extension::V006(ext) => {
201 ext.call_language_server_workspace_configuration(
202 store,
203 &language_server_id.0,
204 resource,
205 )
206 .await
207 }
208 Extension::V004(_) | Extension::V001(_) => Ok(Ok(None)),
209 }
210 }
211
212 pub async fn call_labels_for_completions(
213 &self,
214 store: &mut Store<WasmState>,
215 language_server_id: &LanguageServerName,
216 completions: Vec<latest::Completion>,
217 ) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
218 match self {
219 Extension::V007(ext) => {
220 ext.call_labels_for_completions(store, &language_server_id.0, &completions)
221 .await
222 }
223 Extension::V006(ext) => Ok(ext
224 .call_labels_for_completions(store, &language_server_id.0, &completions)
225 .await?
226 .map(|labels| {
227 labels
228 .into_iter()
229 .map(|label| label.map(Into::into))
230 .collect()
231 })),
232 Extension::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
233 }
234 }
235
236 pub async fn call_labels_for_symbols(
237 &self,
238 store: &mut Store<WasmState>,
239 language_server_id: &LanguageServerName,
240 symbols: Vec<latest::Symbol>,
241 ) -> Result<Result<Vec<Option<CodeLabel>>, String>> {
242 match self {
243 Extension::V007(ext) => {
244 ext.call_labels_for_symbols(store, &language_server_id.0, &symbols)
245 .await
246 }
247 Extension::V006(ext) => Ok(ext
248 .call_labels_for_symbols(store, &language_server_id.0, &symbols)
249 .await?
250 .map(|labels| {
251 labels
252 .into_iter()
253 .map(|label| label.map(Into::into))
254 .collect()
255 })),
256 Extension::V001(_) | Extension::V004(_) => Ok(Ok(Vec::new())),
257 }
258 }
259
260 pub async fn call_complete_slash_command_argument(
261 &self,
262 store: &mut Store<WasmState>,
263 command: &SlashCommand,
264 query: &str,
265 ) -> Result<Result<Vec<SlashCommandArgumentCompletion>, String>> {
266 match self {
267 Extension::V007(ext) => {
268 ext.call_complete_slash_command_argument(store, command, query)
269 .await
270 }
271 Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Ok(Ok(Vec::new())),
272 }
273 }
274
275 pub async fn call_run_slash_command(
276 &self,
277 store: &mut Store<WasmState>,
278 command: &SlashCommand,
279 argument: Option<&str>,
280 resource: Resource<Arc<dyn LspAdapterDelegate>>,
281 ) -> Result<Result<SlashCommandOutput, String>> {
282 match self {
283 Extension::V007(ext) => {
284 ext.call_run_slash_command(store, command, argument, resource)
285 .await
286 }
287 Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
288 Err(anyhow!("`run_slash_command` not available prior to v0.0.7"))
289 }
290 }
291 }
292
293 pub async fn call_index_docs(
294 &self,
295 store: &mut Store<WasmState>,
296 provider: &str,
297 package_name: &str,
298 database: Resource<Arc<IndexedDocsDatabase>>,
299 ) -> Result<Result<(), String>> {
300 match self {
301 Extension::V007(ext) => {
302 ext.call_index_docs(store, provider, package_name, database)
303 .await
304 }
305 Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => {
306 Err(anyhow!("`index_docs` not available prior to v0.0.7"))
307 }
308 }
309 }
310}
311
312trait ToWasmtimeResult<T> {
313 fn to_wasmtime_result(self) -> wasmtime::Result<Result<T, String>>;
314}
315
316impl<T> ToWasmtimeResult<T> for Result<T> {
317 fn to_wasmtime_result(self) -> wasmtime::Result<Result<T, String>> {
318 Ok(self.map_err(|error| error.to_string()))
319 }
320}