extension_lsp_adapter.rs

  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}