1use crate::wasm_host::{wit::LanguageServerConfig, WasmExtension, WasmHost};
2use anyhow::{anyhow, Context, Result};
3use async_trait::async_trait;
4use collections::HashMap;
5use futures::{Future, FutureExt};
6use gpui::AsyncAppContext;
7use language::{Language, LanguageServerName, LspAdapter, LspAdapterDelegate};
8use lsp::LanguageServerBinary;
9use std::{
10 any::Any,
11 path::{Path, PathBuf},
12 pin::Pin,
13 sync::Arc,
14};
15use wasmtime_wasi::WasiView as _;
16
17pub struct ExtensionLspAdapter {
18 pub(crate) extension: WasmExtension,
19 pub(crate) config: LanguageServerConfig,
20 pub(crate) host: Arc<WasmHost>,
21}
22
23#[async_trait(?Send)]
24impl LspAdapter for ExtensionLspAdapter {
25 fn name(&self) -> LanguageServerName {
26 LanguageServerName(self.config.name.clone().into())
27 }
28
29 fn get_language_server_command<'a>(
30 self: Arc<Self>,
31 _: Arc<Language>,
32 _: Arc<Path>,
33 delegate: Arc<dyn LspAdapterDelegate>,
34 _: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
35 _: &'a mut AsyncAppContext,
36 ) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
37 async move {
38 let command = self
39 .extension
40 .call({
41 let this = self.clone();
42 |extension, store| {
43 async move {
44 let resource = store.data_mut().table().push(delegate)?;
45 let command = extension
46 .call_language_server_command(store, &this.config, resource)
47 .await?
48 .map_err(|e| anyhow!("{}", e))?;
49 anyhow::Ok(command)
50 }
51 .boxed()
52 }
53 })
54 .await?;
55
56 let path = self
57 .host
58 .path_from_extension(&self.extension.manifest.id, command.command.as_ref());
59
60 // TODO: This should now be done via the `zed::make_file_executable` function in
61 // Zed extension API, but we're leaving these existing usages in place temporarily
62 // to avoid any compatibility issues between Zed and the extension versions.
63 //
64 // We can remove once the following extension versions no longer see any use:
65 // - toml@0.0.2
66 // - zig@0.0.1
67 if ["toml", "zig"].contains(&self.extension.manifest.id.as_ref()) {
68 #[cfg(not(windows))]
69 {
70 use std::fs::{self, Permissions};
71 use std::os::unix::fs::PermissionsExt;
72
73 fs::set_permissions(&path, Permissions::from_mode(0o755))
74 .context("failed to set file permissions")?;
75 }
76 }
77
78 Ok(LanguageServerBinary {
79 path,
80 arguments: command.args.into_iter().map(|arg| arg.into()).collect(),
81 env: Some(command.env.into_iter().collect()),
82 })
83 }
84 .boxed_local()
85 }
86
87 async fn fetch_latest_server_version(
88 &self,
89 _: &dyn LspAdapterDelegate,
90 ) -> Result<Box<dyn 'static + Send + Any>> {
91 unreachable!("get_language_server_command is overridden")
92 }
93
94 async fn fetch_server_binary(
95 &self,
96 _: Box<dyn 'static + Send + Any>,
97 _: PathBuf,
98 _: &dyn LspAdapterDelegate,
99 ) -> Result<LanguageServerBinary> {
100 unreachable!("get_language_server_command is overridden")
101 }
102
103 async fn cached_server_binary(
104 &self,
105 _: PathBuf,
106 _: &dyn LspAdapterDelegate,
107 ) -> Option<LanguageServerBinary> {
108 unreachable!("get_language_server_command is overridden")
109 }
110
111 async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
112 None
113 }
114
115 fn language_ids(&self) -> HashMap<String, String> {
116 // TODO: The language IDs can be provided via the language server options
117 // in `extension.toml now but we're leaving these existing usages in place temporarily
118 // to avoid any compatibility issues between Zed and the extension versions.
119 //
120 // We can remove once the following extension versions no longer see any use:
121 // - php@0.0.1
122 if self.extension.manifest.id.as_ref() == "php" {
123 return HashMap::from_iter([("PHP".into(), "php".into())]);
124 }
125
126 self.extension
127 .manifest
128 .language_servers
129 .get(&LanguageServerName(self.config.name.clone().into()))
130 .map(|server| server.language_ids.clone())
131 .unwrap_or_default()
132 }
133
134 async fn initialization_options(
135 self: Arc<Self>,
136 delegate: &Arc<dyn LspAdapterDelegate>,
137 ) -> Result<Option<serde_json::Value>> {
138 let delegate = delegate.clone();
139 let json_options = self
140 .extension
141 .call({
142 let this = self.clone();
143 |extension, store| {
144 async move {
145 let resource = store.data_mut().table().push(delegate)?;
146 let options = extension
147 .call_language_server_initialization_options(
148 store,
149 &this.config,
150 resource,
151 )
152 .await?
153 .map_err(|e| anyhow!("{}", e))?;
154 anyhow::Ok(options)
155 }
156 .boxed()
157 }
158 })
159 .await?;
160 Ok(if let Some(json_options) = json_options {
161 serde_json::from_str(&json_options).with_context(|| {
162 format!("failed to parse initialization_options from extension: {json_options}")
163 })?
164 } else {
165 None
166 })
167 }
168}