❄️

nvim-treesitterのパーサーをnixで管理します

2024/12/05に公開

経緯

現在neovimの管理にnixを利用しているのだが、大量にあるnvim-treesitterのパーサーもnixで管理したいと思いはじめた。しかし安易に導入するとnix式の評価に比較的時間がかかることなどから設定の利便性が下がる。そこでnixでの管理と設定の利便性を両立させるように試行錯誤し、その解決法の1つを導いた。

TL;DR

プラグイン本体をnixで生成し適当なパスに配置、またvimrc以下にプラグインの設定を書きパッケージマネージャー側で2つを読み取りマージする。

環境

home-manager + dpp.vim

どうしたか

導入前

dpp.ts
...
const tomlLoad = tomlExt.actions["load"];

const tomls = await Promise.all(
  [
    { path: "/path/to/plugins.toml", lazy: false },
    { path: "/path/to/plugins_lazy.toml", lazy: true },
  ].map((toml) =>.map((toml) =>
    tomlLoad.callback({
      denops: args.denops,
      context,
      options,
      protocols,
      extOptions: tomlOptions,
      extParams: tomlParams,
      actionParams: {
        path: toml.path,
        options: {
          lazy: toml.lazy,
        },
      },
    })
  ),
) as (Toml | undefined)[];

// merge result
for (const toml of tomls) {
  for (const plugin of toml.plugins ?? []) {
    recordPlugins[plugin.name] = plugin;
  }
}
...
plugins_lazy.toml
[[plugins]]
repo = 'nvim-treesitter/nvim-treesitter'
on_event = ['BufRead', 'CursorHold']
hook_post_update = 'TSUpdate'
lua_source = '''
require'nvim-treesitter.configs'.setup {
  ensure_installed = 'all',
  ...
}
'''

アイデア

2つの両立を考えた結果、nixで~/.cache/dpp/_generated.tomlにプラグインとパーサーを吐き出し、一方plugins_lazy.toml内で設定を記述し、dpp.ts側でマージすることにした。

_generated.tomlを~/.cache/dpp以下に配置した理由

自分の環境では~/dotfiles/vim~/.config/nvimにシンボリックリンクしている。この状態で~/.config/nvim/dpp/_generated.tomlにhome-managerで配置しようとしてもエラーが発生する。一方これを回避するために~/dotfiles/vim/dpp/_generated.tomlに配置することも考えたが、これは美しくない。_generated.tomlは消えても再生成できるなどの理由から~/.cache/dpp以下に配置するのが美しいと考えた。

まずnatsukium氏のdotfilesのnix部分nvim-treesitter部分を参考にして書いてみたが、runtimepathが巨大になってしまう。dpp.vimがruntimepathの圧縮をしているのにこれではもったいないので何らかの方法で1つにまとめたい。

そこでstdenv.mkDerivationを用いてnvim-treesitter本体とパーサーを1つのディレクトリにまとめてそれを配置することにした。

実装

まずnix側に手を加える。home.nix

home.nix
home.file.".cache/dpp/_generated.toml".source =
    let tomlFormat = pkgs.formats.toml { };
    in tomlFormat.generate "_generated.toml" (import ./plugins.nix { inherit pkgs; });

を書き加え、plugins.nix

plugin.nix
{ pkgs }: {
  plugins = [
    {
      name = "nvim-treesitter";
      path =
        let
          ts = pkgs.vimPlugins.nvim-treesitter;
          ts-all = pkgs.symlinkJoin {
            name = "ts-all";
            paths = [ ts ] ++ ts.withAllGrammars.dependencies;
          };
        in
        "${ts-all}";
    }
  ];
}

とする。こうすることにより~/.cache/dpp/_generated.tomlにnvim-treesitter全部入りのパスを指定したプラグインを含むtomlが配置される。これをdpp.vimが読めるようにすればよい。

dpp.ts及びplugins_lazy.tomlに以下の変更を加える。

dpp.ts
   
     { path: "/path/to/plugins.toml", lazy: false },
     { path: "/path/to/plugins_lazy.toml", lazy: true },
+    { path: "~/.cache/dpp/_generated.toml", lazy: false },
   ].map((toml) =>.map((toml) =>
...
   for (const plugin of toml.plugins ?? []) {
-    recordPlugins[plugin.name] = plugin;
+    recordPlugins[plugin.name] = {
+      ...plugin,
+      ...recordPlugins[plugin.name],
+    };
   }
plugins_lazy.toml
 [[plugins]]
-repo = 'nvim-treesitter/nvim-treesitter'
+name = 'nvim-treesitter'
 on_event = ['BufRead', 'CursorHold']
-hook_post_update = 'TSUpdate'
 lua_source = '''
 require'nvim-treesitter.configs'.setup {
-  ensure_installed = 'all',
   ...
 }
 '''

この変更で_generated.tomlを読み、既存設定を優先させるようにマージさせ、プラグイン本体とパーサーはnixのものを使うことができる。

これでbuiltinのパーサーについては完了した。次に外部のパーサーを追加する方法を記す。

外部のパーサーを追加する

ここではtree-sitter-satysfiを追加することにしよう。まずsatysfiのパーサーを作成する。適当な場所に

{ tree-sitter, fetchFromGitHub }: tree-sitter.buildGrammar {
  language = "satysfi";
  version = "5519c547418ecb31ac7d63e64653aed726b5d1c3";
  src = fetchFromGitHub {
    owner = "monaqa";
    repo = "tree-sitter-satysfi";
    rev = "5519c547418ecb31ac7d63e64653aed726b5d1c3";
    fetchSubmodules = false;
    sha256 = "sha256-yei8UHiVChYpx2UyPsDyOd3usItZN68rwu0+VoBtPi0=";
  };
}

(一例、nvfetcherを利用してもよい)を配置し、callPackageをする。これをtree-sitter-satysfiとする。これをplugins.nixに渡し、以下のようにすればよい。

plugin.nix
-            paths = [ ts ] ++ ts.withAllGrammars.dependencies;
+            paths = [ ts (pkgs.neovimUtils.grammarToPlugin tree-sitter-satysfi) ] ++ ts.withAllGrammars.dependencies;

これで新たなパーサーが追加された。どうやらREADMEにあるqueries/以下をコピーする工程は必要ないらしい。

おわりに

nvim-treesitterの設定自体はそこまで頻繁にいじるものでもないので完全にnix側に倒してもよかった。だがこの機会に導入してみたことが他の様々なプラグイン本体をnixで管理したくなった時に役立つのだと思う。

変更履歴

  • 12/06 文言を一部修正。ts-allの際にpkgs.symlinkJoinを使うとよいとのことなのでそれを使用するように。
GitHubで編集を提案

Discussion