年末の大掃除としてnvim-treesitterのmainブランチへ移行する
Neovim 0.10から0.11へのアップデートに伴い、nvim-treesitter の役割が大きく変わりました。Neovim 0.11ではtree-sitterの機能が本体に取り込まれ、それに合わせてnvim-treesitterも大規模なリファクタリングが行われました。これまでmasterブランチで提供されていた多くの機能が本体に統合されたことで、nvim-treesitterは必要最小限の機能に絞り込まれ、mainブランチとして生まれ変わっています。ということに年末のこの段階でようやく気づいたので、年末の大掃除と思って移行します。今回はその備忘録です。
今回行った設定変更はdotfilesの一部として公開しています。
Neovim 0.11で何が変わったのか
Neovim 0.11では、treesitterを利用したハイライトや折り畳みについて、vim.treesitter.start()などのネイティブAPIが提供されるようになりました。ただし、これらの機能は自動的には有効化されないため、明示的に有効化する必要があります。
これらの変更により、nvim-treesitterプラグインが担う役割は大幅に縮小されました。以前は「tree-sitterを使うための包括的なインターフェース」だったものが、「tree-sitter機能のためのクエリとパーサーの管理」へと変化しています。masterブランチの開発は凍結され、後方互換性のためだけに維持されるようになっています。
実際の設定方法
ここからは実際に行った設定を順を追って説明します。前提として、Neovim 0.11以降を使用していることが必要です。また、プラグイン管理にはlazy.nvimを使用しているのでその前提での説明になります。
1. tree-sitter-cliのインストール
まず、tree-sitter-cli(バージョン0.26.1以降)をインストールします。公式ドキュメントに記載されている以下の方法でインストールできます。
# cargoの場合
cargo install --locked tree-sitter-cli
# npmの場合
npm install tree-sitter-cliHomebrewを使う場合は以下のコマンドでインストールできます。公式ドキュメントには記載がないですが、brewのサイトには記載がありました。
# Homebrewの場合
brew install tree-sitter私はこれを忘れていて、後述する:TSInstallの実行時に失敗して小一時間を溶かしました。失敗してもめぼしいエラーが出なかったため戸惑いました。nvim-treesitterのREADMEには初めの方にきちんと書かれているので、忘れずにインストールしておきましょう(1敗)。
2. nvim-treesitterでmainブランチを指定
次に、nvim-treesitterの設定でmainブランチを使うように変更します。
-- lua/plugins/treesitter/init.lua
return {
{
"nvim-treesitter/nvim-treesitter",
build = ":TSUpdate",
branch = "main", -- Neovim 0.11に対応したmainブランチを使用
},
}設定可能なオプションはparser / queryのインストールディレクトリの場所のみです。デフォルトのままで問題なければsetup()を呼ぶ必要はありません。
require('nvim-treesitter').setup({
install_dir = vim.fn.stdpath('data') .. '/site'
})3. ハイライト、インデントなどの設定
masterブランチではハイライトやインデントなどの設定を nvim-treesitter.configs.setup() の中で行っていましたが、mainブランチではconfigsモジュールごと削除されています。これらの設定はNeovim本体に統合されたAPIを用いて設定します。
ハイライト
ハイライトを有効化するには、各ファイルタイプでvim.treesitter.start()を呼び出す必要があります
nvim-treesitter/nvim-treesitter#highlighting-- ハイライトを有効化する例
vim.api.nvim_create_autocmd('FileType', {
pattern = { 'lua', 'python', 'javascript', 'typescript', 'rust' }, -- 必要な言語を指定
callback = function()
vim.treesitter.start()
end,
})もしくは、ftpluginディレクトリの各言語用の設定ファイルに追加してもいいかもしれませんね。
-- ~/.config/nvim/ftplugin/lua.lua
vim.treesitter.start()折りたたみ(fold)
こちらも同様にFiletypeのautocommand、もしくはftpluginとして以下の設定を追加しましょう。
nvim-treesitter/nvim-treesitter#foldsvim.wo[0][0].foldexpr = 'v:lua.vim.treesitter.foldexpr()'
vim.wo[0][0].foldmethod = 'expr'インデント
こちらはnvim-treesitterプラグインが提供していますが、現時点ではまだ実験的機能の段階です。有効にするには以下の設定が必要です。
nvim-treesitter/nvim-treesitter#indentationvim.bo.indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()"Injection
Markdownなどで、ファイルの途中に他の言語が挿入される時にハイライトなどを切り替える機能です。こちらに関しては特別な設定は不要で、自動で有効になるようになっています。
4. パーサーのインストール
最後に、これまでの設定内容が実際に適用されるように、必要なparser / queryを:TSInstallコマンドを使ってインストールしておきましょう。
" 手動でパーサーをインストール
:TSInstall lua typescript javascript必要に応じて、提供されているAPIを直接実行して一括インストールすることも可能です。
-- 必要なパーサーを一括インストール
require('nvim-treesitter').install({ 'lua', 'typescript', 'javascript' })いちいち追加していくのが面倒なそこのあなたのために、一括で全てインストールしてしまうことも可能です。
:TSInstall allこれらのparser / queryは一度インストールされれば何度も実行する必要はないので、最初に全てインストールしてしまうのもありかもしれませんね。
関連プラグイン設定の変更
nvim-treesitterのmainブランチに移行すると、これに依存するプラグインの設定方法も変わります。以前はnvim-treesitter.configs.setup()の中でまとめて設定できましたが、移行後は個別のプラグインとして設定する必要があります。私が使っているプラグインの設定例を載せておきます。
nvim-treesitter-textobjects
こちらもnvim-treesitterと同様にmasterブランチが凍結され、mainブランチに切り替わっています。当然ですが、nvim-treesitterとnvim-treesitter-textobjectsの両方がmainブランチで揃っていないと正しく動作しません。
-- lua/plugins/treesitter/init.lua(続き)
return {
-- ... nvim-treesitterの設定 ...
{
"nvim-treesitter/nvim-treesitter-textobjects",
branch = "main", -- textobjectsもmainブランチを指定
dependencies = { "nvim-treesitter/nvim-treesitter" },
event = { "BufNewFile", "BufRead" },
config = function()
-- 設定を別ファイルに分割
require("plugins.configs.treesitter.textobj.select").setup()
require("plugins.configs.treesitter.textobj.move").setup()
require("plugins.configs.treesitter.textobj.swap").setup()
require("plugins.configs.treesitter.textobj.repeat").setup()
end,
},
}キーマッピングも、nvim-treesitter.configsモジュールの廃止に伴い、mainブランチでは明示的にvim.keymap.set()で定義する必要があります。
-- lua/plugins/treesitter/textobj/select.lua
local M = {}
M.setup = function()
local select = require("nvim-treesitter-textobjects.select")
local mode = { "x", "o" }
-- function
vim.keymap.set(mode, "af", function()
select.select_textobject("@function.outer", "textobjects")
end)
vim.keymap.set(mode, "if", function()
select.select_textobject("@function.inner", "textobjects")
end)
-- class
vim.keymap.set(mode, "aC", function()
select.select_textobject("@class.outer", "textobjects")
end)
vim.keymap.set(mode, "iC", function()
select.select_textobject("@class.inner", "textobjects")
end)
-- conditional, loop, statementなども同様に定義
end
return M一見すると冗長になったように見えますが、標準のキーマッピング設定に統一されることや、条件分岐やカスタマイズがしやすいなどのメリットが大きいように思います。
自分はプラグインで提供されているselect、move、swap、repeatの4つの機能ごとに設定ファイルを分割し、それぞれで必要なキーマッピングを定義しています。
lua/plugins/treesitter/
├── init.lua # プラグイン定義
└── textobj/
├── select.lua # テキストオブジェクトの選択(af, if など)
├── move.lua # テキストオブジェクト間の移動(]m, [m など)
├── swap.lua # テキストオブジェクトの入れ替え()f, (f など)
└── repeat.lua # 移動の繰り返し(;, , など)この構造により、例えば「selectの設定だけ見たい」という時に該当ファイルを開けばいいので、メンテナンス性も上がるかなと思っています。一方で関数に関連するマッピングが複数ファイルに分かれてしまうので、賛否分かれるところではあるでしょう。個人の設定ファイルなので、自分がわかりやすければいいのですが、しばらく経ったら変えるかもしれません。
RRethy/nvim-treesitter-endwise
こちらはプラグイン側で対応を吸収してくれているので、大きな変更はありません。configsモジュールの廃止に伴って個別に初期化処理を行う必要があるので、その対応だけ行いましょう。
-- lua/plugins/treesitter/init.lua(続き)
return {
-- ... nvim-treesitterの設定 ...
{
"RRethy/nvim-treesitter-endwise",
event = { "InsertEnter" },
dependencies = { "nvim-treesitter/nvim-treesitter" },
config = function()
require("nvim-treesitter-endwise").init()
end,
},
}おわりに
年末ギリギリで気づきましたがなんとか間に合いました。もう少し感度を高めておかないとダメですね。