astro.rs

  1use std::{env, fs};
  2use zed_extension_api::{self as zed, Result};
  3
  4const SERVER_PATH: &str = "node_modules/@astrojs/language-server/bin/nodeServer.js";
  5const PACKAGE_NAME: &str = "@astrojs/language-server";
  6
  7struct AstroExtension {
  8    did_find_server: bool,
  9}
 10
 11impl AstroExtension {
 12    fn server_exists(&self) -> bool {
 13        fs::metadata(SERVER_PATH).map_or(false, |stat| stat.is_file())
 14    }
 15
 16    fn server_script_path(&mut self, config: zed::LanguageServerConfig) -> Result<String> {
 17        let server_exists = self.server_exists();
 18        if self.did_find_server && server_exists {
 19            return Ok(SERVER_PATH.to_string());
 20        }
 21
 22        zed::set_language_server_installation_status(
 23            &config.name,
 24            &zed::LanguageServerInstallationStatus::CheckingForUpdate,
 25        );
 26        let version = zed::npm_package_latest_version(PACKAGE_NAME)?;
 27
 28        if !server_exists
 29            || zed::npm_package_installed_version(PACKAGE_NAME)?.as_ref() != Some(&version)
 30        {
 31            zed::set_language_server_installation_status(
 32                &config.name,
 33                &zed::LanguageServerInstallationStatus::Downloading,
 34            );
 35            let result = zed::npm_install_package(PACKAGE_NAME, &version);
 36            match result {
 37                Ok(()) => {
 38                    if !self.server_exists() {
 39                        Err(format!(
 40                            "installed package '{PACKAGE_NAME}' did not contain expected path '{SERVER_PATH}'",
 41                        ))?;
 42                    }
 43                }
 44                Err(error) => {
 45                    if !self.server_exists() {
 46                        Err(error)?;
 47                    }
 48                }
 49            }
 50        }
 51
 52        self.did_find_server = true;
 53        Ok(SERVER_PATH.to_string())
 54    }
 55}
 56
 57impl zed::Extension for AstroExtension {
 58    fn new() -> Self {
 59        Self {
 60            did_find_server: false,
 61        }
 62    }
 63
 64    fn language_server_command(
 65        &mut self,
 66        config: zed::LanguageServerConfig,
 67        _worktree: &zed::Worktree,
 68    ) -> Result<zed::Command> {
 69        let server_path = self.server_script_path(config)?;
 70        Ok(zed::Command {
 71            command: zed::node_binary_path()?,
 72            args: vec![
 73                env::current_dir()
 74                    .unwrap()
 75                    .join(&server_path)
 76                    .to_string_lossy()
 77                    .to_string(),
 78                "--stdio".to_string(),
 79            ],
 80            env: Default::default(),
 81        })
 82    }
 83
 84    fn language_server_initialization_options(
 85        &mut self,
 86        _config: zed::LanguageServerConfig,
 87        _worktree: &zed::Worktree,
 88    ) -> Result<Option<String>> {
 89        let initialization_options = r#"{
 90            "provideFormatter": true,
 91            "typescript": {
 92                "tsdk": "node_modules/typescript/lib"
 93            }
 94        }"#;
 95
 96        Ok(Some(initialization_options.to_string()))
 97    }
 98}
 99
100zed::register_extension!(AstroExtension);