Editor setup
Highlighting from the tree-sitter grammar, format on save from hek fmt, and what each editor needs.
heklang ships a tree-sitter grammar and a formatter that reads stdin, which between them
cover most of what an editor needs. There is no language server yet, so there are no
in-editor diagnostics: you get highlighting, indentation, text objects and a symbol picker
from the grammar, and correctness from hekla check . on demand.
What you get
| From | |
|---|---|
tree-sitter-hek |
highlighting, indentation, text objects, and a symbol outline |
hek fmt - |
format on save |
hekla check . |
diagnostics, on demand rather than as you type |
Install the tool first
Every editor below shells out to hek, so install it once:
curl -fsSL https://hekla.tqwewe.com/install.sh | sh -s -- hek
or cargo install hek, or cargo binstall hek.
The grammar
The grammar lives in the heklang repository rather than its own, so one commit can change the lexer and the grammar together.
| Repository | https://git.tqwewe.com/tephra/heklang |
| Path within it | tree-sitter-hek |
| Latest tag | tree-sitter-hek-v0.4.0 |
| Revision | bc7f5171d2f17e14241566652f25477befde15f8 |
| ABI | 14 |
| Queries | highlights, indents, locals, tags, textobjects, rainbows |
src/parser.c is committed, so nothing needs the tree-sitter CLI or Node to build it.
Helix
The best-supported editor, because the grammar was developed against it.
In languages.toml:
[[language]]
name = "hek"
scope = "source.hek"
injection-regex = "hek"
file-types = ["hk"]
roots = []
comment-token = "//"
indent = { tab-width = 2, unit = " " }
formatter = { command = "hek", args = ["fmt", "-"] }
auto-format = true
[[grammar]]
name = "hek"
source = { git = "https://git.tqwewe.com/tephra/heklang", rev = "bc7f5171d2f17e14241566652f25477befde15f8", subpath = "tree-sitter-hek" }
Then build the grammar and put the queries where Helix looks for them:
hx --grammar fetch
hx --grammar build
git clone --depth 1 https://git.tqwewe.com/tephra/heklang /tmp/heklang
mkdir -p ~/.config/helix/runtime/queries/hek
cp /tmp/heklang/tree-sitter-hek/queries/*.scm ~/.config/helix/runtime/queries/hek/
hx --health hek should show ticks across the row. There is no block-comment-tokens
entry because heklang has no block comment form.
With nix
The repository is a flake, so the binary and the grammar come from one commit and cannot drift apart:
inputs.heklang.url = "git+https://git.tqwewe.com/tephra/heklang";
hek = inputs.heklang.packages.${pkgs.system}.hek;
hek-grammar = inputs.heklang.packages.${pkgs.system}.tree-sitter-hek;
The grammar derivation carries its queries, so linking both into Helix’s runtime is:
"helix/runtime/grammars/hek.so".source = "${hek-grammar}/parser";
"helix/runtime/queries/hek".source = "${hek-grammar}/queries";
and the formatter is "${hek}/bin/hek" with [ "fmt" "-" ].
Neovim
Register the parser, then install it. The grammar is in a subdirectory, which is what
location is for:
vim.filetype.add({ extension = { hk = "hek" } })
require("nvim-treesitter.parsers").get_parser_configs().hek = {
install_info = {
url = "https://git.tqwewe.com/tephra/heklang",
location = "tree-sitter-hek",
files = { "src/parser.c" },
branch = "main",
-- src/ is committed, so neither is needed.
generate_requires_npm = false,
requires_generate_from_grammar = false,
},
filetype = "hek",
}
Then :TSInstall hek.
nvim-treesitter does not fetch queries for a grammar it did not ship, so copy them in:
git clone --depth 1 https://git.tqwewe.com/tephra/heklang /tmp/heklang
mkdir -p ~/.config/nvim/queries/hek
cp /tmp/heklang/tree-sitter-hek/queries/*.scm ~/.config/nvim/queries/hek/
Format on save, with conform.nvim:
require("conform").setup({
formatters = {
hek = { command = "hek", args = { "fmt", "-" }, stdin = true },
},
formatters_by_ft = { hek = { "hek" } },
format_on_save = { timeout_ms = 1000 },
})
Without conform, an autocmd running hek fmt over the file and reloading it works too, but
conform handles the failure case properly, which matters here.
Zed
Zed loads a grammar from a git repository, so this is a small local extension. Create a directory with two files:
# extension.toml
id = "hek"
name = "hek"
version = "0.1.0"
schema_version = 1
[grammars.hek]
repository = "https://git.tqwewe.com/tephra/heklang"
rev = "bc7f5171d2f17e14241566652f25477befde15f8"
path = "tree-sitter-hek"
# languages/hek/config.toml
name = "hek"
grammar = "hek"
path_suffixes = ["hk"]
line_comments = ["// "]
tab_size = 2
Copy highlights.scm, indents.scm and textobjects.scm from the grammar’s queries/
into languages/hek/, then install it through the extensions panel with Install Dev
Extension, pointing at that directory.
Format on save, in Zed’s settings.json:
{
"languages": {
"hek": {
"format_on_save": "on",
"formatter": {
"external": { "command": "hek", "arguments": ["fmt", "-"] }
}
}
}
}
VS Code, Sublime Text, and anything TextMate
None of them uses tree-sitter for syntax, so they need a TextMate grammar instead. This site serves the one it highlights its own code samples with:
curl -O https://hekla.tqwewe.com/hek.tmLanguage.json
It declares scope source.hek and file type hk, and it is the same file Shiki renders
every .hk block on these pages with, so what you see here is what you get.
For VS Code, the smallest thing that works is a local extension: a directory under
~/.vscode/extensions/hek/ holding that grammar and a package.json:
{
"name": "hek",
"publisher": "local",
"version": "0.1.0",
"engines": { "vscode": "^1.80.0" },
"contributes": {
"languages": [
{ "id": "hek", "extensions": [".hk"], "configuration": "./language-configuration.json" }
],
"grammars": [
{ "language": "hek", "scopeName": "source.hek", "path": "./hek.tmLanguage.json" }
]
}
}
with a language-configuration.json of { "comments": { "lineComment": "//" } }.
Format on save needs a formatter, and there is no VS Code extension providing one yet. Any
“run command on save” extension pointed at hek fmt covers it, or run hek fmt in a watch
task.
Emacs
Emacs 29’s treesit can build and load the grammar:
(add-to-list 'treesit-language-source-alist
'(hek "https://git.tqwewe.com/tephra/heklang" "main" "tree-sitter-hek/src"))
(treesit-install-language-grammar 'hek)
Being straight about the gap: there is no hek-ts-mode. treesit does not consume
highlights.scm directly the way Helix and Neovim do; it wants font-lock rules written in
elisp inside a major mode, and nobody has written one. Until then the grammar loads and
there is nothing driving it.
Formatting works today without any of that:
(defun hek-format-buffer ()
"Format the current buffer with hek fmt, leaving it alone if it does not parse."
(interactive)
(let ((tmp (make-temp-file "hek"))
(point (point)))
(if (zerop (call-process-region nil nil "hek" nil `((:file ,tmp) nil) nil "fmt" "-"))
(progn (erase-buffer) (insert-file-contents tmp) (goto-char point))
(message "hek fmt: the module does not parse"))
(delete-file tmp)))
Pre-commit
Independent of any editor, and worth having whether or not format on save is set up:
hek fmt --check
hekla check .
hekla test .
hek fmt --check names what would change and writes nothing. Note the last two are hekla
rather than hek: the two tools have different rule sets, and a program can pass
hek check and fail hekla check.
What is missing
Stated plainly so nobody goes looking:
- No language server. No in-editor diagnostics, no go-to-definition, no completion, no
rename.
hekla check .is the diagnostic surface, and its output is designed to be read. - No published editor extensions. Nothing on the VS Code Marketplace, the Zed registry,
the Helix default language list or
nvim-treesitterupstream. Everything above is a local install. - The grammar is deliberately wider than the language. It has no idea which declaration
a body belongs to, so it parses
putoutside a projector andemitinside afn. Nothing valid fails to parse, and some invalid programs parse anyway. The checker is what says no. tree-sitter-hekis on crates.io but not npm, which matters only if your tooling installs grammars from npm. Nothing above does.