nixpkgs Neovim 声明式配置指南:wrapNeovim 包装器、Treesitter 语法解析与插件测试机制
2026/9/17 7:59:02 网站建设 项目流程

nixpkgs Neovim 声明式配置指南:wrapNeovim 包装器、Treesitter 语法解析与插件测试机制

【免费下载链接】nixpkgsNix Packages collection & NixOS项目地址: https://gitcode.com/GitHub_Trending/ni/nixpkgs

本文基于 nixpkgs 官方手册的 Neovim 章节(neovim.section.md),系统讲解如何在 Nix 中声明式配置 Neovim:从neovim-unwrappedneovim两个包的区别入手,深入剖析wrapNeovimwrapNeovimUnstable两套包装器的全部配置项及其底层实现,并结合 Treesitter 语法解析、LuaRocks 插件打包和neovimRequireCheck插件测试三大主题,给出可直接复制、可落地的完整 Nix 配置方案。读完本文后,你将能够构建一个跨机器可复现的 Neovim 环境,并为 Vim/Neovim 插件定义规范的依赖、许可与测试。

一、两个入口:neovim 与 neovim-unwrapped

nixpkgs 中围绕 Neovim 提供两个层级的包,理解它们的分工是所有配置工作的起点:

  • neovim-unwrapped:一个“裸”的 Neovim,不带任何额外配置,最接近你在其他发行版上直接安装 Neovim 的体验。适合需要完全自行接管配置文件的场景,你可以基于它做命令式(imperative)配置。
  • neovim:围绕neovim-unwrapped的包装器(wrapper),内置了一些额外配置,例如自动设置 Python、Node.js、Ruby 等语言 provider(对应:h g:python3_host_prog等选项)。你可以进一步配置这个 wrapper,把常用插件和配置文件固化进去,从而获得跨机器可复现(reproducible)的 Neovim。

包装器的实现位于 wrapper.nix:它本质上是一个stdenv.mkDerivation,通过lndirneovim-unwrapped的内容“软链接”进产物目录(dontUnpack = true+buildPhase中的lndir -silent),再用makeWrapper生成最终的可执行包装脚本,并生成init.lua/rplugin.vim等配置文件。

二、自定义配置:wrapNeovim 与 wrapNeovimUnstable

2.1 两套包装器的定位

围绕原版包pkgs.neovim-unwrapped,nixpkgs 提供两套包装器:

  1. wrapNeovim:历史悠久的包装器,官方文档建议优先使用(the historical one you should use);
  2. wrapNeovimUnstable:定位是未来要取代前者的新包装器。功能更多,但接口尚未稳定(the interface is not stable yet)。

从源码结构看,wrapper.nix 的头部注释明确写道:wrapNeovimUnstable是“a lower-level alternative to wrapNeovim conceived to handle more usecases when wrapping neovim. The interface is being actively worked on so expect breakage.”(接口正在积极打磨,需预期破坏性变更)。

2.2 通过 wrapNeovim 配置 Neovim

历史包装器通过neovim.override传递配置:

neovim.override { withPython3 = true; # see `:h g:python3_host_prog` withNodeJs = false; withRuby = false; configure = { customRC = '' " here your custom viml configuration goes! ''; packages.myVimPackage = with pkgs.vimPlugins; { # See examples below on how to use custom packages. start = [ ]; # If a Vim plugin has a dependency that is not explicitly listed in # `opt`, that dependency will always be added to `start` to avoid confusion. opt = [ ]; }; }; }

要点说明:

  • myVimPackage只是为生成的插件包起的任意名字,你可以取任何喜欢的名称;
  • packages中的插件按start(自动加载)与opt(按需加载)分组;若某个opt插件的依赖没有被显式声明在opt中,该依赖会被自动放入start,以避免混淆;
  • 从 wrapper.nix 的实现可以看到,只要startopt非空,wrapper 就会向最终的可执行文件追加--cmd "set packpath^=..."--cmd "set rtp^=..."两条启动参数,把生成的 pack 目录注入packpathruntimepath头部。

2.3 为 neovim-qt 传入定制后的 Neovim

如果你想用neovim-qt作为图形界面编辑器,可以在 overlay 中对 Neovim 进行 override 后传给neovim-qt,或者直接传入一个被 override 过的 Neovim:

neovim-qt.override { neovim = neovim.override { configure = { customRC = '' " your custom viml configuration ''; }; }; }

2.4 wrapNeovimUnstable 的完整接口

新包装器wrapNeovimUnstable接受一组配置参数。结合 wrapper.nix 中定义的默认值,各选项含义如下:

选项默认值(源码)作用
autoconfiguretrue某些插件要能在 Nix 下工作,必须有一份特定配置(例如sqlite-lua需要设置g:sqlite_clib_path)。nixpkgs 历史上通过补丁修改插件来解决,缺点是可维护性差、加重上游负担。按照约定,这些强制配置以passthru.initLua的形式书签在插件定义中;启用autoconfigure后,包装器会自动拼接这些插件所需的代码片段
autowrapRuntimeDepstrue把插件的运行时依赖追加到PATH。例如rest.nvim需要curl才能工作;启用后curl会被加入你的 Neovim wrapper 可见的PATH,而不是全局PATH
luaRcContent""追加到生成的init.lua中的额外 Lua 代码
neovimRcContentnull由生成的init.lua额外 source 的 vimL 代码
wrapperArgs[ ]透传给makeWrapper调用的额外参数
wrapRctrue由于 Nix 无法写入$HOME,生成的 Neovim 配置通过$VIMINIT环境变量加载,即export VIMINIT='lua dofile("/nix/store/…-init.lua")'。副作用是 Neovim 不再 source$XDG_CONFIG_HOME/nvim下的init.lua(见 Neovim 官方:help startup文档第 7 条)。如果你要自己生成 wrapper,可以关闭它;生成的 vimscript 初始化代码仍可通过neovim.passthru.initRc复用
plugins[ ]要加入 wrapper 的插件列表
extraLuaPackages(_: [ ])传给lua.withPackages的函数
extraPython3Packages(_: [ ])传给python3.withPackages的函数
withPython3/withNodeJs/withRuby/withPerl均为false控制是否启用对应的 Neovim provider(见:h provider
vimAlias/viAlias均为false控制是否把vimvi二进制符号链接到nvim
extraName""追加到包名与 derivation 名的字符串

此外,源码中还有几个文档未逐一列举但实际存在的行为:

  • withPython2仍作为参数签名存在,但传入即抛出异常:Python2 provider 支持已从 wrapper 中移除(见 wrapper.nix 的assert ... -> throw ...);
  • 旧选项packpathDirs已废弃,传入同样会直接throw,需改用plugins
  • Ruby provider 会构建一个bundlerEnv(gemdir 指向 ruby_provider),并把GEM_HOME通过makeWrapper设置给 wrapper;
  • wrapper 的checkPhase会执行$out/bin/nvim -i NONE -e +quitall!做一次冒烟启动测试。

一个使用wrapNeovimUnstable的完整示例:

wrapNeovimUnstable neovim-unwrapped { autoconfigure = true; autowrapRuntimeDeps = true; luaRcContent = '' vim.o.sessionoptions = 'buffers,curdir,help,tabpages,winsize,winpos,localoptions' vim.g.mapleader = ' ' vim.g.maplocalleader = ' ' vim.opt.smoothscroll = true vim.opt.colorcolumn = { 100 } vim.opt.termguicolors = true ''; # plugins accepts a list of either plugins or attribute sets containing: # { plugin = ...; config = ...; type = "viml"|"lua"; } (type defaults to "viml") plugins = with vimPlugins; [ { plugin = vim-obsession; config = '' map <Leader>$ <Cmd>Obsession<CR> ''; } { plugin = grug-far-nvim; type = "lua"; config = '' require('grug-far').setup({ startInInsertMode = false, }) ''; } (nvim-treesitter.withPlugins (p: [ p.nix p.python ])) hex-nvim ]; extraLuaPackages = lp: [ lp.mpack ]; withPython3 = true; withNodeJs = false; withRuby = false; }

从实现上看(wrapper.nix):plugins中的每一项若为{ plugin; config; type; }属性集,其config会按type(默认"viml")分别汇入userPluginConfigs.vimluserPluginConfigs.lua,最终由neovimUtils.makeVimPackageInfo汇总;vimL 部分会被写成独立的init.vim文本并在init.luavim.cmd.sourceplugins中声明的 Lua 依赖也会并入extraLuaPackages,经lua.withPackages生成package.path/package.cpath注入代码。

你也可以用nix repl探索并覆盖这些选项,例如:

neovim.override { autowrapRuntimeDeps = false; }

三、插件的特定事项

3.1 插件的必需配置片段(passthru.initLua)

有些插件必须配置特定选项才能工作。nixpkgs 选择不去补丁(patch)这些插件,而是把必需配置暴露在PLUGIN.passthru.initLua下(Neovim 插件)。例如unicode-vim需要指向 Unicode 数据库的路径,因此vimPlugins.unicode-vim.passthru.initLua中暴露了片段vim.g.Unicode_data_directory="${self.unicode-vim}/autoload/unicode"。这正是上文autoconfigure = true自动拼接的素材来源。

3.2 插件许可证覆盖

自动生成的 Vim 与 Neovim 插件在可能时从 GitHub 的许可证元数据获取meta.license。但有些上游仓库没有暴露 GitHub 可检测的许可证文件,或仅在 README 中提及许可证。这种情况需要在 overrides.nix 中手动添加meta.license覆盖。例如上游声明插件使用 Vim license 但 GitHub 未能检测到时:

{ foo-nvim = super.foo-nvim.overrideAttrs (old: { meta = old.meta // { # README says this plugin is distributed under the Vim license. license = lib.licenses.vim; }; }); }

四、基于 LuaRocks 的插件

为了自动化处理插件依赖,一些 Neovim 插件把自己的包发布到了 LuaRocks。从长期看这减少了 nixpkgs 维护者的工作量,因为依赖会被自动更新。其后果是:这些插件先以 nixpkgs 的 Lua 包 形式打包,再经由buildNeovimPlugin转换成 Vim 插件。这一步转换是必要的,因为 Neovim 期望 Lua 目录位于顶层,而 LuaRocks 默认把 Lua 安装到各种子目录中。

实现位于 build-neovim-plugin.nix。例如:

{ rtp-nvim = neovimUtils.buildNeovimPlugin { luaAttr = luaPackages.rtp-nvim; }; }

维护要点:

  • 更新这类包时,应使用 Lua 的 updater 而不是 Vim 的 updater;
  • 要把一个 Lua 包加入vimPlugins集合,把它加入 luaPackagePlugins.nix 中的luarocksPackageNames列表即可。

从当前仓库的 luaPackagePlugins.nix 看,该列表已包含gitsigns-nvimlualine-nvimluasnipneotestnvim-cmpplenary-nvimrest-nvimrtp-nvimtelescope-nvimoil-nvimrustaceanvim等 40 余个包,且通过lib.genAttrs统一套用buildNeovimPlugin { luaAttr = luaPackages.${name}; }的生成模式——新增一个 LuaRocks 包只需在排序列表中加一行。

五、Treesitter

Treesitter 为 Neovim 提供语法解析能力,支撑高级语法高亮、代码折叠、精确缩进等特性。多数 Neovim 用户通过nvim-treesitter插件来管理 Treesitter,它提供:

  • 管理 grammar 与 query 的命令,例如:TSInstall,会在运行时下载、编译并安装它们;
  • 针对拥有indents.scmquery 的语言的自定义缩进实现(:h indentexpr)。

这些特性构建在 Neovim 内置的 Treesitter 能力之上。而在 nixpkgs 中,grammar 与 query 是预先编译并单独打包的,这带来三点直接收益:

  • 无需安装nvim-treesitter即可使用 Treesitter 功能;
  • 只有当你需要nvim-treesitter的自定义缩进表达式时,才需要它;
  • 依赖 grammar 的插件可以直接引用它们。

5.1 方案一:nvim-treesitter + 预编译 grammar

适合希望使用nvim-treesitter自定义缩进表达式的场景。用nvim-treesitter.withPlugins安装插件并挂载一组预编译 grammar:

(pkgs.neovim.override { configure = { packages.myPlugins = with pkgs.vimPlugins; { start = [ (nvim-treesitter.withPlugins ( plugins: with plugins; [ nix python ] )) ]; }; }; })

如需启用 nixpkgs 打包的全部 grammar,使用pkgs.vimPlugins.nvim-treesitter.withAllGrammars

关于nvim-treesitter本身的配置(语法高亮、缩进、折叠等),请参考插件自带的:help nvim-treesitter-quickstart文档。

注意:使用 Nix 管理的 grammar 时,:checkhealth nvim-treesitter会报告“没有安装任何语言”。这是预期行为,因为nvim-treesitter的健康检查只搜索它自己配置的安装目录,而 Nix 把 grammar 安装到 Nix store 并加入runtimepath验证 Nix 管理的 parser 与 query,请改用:checkhealth vim.treesitter

5.2 方案二:独立 grammar 与 query(最小依赖)

如果你追求最小依赖、且不需要nvim-treesitter的自定义缩进表达式,可以直接安装独立的 parser 与 query,完全不装nvim-treesitter

(pkgs.neovim.override { configure = { packages.myPlugins = with pkgs.vimPlugins; let # Select the grammars you need treesitter-grammars = with nvim-treesitter-parsers; [ nix python ]; # Queries are needed for treesitter based syntax highlighting and folds. treesitter-queries = map (p: p.associatedQuery) treesitter-grammars; in { start = [ # regular plugins ] ++ treesitter-grammars ++ treesitter-queries; }; }; })

每个 grammar 派生都带有associatedQuery属性指向配套的 query 包,map (p: p.associatedQuery) treesitter-grammars可以批量取出;query 是 Treesitter 语法高亮与折叠所必需的。

5.3 方案三:WASM parser 与 query

当 Neovim 以 Wasmtime 支持构建时,可以加载 WASM parser。在 nixpkgs 中,WASM parser 插件来自wasm32-wasip1交叉编译包集合:

(pkgs.wrapNeovim (pkgs.neovim-unwrapped.override { wasmSupport = true; }) { configure = { packages.myPlugins = with pkgs.pkgsCross.wasm32-wasip1.vimPlugins; let # Select the grammars you need treesitter-grammars = with nvim-treesitter-parsers; [ nix python ]; # Queries are needed for treesitter based syntax highlighting and folds. treesitter-queries = map (p: p.associatedQuery) treesitter-grammars; in { start = [ # regular plugins ] ++ treesitter-grammars ++ treesitter-queries; }; }; })

关键约束与验证方式:

  • 不要为同一种语言同时安装原生与 WASM parser。例如同时安装pkgs.vimPlugins.nvim-treesitter-parsers.nixpkgs.pkgsCross.wasm32-wasip1.vimPlugins.nvim-treesitter-parsers.nix是非法的,因为 Neovim 只会加载runtimepath上找到的第一个parser/nix.*
  • 使用:checkhealth vim.treesitter验证 Nix 管理的 WASM parser。

安装好 grammar 后,可以在FileType自动命令或ftplugin/<language>.lua脚本中为对应语法启用 Treesitter 功能:

vim.api.nvim_create_autocmd('FileType', { pattern = { 'rust', 'javascript', 'zig' }, callback = function(ev) local bufnr = ev.buf -- Enable treesitter syntax highlighting and parsing for the current buffer -- (Requires queries to be installed) vim.treesitter.start(bufnr) -- Enable treesitter based code folding -- (folds are window-scoped, not buffer-scoped) -- (Requires queries to be installed) vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()' vim.wo.foldmethod = 'expr' end, })

5.4 把 grammar 声明为插件依赖

一些 Neovim 插件(如neotest适配器、markdoc-nvimhurl-nvim)依赖 Treesitter grammar,这些依赖通常在插件的 override 中声明。

重要:某些插件 README 可能会声称它们依赖nvim-treesitter但绝大多数情况下并非如此nvim-treesitter已经不再提供可供其他插件调用的 Lua 模块 API。绝大多数情况下,这些插件:

  • 依赖的是 parser(而不是nvim-treesitter或它的 query);
  • 自带 query(以*.scm文件形式,或硬编码在 Lua 源码中)。

添加 grammar 作为插件依赖,向 overrides.nix 加入 override:

{ foo-nvim = super.foo-nvim.overrideAttrs { dependencies = with self.nvim-treesitter-parsers; [ markdown markdown_inline html ]; }; }

如果某个插件确实依赖nvim-treesitter的旧模块 API,可以把nvim-treesitter-legacy加为依赖:

{ foo-legacy-nvim = super.foo-legacy-nvim.overrideAttrs { dependencies = with self; [ nvim-treesitter-legacy nvim-treesitter-parsers.nix ]; }; }

警告nvim-treesitter-legacy仅存在于过渡期,计划在 26.11 移除。若某个 Neovim 配置同时包含nvim-treesitternvim-treesitter-legacy,它将评估失败。仓库中nvim-treesitternvim-treesitter-legacy分别位于 nvim-treesitter/ 与 nvim-treesitter-legacy/ 目录,二者各自维护generated.nix/overrides.nix

六、测试 Neovim 插件:neovimRequireCheck

6.1 冒烟加载测试

neovimRequireCheck是一个简单的测试:检查 Neovim 能否无错误地require各 Lua 模块,这通常足以捕获缺失依赖。它接受单个模块名字符串,或模块名字符串列表:

  • nvimRequireCheck = MODULE;
  • nvimRequireCheck = [ MODULE1 MODULE2 ];

当未显式指定nvimRequireCheck时,构建系统会搜索插件目录中的 Lua 模块并尝试加载,作为一次快速的冒烟测试,捕获明显的依赖错误。检查 hook 会在任何模块无法加载时使构建失败,从而促使维护者检查日志定位潜在问题。

若只想检查某个特定模块,手动把它加入插件定义的 overrides.nix:

{ gitsigns-nvim = super.gitsigns-nvim.overrideAttrs { dependencies = [ self.plenary-nvim ]; nvimRequireCheck = "gitsigns"; }; }

6.2 跳过特定模块(nvimSkipModules)

有些插件的 Lua 模块需要用户配置才能正常工作,或包含我们不希望 require 的可选模块。可以用nvimSkipModules跳过这些模块,与nvimRequireCheck类似,它接受字符串列表:

  • nvimSkipModules = [ MODULE1 MODULE2 ];
{ asyncrun-vim = super.asyncrun-vim.overrideAttrs { nvimSkipModules = [ # vim plugin with optional toggleterm integration "asyncrun.toggleterm" "asyncrun.toggleterm2" ]; }; }

6.3 完全禁用检查(doCheck = false)

在少数情况下,我们不想真正测试加载某个插件的 Lua 模块,此时可以用doCheck = false;禁用neovimRequireCheck,同样通过 overrides.nix 手动添加:

{ vim-test = super.vim-test.overrideAttrs { # Vim plugin with a test lua file doCheck = false; }; }

七、小结:从文档到仓库的索引

主题关键文件(仓库相对路径)
本文档主体doc/languages-frameworks/neovim.section.md
wrapNeovimUnstable实现(默认值、VIMINIT、rplugin 生成)pkgs/applications/editors/neovim/wrapper.nix
包装器工具函数(packDir / makeVimPackageInfo)pkgs/applications/editors/neovim/utils.nix
LuaRocks 包 → Neovim 插件的转换pkgs/applications/editors/neovim/build-neovim-plugin.nix
LuaRocks 插件名清单pkgs/applications/editors/vim/plugins/luaPackagePlugins.nix
插件 override(许可证、依赖、requireCheck)pkgs/applications/editors/vim/plugins/overrides.nix
Treesitter 插件与 grammar 生成pkgs/applications/editors/vim/plugins/nvim-treesitter/generated.nix

按照本文的路线:先用neovim.override/wrapNeovimUnstable固化 provider 与插件集,再用passthru.initLuaautowrapRuntimeDeps处理插件的强制配置与运行时依赖,随后按需选择三种 Treesitter 方案之一接入 grammar 与 query,最后用nvimRequireCheck/nvimSkipModules/doCheck为插件定义建立可靠的构建期质量门,即可得到一份既能自解释、又能在任意 Nix 机器上复现的 Neovim 工程配置。

【免费下载链接】nixpkgsNix Packages collection & NixOS项目地址: https://gitcode.com/GitHub_Trending/ni/nixpkgs

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询