`extras: $ => []` in the cart grammar made the parser fail at byte 0 on any whitespace-only or empty line before `pico-8 cartridge //...`. Real PICO-8 carts always start with the header at byte 0 so this rarely surfaced in production, but it ( a ) broke the `tree-sitter test` corpus harness, which prepends a newline to each fixture, and ( b ) would mis-flag a hand-edited cart that gained an accidental blank line up top. Fix: prefix the `cartridge` rule with `repeat($._blank_line)` and add a hidden `_blank_line` token matching `[ \t]*\n`. Junk content before the header ( a non-blank, non-magic line ) still produces an ERROR. Restores the test corpus that was dropped in v0.1 ( previously failing on this same edge case ) and adds a fixture for the unknown_section fallback while the corpus is being rebuilt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zed-p8
A Zed extension for the PICO-8 fantasy
console. Reasonable editor support for the entire .p8 cartridge file format
and for PICO-8's Lua dialect — including the parts where PICO-8 deviates from
standard Lua 5.2 (compound assignments, ? print shorthand, single-line
if (cond) ..., !=, 0b... binary literals, integer divide \, peek
operators @/%/$, the rotate/logical-shift family <<> / >>< / >>>,
and #include).
Status — v0.2
Working today:
tree-sitter-p8-cart(grammars/p8-cart/) — parses the.p8cartridge container: the magic header line,versionline, and the named sections__lua__,__gfx__,__gff__,__label__,__map__,__sfx__,__music__. Unknown__name__markers fall through tounknown_section.tree-sitter-pico8-lua(grammars/pico-8-lua/) — fork oftree-sitter-grammars/tree-sitter-luawith the PICO-8 dialect added. Handles every dialect form the manual documents: see Dialect coverage below. Upstream attribution is preserved ingrammars/pico-8-lua/UPSTREAM-LICENSE.md.Pico-8 Cartridgelanguage (languages/pico-8-cart/, suffix.p8) — config, marker highlights, outline view, and an injection that hands the__lua__body to the Pico-8 Lua grammar.Pico-8 Lualanguage (languages/pico-8-lua/, suffix.p8lua) — config, dialect-aware highlights with PICO-8 builtins recognized, brackets, indents, outline.
The .p8lua suffix is the convention for bare Pico-8 Lua source files — the
ones pulled into a cart via #include. Plain .lua files are intentionally
not claimed by this extension, so users who keep stock Lua files alongside
their PICO-8 work continue to get standard Lua treatment.
Dialect coverage
| Feature | Status |
|---|---|
!= ( alias for ~= ) |
✓ |
Compound assignment: += -= *= /= %= \= ^= ..= &= |= ^^= <<= >>= >>>= <<>= >><= |
✓ |
Integer divide \ and modulo % ( binary ) |
✓ |
Bitwise XOR ^^ ( binary, in addition to upstream's ~ ) |
✓ |
Logical shift right >>>, rotate left <<>, rotate right >>< |
✓ |
Hex literals with fractional part: 0x11.4000 |
✓ |
Binary literals: 0b1010 |
✓ |
Memory peek prefix unary: @addr, %addr, $addr |
✓ |
Single-line if (cond) stmt [else stmt] ( no then/end ) |
✓ |
Single-line while (cond) stmt ( no do/end ) |
✓ |
? print shorthand statement |
✓ |
#include path directive |
✓ |
_init / _update / _update60 / _draw highlighted as builtins |
✓ |
Known limitations
- No language server. No completion, hover docs, or diagnostics for
PICO-8 builtins yet — only a static
function.builtinhighlight on recognized names. See Roadmap. - No
.p8.pngsupport. Only the plain-text.p8format is handled — the PNG-steganography variant is out of scope for a text-focused IDE extension. - Hex sections are unhighlighted blobs.
__gfx__,__map__,__sfx__,__gff__,__music__,__label__parse as opaque line bodies. Roadmap v0.4 covers per-section highlighters.
Repository layout
zed-p8/
extension.toml ← Zed extension manifest
package.json ← workspace root; hosts tree-sitter-cli
grammars/
p8-cart/ ← cart-format tree-sitter grammar
grammar.js
tree-sitter.json
src/ ← generated parser ( committed )
pico-8-lua/ ← Pico-8 Lua dialect tree-sitter grammar
grammar.js
tree-sitter.json
package.json ← marks this dir as ESM for node
src/ ← generated parser + scanner.c ( committed )
UPSTREAM-LICENSE.md ← MIT, tree-sitter-lua by Munif Tanjim
languages/
pico-8-cart/ ← Pico-8 Cartridge language files
config.toml
highlights.scm
injections.scm ← injects pico-8-lua into __lua__ body
outline.scm
pico-8-lua/ ← Pico-8 Lua language files
config.toml
highlights.scm
brackets.scm
indents.scm
injections.scm
outline.scm
examples/
hello.p8 ← minimal test cart
references/ ← upstream PICO-8 manual + Zed doc links
Both grammars live in subdirectories of this same repository. Zed's
[grammars.*] block supports a path field, so the extension manifest
points each grammar at this repo's git URL plus the relevant subdir.
Local development
Prerequisites: Node.js ( for tree-sitter-cli ) and Zed. Rust is not
required unless / until we add a language-server harness ( v0.3 ).
npm install # one-time, installs tree-sitter-cli
Edit a grammar and reload
-
Edit
grammars/<name>/grammar.js. -
Regenerate from the grammar's directory:
( cd grammars/p8-cart && npx tree-sitter generate ) # or ( cd grammars/pico-8-lua && npx tree-sitter generate ) -
Sanity-check by parsing a sample file:
( cd grammars/p8-cart && npx tree-sitter parse ../../examples/hello.p8 ) ( cd grammars/pico-8-lua && npx tree-sitter parse path/to/file.p8lua ) -
Commit the regenerated
src/parser.calong with the grammar change. Zed clones the grammar repo at the SHA inextension.toml, so changes only take effect after a commit. -
Update the
revfield of the affected[grammars.*]block(s) inextension.tomlto the new SHA, then in Zed runzed: install dev extension( or Install Dev Extension on the Extensions page ) and select this directory. Reinstall after every commit that should be picked up.Logs:
zed: open log. Runzed --foregroundfor live stdout.
Edit only language queries
Files under languages/*/ ( highlights.scm, injections.scm, etc. )
are loaded directly by Zed — no regeneration step needed. Reinstall the
dev extension to pick up changes.
Tests
Sample carts live under examples/; parse them directly with
tree-sitter parse <file> for ad-hoc checks.
The cart grammar has a corpus under grammars/p8-cart/test/corpus/ —
run ( cd grammars/p8-cart && npx tree-sitter test ). The corpus
covers the empty-section skeleton, normal Lua content, the case where
a Lua identifier resembles a section marker ( e.g. local __foo__ = 1
must remain a line, not be re-tokenized as a marker ), and the
fallback unknown_section rule.
Roadmap
v0.3 — Language server integration
Wire up japhib/pico8-ls ( or whichever
PICO-8 LSP is most maintained at the time ) for:
- Completion of PICO-8 builtins (
spr,circfill,btn,flr, … ). - Signature help and hover docs sourced from the manual.
- Cart-aware analysis — the LSP already understands
.p8section markers and only analyzes the__lua__body. - Per-cart diagnostics.
This requires a Rust component ( the zed_extension_api crate ) that
downloads the LSP binary and defines language_server_command.
See Zed's developing-extensions docs.
v0.4 — Polish
- LuaCATS / EmmyLua stub file enumerating PICO-8's ~110 globals, for users
who'd rather wire up
lua-language-serveragainst their#include-d.p8luafiles. - Per-section highlighters for the hex blocks:
__gfx__colored by palette index,__sfx__/__music__parsed as note streams,__map__as tile indices,__gff__as flag bytes. - Snippets for common idioms ( the
_init/_update/_drawtriad,for x=0,127 do … end, palette swap setup, etc. ).
References
- PICO-8 manual:
references/pico-8_manual.txt - Zed extension docs: see links in
references/zed-doc-links.md - Cart file-format spec ( community wiki, not in the official manual ): https://pico-8.fandom.com/wiki/P8FileFormat
- Upstream Lua grammar: https://github.com/tree-sitter-grammars/tree-sitter-lua
( MIT, by Munif Tanjim — preserved in
grammars/pico-8-lua/UPSTREAM-LICENSE.md)
License
The cart grammar and the Zed extension files are 0BSD ( see LICENSE ).
The PICO-8 Lua grammar is a fork of MIT-licensed tree-sitter-lua; the
upstream license is preserved at grammars/pico-8-lua/UPSTREAM-LICENSE.md
and applies to the derived files in that directory.