helix-carve
August 28, 2026 · View on GitHub
Carve markup language support for the Helix editor: syntax highlighting, textobjects, injections, and indentation, backed by the tree-sitter-carve grammar.
Carve is a post-Markdown lightweight markup language. Files use the .crv
extension.
What you get
- Highlighting for headings (all six levels), bold / italic / underline / strikethrough, inline and block code, math, lists (bullet, ordered, task, definition), links and images, tables, divs, attributes, footnotes, and comments.
- Textobjects (
function,class,parameter,entry,comment) for tree-sitter based selection and navigation. - Language injections so fenced code blocks, raw blocks, inline raw spans, math (LaTeX), and frontmatter are highlighted with their target grammar.
- A
%%comment token, sogccomment toggling works. - Language-server support via carve-lsp - diagnostics, hover, completion, go-to-definition, workspace-wide rename, find-references, code actions and formatting. Optional: install the server and it works, skip it and everything above still does.
The queries are Helix-flavored: they use Helix's themable scope list
(@markup.heading.1, @markup.bold, @markup.raw.inline,
@markup.link.url, @comment, @punctuation.special, and so on), which
differs from the Neovim-flavored captures the upstream grammar ships.
Install
1. Merge the language and grammar definitions
Copy the [[language]] and [[grammar]] entries from this repo's
languages.toml into your Helix languages.toml
(~/.config/helix/languages.toml):
[[language]]
name = "carve"
scope = "source.carve"
file-types = ["crv"]
roots = []
comment-token = "%%"
indent = { tab-width = 2, unit = " " }
[[grammar]]
name = "carve"
source = { git = "https://github.com/markup-carve/tree-sitter-carve", rev = "c515ba34838c3c5933694ce621575caa82aaf3bf" }
The
revpins a known-good grammar commit. Bump it when you want a newer grammar. For a fully offline build, pointsourceat a local checkout instead:source = { path = "/abs/path/to/tree-sitter-carve" }.
2. Fetch and build the grammar
hx --grammar fetch
hx --grammar build
fetch clones the grammar source (skipped for a local path source); build
compiles it into <helix-runtime>/grammars/carve.so.
3. Install the queries
Copy this repo's runtime/queries/carve/ into your Helix runtime's
queries/carve/ directory. With the default config-dir runtime:
mkdir -p ~/.config/helix/runtime/queries/carve
cp runtime/queries/carve/*.scm ~/.config/helix/runtime/queries/carve/
Alternatively, set HELIX_RUNTIME to a directory that contains both
grammars/ and queries/, and place the queries under queries/carve/ there.
4. Install the language server (optional)
npm i -g @markup-carve/carve-lsp
languages.toml already declares it as carve-lsp --stdio. Skip this step and
Helix simply reports the server as unavailable; highlighting, textobjects,
injections and indentation are unaffected.
What it adds over the queries: the queries know the document's SHAPE, the server
knows what its identifiers MEAN. Unresolved [^footnote] references, </#id>
cross-references that point at nothing, and Markdown habits that silently render
wrong in Carve (**bold** is two literal asterisks around bold text here) are
diagnostics, not highlighting. Rename is workspace-wide, so renaming a heading
id updates every reference to it.
5. Verify
hx --health carve
You should see the parser, highlight queries, textobject queries, and indent
queries all marked present. If you installed the server, the language servers
line names carve-lsp.
Open a .crv file (for example sample.crv) in Helix and
confirm headings, emphasis, code, lists, links, tables, divs, and comments are
colored.
Files
helix-carve/
├── languages.toml # [[language]] + [[grammar]] entries to merge
├── runtime/
│ └── queries/
│ └── carve/
│ ├── highlights.scm # Helix-flavored scopes; correct h1-h6 mapping
│ ├── injections.scm # code / raw / math / frontmatter injections
│ ├── textobjects.scm # function/class/parameter/entry/comment
│ └── indents.scm # container-based indent scopes
├── scripts/
│ └── highlight-captures.mjs # what the highlights query actually paints
├── sample.crv # feature-exercising example document
├── README.md
├── LICENSE # MIT
└── .gitignore
Notes on the queries
- Heading levels. The upstream
tree-sitter-carve/queries/highlights.scmhas an off-by-one in its level-4/5/6 marker matches (it skips the four-hash marker and references a non-existent seven-hash marker). The query here maps all six markers (#through######) to@markup.heading.1..@markup.heading.6correctly. Verified against a six-level sample (see below). - Dropped Neovim-only bits. Neovim's
@spell/@nospellcaptures and the conceal /#offset!directives used purely for concealing markers are not part of Helix's model, so they were left out to keep the queries clean. - Textobject suffixes. Helix uses
.inside/.around(not Neovim's.inner/.outer), and supports a fixed set of kinds; only the kinds that map onto Carve are kept. - Dropped priority directives. Upstream tags some patterns with
(#set! priority N). This file carries none: Helix 25.07 ships no priority directive in any of its own bundled queries, and layers overlapping captures in the order they are written so the later one patches over the earlier - which is already what makes@markup.heading.1win over the(heading) @markup.headingline above it. Where upstream expresses precedence with a number, the port expresses it with position, so new patterns have to go in the right place rather than anywhere in the file.
Effective captures
tree-sitter query prints every match. Several patterns claim the same node and
only one of them reaches the screen, so a pattern that never wins looks exactly
like a pattern that is not there - a compile check cannot tell them apart.
scripts/highlight-captures.mjs resolves the winner at a position the way Helix
does, and asserts it. It shells out to the tree-sitter CLI, so it needs no
dependencies of its own; point it at a built checkout of the pinned grammar:
TS_CWD=/path/to/tree-sitter-carve node scripts/highlight-captures.mjs