- Zig 52.5%
- Shell 30.3%
- Lua 16.8%
- Scheme 0.4%
|
|
||
|---|---|---|
| docs/superpowers | ||
| grammars | ||
| lib | ||
| queries | ||
| src | ||
| tests | ||
| themes | ||
| tools | ||
| vendor | ||
| build.zig | ||
| build.zig.zon | ||
| README.md | ||
| THIRD_PARTY_NOTICES.md | ||
| treesitter4zsh.plugin.zsh | ||
| zig-version.txt | ||
treesitter4zsh
A Tree-sitter syntax highlighter for the Zsh line editor (ZLE). It parses the current input as source text; it does not run, evaluate, source, expand, or probe commands or paths to decide how to highlight them.
Build and enable
Requires Zsh 5.9 or newer, Zig 0.16.0 (zig-version.txt), and a C
compiler for the vendored Tree-sitter runtime and parsers. From this checkout:
zig build -Doptimize=ReleaseFast
zig build test --summary all
The installed helper is zig-out/bin/treesitter4zsh-worker. Build it before
loading the plugin; loading never compiles or downloads anything. In an
interactive Zsh session (for example, from .zshrc):
source /absolute/path/to/treesitter4zsh/treesitter4zsh.plugin.zsh
Sourcing from a noninteractive shell does nothing. The helper starts on the
first eligible nonempty input change, not at source time. There is one
persistent worker per interactive shell, not one process per edit. The
line-pre-redraw hook sends requests without waiting for a response; a
private zle -F -w callback applies a complete response to the still-current
PREBUFFER/BUFFER and refreshes the display. Rapid edits coalesce to one
in-flight request plus the newest pending snapshot.
Configuration
Set styles before sourcing (all keys use :treesitter4zsh:config):
zstyle ':treesitter4zsh:config' theme default
zstyle ':treesitter4zsh:config' worker-path /absolute/path/to/treesitter4zsh-worker
zstyle ':treesitter4zsh:config' response-timeout-ms 250
zstyle ':treesitter4zsh:config' max-buffer-length 4096
zstyle ':treesitter4zsh:config' max-injection-count 256
zstyle ':treesitter4zsh:config' max-embedded-bytes 65536
| Setting | Default | Supported values |
|---|---|---|
theme |
default |
Name of a local themes/<name>.ini (alphanumeric, _, -) |
worker-path |
<plugin-dir>/zig-out/bin/treesitter4zsh-worker |
Executable helper path; quote spaces in Zsh |
response-timeout-ms |
250 |
1–60000 ms; maximum asynchronous request age, not a synchronous redraw wait |
max-buffer-length |
4096 |
1–4096 total Unicode scalars in PREBUFFER + BUFFER |
max-injection-count |
256 |
1–256 injected regions, not total returned highlight spans |
max-embedded-bytes |
65536 |
1–65536 cumulative parsed child-source bytes |
Invalid numeric settings fall back to their defaults; the worker also rejects
startup overrides above the hard caps. Theme files map semantic capture names
to ZLE region_highlight styles, using themes/default.ini as the base. A
missing helper reports one diagnostic and leaves ZLE usable.
The age limit is checked on redraw and response events, without a polling
timer: an idle silent worker can remain until the next event or unload, but
it never blocks a redraw. A response past the limit is discarded. On malformed
output, EOF, or timeout, plugin-owned spans are cleared, the worker is stopped,
and at most one automatic restart is permitted; the second failure disables
highlighting until reload. Empty, pending, oversized, or changed input clears
only plugin-owned spans; other plugins' region_highlight entries are retained.
Grammar and injection coverage
The default binary includes a Zsh host grammar plus these command-string injectors, selected only for statically identifiable script operands:
| Command | Selected grammar | Qualification |
|---|---|---|
perl -e / perl -E |
Perl | Separated and attached script operands |
sh -c, dash -c |
POSIX sh |
Supplemental POSIX grammar, not Dash-specific |
bash -c |
Bash | No POSIX mode |
ksh -c |
Bash | Approximate fallback, not Ksh validation |
zsh -c |
Zsh | Recursive injections supported within budgets |
For example, sh -c 'zsh -c "perl -e print"' is parsed through the POSIX,
Zsh, and Perl layers. Host quote delimiters retain their host styles; decoded
static child spans are mapped back to visible input offsets, excluding
PREBUFFER. Runtime substitutions, ambiguous expansions, and unsafe source
maps are not evaluated or guessed: highlighting of that injection is
omitted. Incomplete input and Tree-sitter ERROR nodes are normal while
editing. Highlighting is not shell execution, syntax validation, command/path
checking, or a guarantee that a script is valid for another shell.
The pinned grammars/catalog.json is metadata, not a set of automatically
installed parsers. An optional Python grammar module can be installed only
with an explicit operation:
zig build install-grammar -Dlanguage=python
This writes its library, query, and manifest under
${XDG_DATA_HOME:-$HOME/.local/share}/treesitter4zsh/grammars/python/. The
helper validates metadata/ABI when an optional grammar is requested; missing
modules do not prevent host highlighting. The shipped command-string injectors
above do not select Python, so installing that module alone does not add
Python command-string highlighting. No grammar build, installation, network
fetch, or generated file creation happens when the plugin is passively sourced.
Verification and unload
zig build test --summary all
zig build --summary all
for file in treesitter4zsh.plugin.zsh lib/*.zsh; do zsh -f -n "$file" || exit; done
for test in tests/integration/test-*.zsh; do zsh -f -i "$test" || exit; done
The passive-load syscall test uses Linux strace; complete corpus fixtures in
tests/fixtures/{zsh,injections}/ are checked with zsh -f -n. For the
1,000-edit ReleaseFast callback/response benchmark, run
zsh -f -i tests/integration/benchmark-async.zsh after a ReleaseFast build.
Call treesitter4zsh_unload to clear the plugin's spans, remove its hook and
owned fd widget/registration, stop and reap its worker, and release plugin
functions. Shell exit also stops the worker. Caller-owned widgets, hooks,
regions, and replacement fd handlers are not deleted: if a caller takes over
the worker fd, a later restart fails closed rather than overwriting that
handler. A caller handler retained on an fd closed by the plugin can receive
an invalid-fd event; its owner is responsible for removing that handler.