Skip to content

Commit 43f6c07

Browse files
authored
add zed extension (#180)
1 parent 16e5703 commit 43f6c07

8 files changed

Lines changed: 214 additions & 3 deletions

File tree

.gitignore

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
### Generated by gibo (https://github.com/simonwhitaker/gibo)
2-
### https://raw.github.com/github/gitignore/ce5da10a3a43c4dd8bd9572eda17c0a37ee0eac1/Node.gitignore
2+
### https://raw.github.com/github/gitignore/274c32303f1f86a42db55e2dab3107e560714010/Node.gitignore
33

44
# Logs
55
logs
@@ -131,5 +131,30 @@ dist
131131
.yarn/build-state.yml
132132
.yarn/install-state.gz
133133
.pnp.*
134+
### Generated by gibo (https://github.com/simonwhitaker/gibo)
135+
### https://raw.github.com/github/gitignore/274c32303f1f86a42db55e2dab3107e560714010/Rust.gitignore
136+
137+
# Generated by Cargo
138+
# will have compiled files and executables
139+
debug/
140+
target/
141+
142+
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
143+
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
144+
Cargo.lock
145+
146+
# These are backup files generated by rustfmt
147+
**/*.rs.bk
148+
149+
# MSVC Windows builds of rustc generate these, which store debugging information
150+
*.pdb
151+
152+
# RustRover
153+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
154+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
155+
# and can be added to the global gitignore or merged into this file. For a more nuclear
156+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
157+
#.idea/
134158

135-
# User
159+
### User
160+
/crates/zed/extension.wasm

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[workspace]
2+
members = ["crates/*"]

crates/zed/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "css-modules-kit-zed"
3+
version = "0.0.1"
4+
edition = "2024"
5+
license = "MIT"
6+
7+
[lib]
8+
crate-type = ["cdylib"]
9+
10+
[dependencies]
11+
serde = { version = "1.0.219", features = ["derive"] }
12+
zed_extension_api = "0.5.0"

crates/zed/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 mizdra
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

crates/zed/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# css-modules-kit-zed
2+
3+
The Zed extension for CSS Modules Kit
4+
5+
## Installation
6+
7+
1. Install "CSS Modules Kit" extension on Zed.
8+
2. Add the following to your `~./config/zed/settings.json` file:
9+
```json
10+
{
11+
"languages": {
12+
"CSS": {
13+
"language_servers": ["vtsls", "..."]
14+
}
15+
}
16+
}
17+
```
18+
3. Restart Zed.

crates/zed/extension.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
id = "css-modules-kit"
2+
name = "CSS Modules Kit"
3+
version = "0.0.1"
4+
schema_version = 1
5+
authors = ["mizdra <pp.mizdra@gmail.com>"]
6+
description = "The Zed extension for CSS Modules Kit"
7+
repository = "https://github.com/mizdra/css-modules-kit"
8+
9+
[language_servers.css-modules-kit-language-server]
10+
name = "CSS Modules Kit Language Server"
11+
languages = ["TypeScript", "TSX", "JavaScript", "CSS"]

crates/zed/src/lib.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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);

docs/get-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ To enable CSS Modules language features in your editor, you need to install [`@c
1111
- For Emacs:
1212
- Not yet supported
1313
- For Zed:
14-
- Not yet supported
14+
- See [crates/zed/README.md](../crates/zed/README.md)
1515
- For WebStorm:
1616
- Not yet supported
1717
- For StackBlitz Codeflow:

0 commit comments

Comments
 (0)