From 0cec01b6487cfc8d61d995937f52235c0ad0c7bc Mon Sep 17 00:00:00 2001 From: "Luke Boswell (Linux-Desktop)" Date: Fri, 26 Jun 2026 16:55:18 +1000 Subject: [PATCH 1/2] Update extension for latest Roc LSP --- .github/workflows/rust.yml | 10 +- Cargo.toml | 4 +- README.md | 41 +++++--- extension.toml | 2 +- justfile | 17 ++- languages/roc/outline.scm | 15 +++ src/lib.rs | 2 +- src/roc.rs | 206 ------------------------------------- 8 files changed, 69 insertions(+), 228 deletions(-) delete mode 100644 src/roc.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9fd45e0..f396beb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -16,7 +16,13 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Build - run: cargo build --verbose + - name: Install Zed extension target + run: rustup target add wasm32-wasip2 + - name: Check formatting + run: cargo fmt -- --check + - name: Lint + run: cargo clippy --all-targets -- -D warnings + - name: Build extension + run: cargo build --target wasm32-wasip2 --verbose - name: Run tests run: cargo test --verbose diff --git a/Cargo.toml b/Cargo.toml index 1e5ba68..ec8ca3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "zed_roc" -version = "0.1.1" +version = "0.1.2" authors = ["Alf Richter "] edition = "2021" [dependencies] -zed_extension_api = "0.0.6" +zed_extension_api = "0.7.0" [lib] crate-type = ["cdylib"] diff --git a/README.md b/README.md index e51a69f..b386f80 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ An extension for Zed that adds Roc language support: - Install Roc from the [roc-lang](https://roc-lang.org) website - Ensure the `roc` binary is in your PATH - Install Zed from the [Zed](https://zed.dev) website +- Install the Zed extension WebAssembly target for development: + ```sh + rustup target add wasm32-wasip2 + ``` ## ROC @@ -26,7 +30,7 @@ An extension for Zed that adds Roc language support: 3. Select this repository folder 4. Click "Rebuild" if prompted -**Note:** The version shown in Zed's Extensions panel may display the marketplace version (e.g., v0.0.6) even when the dev extension is active. This is a Zed caching behavior when the extension ID matches a marketplace extension. +**Note:** The version shown in Zed's Extensions panel may display the marketplace version even when the dev extension is active. This is a Zed caching behavior when the extension ID matches a marketplace extension. ### Verifying the Dev Extension is Running @@ -42,22 +46,29 @@ If the path points to your local repository (not `~/Library/Application Support/ - `extension.toml` - Extension manifest (id, version, grammar source, language config) - `languages/roc/config.toml` - Language configuration (file extensions, comments, brackets) - `languages/roc/*.scm` - Tree-sitter queries (highlights, indents, etc.) -- `grammars/roc/` - Git clone of tree-sitter-roc (must match repository in extension.toml) +- `src/lib.rs` - Rust extension entry point that launches `roc experimental-lsp --stdio` + +### Validating Changes + +```sh +cargo fmt -- --check +cargo clippy --all-targets -- -D warnings +cargo test +cargo build --target wasm32-wasip2 +``` ### Updating the Tree-sitter Grammar -The `grammars/roc/` directory must be a git clone of the repository specified in `extension.toml`. To update: +Zed fetches the grammar from the repository and commit in `extension.toml`. The local `grammars/roc/` directory is ignored and only used as a working checkout when syncing query files. + +To update: 1. Update the commit hash in `extension.toml` under `[grammars.roc]` -2. Update the grammar submodule: +2. Sync query files: ```sh - cd grammars/roc - git fetch origin - git checkout + just sync-queries ``` -3. Sync query files: `cp grammars/roc/queries/*.scm languages/roc/` -4. Delete cached wasm to force recompilation: `rm grammars/roc.wasm` -5. In Zed, click "Rebuild" on the extension +3. In Zed, click "Rebuild" on the extension ### Troubleshooting @@ -73,20 +84,22 @@ The `grammars/roc/` directory must be a git clone of the repository specified in - Linux: `~/.local/share/zed/extensions/` **Grammar compilation errors:** -- Ensure `grammars/roc/` is a git clone of the repository URL in `extension.toml` -- The git remote origin must match the repository URL exactly +- Check that `[grammars.roc]` in `extension.toml` points to a valid repository and commit +- Remove any cached `grammars/roc.wasm` or `extension.wasm`, then rebuild the extension ### Useful Commands ```sh # Sync query files from grammar to languages -cp grammars/roc/queries/*.scm languages/roc/ +just sync-queries # Clean build artifacts -rm -f grammars/roc.wasm extension.wasm +just clean # Check Zed logs (macOS) tail -f ~/Library/Logs/Zed/Zed.log | grep -i roc ``` +The `just` recipes are optional conveniences. The validation commands above only require Rust. + ![Zed Example](https://github.com/h2000/zed-roc/assets/187650/1ec0cda3-3679-4223-bc5e-3272babde364) diff --git a/extension.toml b/extension.toml index f4d259a..4cd8c8c 100644 --- a/extension.toml +++ b/extension.toml @@ -1,7 +1,7 @@ id = "roc" name = "Roc" description = "Roc language support for Zed" -version = "0.1.1" +version = "0.1.2" schema_version = 1 authors = ["Alf Richter "] repository = "https://github.com/h2000/zed-roc" diff --git a/justfile b/justfile index 700ef11..5598fc5 100644 --- a/justfile +++ b/justfile @@ -1,11 +1,24 @@ -# Sync query files from tree-sitter-roc to languages/roc +grammar_commit := `sed -n 's/^commit = "\(.*\)"/\1/p' extension.toml` + +check: + cargo fmt -- --check + cargo clippy --all-targets -- -D warnings + cargo test + cargo build --target wasm32-wasip2 + +# Sync query files from the tree-sitter-roc commit in extension.toml. sync-queries: + mkdir -p grammars + if [ ! -d grammars/roc/.git ]; then git clone https://github.com/faldor20/tree-sitter-roc.git grammars/roc; fi + cd grammars/roc && git fetch origin && git checkout {{grammar_commit}} cp -v grammars/roc/queries/*.scm languages/roc/ # Update grammar to a specific commit and sync queries update-grammar COMMIT: + mkdir -p grammars + if [ ! -d grammars/roc/.git ]; then git clone https://github.com/faldor20/tree-sitter-roc.git grammars/roc; fi cd grammars/roc && git fetch origin && git checkout {{COMMIT}} - just sync-queries + cp -v grammars/roc/queries/*.scm languages/roc/ rm -f grammars/roc.wasm @echo "Done. Click 'Rebuild' in Zed to recompile the grammar." diff --git a/languages/roc/outline.scm b/languages/roc/outline.scm index d777f56..336decc 100644 --- a/languages/roc/outline.scm +++ b/languages/roc/outline.scm @@ -1,3 +1,8 @@ +(file + (nominal_type_def + (apply_type + (concrete_type) @name)) @item) + (file (opaque_type_def (apply_type @@ -18,3 +23,13 @@ (decl_left (identifier_pattern (identifier) @name))) @item) + +(file + (var_declaration + name: (identifier) @name) @item) + +(nominal_methods + (value_declaration + (decl_left + (identifier_pattern + (identifier) @name))) @item) diff --git a/src/lib.rs b/src/lib.rs index 14f24ed..7fa9023 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,7 @@ impl zed::Extension for RocExtension { Ok(zed::Command { command: path, - args: vec!["experimental-lsp".to_string()], + args: vec!["experimental-lsp".to_string(), "--stdio".to_string()], env: Default::default(), }) } diff --git a/src/roc.rs b/src/roc.rs deleted file mode 100644 index bdeb13d..0000000 --- a/src/roc.rs +++ /dev/null @@ -1,206 +0,0 @@ -// use anyhow::{anyhow, Context, Result}; -// use async_compression::futures::bufread::GzipDecoder; -// use async_tar::Archive; -// use async_trait::async_trait; -// use futures::{io::BufReader, StreamExt}; -// use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; -// use lsp::LanguageServerBinary; -// use smol::fs; -// use std::env::consts::ARCH; -// use std::{any::Any, path::PathBuf}; -// use util::async_maybe; -// use util::github::latest_github_release; -// use util::{github::GitHubLspBinaryVersion, ResultExt}; - -// pub struct RocLspAdapter; - -// // GithubRelease { name: "Nightly build", pre_release: true, -// // assets: [ -// // GithubReleaseAsset { name: "roc_nightly-linux_arm64-latest.tar.gz", browser_download_url: "https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_arm64-latest.tar.gz" }, -// // GithubReleaseAsset { name: "roc_nightly-linux_x86_64-latest.tar.gz", browser_download_url: "https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_x86_64-latest.tar.gz" }, -// // GithubReleaseAsset { name: "roc_nightly-macos_apple_silicon-latest.tar.gz", browser_download_url: "https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-latest.tar.gz" }, -// // GithubReleaseAsset { name: "roc_nightly-macos_x86_64-latest.tar.gz", browser_download_url: "https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_x86_64-latest.tar.gz" }, -// // GithubReleaseAsset { name: "roc_nightly-old_linux_arm64-latest.tar.gz", browser_download_url: "https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-old_linux_arm64-latest.tar.gz" }, -// // GithubReleaseAsset { name: "roc_nightly-old_linux_x86_64-latest.tar.gz", browser_download_url: "https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-old_linux_x86_64-latest.tar.gz" }, -// // GithubReleaseAsset { name: "roc_repl_wasm.tar.gz", browser_download_url: "https://github.com/roc-lang/roc/releases/download/nightly/roc_repl_wasm.tar.gz" } -// // ], -// // tarball_url: "https://api.github.com/repos/roc-lang/roc/tarball/nightly", -// // zipball_url: "https://api.github.com/repos/roc-lang/roc/zipball/nightly" -// // } -// fn select_archive_name_from_arch() -> &'static str { -// if ARCH == "x86_64" { -// "roc_nightly-macos_x86_64-latest.tar.gz" -// } else if ARCH == "aarch64" { -// "roc_nightly-macos_apple_silicon-latest.tar.gz" -// } else { -// // FIXME add better error handling -// panic!( -// "Unsupported architecture: {}, supported are macos intel 64 and apple silicon", -// ARCH -// ); -// } -// } - -// const SERVER_BINARY_NAME: &'static str = "roc_lang_server"; - -// #[async_trait] -// impl LspAdapter for RocLspAdapter { -// fn name(&self) -> LanguageServerName { -// LanguageServerName("roc_lang_server".into()) -// } - -// fn short_name(&self) -> &'static str { -// "roc_lang_server" -// } - -// /// Fetch latest build url using github API -// async fn fetch_latest_server_version( -// &self, -// delegate: &dyn LspAdapterDelegate, -// ) -> Result> { -// log::error!(">> fetching latest server version for roc"); - -// // calls https://api.github.com/repos/roc-lang/roc/releases -// let release = latest_github_release("roc-lang/roc", true, delegate.http_client()).await?; -// log::error!("found release:\n{:?}", release); -// let archive_name = select_archive_name_from_arch(); -// log::error!("searching for asset in release assets: {}", archive_name); -// let asset = release -// .assets -// .iter() -// .find(|asset| asset.name == archive_name) -// .ok_or_else(|| anyhow!("no asset found matching {:?}", archive_name))?; - -// let binary_url = GitHubLspBinaryVersion { -// name: release.name, -// url: asset.browser_download_url.clone(), -// }; -// log::error!("found asset url: {}", asset.browser_download_url); -// Ok(Box::new(binary_url) as Box<_>) -// } - -// /// Fetches the server binary from the given version (url) if not present in the container_dir, -// /// extracts the binary from the tarball, search for language server binary and -// /// returns the path to the binary -// async fn fetch_server_binary( -// &self, -// version: Box, -// container_dir: PathBuf, -// delegate: &dyn LspAdapterDelegate, -// ) -> Result { -// log::error!( -// ">> fetching server binary (if not present) into: {} ", -// container_dir.display() -// ); -// let version = version.downcast::().unwrap(); - -// if let Some(sever_bin_path) = search_server_binary(&container_dir).await? { -// return Ok(LanguageServerBinary { -// path: sever_bin_path, -// arguments: Vec::new(), -// }); -// }; - -// log::error!("downloading release from {}", version.url); -// let mut response = delegate -// .http_client() -// .get(&version.url, Default::default(), true) -// .await -// .context( -// "error downloading roc nightly release from ".to_string() + version.url.as_str(), -// )?; -// log::error!("downloaded release, decompressing..."); -// let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut())); -// let archive = Archive::new(decompressed_bytes); -// archive.unpack(&container_dir).await?; -// log::error!("unpacked archive"); - -// let server_bin_path = search_server_binary(&container_dir).await; -// return if let Ok(Some(bin_path)) = server_bin_path { -// Ok(LanguageServerBinary { -// path: bin_path, -// arguments: Vec::new(), -// }) -// } else { -// log::error!("no binary found in {}", container_dir.display()); -// Err(anyhow!("no binary found")) -// } -// } - -// async fn cached_server_binary( -// &self, -// container_dir: PathBuf, -// _: &dyn LspAdapterDelegate, -// ) -> Option { -// get_cached_server_binary(container_dir).await -// } - -// async fn installation_test_binary( -// &self, -// container_dir: PathBuf, -// ) -> Option { -// get_cached_server_binary(container_dir).await -// // FIXME how to test if the binary is working? -// // .map(|mut binary| { -// // binary.arguments = vec!["--help".into()]; -// // binary -// // }) -// } -// } - -// async fn get_cached_server_binary(container_dir: PathBuf) -> Option { -// async_maybe!({ -// log::error!( -// ">> getting cached server binary from {}", -// container_dir.display() -// ); - -// if let Some(path) = search_server_binary(&container_dir).await? { -// Ok(LanguageServerBinary { -// path, -// arguments: Vec::new(), -// }) -// } else { -// Err(anyhow!("no cached binary")) -// } -// }) -// .await -// .log_err() -// } - -// /// Searches for the language server binary in the given directory -// /// -// /// e.g. given the default container dir: "~/Library/Application Support/Zed/languages/roc_lang_server", -// /// search for first roc_nightly-* dir, go into it, -// /// and check that a file named "roc_lang_server" exists. -// /// if not, an error Ok(None) is returned. -// async fn search_server_binary(container_dir: &PathBuf) -> Result> { -// log::error!( -// ">> searching for server binary in {}", -// container_dir.display() -// ); -// let mut entries = fs::read_dir(&container_dir).await?; - -// while let Some(entry) = entries.next().await { -// let path = entry?.path(); -// log::error!("checking path: {}", path.display()); -// // if path is a directory and starts with "/roc_nightly-" -// if path.is_dir() && path.display().to_string().contains("/roc_nightly-") { -// let bin_path = path.join(SERVER_BINARY_NAME); -// if bin_path.exists() { -// if let Ok(metadata) = bin_path.metadata() { -// if metadata.is_file() { -// log::error!("found lang server binary: {}", bin_path.display()); -// return Ok(Some(bin_path)); -// } else { -// log::error!("not a file: {}", bin_path.display()); -// } -// } else { -// log::error!("could not read metadata: {}", bin_path.display()); -// } -// } -// } -// } - -// return Ok(None); -// } From 4d27d14bc3b40c2d26c6797d75ba7a6e7ac080ec Mon Sep 17 00:00:00 2001 From: "Luke Boswell (Linux-Desktop)" Date: Fri, 26 Jun 2026 17:05:14 +1000 Subject: [PATCH 2/2] Match grammar checkout URL to manifest --- justfile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/justfile b/justfile index 5598fc5..4830ce0 100644 --- a/justfile +++ b/justfile @@ -1,4 +1,5 @@ -grammar_commit := `sed -n 's/^commit = "\(.*\)"/\1/p' extension.toml` +grammar_repository := `sed -n '/^\[grammars\.roc\]/,/^\[/s/^repository = "\(.*\)"/\1/p' extension.toml | head -n 1` +grammar_commit := `sed -n '/^\[grammars\.roc\]/,/^\[/s/^commit = "\(.*\)"/\1/p' extension.toml | head -n 1` check: cargo fmt -- --check @@ -9,14 +10,16 @@ check: # Sync query files from the tree-sitter-roc commit in extension.toml. sync-queries: mkdir -p grammars - if [ ! -d grammars/roc/.git ]; then git clone https://github.com/faldor20/tree-sitter-roc.git grammars/roc; fi + if [ ! -d grammars/roc/.git ]; then git clone {{grammar_repository}} grammars/roc; fi + cd grammars/roc && git remote set-url origin {{grammar_repository}} cd grammars/roc && git fetch origin && git checkout {{grammar_commit}} cp -v grammars/roc/queries/*.scm languages/roc/ # Update grammar to a specific commit and sync queries update-grammar COMMIT: mkdir -p grammars - if [ ! -d grammars/roc/.git ]; then git clone https://github.com/faldor20/tree-sitter-roc.git grammars/roc; fi + if [ ! -d grammars/roc/.git ]; then git clone {{grammar_repository}} grammars/roc; fi + cd grammars/roc && git remote set-url origin {{grammar_repository}} cd grammars/roc && git fetch origin && git checkout {{COMMIT}} cp -v grammars/roc/queries/*.scm languages/roc/ rm -f grammars/roc.wasm