读书越多,越感到腹中空虚。——雪莱

nvim-treesitter:一位“语法工程师”的自我修养(以及它如何让 Neovim 读懂你的代码)

它第一次走进 Neovim 的那天,没带花里胡哨的 UI,也没带“我能替你写代码”的野心。

它只带了三样东西,像一个极度务实、穿着工装的工程师站在门口,敲了敲桌子:

  1. 我负责安装、更新、移除 tree-sitter parsers
  2. 我带来一整套queries,让 Neovim 内置的 tree-sitter 功能真的能在各种语言上跑起来;
  3. 我还当一块“试验田”——把一些可能会被 upstream 到 Neovim 的 treesitter 特性先放这里孵化。

它叫 nvim-treesitter
它的描述很简短,但气质很明确:

Nvim Treesitter configurations and abstraction layer

它不是来抢 Neovim 风头的,它更像是 Neovim 的“语法后勤部长”,专门负责把 parser、query、特性开关这些琐碎但关键的事情,收拾得干干净净、能持续维护、能规模化扩展。


先把误会说清楚:它不是“解析器”,也不是“高亮引擎”

nvim-treesitter 的性格很硬:边界感强。

它会提醒你:tree-sitter 的高亮、折叠等,是 Neovim 提供的;nvim-treesitter做的是“让这些能力在更多语言、更稳定的版本组合上可用”,并且提供一些插件侧的实验功能(比如缩进)。

如果你把 Neovim 想象成一座城市:

  • Neovim 的 vim.treesitter 是城市里的“基础设施”(道路、供水、电网);
  • tree-sitter parser 是“语言翻译官”(把文本解析成树);
  • query(.scm 文件)是“规则手册”(哪些节点是函数、哪些是变量、怎样折叠、怎样注入);
  • nvim-treesitter 就是那个管物资、管版本、管安装、管更新、还顺便跑实验项目的人。

它不喧哗,但你一旦离开它,就会发现:
“咦?我明明装了 Neovim,为什么某些语言的 tree-sitter 就是不好用?”
它会在旁边翻着表格说:
“你 parser 没装,query 没对齐,版本也没更新,你指望它自己长出来?”


它已经“归档”了,但它留下的秩序还在

打开仓库页面,你会看到一个清晰的事实:它在 2026-04-03 被仓库所有者归档(archived)了,变成只读

归档不等于它的思想消失了。更像是一位老工程师把系统规约写进文档、把流程固化进工具链、把经验封装成默认策略,然后拍拍你肩膀:

“以后你自己也能维护好这套语法供应链了。”


一句警告写得像红线:这是一次“完全不兼容重写”

README 里还有一句语气很重的话,几乎像一张贴在机房门上的告示:

  • 这是一次完全不兼容的重写
  • 把它当成一个需要你从零重新配置的新插件

如果你不想升级、或者暂时不具备升级条件,README 也给了“后门”:
你可以指定 master 分支(被锁定、用于兼容 Nvim 0.11 的后向兼容)。

它像在对你说:

“你要新世界,就按新世界的规则来;你要旧世界,我也给你留条路,但别指望我继续为旧世界提供同样的保障。”


Quickstart:把它请进来(要求、安装、配置、第一批 parser)

Requirements:它要的不是“玄学”,是现实的工具链

它列的要求非常工程化:

  • Neovim 0.12.0+(nightly)
  • 系统里要有:tarcurl
  • 要有 tree-sitter-cli 0.26.1+(通过系统包管理器安装,不是 npm
  • 要有 C 编译器(编译 parser 用)

它还明确了 Neovim 的支持策略:只围绕最新 stable 和最新 nightly prerelease。

这就是它的风格:
不做“我大概能跑”的承诺,只做“我测试过、我支持”的承诺。


Installation:用你喜欢的插件管理器装它,但别想 lazy-load

它允许你使用任何插件管理器,但同时给你一条“硬限制”:

This plugin does not support lazy-loading.

它说得很像个不愿陪你玩花活的后端负责人:

“我负责的是 runtimepath、parser 安装、query 管理。你要把我当成随叫随到的临时工?不行。
要用,就把我常驻。”

README 给了一个 lazy.nvim 的安装示例(核心点在 build = ':TSUpdate'):

1
2
3
4
5
{
'nvim-treesitter/nvim-treesitter',
lazy = false,
build = ':TSUpdate'
}

它在提醒你另一条非常关键的事实:

插件只保证与特定版本的 parser 组合工作(版本信息在 parser 表里)。
升级插件后必须更新 parser(:TSUpdate),最好自动化。

它对“版本一致性”的执念,像一个做基础设施的人:
宁可你麻烦一点,也不要你埋雷。


Setup:它可以零配置运行,但它也给你一个“安装目录”的方向盘

你可以不调用 setup,默认值也能工作。
但如果你希望更明确地控制 parser 与 query 的安装位置,它给出一个最基础的配置骨架:

1
2
3
4
require('nvim-treesitter').setup {
-- Directory to install parsers and queries to (prepended to `runtimepath` to have priority)
install_dir = vim.fn.stdpath('data') .. '/site'
}

它的语气很像在说:

“我不会替你决定所有事情,但我会把关键的那个‘方向盘’放在你手里。”


安装第一批语言 parser:它是异步的,还会教你怎么在脚本里等待它

nvim-treesitter 的安装器像一个高效的调度员:默认异步执行。

你可以这样安装一组语言:

1
require('nvim-treesitter').install { 'rust', 'javascript', 'zig' }

如果你在“启动脚本/引导安装”场景需要同步等待,它甚至把 wait() 的用法写进 README:

1
require('nvim-treesitter').install({ 'rust', 'javascript', 'zig' }):wait(300000) -- wait max. 5 minutes

它不只是给你 API,它在给你“可复制的工程实践”。


Supported languages:它的世界观是“parser + query 才算真支持”

它对“支持某语言”的定义非常严格:

  • 有 parser
  • 有对应功能的 query 文件

缺一不可。

它还把 supported languages 的列表单独维护在页面里,并明确告诉你:

想加新语言、想改 query,请看 contributing guide。

这是一种“生产系统”式的表达:

“我不欢迎随意承诺支持,我欢迎你把支持做完整。”


Supported features:它把能力分清楚、把开关交给你(而且默认不自动启用)

nvim-treesitter 提供了多种功能的 query(高亮、折叠、缩进、注入等),但它强调:

These are not automatically enabled.

它像一个谨慎的系统管理员:

“能力我给你放这了,但你要不要开、开什么范围、怎么开,得你自己决定。”


Highlighting:Neovim 提供高亮,nvim-treesitter提供 query,你负责启动

README 给了一种非常明确的启用方式:在对应 filetype 里调用 vim.treesitter.start()

你可以在 ftplugin/<filetype>.lua 中启用,或者用 FileType autocmd。

示例(按 README 的逻辑):

1
2
3
4
vim.api.nvim_create_autocmd('FileType', {
pattern = { '<filetype>' },
callback = function() vim.treesitter.start() end,
})

它像在对你说:

“我不替你偷偷打开,因为你的配置可能有依赖、有兼容性、也可能需要渐进迁移。
但我把最标准的启动姿势给你了。”


Folds:折叠同样由 Neovim 提供,你只要把 foldexpr/foldmethod 设置好

它把折叠的启用写得非常直给:

1
2
vim.wo[0][0].foldexpr = 'v:lua.vim.treesitter.foldexpr()'
vim.wo[0][0].foldmethod = 'expr'

像一个会把“正确的那两行”贴在你工位上的同事:
“别绕圈,就这两行。”


Indentation:缩进由插件提供,但它自己都承认“实验性”

它对缩进的态度很诚实:实验性。

启用方式(注意引号,README 甚至特地提醒你引号要对):

1
vim.bo.indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()"

它像一个很严格但不装的工程师:

“我敢给你,但我也敢告诉你它还在实验阶段。���用,就得自己承担并验证。”


Injections:多语言文档的注入能力,不需要额外配置

它把 injections 归到 Neovim 的能力里,并直接告诉你:

不需要设置。

像一个熟悉系统边界的人:

“这个不归我管,我只把 query 放好,Neovim 自己会干活。”


命令:它保留了一个“指挥台”,你随时可以去查

README 里给了帮助入口:

  • :h nvim-treesitter-commands

它像对你说:

“我不怕你用,我怕你不用。你要操作,我把命令表给你。”


结尾:nvim-treesitter 的人格——沉默但可靠的语法后勤

它并不追求成为“最闪耀的插件”。
它更像一个在 Neovim 里长期驻守的“语法供应链负责人”:

  • 你写什么语言,它就去找对应 parser;
  • 你想启用什么特性,它就把 query 给你摆好;
  • 你升级了插件,它就盯着你更新 parser,别让版本不一致变成隐形炸弹;
  • 你要脚本化安装,它甚至把等待机制都考虑到了。

它的存在,让 Neovim 的 tree-sitter 能力从“看起来很强”变成“真的可用、真的可维护、真的能扩展到很多语言”。

它不说漂亮话,它只做一件事:
把你的编辑器从“看懂一点点语法”推进到“像读 AST 一样读代码”。

当你下一次打开一个陌生语言的文件,光标落下去,高亮、折叠、注入、结构都像理所当然地运作时——
你会在某个瞬间想起它曾经站在门口说的那三句话。

它不是在抢功劳。
它只是把秩序留在了这里。