Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 75 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,94 @@
# Roc lang support for Zed

> **Note:** This branch (`use-new-roc-lsp`) uses a fork of tree-sitter-roc with modern Roc syntax support. It is not ready to merge until the changes land upstream at [faldor20/tree-sitter-roc](https://github.com/faldor20/tree-sitter-roc).

An extension for Zed that adds Roc language support:

- syntax highlighting through treesitter and
- type tooltips, error marker, completion ... through the roc language server

## Preqrequisites
## Prerequisites

- install roc from the [roc-lang](https://roc-lang.org) website
- add roc and roc_language_server to your PATH
- install Zed from the [Zed](https://zed.dev) website
- 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

## ROC

- Website: [roc-lang.org](https://roc-lang.org)
- Tree Sitter: [tree-sitter-roc](https://github.com/faldor20/tree-sitter-roc)
- Language Server: [roc-language-server](https://github.com/roc-lang/roc/tree/main) included in nightly builds of the ROC compiler e.g. https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-latest.tar.gz, the roc_language_server binary should be included in PATH.
- Tree Sitter: [tree-sitter-roc](https://github.com/lukewilliamboswell/tree-sitter-roc)
- Language Server: This extension uses the new experimental LSP integrated into the `roc` CLI via `roc experimental-lsp`.

## Development

### Installing the Dev Extension

1. Open Zed's command palette (Cmd+Shift+P / Ctrl+Shift+P)
2. Run "zed: install dev extension"
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.

### Verifying the Dev Extension is Running

Check Zed's logs (Command palette → "zed: open log file") and look for:
```
compiling Rust extension /path/to/your/zed-roc
```

If the path points to your local repository (not `~/Library/Application Support/Zed/extensions/installed/roc`), the dev extension is active.

### Key Files

- `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)

### Updating the Tree-sitter Grammar

The `grammars/roc/` directory must be a git clone of the repository specified in `extension.toml`. To update:

1. Update the commit hash in `extension.toml` under `[grammars.roc]`
2. Update the grammar submodule:
```sh
cd grammars/roc
git fetch origin
git checkout <commit-hash>
```
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

### Troubleshooting

**Extension not detecting .roc files:**
- Check Zed logs for errors (Command palette → "zed: open log file")
- Ensure `languages = ["languages/roc"]` is in `extension.toml`

**Conflict with marketplace version:**
- Uninstall the marketplace Roc extension first
- If uninstall doesn't work, quit Zed and manually edit the extensions index.json to remove "roc" entries
- Zed stores extensions in:
- macOS: `~/Library/Application Support/Zed/extensions/`
- 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

### Useful Commands

```sh
~/Library/Application\ Support/Zed/extensions/installed/
less ~/Library/Logs/Zed/Zed.log
# Sync query files from grammar to languages
cp grammars/roc/queries/*.scm languages/roc/

# Clean build artifacts
rm -f grammars/roc.wasm extension.wasm

# Check Zed logs (macOS)
tail -f ~/Library/Logs/Zed/Zed.log | grep -i roc
```

![Zed Example](https://github.com/h2000/zed-roc/assets/187650/1ec0cda3-3679-4223-bc5e-3272babde364)
7 changes: 4 additions & 3 deletions extension.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
id = "roc"
name = "Roc"
description = "Roc language support for Zed"
version = "0.0.6"
version = "0.1.0"
schema_version = 1
authors = ["Alf Richter <kuehlboxen.vorgemisch0e@icloud.com>"]
repository = "https://github.com/h2000/zed-roc"
languages = ["languages/roc"]

[grammars.roc]
repository = "https://github.com/faldor20/tree-sitter-roc"
commit = "6ea64b6434a45472bd87b0772fd84a017de0a557"
repository = "https://github.com/lukewilliamboswell/tree-sitter-roc"
commit = "80b6bbe1bab45396db0943db410b97e6f4579801"

[language_servers.roc]
language = "Roc"
Expand Down
22 changes: 13 additions & 9 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
update-tree-sitter:
-rm languages/roc/*.scm
cd ../tree-sitter-roc && git pullr
cp -v ../tree-sitter-roc/queries/*.scm languages/roc
# Sync query files from tree-sitter-roc to languages/roc
sync-queries:
cp -v grammars/roc/queries/*.scm languages/roc/

clean:
-rm -rf grammars
-rm -f extension.wasm
# Update grammar to a specific commit and sync queries
update-grammar COMMIT:
cd grammars/roc && git fetch origin && git checkout {{COMMIT}}
just sync-queries
rm -f grammars/roc.wasm
@echo "Done. Click 'Rebuild' in Zed to recompile the grammar."

update-extension:
cd ../zed-roc-extensions && git pullr && cd extensions/roc && git pullr
# Clean build artifacts
clean:
rm -f grammars/roc.wasm
rm -f extension.wasm
30 changes: 13 additions & 17 deletions languages/roc/highlights.scm
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
;; opinion: typedefs cross into documentation and should be
;; highlighted differently from normal code

(opaque_type_def (_ (concrete_type) @type.definition))
(nominal_type_def (_ (concrete_type) @type.definition))
(function_type (arrow) @punctuation.delimiter.structural.typedef)
(function_type (effect_arrow) @punctuation.delimiter.structural.typedef)

(parenthesized_type ["(" ")"] @punctuation.bracket.typedef)
(tuple_type ["(" ")"] @punctuation.bracket.typedef)
Expand All @@ -24,14 +25,8 @@
(record_field_type ":" @punctuation.delimiter.typedef)

(record_field_type (field_name) @variable.other.enum.typedef)
(ability_chain "&" @operator.typedef)

(where_implements _
(where) @type.keyword
(identifier) @type.parameter
(implements) @type.keyword
(ability_chain) @type.parameter)

(where_clause
(where) @type.keyword)

((concrete_type) @type.builtin
(#match? @type.builtin "^(Bool|Str|Num|List|Result|Dict|Set|Dec)"))
Expand All @@ -50,8 +45,10 @@
"?"
(arrow)
(back_arrow)
(backslash)
] @punctuation.delimiter.structural

; Lambda pipes
(anon_fun_expr "|" @punctuation.delimiter.structural)
(bang_expr "!" @punctuation.delimiter.structural)
[
","
Expand Down Expand Up @@ -79,18 +76,18 @@

[
"if"
"then"
"else"
] @keyword.control.conditional

[
(implements)
(when)
(is)
"match"
"as"
(to)
] @keyword.control.roc

; Match expression fat arrow
(match_branch "=>" @punctuation.delimiter.structural)

;----headers-----

[
Expand Down Expand Up @@ -154,8 +151,6 @@
(function_call_expr
caller: (field_access_expr (identifier)@function .))

(bin_op_expr (operator "|>")@operator(variable_expr(identifier)@function))

;----function arguments----

(argument_patterns(identifier_pattern
Expand All @@ -167,7 +162,7 @@
(argument_patterns(_(_(_(_(_(identifier_pattern(identifier)@variable.parameter)))))))

; pattern captures
(when_is_branch pattern: (_ (identifier_pattern (identifier) @variable.parameter)))
(match_branch pattern: (_ (identifier_pattern (identifier) @variable.parameter)))
(range_pattern (identifier) @variable.parameter)


Expand Down Expand Up @@ -200,6 +195,7 @@

(string)@string
(multiline_string)@string
(line_string)@string
(char) @constant.character
(escape_char)@constant.character.escape

Expand Down
4 changes: 2 additions & 2 deletions languages/roc/locals.scm
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
(exposes_list(ident)@local.reference)
(import_expr(as)(module)@local.definition)

(opaque_type_def(apply_type(concrete_type)@local.definition))
(nominal_type_def(apply_type(concrete_type)@local.definition))
(alias_type_def(apply_type(concrete_type)@local.definition))

(when_is_branch pattern: (_ (identifier_pattern (identifier) @local.definition)))
(match_branch pattern: (_ (identifier_pattern (identifier) @local.definition)))
(range_pattern (identifier) @local.definition)

(identifier)@local.reference
Expand Down
2 changes: 1 addition & 1 deletion languages/roc/textobjects.scm
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
[
(annotation_type_def ) @class.inside
(alias_type_def ) @class.inside
(opaque_type_def ) @class.inside
(nominal_type_def ) @class.inside
] @class.around

(apply_type_arg) @parameter.inside
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ impl zed::Extension for RocExtension {
_config: &zed::LanguageServerId,
worktree: &zed::Worktree,
) -> Result<zed::Command> {
let binary_name = "roc_language_server";
let binary_name = "roc";
let path = worktree
.which(binary_name)
.ok_or_else(|| format!("{binary_name} executable not found in $PATH"))?;

Ok(zed::Command {
command: path,
args: vec![],
args: vec!["experimental-lsp".to_string()],
env: Default::default(),
})
}
Expand Down