neovim/neovim
Treesitter integration
Purpose
Tree-sitter is an incremental parsing library; Neovim ships C bindings to it plus a Lua API for parsing buffers, running queries against the resulting trees, and using the results for syntax highlighting, folding, indentation, and structural editing. The integration replaces (and now happily coexists with) Vim's regex-based syntax engine for languages that have a tree-sitter grammar.
Directory layout
src/nvim/lua/
└── treesitter.c (~50k bytes) C bindings: parser, tree, node, query, query_cursor
runtime/lua/vim/treesitter/
├── _fold.lua Treesitter-driven folding
├── _headings.lua Markdown/help heading queries
├── _meta/ Type stubs
├── _query_linter.lua Lint query files
├── _range.lua Range arithmetic helpers
├── _select.lua Treesitter text-objects
├── dev.lua :InspectTree, :EditQuery, the playground
├── health.lua :checkhealth treesitter
├── highlighter.lua The syntax highlighter (decoration provider)
├── language.lua Parser registration / loading
├── languagetree.lua (~44k bytes) Per-buffer parse state
└── query.lua (~38k bytes) Query API + predicate registry
runtime/lua/vim/treesitter.lua Top-level entry point
runtime/queries/ Bundled query files
runtime/parser/ Bundled parser .so filesKey abstractions
| Type / function | File | Description |
|---|---|---|
LanguageTree |
treesitter/languagetree.lua |
Per-buffer parser state. Tracks the source range it parses, child trees for embedded languages, edit ranges. |
vim.treesitter.get_parser(buf, lang) |
treesitter.lua |
Get-or-create the per-buffer LanguageTree. |
Query |
treesitter/query.lua |
A compiled query (a tree-sitter S-expression). Has captures and predicates. |
Highlighter |
treesitter/highlighter.lua |
The decoration provider that turns parsed trees into highlight extmarks. |
vim.treesitter.start(buf, lang) |
treesitter.lua |
Attach a Highlighter to the buffer. |
inspect_tree, edit_query |
treesitter/dev.lua |
Developer playground commands. |
How it works
graph LR
Buf[Buffer change] --> LT[LanguageTree]
LT --> Edit[ts_tree_edit<br/>tell parser the edit]
LT --> Parse[parser:parse<br/>incremental]
Parse --> Tree[New tree]
Tree --> HL[Highlighter<br/>decoration provider]
HL --> Vis[visible lines]
Vis --> Q[query iter_captures]
Q --> EM[set_extmark with hl_group]Per-buffer state
When a plugin (or vim.treesitter.start) calls vim.treesitter.get_parser(buf, lang) for the first time, a LanguageTree is created and stored on the buffer. The LanguageTree:
- Loads the parser shared object (
runtime/parser/<lang>.so). - Parses the whole buffer initially.
- Subscribes to
nvim_buf_attachcallbacks so it sees every byte change. - Tracks "edit ranges" —
ts_tree_editis called with each byte-range change, thenparser:parse(tree)reuses the unaffected subtrees. - Recursively manages child trees for injected languages (e.g., a code block inside markdown).
The "languagetree" name reflects the recursion: each language tree may have child trees of other languages, walking down to the leaves.
Queries
A query is an S-expression that captures patterns:
(function_declaration
name: (identifier) @function.name)
(comment) @commentThe query language and the bundled grammar live under runtime/queries/. They're plain text files at runtime/queries/<lang>/<topic>.scm (highlights.scm, injections.scm, folds.scm, indents.scm, locals.scm, textobjects.scm).
treesitter/query.lua compiles a query against a parser, exposes iter_captures(tree, start, end) to walk matches, and includes a registry of predicates (#match?, #eq?, #has-ancestor?, #lua-match?, ...).
Highlighting
treesitter/highlighter.lua is a decoration provider. On every on_win redraw event for a buffer with treesitter highlighting active, it:
- Asks
LanguageTreefor the current tree (parsing if needed). - Walks the visible-line region with the
highlights.scmquery. - For each capture, sets an extmark with the corresponding highlight group.
The capture name (@function.name) maps to a highlight group (@function.name itself is a group; falls back to @function, then Function). The highlight.c side is responsible for the cascade.
Injections
A second query (injections.scm) tells the languagetree where to spawn child trees. A typical entry:
((code_block
(info_string) @injection.language
(code_fence_content) @injection.content))When the parent tree is parsed, the injection query runs and the matched ranges become a child tree of the language whose name matched @injection.language. This is how a markdown file gets syntax highlighting for the languages of its embedded code blocks, with no special-casing.
Edit propagation
Edit propagation is the reason treesitter is fast on large files. When the buffer changes:
nvim_buf_attachsees the change and posts anon_bytesevent.- The languagetree converts the byte range to a
TSInputEditand callsts_tree_edit(old_tree, &edit). - On the next
parse(), only subtrees overlapping the edit are reparsed; everything outside is reused.
The result: typing one character in a 10,000-line buffer is roughly free.
Other consumers
The same parser/tree/query infrastructure powers:
- Folding (
_fold.lua) — generic folder driven byfolds.scmqueries. - Indentation —
runtime/queries/<lang>/indents.scmplus a Lua engine forindentexpr. - Text objects (
_select.lua) —[m,]m,vif, etc., backed bytextobjects.scm. - Locals — variable scope inference for
gdand rename, vialocals.scm. - Headings (
_headings.lua) — markdown / vimdoc heading navigation.
Bundled grammars
Several parsers are bundled in the binary via cmake.deps/:
tree-sitter-c,tree-sitter-lua,tree-sitter-vimdoc,tree-sitter-vim,tree-sitter-markdown,tree-sitter-bash,tree-sitter-query.
Other languages need a parser installed by the user, typically via nvim-treesitter or vim.pack. The runtime looks for parsers in runtime/parser/, $VIMRUNTIME/parser/, and any directory on the runtimepath.
Integration points
- C-side bindings —
src/nvim/lua/treesitter.cexposes the C library. The Lua wrappers above are thin layers over those bindings. - Decoration provider — the highlighter is registered with
nvim_set_decoration_provider, so it cooperates with the renderer's per-line callback contract. buf_attach— every languagetree subscribes to changes vianvim_buf_attach.- Folding —
vim.opt_local.foldmethod = 'expr',foldexpr = 'v:lua.vim.treesitter.foldexpr()'. - Indent — same idea,
indentexpr = 'v:lua.require"nvim-treesitter".indentexpr()'(community plugin) or the built-in helpers. - Query linter —
:checkhealth treesitterand:lua require'vim.treesitter._query_linter'.check()validate query files at edit time.
Entry points for modification
- Improve the highlighter.
treesitter/highlighter.luais a single file. Most user-visible behavior changes (priority handling, cterm fallbacks, conceal interaction) live there. - Add a query predicate. Extend
treesitter/query.lua's predicate registry. Predicates are pure functions over capture matches. - Bind a new tree-sitter API. Add it to
src/nvim/lua/treesitter.cand expose the wrapper inruntime/lua/vim/treesitter/_meta/. - Bundle a new grammar. Edit
cmake.deps/deps.txt, add tocmake.deps/cmake/BuildTreeSitterParsers.cmake, shipruntime/queries/<lang>/.
Key source files
| File | Purpose |
|---|---|
src/nvim/lua/treesitter.c |
C bindings (parser, tree, node, query, cursor) |
runtime/lua/vim/treesitter.lua |
Top-level entry point (vim.treesitter.*) |
runtime/lua/vim/treesitter/languagetree.lua |
Per-buffer parse state, injections, edit propagation |
runtime/lua/vim/treesitter/highlighter.lua |
Syntax highlighter (decoration provider) |
runtime/lua/vim/treesitter/query.lua |
Query API + predicates |
runtime/lua/vim/treesitter/_fold.lua |
Folding |
runtime/lua/vim/treesitter/_select.lua |
Text objects |
runtime/lua/vim/treesitter/dev.lua |
Inspector and query playground |
runtime/queries/* |
Bundled query files |
runtime/parser/* |
Bundled compiled parsers |
runtime/doc/treesitter.txt |
User docs |
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.