diff --git a/.gitignore b/.gitignore index ad67955..54a3c03 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ debug target +# Zed compiles the extension crate to wasm +*.wasm + # These are backup files generated by rustfmt **/*.rs.bk diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000..9fc3aae --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,29 @@ +{ + // Enable semantic tokens for languages where UnoCSS utilities appear. These + // per-language `semantic_tokens` settings work fine in this project file. + // The underline STYLING rule is separate: it uses `global_lsp_settings`, which + // Zed reads only from USER settings (~/.config/zed/settings.json) — that key is + // not available in a project-local .zed/settings.json. Add it there: + // { "global_lsp_settings": { "semantic_token_rules": [{ "token_type": "unocss", "underline": "#888888" }] } } + "languages": { + "TypeScript": { "semantic_tokens": "combined" }, + "HTML": { "semantic_tokens": "combined" }, + "CSS": { "semantic_tokens": "combined" } + }, + + "lsp": { + "unocss": { + // The server emits `unocss` tokens by default, so no `settings` are needed + // here. To turn them off: "settings": { "semanticTokens": false } + + // Optional: point at a locally-built server. Omit to use the auto-installed npm version. + // "binary": { + // "path": "node", + // "arguments": [ + // "/abs/path/to/packages-integrations/language-server/bin/unocss-language-server.js", + // "--stdio", + // ], + // }, + } + } +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..72eb70c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,818 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "auditable-serde" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7bf8143dfc3c0258df908843e169b5cc5fcf76c7718bd66135ef4a9cd558c5" +dependencies = [ + "semver", + "serde", + "serde_json", + "topological-sort", +] + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "log" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "spdx" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e17e880bafaeb362a7b751ec46bdc5b61445a188f80e0606e68167cd540fa3" +dependencies = [ + "smallvec", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "topological-sort" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "wasm-encoder" +version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80bb72f02e7fbf07183443b27b0f3d4144abf8c114189f2e088ed95b696a7822" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1ef0faabbbba6674e97a56bee857ccddf942785a336c8b47b42373c922a91d" +dependencies = [ + "anyhow", + "auditable-serde", + "flate2", + "indexmap", + "serde", + "serde_derive", + "serde_json", + "spdx", + "url", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f51cad774fb3c9461ab9bccc9c62dfb7388397b5deda31bf40e8108ccd678b2" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10fb6648689b3929d56bbc7eb1acf70c9a42a29eb5358c67c10f54dbd5d695de" +dependencies = [ + "wit-bindgen-rt", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92fa781d4f2ff6d3f27f3cc9b74a73327b31ca0dc4a3ef25a0ce2983e0e5af9b" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db52a11d4dfb0a59f194c064055794ee6564eb1ced88c25da2cf76e50c5621" +dependencies = [ + "bitflags", + "futures", + "once_cell", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d0809dc5ba19e2e98661bf32fc0addc5a3ca5bf3a6a7083aa6ba484085ff3ce" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad19eec017904e04c60719592a803ee5da76cb51c81e3f6fbf9457f59db49799" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "635c3adc595422cbf2341a17fb73a319669cc8d33deed3a48368a841df86b676" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf445ed5157046e4baf56f9138c124a0824d4d1657e7204d71886ad8ce2fc11" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zed_extension_api" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0729d50b4ca0a7e28e590bbe32e3ca0194d97ef654961451a424c661a366fca0" +dependencies = [ + "serde", + "serde_json", + "wit-bindgen", +] + +[[package]] +name = "zed_unocss" +version = "0.1.0" +dependencies = [ + "serde_json", + "zed_extension_api", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9cee740 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "zed_unocss" +version = "0.1.0" +edition = "2021" +publish = false +license = "MIT" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +zed_extension_api = "0.7.0" +serde_json = "1.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..ceca804 --- /dev/null +++ b/README.md @@ -0,0 +1,136 @@ +# UnoCSS for Zed + +A [Zed](https://zed.dev) extension that runs the official +[`@unocss/language-server`](https://www.npmjs.com/package/@unocss/language-server) +for completion, hover, color previews and matched-utility underlining. + +Highlights: + +- attaches to **`TypeScript`** and **`CSS`** (not just `TSX`), so plain `.ts` + files — e.g. **Lit** components with `class="..."` inside `` html`...` `` + template literals — get completion and hover. +- uses the **official** `@unocss/language-server`. + +## Install (dev extension) + +1. `zed: install dev extension` from the command palette. +2. Point it at this repository's root directory. + +Zed compiles the Rust crate to wasm; the language server itself is fetched from +npm on first use via Zed's bundled Node. + +## Requirement for plain `.ts` / `.js` (Lit, vanilla web components) + +The language server only offers completion in files that pass UnoCSS's content +**pipeline filter**. By default that filter does **not** include `.ts`/`.js` +(only `.vue`, `.svelte`, `.[jt]sx`, `.html`, …). This is a server/config +behaviour — no editor extension can override it. + +To get completion inside Lit `.ts` files, opt them into the pipeline in your +`uno.config` / Vite config: + +```ts +UnoCSS({ + content: { + pipeline: { + include: [ + /\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html)($|\?)/, + /\.[jt]s($|\?)/, // <- scan plain .ts/.js (Lit html`...` templates) + ], + }, + }, + // ...presets +}) +``` + +Alternatively add `// @unocss-include` to the top of individual files. + +## Settings + +Configure the server under `lsp.unocss.settings` in Zed `settings.json`. Keys +are forwarded under the `unocss` namespace the server expects: + +```json +{ + "lsp": { + "unocss": { + "settings": { + "root": "./", + "remToPxRatio": 16, + "autocomplete": { "matchType": "fuzzy" } + } + } + } +} +``` + +## Languages + +`HTML`, `CSS`, `JavaScript`, `TypeScript`, `TSX`, `Vue.js`, `Svelte`, `Astro`, +`Markdown`, `PHP`, `ERB`. (Framework languages such as `Vue.js`/`Svelte`/`Astro` +only exist when their own Zed extensions are installed.) + +## Underlining matched utilities (semantic tokens) + +The server emits every matched utility as a `unocss` semantic token, so editors +can style them — an editor-agnostic take on the VSCode `unocss.underline` +decoration. The server emits the `unocss` token **by default**; you opt in on +the Zed side with two pieces of config: + +1. Enable semantic tokens for the relevant languages. This works in your user + settings or a per-project `.zed/settings.json` (an example is shipped in this + directory): + ```json + { + "languages": { + "HTML": { "semantic_tokens": "combined" }, + "TypeScript": { "semantic_tokens": "combined" } + } + } + ``` + You can turn the server tokens off with: + ```json + { + "lsp": { + "unocss": { + "settings": { "semanticTokens": false } + } + } + } + ``` +2. Add the styling rule. **This MUST go in your USER settings** + (`~/.config/zed/settings.json`) — `global_lsp_settings` is a global setting + Zed reads only from your user settings, so unlike the per-language + `semantic_tokens` above it is not picked up from a project-local + `.zed/settings.json`: + ```json + { + "global_lsp_settings": { + "semantic_token_rules": [ + { "token_type": "unocss", "underline": true } + ] + } + } + ``` + +`underline` accepts `true` (text color) or a hex string. For the other fields +(`foreground_color`, `background_color`, `font_weight`, `font_style`, +`strikethrough`, `style`) see Zed's +[semantic token rules](https://zed.dev/docs/semantic-tokens) docs. + +### Using theme styles + +Instead of a fixed underline you can borrow a color from the active theme with +the `style` field — a list of theme style names, first match wins. `unocss` is a +custom token type with no dedicated theme entry, so point it at an existing +style; the look then adapts as you switch themes: + +```json +{ + "global_lsp_settings": { + "semantic_token_rules": [ + { "token_type": "unocss", "style": ["link_text", "function"] } + ] + } +} +``` diff --git a/extension.toml b/extension.toml new file mode 100644 index 0000000..df4fc32 --- /dev/null +++ b/extension.toml @@ -0,0 +1,48 @@ +id = "unocss" +name = "UnoCSS" +description = "UnoCSS language server support for Zed (completion, hover, colors, utility underline)" +version = "0.1.0" +schema_version = 1 +authors = [ "oddcelot " ] +repository = "https://github.com/unocss/zed" + +[language_servers.unocss] +name = "UnoCSS Language Server" +# Includes plain TypeScript and CSS so Lit components and stylesheets are covered. +# Elm, Marko, and Pug aren't built into Zed — they only match when the user has +# the corresponding community language extension installed; until then Zed simply +# never associates those files, so listing them here is harmless. The server's +# extractors find UnoCSS classes in those files regardless of languageId. +languages = [ + "HTML", + "CSS", + "JavaScript", + "TypeScript", + "TSX", + "Vue.js", + "Svelte", + "Astro", + "Markdown", + "PHP", + "ERB", + "Elm", + "Marko", + "Pug", +] + +# Map Zed language names to the server's expected LSP languageId strings. +[language_servers.unocss.language_ids] +HTML = "html" +CSS = "css" +JavaScript = "javascript" +TypeScript = "typescript" +TSX = "typescriptreact" +"Vue.js" = "vue" +Svelte = "svelte" +Astro = "astro" +Markdown = "markdown" +PHP = "php" +ERB = "erb" +Elm = "elm" +Marko = "marko" +Pug = "pug" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0606e04 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,133 @@ +use zed_extension_api::settings::LspSettings; +use zed_extension_api::{self as zed, LanguageServerId, Result}; + +const SERVER_NAME: &str = "unocss"; +const PACKAGE_NAME: &str = "@unocss/language-server"; +const SERVER_PATH: &str = "node_modules/@unocss/language-server/bin/unocss-language-server.js"; + +struct UnocssExtension { + did_find_server: bool, +} + +impl UnocssExtension { + fn server_exists(&self) -> bool { + std::fs::metadata(SERVER_PATH).is_ok_and(|stat| stat.is_file()) + } + + fn server_script_path(&mut self, id: &LanguageServerId) -> Result { + let server_exists = self.server_exists(); + if self.did_find_server && server_exists { + return Ok(SERVER_PATH.to_string()); + } + + zed::set_language_server_installation_status( + id, + &zed::LanguageServerInstallationStatus::CheckingForUpdate, + ); + let version = zed::npm_package_latest_version(PACKAGE_NAME)?; + + if !server_exists + || zed::npm_package_installed_version(PACKAGE_NAME)?.as_deref() + != Some(version.as_str()) + { + zed::set_language_server_installation_status( + id, + &zed::LanguageServerInstallationStatus::Downloading, + ); + let result = zed::npm_install_package(PACKAGE_NAME, &version); + match result { + Ok(()) => { + if !self.server_exists() { + return Err(format!( + "installed package '{PACKAGE_NAME}' did not contain expected path '{SERVER_PATH}'" + )); + } + } + Err(error) => { + // Update failed (e.g. offline) — fall back to any existing server. + if !self.server_exists() { + return Err(error); + } + } + } + } + + self.did_find_server = true; + Ok(SERVER_PATH.to_string()) + } +} + +impl zed::Extension for UnocssExtension { + fn new() -> Self { + Self { + did_find_server: false, + } + } + + fn language_server_command( + &mut self, + id: &LanguageServerId, + worktree: &zed::Worktree, + ) -> Result { + // Honor an explicit `binary` override from settings (e.g. a local build). + if let Some(binary) = LspSettings::for_worktree(SERVER_NAME, worktree) + .ok() + .and_then(|lsp| lsp.binary) + { + if let Some(path) = binary.path { + return Ok(zed::Command { + command: path, + args: binary.arguments.unwrap_or_default(), + env: worktree.shell_env(), + }); + } + } + + let server_path = self.server_script_path(id)?; + // The path is relative to the extension dir, but Zed runs the command + // with the worktree as cwd — resolve it to an absolute path. + let server_path = std::env::current_dir() + .map_err(|e| format!("failed to read extension working directory: {e}"))? + .join(&server_path) + .to_string_lossy() + .to_string(); + Ok(zed::Command { + command: zed::node_binary_path()?, + args: vec![server_path, "--stdio".to_string()], + env: worktree.shell_env(), + }) + } + + fn language_server_workspace_configuration( + &mut self, + _id: &LanguageServerId, + worktree: &zed::Worktree, + ) -> Result> { + let settings = LspSettings::for_worktree(SERVER_NAME, worktree) + .ok() + .and_then(|lsp| lsp.settings) + .unwrap_or_else(|| serde_json::json!({})); + + // Wrap settings under the `unocss` namespace the server expects, unless + // the user already nested them there. + let configuration = if settings.get("unocss").is_some() { + settings + } else { + serde_json::json!({ "unocss": settings }) + }; + + Ok(Some(configuration)) + } + + fn language_server_initialization_options( + &mut self, + _id: &LanguageServerId, + worktree: &zed::Worktree, + ) -> Result> { + Ok(LspSettings::for_worktree(SERVER_NAME, worktree) + .ok() + .and_then(|lsp| lsp.initialization_options)) + } +} + +zed::register_extension!(UnocssExtension);