|
| 1 | +use std::collections::HashMap; |
| 2 | +use std::env; |
| 3 | + |
| 4 | +use serde::Deserialize; |
| 5 | +use zed_extension_api::{self as zed, Result, serde_json}; |
| 6 | + |
| 7 | +const TS_PLUGIN_PACKAGE_NAME: &str = "@css-modules-kit/ts-plugin"; |
| 8 | + |
| 9 | +#[derive(Debug, Deserialize)] |
| 10 | +#[serde(rename_all = "camelCase")] |
| 11 | +struct PackageJson { |
| 12 | + #[serde(default)] |
| 13 | + dependencies: HashMap<String, String>, |
| 14 | + #[serde(default)] |
| 15 | + dev_dependencies: HashMap<String, String>, |
| 16 | +} |
| 17 | + |
| 18 | +struct CSSModulesKitExtension {} |
| 19 | + |
| 20 | +impl CSSModulesKitExtension { |
| 21 | + fn install_ts_plugin_if_needed(&self) -> Result<()> { |
| 22 | + let installed_plugin_version = zed::npm_package_installed_version(TS_PLUGIN_PACKAGE_NAME)?; |
| 23 | + let latest_plugin_version = zed::npm_package_latest_version(TS_PLUGIN_PACKAGE_NAME)?; |
| 24 | + |
| 25 | + if installed_plugin_version.as_ref() != Some(&latest_plugin_version) { |
| 26 | + println!("installing {TS_PLUGIN_PACKAGE_NAME}@{latest_plugin_version}"); |
| 27 | + zed::npm_install_package(TS_PLUGIN_PACKAGE_NAME, &latest_plugin_version)?; |
| 28 | + } else { |
| 29 | + println!("ts-plugin already installed"); |
| 30 | + } |
| 31 | + Ok(()) |
| 32 | + } |
| 33 | + |
| 34 | + fn get_ts_plugin_root_path(&self, worktree: &zed::Worktree) -> Result<Option<String>> { |
| 35 | + let package_json = worktree.read_text_file("package.json")?; |
| 36 | + let package_json: PackageJson = serde_json::from_str(&package_json) |
| 37 | + .map_err(|err| format!("failed to parse package.json: {err}"))?; |
| 38 | + |
| 39 | + let has_local_plugin = package_json |
| 40 | + .dev_dependencies |
| 41 | + .contains_key(TS_PLUGIN_PACKAGE_NAME) |
| 42 | + || package_json |
| 43 | + .dependencies |
| 44 | + .contains_key(TS_PLUGIN_PACKAGE_NAME); |
| 45 | + |
| 46 | + if has_local_plugin { |
| 47 | + println!("Using local installation of {TS_PLUGIN_PACKAGE_NAME}"); |
| 48 | + return Ok(None); |
| 49 | + } |
| 50 | + |
| 51 | + self.install_ts_plugin_if_needed()?; |
| 52 | + |
| 53 | + println!("Using global installation of {TS_PLUGIN_PACKAGE_NAME}"); |
| 54 | + Ok(Some( |
| 55 | + env::current_dir().unwrap().to_string_lossy().to_string(), |
| 56 | + )) |
| 57 | + } |
| 58 | +} |
| 59 | + |
| 60 | +impl zed::Extension for CSSModulesKitExtension { |
| 61 | + fn new() -> Self { |
| 62 | + Self {} |
| 63 | + } |
| 64 | + |
| 65 | + fn language_server_command( |
| 66 | + &mut self, |
| 67 | + _language_server_id: &zed::LanguageServerId, |
| 68 | + _worktree: &zed::Worktree, |
| 69 | + ) -> Result<zed::Command> { |
| 70 | + // A language server is not normally required. However, |
| 71 | + // Zed does not seem to load ts-plugin unless a language server is configured. |
| 72 | + // ref: https://github.com/zed-industries/zed/issues/22410#issuecomment-2815582825 |
| 73 | + Ok(zed::Command { |
| 74 | + command: zed::node_binary_path()?, |
| 75 | + args: vec!["-e".into(), r#"import('data:text/javascript;base64,Y29uc3QgeyBzdGRpbiwgc3Rkb3V0IH0gPSBwcm9jZXNzOwoKc3RkaW4uc2V0RW5jb2RpbmcoInV0ZjgiKTsKCmZ1bmN0aW9uIHNlbmQobXNnKSB7CiAgY29uc3QganNvbiA9IEpTT04uc3RyaW5naWZ5KG1zZyk7CiAgY29uc3QgbGVuID0gQnVmZmVyLmJ5dGVMZW5ndGgoanNvbiwgInV0ZjgiKTsKICBzdGRvdXQud3JpdGUoYENvbnRlbnQtTGVuZ3RoOiAke2xlbn1cclxuXHJcbmApOwogIHN0ZG91dC53cml0ZShqc29uKTsKfQoKbGV0IGJ1ZmZlciA9ICIiOwpsZXQgY29udGVudExlbmd0aCA9IDA7CgpzdGRpbi5vbigiZGF0YSIsIChjaHVuaykgPT4gewogIGJ1ZmZlciArPSBjaHVuazsKCiAgd2hpbGUgKHRydWUpIHsKICAgIGlmIChjb250ZW50TGVuZ3RoID09PSAwKSB7CiAgICAgIGNvbnN0IGhlYWRlckVuZCA9IGJ1ZmZlci5pbmRleE9mKCJcclxuXHJcbiIpOwogICAgICBpZiAoaGVhZGVyRW5kID09PSAtMSkgcmV0dXJuOwoKICAgICAgY29uc3QgaGVhZGVyID0gYnVmZmVyLnNsaWNlKDAsIGhlYWRlckVuZCk7CiAgICAgIGNvbnN0IG1hdGNoID0gaGVhZGVyLm1hdGNoKC9Db250ZW50LUxlbmd0aDogKFxkKykvaSk7CiAgICAgIGlmICghbWF0Y2gpIHJldHVybjsgLy8gSWdub3JlIG1hbGZvcm1lZCBoZWFkZXJzCgogICAgICBjb250ZW50TGVuZ3RoID0gcGFyc2VJbnQobWF0Y2hbMV0sIDEwKTsKICAgICAgYnVmZmVyID0gYnVmZmVyLnNsaWNlKGhlYWRlckVuZCArIDQpOwogICAgfQoKICAgIC8vIFdhaXQgZm9yIHRoZSBmdWxsIG1lc3NhZ2UgYm9keQogICAgaWYgKGJ1ZmZlci5sZW5ndGggPCBjb250ZW50TGVuZ3RoKSByZXR1cm47CgogICAgY29uc3QgbWVzc2FnZUpzb24gPSBidWZmZXIuc2xpY2UoMCwgY29udGVudExlbmd0aCk7CiAgICBidWZmZXIgPSBidWZmZXIuc2xpY2UoY29udGVudExlbmd0aCk7CiAgICBjb250ZW50TGVuZ3RoID0gMDsgLy8gUmVzZXQgZm9yIHRoZSBuZXh0IG1lc3NhZ2UKCiAgICB0cnkgewogICAgICBjb25zdCByZXEgPSBKU09OLnBhcnNlKG1lc3NhZ2VKc29uKTsKCiAgICAgIGlmIChyZXEubWV0aG9kID09PSAiaW5pdGlhbGl6ZSIpIHsKICAgICAgICBzZW5kKHsKICAgICAgICAgIGpzb25ycGM6ICIyLjAiLAogICAgICAgICAgaWQ6IHJlcS5pZCwKICAgICAgICAgIHJlc3VsdDogewogICAgICAgICAgICBjYXBhYmlsaXRpZXM6IHt9LCAvLyBNaW5pbWFsIGNhcGFiaWxpdGllcwogICAgICAgICAgfSwKICAgICAgICB9KTsKICAgICAgfSBlbHNlIGlmIChyZXEubWV0aG9kID09PSAic2h1dGRvd24iKSB7CiAgICAgICAgLy8gUmVzcG9uZCB0byBzaHV0ZG93biBiZWZvcmUgZXhpdGluZwogICAgICAgIHNlbmQoewogICAgICAgICAganNvbnJwYzogIjIuMCIsCiAgICAgICAgICBpZDogcmVxLmlkLAogICAgICAgICAgcmVzdWx0OiBudWxsLAogICAgICAgIH0pOwogICAgICAgIC8vIFRoZSBjbGllbnQgc2hvdWxkIHNlbmQgJ2V4aXQnIGFmdGVyIHRoaXMsIGJ1dCB3ZSBjYW4gZXhpdCBoZXJlIGlmIG5lZWRlZCwKICAgICAgICAvLyB0aG91Z2ggdGVjaG5pY2FsbHkgTFNQIHNwZWMgc3VnZ2VzdHMgd2FpdGluZyBmb3IgJ2V4aXQnCiAgICAgICAgLy8gcHJvY2Vzcy5leGl0KDApOwogICAgICB9IGVsc2UgaWYgKHJlcS5tZXRob2QgPT09ICJleGl0IikgewogICAgICAgIHByb2Nlc3MuZXhpdCgwKTsgLy8gRXhpdCBjbGVhbmx5CiAgICAgIH0gZWxzZSBpZiAocmVxLmlkICE9PSB1bmRlZmluZWQpIHsKICAgICAgICAvLyBSZXNwb25kIHdpdGggYW4gZXJyb3IgZm9yIHVuc3VwcG9ydGVkIHJlcXVlc3RzIHRoYXQgaGF2ZSBhbiBJRAogICAgICAgIHNlbmQoewogICAgICAgICAganNvbnJwYzogIjIuMCIsCiAgICAgICAgICBpZDogcmVxLmlkLAogICAgICAgICAgZXJyb3I6IHsKICAgICAgICAgICAgY29kZTogLTMyNjAxLCAvLyBNZXRob2ROb3RGb3VuZAogICAgICAgICAgICBtZXNzYWdlOiBgTWV0aG9kIG5vdCBmb3VuZDogJHtyZXEubWV0aG9kfWAsCiAgICAgICAgICB9LAogICAgICAgIH0pOwogICAgICB9CiAgICAgIC8vIElnbm9yZSBub3RpZmljYXRpb25zIChyZXF1ZXN0cyB3aXRob3V0IGFuIElEKSBvdGhlciB0aGFuICdleGl0JwogICAgfSBjYXRjaCAoZXJyb3IpIHsKICAgICAgLy8gSGFuZGxlIEpTT04gcGFyc2luZyBlcnJvcnMgb3Igb3RoZXIgaXNzdWVzCiAgICAgIGNvbnNvbGUuZXJyb3IoIkVycm9yIHByb2Nlc3NpbmcgbWVzc2FnZToiLCBlcnJvcik7CiAgICAgIC8vIE9wdGlvbmFsbHkgc2VuZCBhIGdlbmVyaWMgZXJyb3IgcmVzcG9uc2UgaWYgcG9zc2libGUKICAgIH0KICB9Cn0pOwoKc3RkaW4ub24oImVuZCIsICgpID0+IHsKICAvLyBIYW5kbGUgc3RyZWFtIGVuZCBpZiBuZWNlc3NhcnkKICBwcm9jZXNzLmV4aXQoMCk7Cn0pOwoKLy8gSGFuZGxlIHBvdGVudGlhbCBlcnJvcnMgb24gc3RyZWFtcwpzdGRpbi5vbigiZXJyb3IiLCAoZXJyKSA9PiB7CiAgY29uc29sZS5lcnJvcigiU3RkaW4gRXJyb3I6IiwgZXJyKTsKICBwcm9jZXNzLmV4aXQoMSk7Cn0pOwoKc3Rkb3V0Lm9uKCJlcnJvciIsIChlcnIpID0+IHsKICBjb25zb2xlLmVycm9yKCJTdGRvdXQgRXJyb3I6IiwgZXJyKTsKICBwcm9jZXNzLmV4aXQoMSk7Cn0pOwo=')"#.to_string()], |
| 76 | + env: Default::default(), |
| 77 | + }) |
| 78 | + } |
| 79 | + |
| 80 | + fn language_server_additional_initialization_options( |
| 81 | + &mut self, |
| 82 | + _language_server_id: &zed::LanguageServerId, |
| 83 | + target_language_server_id: &zed::LanguageServerId, |
| 84 | + worktree: &zed::Worktree, |
| 85 | + ) -> Result<Option<serde_json::Value>> { |
| 86 | + match target_language_server_id.as_ref() { |
| 87 | + "typescript-language-server" => Ok(Some(serde_json::json!({ |
| 88 | + "plugins": [{ |
| 89 | + "name": TS_PLUGIN_PACKAGE_NAME, |
| 90 | + "location": self.get_ts_plugin_root_path(worktree)?.unwrap_or_else(|| worktree.root_path()), |
| 91 | + "languages": ["css"] |
| 92 | + }], |
| 93 | + }))), |
| 94 | + _ => Ok(None), |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + fn language_server_additional_workspace_configuration( |
| 99 | + &mut self, |
| 100 | + _language_server_id: &zed::LanguageServerId, |
| 101 | + target_language_server_id: &zed::LanguageServerId, |
| 102 | + worktree: &zed::Worktree, |
| 103 | + ) -> Result<Option<serde_json::Value>> { |
| 104 | + match target_language_server_id.as_ref() { |
| 105 | + "vtsls" => Ok(Some(serde_json::json!({ |
| 106 | + "vtsls": { |
| 107 | + "tsserver": { |
| 108 | + "globalPlugins": [{ |
| 109 | + "name": TS_PLUGIN_PACKAGE_NAME, |
| 110 | + "location": self.get_ts_plugin_root_path(worktree)?.unwrap_or_else(|| worktree.root_path()), |
| 111 | + "enableForWorkspaceTypeScriptVersions": true, |
| 112 | + "languages": ["css"] |
| 113 | + }] |
| 114 | + } |
| 115 | + }, |
| 116 | + }))), |
| 117 | + _ => Ok(None), |
| 118 | + } |
| 119 | + } |
| 120 | +} |
| 121 | + |
| 122 | +zed::register_extension!(CSSModulesKitExtension); |
0 commit comments