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                #[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}