diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0bf788747..6f0937287 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,6 +35,14 @@ jobs: - run: npm test -- --forbid-only - #- run: npm run lint + # - run: npm run lint - run: npm run release + + # - name: Release Zed Extension + # uses: huacnlee/zed-extension-action@v1 + # with: + # extension-name: spyglass + # push-to: SpyglassMC/zed-extensions + # env: + # COMMITTER_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/packages/zed-extension/.gitignore b/packages/zed-extension/.gitignore new file mode 100644 index 000000000..59b5e339e --- /dev/null +++ b/packages/zed-extension/.gitignore @@ -0,0 +1,4 @@ +/target +grammars/ +*.wasm +.DS_Store diff --git a/packages/zed-extension/Cargo.lock b/packages/zed-extension/Cargo.lock new file mode 100644 index 000000000..1303ab10e --- /dev/null +++ b/packages/zed-extension/Cargo.lock @@ -0,0 +1,322 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "proc-macro2" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.202" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.202" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "spdx" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ef1a0fa1e39ac22972c8db23ff89aea700ab96aa87114e1fb55937a631a0c9" +dependencies = [ + "smallvec", +] + +[[package]] +name = "syn" +version = "2.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "wasm-encoder" +version = "0.201.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9c7d2731df60006819b013f64ccc2019691deccf6e11a1804bc850cd6748f1a" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-metadata" +version = "0.201.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fd83062c17b9f4985d438603cde0a5e8c5c8198201a6937f778b607924c7da2" +dependencies = [ + "anyhow", + "indexmap", + "serde", + "serde_derive", + "serde_json", + "spdx", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.201.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708" +dependencies = [ + "bitflags", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "288f992ea30e6b5c531b52cdd5f3be81c148554b09ea416f058d16556ba92c27" +dependencies = [ + "bitflags", + "wit-bindgen-rt", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e85e72719ffbccf279359ad071497e47eb0675fe22106dea4ed2d8a7fcb60ba4" +dependencies = [ + "anyhow", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb8738270f32a2d6739973cbbb7c1b6dd8959ce515578a6e19165853272ee64" + +[[package]] +name = "wit-bindgen-rust" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a39a15d1ae2077688213611209849cad40e9e5cccf6e61951a425850677ff3" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d376d3ae5850526dfd00d937faea0d81a06fa18f7ac1e26f386d760f241a8f4b" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.201.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.201.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zed-spyglass" +version = "0.4.0" +dependencies = [ + "zed_extension_api", +] + +[[package]] +name = "zed_extension_api" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca8bcaea3feb2d2ce9dbeb061ee48365312a351faa7014c417b0365fe9e459" +dependencies = [ + "serde", + "serde_json", + "wit-bindgen", +] diff --git a/packages/zed-extension/Cargo.toml b/packages/zed-extension/Cargo.toml new file mode 100644 index 000000000..1cc4a8aac --- /dev/null +++ b/packages/zed-extension/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "zed-spyglass" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +zed_extension_api = "^0.0.6" diff --git a/packages/zed-extension/DEVELOPMENT b/packages/zed-extension/DEVELOPMENT new file mode 100644 index 000000000..382398573 --- /dev/null +++ b/packages/zed-extension/DEVELOPMENT @@ -0,0 +1,12 @@ +# Development Guide + +You must install [zed-extension](https://github.com/zed-industries/zed/tree/main/crates/extension_cli) cli first. + +hello你好 + +## Release + +1. Update the version in `Cargo.toml` and `extension.toml`. +2. Commit with message `Release vX.Y.Z`. +3. Create git tag `vX.Y.Z` and push. +4. Then GitHub Actions will automatically create a release PR to [zed-extensions](https://github.com/zed-industries/extensions/pulls). diff --git a/packages/zed-extension/Makefile b/packages/zed-extension/Makefile new file mode 100644 index 000000000..547450d76 --- /dev/null +++ b/packages/zed-extension/Makefile @@ -0,0 +1,6 @@ +build: + mkdir -p target/spyglass + zed-extension --source-dir ./ --output-dir ./target/ --scratch-dir ./target + tar -xzf target/archive.tar.gz -C target/spyglass + cp -Rf target/spyglass ~/Library/Application\ Support/Zed/extensions/installed/ + tree ~/Library/Application\ Support/Zed/extensions/installed/spyglass diff --git a/packages/zed-extension/README.md b/packages/zed-extension/README.md new file mode 100644 index 000000000..21642aa11 --- /dev/null +++ b/packages/zed-extension/README.md @@ -0,0 +1,17 @@ +# Spyglass for Zed + +[Spyglass](https://github.com/SpyglassMC/Spyglass) provides heavy language features for Minecraft: Java Edition data pack files. + +Based on the Spyglass LSP, you can use it to check and fix your text in real-time. + +## Features + +- Automatically downloads Spyglass. + +## Installation + +Open `Extensions` on your Zed, search for `Spyglass` and click on `Install`. + +## License + +Credit to Jason Lee's [zed-autocorrect](https://github.com/huacnlee/zed-autocorrect) extension providing an example implementation. diff --git a/packages/zed-extension/extension.toml b/packages/zed-extension/extension.toml new file mode 100644 index 000000000..a0cf62f53 --- /dev/null +++ b/packages/zed-extension/extension.toml @@ -0,0 +1,16 @@ +id = "spyglass" +name = "Datapack Helper Plus by Spyglass" +description = "Heavy language features for Minecraft: Java Edition data pack files." +version = "0.4.0" +schema_version = 1 +authors = ["Spyglass Maintainers"] +repository = "https://github.com/SpyglassMC/zed-spyglass" + +[language_servers.spyglass] +name = "Spyglass LSP" +languages = [ + "JSON", + "MCFunction", + "SNBT", +] +code_action_kinds = ["quickfix"] diff --git a/packages/zed-extension/src/lib.rs b/packages/zed-extension/src/lib.rs new file mode 100644 index 000000000..73b655d70 --- /dev/null +++ b/packages/zed-extension/src/lib.rs @@ -0,0 +1,144 @@ +use std::fs; +use zed_extension_api::{self as zed, Result}; + +static GITHUB_REPO: &'static str = "SpyglassMC/Spyglass"; +static BIN_NAME: &'static str = "spyglass"; + +struct SpyglassExtension { + cached_binary_path: Option, +} + +enum Status { + None, + CheckingForUpdate, + Downloading, + Failed(String), +} + +fn update_status(id: &zed::LanguageServerId, status: Status) { + match status { + Status::None => zed::set_language_server_installation_status( + id, + &zed::LanguageServerInstallationStatus::None, + ), + Status::CheckingForUpdate => zed::set_language_server_installation_status( + id, + &zed::LanguageServerInstallationStatus::CheckingForUpdate, + ), + Status::Downloading => zed::set_language_server_installation_status( + id, + &zed::LanguageServerInstallationStatus::Downloading, + ), + Status::Failed(msg) => zed::set_language_server_installation_status( + id, + &zed::LanguageServerInstallationStatus::Failed(msg), + ), + } +} + +impl SpyglassExtension { + fn language_server_binary_path( + &mut self, + id: &zed::LanguageServerId, + worktree: &zed::Worktree, + ) -> Result { + // Check if the binary is already installed by manually checking the path + if let Some(path) = worktree.which(BIN_NAME) { + return Ok(path); + } + + if let Some(path) = &self.cached_binary_path { + if fs::metadata(path).map_or(false, |stat| stat.is_file()) { + update_status(id, Status::None); + return Ok(path.clone()); + } + } + let (platform) = zed::current_platform(); + + if fs::metadata(BIN_NAME).map_or(false, |stat| stat.is_file()) { + update_status(id, Status::None); + return Ok(BIN_NAME.to_string()); + } + + update_status(id, Status::CheckingForUpdate); + + let release = zed::latest_github_release( + GITHUB_REPO, + zed::GithubReleaseOptions { + require_assets: true, + pre_release: true, + }, + )?; + + let asset_name = format!( + "spyglass-zed.{ext}", + ext = match platform { + zed::Os::Windows => "zip", + _ => "tar.gz", + } + ); + + let file_type = match platform { + zed::Os::Windows => zed::DownloadedFileType::Zip, + _ => zed::DownloadedFileType::GzipTar, + }; + + let asset = release + .assets + .iter() + .find(|asset| asset.name == asset_name) + .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?; + + let version_dir = format!("spyglass-{}", release.version); + let binary_path = format!("{version_dir}/{BIN_NAME}"); + + if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) { + update_status(id, Status::Downloading); + zed::download_file(&asset.download_url, &version_dir, file_type) + .map_err(|e| format!("failed to download file: {e}"))?; + + let entries = + fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?; + for entry in entries { + let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?; + if entry.file_name().to_str() != Some(&version_dir) { + fs::remove_dir_all(&entry.path()).ok(); + } + } + + update_status(id, Status::None); + } + + self.cached_binary_path = Some(binary_path.clone()); + Ok(binary_path) + } +} + +impl zed::Extension for SpyglassExtension { + fn new() -> Self { + Self { + cached_binary_path: None, + } + } + + fn language_server_command( + &mut self, + id: &zed::LanguageServerId, + worktree: &zed::Worktree, + ) -> Result { + let command = self + .language_server_binary_path(id, worktree) + .map_err(|err| { + update_status(id, Status::Failed(err.to_string())); + err + })?; + + Ok(zed::Command { + command, + args: vec!["server".to_string()], + env: Default::default(), + }) + } +} + +zed::register_extension!(SpyglassExtension); diff --git a/packages/zed-extension/test/.exists b/packages/zed-extension/test/.exists new file mode 100644 index 000000000..e69de29bb