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 && path.starts_with(&self.host.work_dir)
69 {
70 #[cfg(not(windows))]
71 {
72 use std::fs::{self, Permissions};
73 use std::os::unix::fs::PermissionsExt;
74
75 fs::set_permissions(&path, Permissions::from_mode(0o755))
76 .context("failed to set file permissions")?;
77 }
78 }
79
80 Ok(LanguageServerBinary {
81 path,
82 arguments: command.args.into_iter().map(|arg| arg.into()).collect(),
83 env: Some(command.env.into_iter().collect()),
84 })
85 }
86 .boxed_local()
87 }
88
89 async fn fetch_latest_server_version(
90 &self,
91 _: &dyn LspAdapterDelegate,
92 ) -> Result<Box<dyn 'static + Send + Any>> {
93 unreachable!("get_language_server_command is overridden")
94 }
95
96 async fn fetch_server_binary(
97 &self,
98 _: Box<dyn 'static + Send + Any>,
99 _: PathBuf,
100 _: &dyn LspAdapterDelegate,
101 ) -> Result<LanguageServerBinary> {
102 unreachable!("get_language_server_command is overridden")
103 }
104
105 async fn cached_server_binary(
106 &self,
107 _: PathBuf,
108 _: &dyn LspAdapterDelegate,
109 ) -> Option<LanguageServerBinary> {
110 unreachable!("get_language_server_command is overridden")
111 }
112
113 async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
114 None
115 }
116
117 fn language_ids(&self) -> HashMap<String, String> {
118 // TODO: The language IDs can be provided via the language server options
119 // in `extension.toml now but we're leaving these existing usages in place temporarily
120 // to avoid any compatibility issues between Zed and the extension versions.
121 //
122 // We can remove once the following extension versions no longer see any use:
123 // - php@0.0.1
124 if self.extension.manifest.id.as_ref() == "php" {
125 return HashMap::from_iter([("PHP".into(), "php".into())]);
126 }
127
128 self.extension
129 .manifest
130 .language_servers
131 .get(&LanguageServerName(self.config.name.clone().into()))
132 .map(|server| server.language_ids.clone())
133 .unwrap_or_default()
134 }
135
136 async fn initialization_options(
137 self: Arc<Self>,
138 delegate: &Arc<dyn LspAdapterDelegate>,
139 ) -> Result<Option<serde_json::Value>> {
140 let delegate = delegate.clone();
141 let json_options = self
142 .extension
143 .call({
144 let this = self.clone();
145 |extension, store| {
146 async move {
147 let resource = store.data_mut().table().push(delegate)?;
148 let options = extension
149 .call_language_server_initialization_options(
150 store,
151 &this.config,
152 resource,
153 )
154 .await?
155 .map_err(|e| anyhow!("{}", e))?;
156 anyhow::Ok(options)
157 }
158 .boxed()
159 }
160 })
161 .await?;
162 Ok(if let Some(json_options) = json_options {
163 serde_json::from_str(&json_options).with_context(|| {
164 format!("failed to parse initialization_options from extension: {json_options}")
165 })?
166 } else {
167 None
168 })
169 }
170}