[DEPRECATED] Distributed-maintenance fork — please use nvim-treesitter/nvim-treesitter (actively maintained)
See the code[!WARNING] This project is deprecated. Please use nvim-treesitter/nvim-treesitter — see Migrating to upstream below.
This fork was born from a gap, and is closing because the gap closed.
The gap. Upstream nvim-treesitter went dormant and its repository was
archived after years in which nearly all maintenance had serialized on a very
small team. This fork was an attempt to keep the project alive by removing
that single point of failure: instead of one monolith owning every language's
queries, responsibility would be distributed to the people closest to each
language — grammar owners and heavy users — backed by per-language query repos
(nvim-treesitter-queries-<lang>), a shared parser registry for
versioning, and CI validating every query change against its parser.
Finding 1: the gap closed. Upstream was resurrected by its original
maintainers and is actively developed again, carrying the same main-branch
rewrite this fork was based on. When the canonical project is healthy, a
parallel universe of ~330 per-language repos is more coordination than value.
Finding 2: distributed maintenance only partly landed. Some query repos found maintainers; most didn't. This turned out to be structural, not a recruiting problem: a grammar is written once, but its queries are perpetual, editor-specific upkeep, so grammar owners rarely want the second job even when the infrastructure is handed to them. Upstream independently reached the same conclusion a few months later, dropping its own query-maintainer roster with the note that "most maintainers stopped being responsive very quickly." Two systems, one lesson: distributing the structure of responsibility doesn't distribute the labor.
What lives on: the zsh grammar maintained by this fork's author was upstreamed and is now upstream's parser for zsh; the query-validation CI setup is worth borrowing; and the registry experiment remains preserved below and across the org for anyone who wants to pick it up.
Thank you to everyone who starred, filed issues, adopted a query repo, or contributed along the way. Please point your configs at upstream — the migration notes cover the one rough edge (old copied query directories need resetting).
Upstream installs queries as symlinks into its own runtime; this fork
copied them into ~/.local/share/nvim/site/queries/<lang>/ as real
directories, which upstream's installer cannot replace — you will see
EEXIST: file already exists errors from :TSUpdate until you reset them:
rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/queries
Then reinstall with the bang (plain :TSInstall / :TSUpdate skips
already-installed parsers without relinking queries):
:TSInstall! <your languages>
[!CAUTION] This is a full, incompatible rewrite. Treat it as a new plugin and set it up from scratch following the instructions below. If you need the previous version, use the
masterbranch (locked, no new features).
| Requirement | Version |
|---|---|
| Neovim | 0.10.0 or later |
| treesitter-parser-registry | latest |
tree-sitter CLI | 0.26.1 or later — install via your system package manager, not npm |
| C compiler | see cc requirements |
curl | any recent version (used for HTTP downloads) |
[!IMPORTANT] Neovim support tracks the latest stable release and the latest nightly prerelease only. Other versions may work but are not tested.
{
'nvim-treesitter/nvim-treesitter',
dependencies = { 'neovim-treesitter/treesitter-parser-registry' },
lazy = false,
build = ':TSUpdate',
}
[!IMPORTANT] This plugin does not support lazy-loading.
Add :TSUpdate as a post-install / post-update build step.
You do not need to call setup unless you want to change the install
directory.
-- optional — only needed to override the default install_dir
require('nvim-treesitter').setup {
-- parsers and queries are installed here (prepended to runtimepath)
install_dir = vim.fn.stdpath('data') .. '/site',
}
Install parsers and their queries:
require('nvim-treesitter').install { 'rust', 'python', 'typescript' }
Then enable features per language. Features are not enabled automatically.
vim.api.nvim_create_autocmd('FileType', {
pattern = { 'rust', 'python', 'typescript' },
callback = function()
vim.treesitter.start() -- highlighting
vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()' -- folds
vim.wo.foldmethod = 'expr'
vim.bo.indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()" -- indentation
end,
})
| Command | Description |
|---|---|
:TSInstall {lang...} | Install parsers and queries. No-op if already installed. |
:TSInstall! {lang...} | Force reinstall (useful after upgrading the plugin). |
:TSUpdate [{lang...}] | Update installed parsers and queries to latest release. Omit languages to update all. |
:TSUpdate! [{lang...}] | Update, bypassing the per-parser version cache (24h TTL). |
:TSUninstall {lang...} | Remove parsers and queries for the specified languages. |
:TSRegistryUpdate | Force-refresh the registry from GitHub, bypassing the 7-day cache. |
:TSStatus | Open a status buffer showing installed version vs. latest for each language. |
:TSLog | Show log from the last install/update/uninstall run. |
Languages are discovered from the neovim-treesitter registry. Any language in the registry can be installed; there are no tiers.
Two caches avoid unnecessary network requests:
Registry cache (7-day TTL) — the full registry.json is fetched from
GitHub on first use and stored at <stdpath('data')>/site/registry/.
Subsequent loads within 7 days use the cached copy. If a fetch fails, the
stale cache is used as a fallback.
Per-parser version cache (24-hour TTL) — for each installed language,
the latest known parser and query versions are cached so :TSUpdate doesn't
hit the network for every language on every run.
To refresh each cache independently:
" Force re-fetch the registry (e.g. after a new language is added)
:TSRegistryUpdate
" Force re-check per-parser versions (bypasses the 24h cache)
:TSUpdate!
For each language, nvim-treesitter installs:
.so fetched from the upstream grammar repository.scm files sourced from whichever location the registry designates:
nvim-treesitter-queries-<lang> under the
neovim-treesitter GitHub org) for external_queries languagesself_contained languages (where the parser
author ships Neovim queries alongside the grammar)The source type is transparent to users — :TSInstall handles both.
| Query file | Feature | How to enable |
|---|---|---|
highlights.scm | Syntax highlighting | vim.treesitter.start() |
injections.scm | Multi-language documents | automatic after start() |
folds.scm | Treesitter-based folds | vim.wo.foldmethod = 'expr' |
indents.scm | Treesitter-based indentation | vim.bo.indentexpr = ... |
locals.scm | Scope/definition lookup | used by other plugins |
Query files in your Neovim config's runtimepath take precedence over
installed queries. Place a file at:
~/.config/nvim/queries/<lang>/<type>.scm
To extend (not replace) the installed queries, add this as the first line:
; extends
To replace them entirely, omit that line.
See :h treesitter-query-modelines for details.
Point install_dir at a directory you manage, then place your compiled parser
and queries there directly:
<install_dir>/
parser/
<lang>.so ← compiled parser binary
queries/
<lang>/
highlights.scm ← query files
require('nvim-treesitter').setup {
install_dir = '/path/to/my/parsers',
}
Neovim will use parsers and queries from install_dir as long as it is on
runtimepath, which setup ensures. You can still use :TSInstall for other
languages alongside your local overrides.
To install a parser that is not in the registry, or to use your own fork of a
parser, add it to local_parsers in setup(). Each value is a registry
entry — the same shape used in registry.json — with a source field:
-- Local directory checkout (no network fetch)
require('nvim-treesitter').setup {
local_parsers = {
zsh = {
source = {
type = 'local',
path = '~/Development/tree-sitter-zsh',
queries_path = 'nvim-queries/zsh', -- subdir containing .scm files
},
filetypes = { 'zsh' },
},
},
}
-- Remote URL not in the registry (self_contained = ships its own queries)
require('nvim-treesitter').setup {
local_parsers = {
zsh = {
source = {
type = 'self_contained',
url = 'https://github.com/georgeharker/tree-sitter-zsh',
semver = false,
queries_path = 'nvim-queries/zsh',
},
filetypes = { 'zsh' },
},
},
}
Then: :TSInstall zsh
If Neovim does not detect your language's filetype by default, register the parser name manually:
vim.treesitter.language.register('mylang', { 'ml' })
Each language's queries live in their own repository under the neovim-treesitter GitHub org:
https://github.com/neovim-treesitter/nvim-treesitter-queries-<lang>
Open a pull request there. CI will validate your changes automatically. See the contributing guide for the full workflow.
registry.json in the registry reponvim-treesitter-queries-<lang> repository following the
query repo setup guideAlternatively, if you maintain the parser itself you can ship queries directly from your parser repo using the self-contained model — see the self-contained migration guide.
Add yourself to the CODEOWNERS file in the language's query repo and open
a PR. See the governance guide for details.
The previous version of nvim-treesitter pinned specific parser revisions
inside the plugin and used a tiered system (stable/unstable/unmaintained).
The new version:
parsers.lua — per-language config via parsers.<lang> = {}
is not supported in this version:TSInstallFromGrammar — parser generation from grammar source is
not part of the installer's scopeReplace any reference to tiers in your build step:
-- before (no longer valid)
build = ':TSUpdate stable'
-- after
build = ':TSUpdate'
The old pattern using a User TSUpdate autocmd no longer works:
-- OLD — no longer supported
vim.api.nvim_create_autocmd('User', {
pattern = 'TSUpdate',
callback = function()
require('nvim-treesitter.parsers').zsh = {
install_info = {
url = 'https://github.com/georgeharker/tree-sitter-zsh',
queries = 'nvim-queries/zsh',
},
tier = 1,
}
end,
})
Use local_parsers in setup() instead:
-- NEW — remote URL (self_contained: ships its own queries)
require('nvim-treesitter').setup {
local_parsers = {
zsh = {
source = {
type = 'self_contained',
url = 'https://github.com/georgeharker/tree-sitter-zsh',
queries_path = 'nvim-queries/zsh',
},
filetypes = { 'zsh' },
},
},
}
For a local directory checkout instead of a remote URL:
require('nvim-treesitter').setup {
local_parsers = {
zsh = {
source = {
type = 'local',
path = '~/Development/tree-sitter-zsh',
queries_path = 'nvim-queries/zsh',
},
filetypes = { 'zsh' },
},
},
}
Then install as normal: :TSInstall zsh.
| Old field | New field | Notes |
|---|---|---|
url = '...' | source.url = '...' | moved under source |
path = '...' | source.path = '...' | moved under source, implies type = 'local' |
location = '...' | source.location = '...' | monorepo subdir, moved under source |
queries = 'subdir' | source.queries_path = 'subdir' | renamed and moved under source |
semver = false | source.semver = false | moved under source |
revision / min_version | (removed) | version managed by the registry |
tier | (removed) | no tiers in new system |
generate / generate_from_json | (removed) | parser generation not supported |
Run :checkhealth nvim-treesitter to verify the installation.
(top 30 of 373)
Tree-sitter Query
67.1%
Lua
20.9%
Cap'n Proto
2.8%
Shell
2.1%
[DEPRECATED] Distributed-maintenance fork — please use nvim-treesitter/nvim-treesitter (actively maintained)
See the code[!WARNING] This project is deprecated. Please use nvim-treesitter/nvim-treesitter — see Migrating to upstream below.
This fork was born from a gap, and is closing because the gap closed.
The gap. Upstream nvim-treesitter went dormant and its repository was
archived after years in which nearly all maintenance had serialized on a very
small team. This fork was an attempt to keep the project alive by removing
that single point of failure: instead of one monolith owning every language's
queries, responsibility would be distributed to the people closest to each
language — grammar owners and heavy users — backed by per-language query repos
(nvim-treesitter-queries-<lang>), a shared parser registry for
versioning, and CI validating every query change against its parser.
Finding 1: the gap closed. Upstream was resurrected by its original
maintainers and is actively developed again, carrying the same main-branch
rewrite this fork was based on. When the canonical project is healthy, a
parallel universe of ~330 per-language repos is more coordination than value.
Finding 2: distributed maintenance only partly landed. Some query repos found maintainers; most didn't. This turned out to be structural, not a recruiting problem: a grammar is written once, but its queries are perpetual, editor-specific upkeep, so grammar owners rarely want the second job even when the infrastructure is handed to them. Upstream independently reached the same conclusion a few months later, dropping its own query-maintainer roster with the note that "most maintainers stopped being responsive very quickly." Two systems, one lesson: distributing the structure of responsibility doesn't distribute the labor.
What lives on: the zsh grammar maintained by this fork's author was upstreamed and is now upstream's parser for zsh; the query-validation CI setup is worth borrowing; and the registry experiment remains preserved below and across the org for anyone who wants to pick it up.
Thank you to everyone who starred, filed issues, adopted a query repo, or contributed along the way. Please point your configs at upstream — the migration notes cover the one rough edge (old copied query directories need resetting).
Upstream installs queries as symlinks into its own runtime; this fork
copied them into ~/.local/share/nvim/site/queries/<lang>/ as real
directories, which upstream's installer cannot replace — you will see
EEXIST: file already exists errors from :TSUpdate until you reset them:
rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/queries
Then reinstall with the bang (plain :TSInstall / :TSUpdate skips
already-installed parsers without relinking queries):
:TSInstall! <your languages>
[!CAUTION] This is a full, incompatible rewrite. Treat it as a new plugin and set it up from scratch following the instructions below. If you need the previous version, use the
masterbranch (locked, no new features).
| Requirement | Version |
|---|---|
| Neovim | 0.10.0 or later |
| treesitter-parser-registry | latest |
tree-sitter CLI | 0.26.1 or later — install via your system package manager, not npm |
| C compiler | see cc requirements |
curl | any recent version (used for HTTP downloads) |
[!IMPORTANT] Neovim support tracks the latest stable release and the latest nightly prerelease only. Other versions may work but are not tested.
{
'nvim-treesitter/nvim-treesitter',
dependencies = { 'neovim-treesitter/treesitter-parser-registry' },
lazy = false,
build = ':TSUpdate',
}
[!IMPORTANT] This plugin does not support lazy-loading.
Add :TSUpdate as a post-install / post-update build step.
You do not need to call setup unless you want to change the install
directory.
-- optional — only needed to override the default install_dir
require('nvim-treesitter').setup {
-- parsers and queries are installed here (prepended to runtimepath)
install_dir = vim.fn.stdpath('data') .. '/site',
}
Install parsers and their queries:
require('nvim-treesitter').install { 'rust', 'python', 'typescript' }
Then enable features per language. Features are not enabled automatically.
vim.api.nvim_create_autocmd('FileType', {
pattern = { 'rust', 'python', 'typescript' },
callback = function()
vim.treesitter.start() -- highlighting
vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()' -- folds
vim.wo.foldmethod = 'expr'
vim.bo.indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()" -- indentation
end,
})
| Command | Description |
|---|---|
:TSInstall {lang...} | Install parsers and queries. No-op if already installed. |
:TSInstall! {lang...} | Force reinstall (useful after upgrading the plugin). |
:TSUpdate [{lang...}] | Update installed parsers and queries to latest release. Omit languages to update all. |
:TSUpdate! [{lang...}] | Update, bypassing the per-parser version cache (24h TTL). |
:TSUninstall {lang...} | Remove parsers and queries for the specified languages. |
:TSRegistryUpdate | Force-refresh the registry from GitHub, bypassing the 7-day cache. |
:TSStatus | Open a status buffer showing installed version vs. latest for each language. |
:TSLog | Show log from the last install/update/uninstall run. |
Languages are discovered from the neovim-treesitter registry. Any language in the registry can be installed; there are no tiers.
Two caches avoid unnecessary network requests:
Registry cache (7-day TTL) — the full registry.json is fetched from
GitHub on first use and stored at <stdpath('data')>/site/registry/.
Subsequent loads within 7 days use the cached copy. If a fetch fails, the
stale cache is used as a fallback.
Per-parser version cache (24-hour TTL) — for each installed language,
the latest known parser and query versions are cached so :TSUpdate doesn't
hit the network for every language on every run.
To refresh each cache independently:
" Force re-fetch the registry (e.g. after a new language is added)
:TSRegistryUpdate
" Force re-check per-parser versions (bypasses the 24h cache)
:TSUpdate!
For each language, nvim-treesitter installs:
.so fetched from the upstream grammar repository.scm files sourced from whichever location the registry designates:
nvim-treesitter-queries-<lang> under the
neovim-treesitter GitHub org) for external_queries languagesself_contained languages (where the parser
author ships Neovim queries alongside the grammar)The source type is transparent to users — :TSInstall handles both.
| Query file | Feature | How to enable |
|---|---|---|
highlights.scm | Syntax highlighting | vim.treesitter.start() |
injections.scm | Multi-language documents | automatic after start() |
folds.scm | Treesitter-based folds | vim.wo.foldmethod = 'expr' |
indents.scm | Treesitter-based indentation | vim.bo.indentexpr = ... |
locals.scm | Scope/definition lookup | used by other plugins |
Query files in your Neovim config's runtimepath take precedence over
installed queries. Place a file at:
~/.config/nvim/queries/<lang>/<type>.scm
To extend (not replace) the installed queries, add this as the first line:
; extends
To replace them entirely, omit that line.
See :h treesitter-query-modelines for details.
Point install_dir at a directory you manage, then place your compiled parser
and queries there directly:
<install_dir>/
parser/
<lang>.so ← compiled parser binary
queries/
<lang>/
highlights.scm ← query files
require('nvim-treesitter').setup {
install_dir = '/path/to/my/parsers',
}
Neovim will use parsers and queries from install_dir as long as it is on
runtimepath, which setup ensures. You can still use :TSInstall for other
languages alongside your local overrides.
To install a parser that is not in the registry, or to use your own fork of a
parser, add it to local_parsers in setup(). Each value is a registry
entry — the same shape used in registry.json — with a source field:
-- Local directory checkout (no network fetch)
require('nvim-treesitter').setup {
local_parsers = {
zsh = {
source = {
type = 'local',
path = '~/Development/tree-sitter-zsh',
queries_path = 'nvim-queries/zsh', -- subdir containing .scm files
},
filetypes = { 'zsh' },
},
},
}
-- Remote URL not in the registry (self_contained = ships its own queries)
require('nvim-treesitter').setup {
local_parsers = {
zsh = {
source = {
type = 'self_contained',
url = 'https://github.com/georgeharker/tree-sitter-zsh',
semver = false,
queries_path = 'nvim-queries/zsh',
},
filetypes = { 'zsh' },
},
},
}
Then: :TSInstall zsh
If Neovim does not detect your language's filetype by default, register the parser name manually:
vim.treesitter.language.register('mylang', { 'ml' })
Each language's queries live in their own repository under the neovim-treesitter GitHub org:
https://github.com/neovim-treesitter/nvim-treesitter-queries-<lang>
Open a pull request there. CI will validate your changes automatically. See the contributing guide for the full workflow.
registry.json in the registry reponvim-treesitter-queries-<lang> repository following the
query repo setup guideAlternatively, if you maintain the parser itself you can ship queries directly from your parser repo using the self-contained model — see the self-contained migration guide.
Add yourself to the CODEOWNERS file in the language's query repo and open
a PR. See the governance guide for details.
The previous version of nvim-treesitter pinned specific parser revisions
inside the plugin and used a tiered system (stable/unstable/unmaintained).
The new version:
parsers.lua — per-language config via parsers.<lang> = {}
is not supported in this version:TSInstallFromGrammar — parser generation from grammar source is
not part of the installer's scopeReplace any reference to tiers in your build step:
-- before (no longer valid)
build = ':TSUpdate stable'
-- after
build = ':TSUpdate'
The old pattern using a User TSUpdate autocmd no longer works:
-- OLD — no longer supported
vim.api.nvim_create_autocmd('User', {
pattern = 'TSUpdate',
callback = function()
require('nvim-treesitter.parsers').zsh = {
install_info = {
url = 'https://github.com/georgeharker/tree-sitter-zsh',
queries = 'nvim-queries/zsh',
},
tier = 1,
}
end,
})
Use local_parsers in setup() instead:
-- NEW — remote URL (self_contained: ships its own queries)
require('nvim-treesitter').setup {
local_parsers = {
zsh = {
source = {
type = 'self_contained',
url = 'https://github.com/georgeharker/tree-sitter-zsh',
queries_path = 'nvim-queries/zsh',
},
filetypes = { 'zsh' },
},
},
}
For a local directory checkout instead of a remote URL:
require('nvim-treesitter').setup {
local_parsers = {
zsh = {
source = {
type = 'local',
path = '~/Development/tree-sitter-zsh',
queries_path = 'nvim-queries/zsh',
},
filetypes = { 'zsh' },
},
},
}
Then install as normal: :TSInstall zsh.
| Old field | New field | Notes |
|---|---|---|
url = '...' | source.url = '...' | moved under source |
path = '...' | source.path = '...' | moved under source, implies type = 'local' |
location = '...' | source.location = '...' | monorepo subdir, moved under source |
queries = 'subdir' | source.queries_path = 'subdir' | renamed and moved under source |
semver = false | source.semver = false | moved under source |
revision / min_version | (removed) | version managed by the registry |
tier | (removed) | no tiers in new system |
generate / generate_from_json | (removed) | parser generation not supported |
Run :checkhealth nvim-treesitter to verify the installation.
(top 30 of 373)
Tree-sitter Query
67.1%
Lua
20.9%
Cap'n Proto
2.8%
Shell
2.1%