Neovimでlazy loadするプラグインのヘルプを読めるようにする
tl;dr
遅延読み込みしているプラグインのヘルプは:help
が引けないという苦しみを、自分なりの方法で解消してみました。
背景
プラグインと遅延読み込み
Vim/Neovimにはプラグインが色々とありますが、プラグインが増えるほどにVim/Neovim自体の起動時間も増えてしまいます。
そこで各種のプラグインマネージャは遅延読み込み(Lazy loading)という仕組みを用意しています。
プラグインが読み込まれるタイミングを任意の「プラグインが必要になったタイミング」に遅延させることで、起動時間を節約しようという企みです。
遅延読み込みとヘルプ
しかし、遅延読み込みにおいては、共通しがちな苦しみとして
未だ読み込まれていないプラグインの:helpが引けない
という問題があります。
プラグインマネージャーはVim/Neovimの機構からは外にあるものですから、Vim/Neovimの標準機能である:help
が引けないのも無理からぬ事です。
プラグインマネージャーがそこまで面倒を見てくれるケースもありますが、そう多くはありません。
例えば、lazy.nvimなどは起票されたIssueがNot plannedとしてCloseされています
「:helpが引ける」状態とは
ところで、:help foobar
と引いたとき、特定のヘルプが表示されるのはどういう仕組みなのでしょうか。
ここではあくまでも雑な誤り含みの解説にとどめますが、概ね次のような仕組みになっています。
-
'runtimepath'
に設定されたパスの一覧を走査する - 各パスの
doc
ディレクトリからtags
ファイルを探す -
tags
ファイルの中からキーワードfoobar
に相当するファイルパスを探す - 見つかったファイルパスを開く
そしてdoc
ディレクトリの中のtags
ファイルは、同梱されたヘルプドキュメント(*.txt
ファイル)から:helptags
コマンド[1]によって生成されています。
多くのプラグインマネージャは、インストールや更新の後で:helptags
コマンドを呼んでtags
ファイルを生成しています。
tags
ファイルとは
tags
ファイル自体は、(ヘルプに限らず)Vimのキーワードでジャンプする機能を支えるものです。[2]
またも非常に雑な誤り含みに説明すると、次のようなテキストファイルになっています。
<対象のキーワード><TAB><ジャンプ先のファイル名><TAB><ファイル内でジャンプする方法>
たとえば、:helptags
自体のタグは次のように記録されています。
:helptags helphelp.txt /*:helptags*
- このファイルはキーワードの昇順に並んでいる必要がある
- ファイル名には多言語対応のため、Language Codeが末尾につく場合がある(例:
tags-ja
)
点には留意が必要です。[2:1]
解決
ここまでの背景を踏まえると、要は遅延読み込みのプラグインも含めて、tags
ファイルをすべて読み込めれば、すべてのヘルプを引くことができるようになることが分かります。
そしてtags
ファイルは単なるテキストファイルですから、読み込まれる場所にマージしてしまえば良いのです。
解決全体は私の設定を見ていただくとして、ここでは重要な要素のみピックアップして解説します。
登録されたプラグインのディレクトリを列挙する
遅延読み込み対象か否かに関わらず、すべてのプラグインを列挙します。
lazy.nvimの場合はrequire("lazy.core.config").plugins
で列挙できます。
各エントリのdir
プロパティにはインストール先のディレクトリパスが入っています。
local plugins = require("lazy.core.config").plugins
for _, p in pairs(plugins) do
local dir = vim.fs.joinpath(p.dir, "doc")
...
end
tags
ファイルをマージする
tags
ファイルを開いて、2番目の列(ファイル名)を絶対パスに変換していきます。
(ここでは後の処理のためにバッファへ書き込んでいます)
local function is_tags_file(source_name, source_type)
if source_type ~= "file" then
return false
end
if source_name == "tags" then
return true
end
if string.find(source_name, "^tags-.+$") then
return true
end
return false
end
...
for fname in vim.iter(vim.fs.dir(dir)):filter(is_tags_file) do
local words = vim.split(line, "\t")
words[2] = vim.fs.joinpath(dir, words[2])
vim.api.nvim_buf_set_lines(buf, 0, 0, true, { vim.fn.join(words, "\t") })
end
sortして保存する
並び順を:sort
コマンドで昇順にしたうえで、Neovim設定ディレクトリのafter/doc
配下に保存します。
after
は'runtimepath'
に登録されているため[3]、これでtags
ファイルとして読み込まれます。
local after_docs_dir = vim.fs.joinpath(vim.fn.stdpath("config") --[[@as string]], "after", "doc")
...
vim.api.nvim_buf_call(buf, function()
vim.cmd.sort("u")
vim.cmd.write({ args = { vim.fs.joinpath(after_docs_dir, name) }, bang = true })
end)
とりまとめ処理を呼び出す
これらの処理を、プラグインのインストールや更新が行われた後で呼び出します。
lazy.nvimの場合はLazyUpdate
、LazyInstall
ユーザーイベントが相当します。
vim.api.nvim_create_autocmd("User", {
group = vim.api.nvim_create_augroup("kyoh86-lazy-help-doc", { clear = true }),
pattern = { "LazyInstall", "LazyUpdate" },
callback = require("kyoh86.lib.lazy_help").collect,
})
まとめ
私のこれに関する設定全体を再掲しておきます。
みなさんの環境や設定に応じて、各要素が参考になれば幸いです。
遅延読み込みと:help
の機能性を両立したい方は、是非試してみてください。
Discussion