2022/12/3 本日公開の 爆速で起動する Neovim を packer.nvim で作る - Qiita に合わせ、最新の情報を含めて修正しました。
0. 前置き
昔々、NeoBundle から dein.vim に乗り換える話を書きました。
しかし 5 年も経てば世の中色々変わります。Neovim も色々変わりましたが、最近一番ホットな話題といえばなんと言っても Lua でしょう。プラグインを書くための言語としてだけではなく、設定ファイルである init.vim
すら Lua で書くことが可能になったのです。
(クソコラみたいな画像ですね……)
このビッグウェーブに乗るべく、今までプラグインマネージャーとして使ってきた dein.vim から、packer.nvim に乗り換えてみました。packer.nvim は目下 Neovim Lua 界で一番ポピュラーなプラグインマネージャです。その設定方法から、ディープな使い方、前回紹介した dein.vim との比較まで書いて行きます。
なお、この記事では Neovim の設定を Lua で書く方法について説明しません。下記のガイドが非常によく纏まっておりますので、困ったらこちらを読んでください。
Getting started using Lua in Neovim(日本語版)
1. そもそも packer.nvim とは
packer.nvim は以下のような点を特徴としています。
1.1 全て Lua で書かれてる。
そのため……というわけでもありませんが、packer.nvim 自体の設定も Lua で書く必要があります。とはいえ最低限の設定ならプラグイン名を羅列するだけです。
1.2 Vim8 / Neovim 標準のプラグイン機能を利用している。
Vim8 / Neovim には標準でプラグイン管理機能(以下では「Vim パッケージ」と呼びます)が付属しています。ただこれ自体は非常に原始的で、以下のような機能しかありません。
-
start
ディレクトリに置いたプラグイン(以下 start プラグインと呼びます)を置いておくと、起動時に読み込んでくれる。 -
opt
ディレクトリに置いたプラグイン(以下 opt プラグインと呼びます)は起動後にpackadd
コマンドで遅延読み込みできる。
packer.nvim はこれに以下のような機能を追加してプラグイン管理を助けてくれます。
- プラグインのインストール・更新・削除をコマンド一つで実行できる。
- opt プラグインを様々な契機で遅延読み込みするタイミングを記述できる。
- プラグインごとの設定を(Lua で)記述でき、「コンパイル」することで Neovim 起動時に高速に読み込むことができる。
どちらかというとシンプルで最小限の機能だけを実装してあり、とにかく多機能でパワフルな dein.vim と比べると見劣りする点もあります。それは追々述べていくことにしましょう。
2. packer.nvim の導入方法
2.1 最新版の Neovim をインストールする
これは packer.nvim に限らないことですが、Lua 製プラグインは Neovim の HEAD(最新開発版)を前提としていることが多いです。リリースページからバイナリをインストールするか、Homebrew などを使ってコンパイルしましょう。
brew install neovim --HEAD
# 更新時はこれだけで OK
brew reinstall neovim
2.2 packer.nvim のインストールと最低限の設定
README 読んで貰えば一目瞭然なのですが一応書いておきます。
# opt ディレクトリに packer.nvim をクローン
git clone https://github.com/wbthomason/packer.nvim \
~/.local/share/nvim/site/pack/packer/opt/packer.nvim
# 設定ファイルに追記
nvim ~/.config/nvim/init.lua
require "plugins"
packer.nvim の設定は init.lua
に直接書くこともできますが、後述の理由から別ファイルに分けることをお勧めします。上記のように書いておくと、~/.config/nvim/lua/plugins.lua
を読み込んでくれます。
-
~/.config/nvim/lua
ディレクトリなど、&runtimepath
+/lua
ディレクトリに置かれたファイルはrequire "hogehoge"
という単純な書式で読み込むことができます。
vim.cmd.packadd "packer.nvim"
require("packer").startup(function()
-- 起動時に読み込むプラグインは名前を書くだけです
use "tpope/vim-fugitive"
use "tpope/vim-repeat"
-- opt オプションを付けると遅延読み込みになります。
-- この場合は opt だけで読み込む契機を指定していないため、
-- packadd コマンドを叩かない限り読み込まれることはありません。
use { "wbthomason/packer.nvim", opt = true }
-- packer.nvim 自体を遅延読み込みにする理由はまた後ほど。
-- コマンドを叩いたときに読み込む。
use { "rhysd/git-messenger.vim", opt = true, cmd = { "GitMessenger" } }
-- 特定のイベントで読み込む
use { "tpope/vim-unimpaired", opt = true, event = { "FocusLost", "CursorHold" } }
-- 特定のファイルタイプのファイルを開いたら読み込む
use { "fatih/vim-go", opt = true, ft = { "go" } }
-- 特定のキーを叩いたら読み込む
-- この例ではノーマルモードの <CR> にマッピングしていますが、
-- モードを指定する場合はテーブルを入れ子にします。
-- keys = {
-- { "n", "<CR>" },
-- { "v", "<CR>" },
-- }
use {
"arecarn/vim-fold-cycle",
opt = true,
keys = { "<CR>" },
}
-- 特定の VimL 関数を呼ぶと読み込む
-- この例だと、任意の場所で Artify("hoge", "bold") のように呼び出された時に、
-- このプラグインも読み込まれます。
use { "sainnhe/artify.vim", opt = true, fn = { "Artify" } }
-- 実は opt = true は省略できます。読み込む契機(この例では cmd)を指定すると、
-- 自動的に遅延読み込みとみなされます。
use {
"npxbr/glow.nvim",
cmd = { "Glow", "GlowInstall" },
-- run オプションを指定すると、インストール時・更新時に
-- 実行するコマンドを指定できます。
run = [[:GlowInstall]],
-- 先頭に : がついていないなら bash -c "..." で実行されます。
-- run = [[npm install]],
-- 関数も指定可能です。
-- run = function()
-- vim.cmd.GlowInstall()
-- end,
}
-- 条件が真の時のみ読み込みます。条件は起動時に毎回判定されます。
use {
"thinca/vim-fontzoom",
cond = [[vim.fn.has"gui" == 1]], -- GUI の時のみ読み込む。
-- 関数も指定できます。
-- conf = function()
-- return vim.fn.has "gui" == 1
-- end,
}
-- 依存関係も管理できます。vim-prettyprint は capture.vim に依存しています。
use {
"tyru/capture.vim",
requires = {
{ "thinca/vim-prettyprint" },
},
}
end)
基本的な使い方を羅列してみました。この時点で Neovim を起動しても、特に何も表示は変わりません。設定ファイルを書き換えた後は以下のコマンドを叩いてプラグインをインストールします。
:PackerInstall
さらに、設定ファイルを「コンパイル」します。PackerCompile
コマンドにより、~/.config/nvim/plugin/packer_compiled.vim
が生成されます。
:PackerCompile
これは設定ファイルを更新するたびに叩く必要がありますので、ついでに autocmd
を設定しておきましょう。
vim.api.nvim_create_autocmd("BufWritePost", {
pattern = { "plugins.lua" },
command = "PackerCompile",
})
plugins.lua
を init.lua
と分けて記述したのはこれが理由です。
2.3 コマンド一覧
packer.nvim をインストールすると以下のコマンドが使えるようになります。
PackerInstall
- プラグインをインストールする。
PackerUpdate
- 追加されたプラグインをインストールし、既存のものは更新する。
PackerClean
- 必要なくなったプラグインを削除する。
PackerSync
-
PackerClean
の後にPackerUpdate
を行う。 PackerCompile
- 設定ファイルを「コンパイル」する。
簡単ですね。一度設定した後は PackerSync
を定期的に叩くだけでプラグインは最新のものになります。
3. プラグインの設定を書く
多くのプラグインは様々な設定をカスタマイズして利用します。このカスタマイズスクリプトを書く方法が 2 つ(config
と setup
)あり、これらを使い分けるには packer.nvim の元になっている Vim パッケージについて知る必要があります。
3.1 Vim パッケージの仕組み
Neovim 起動時に Vim パッケージによって管理されたプラグインやスクリプトは以下の順序で読み込まれます(完全なリストはこちら)。Vim パッケージは Vim8 / Neovim 組み込みの機能ですので、この順序は packer.nvim とは直接関係ありません。
-
init.lua
とそれがrequire
するもの。 - start プラグイン。
- その他、
&runtimepath
内のプラグインスクリプト。- この中には
PackerCompile
によって生成される~/.config/nvim/plugin/packer_compiled.vim
を含みます。
- この中には
- opt プラグインのうち、
setup
オプションのみが指定されたもの(後述)。- ここだけは packer.nvim による拡張です。
-
VimEnter
イベントによって読み込まれるもの。- ここで Neovim の起動完了です。
- その後、
cmd
やfn
その他の設定によってpackadd
コマンドされるopt
プラグインたち。
キモは 3. の Vim スクリプトです。この中では各プラグインの設定を行なっているのですが、この内容を記述するのが config
, setup
オプションです。
3.2 config
オプションの使い所
プラグインを読み込んだ後に設定を適用するには config
オプションを使います。
use {
"neovim/nvim-lspconfig",
config = function()
require("lspconfig").tsserver.setup {}
end,
},
この例では nvim-lspconfig
が start
プラグインとして、つまり、Neovim 起動時に読み込まれています。require "lspconfig"
する必要があるため、プラグインの設定は config
オプションを使ってプラグインの読み込み後に行います。
3.3 setup
オプションの使い所
プラグインを読み込む前に設定を適用するには setup
オプションを使います。
use {
"dhruvasagar/vim-table-mode",
setup = function()
vim.g.table_mode_corner = "|"
end,
}
vim-table-mode
プラグインは読み込まれた時にグローバル変数(g:table_mode_corner
など)を見て動作を変えます。このような場合 config
オプションではすでに遅いので setup
オプションを使います。
注意しなくてはいけないのは、setup
オプションを使う場合、そのプラグインは必ず opt
プラグインになる(つまり、遅延読み込みになる)ということです。start
プラグインは packer_compiled.vim
より先に読み込まれるため、config
オプションでは間に合わないのです。
ただし今回の場合のように setup
オプションだけを指定した場合、packer.nvim はこれを特別扱いします。opt
プラグインであるにも関わらず、packer_compiled.vim
の後に自動的に読み込んでくれるのです。そのため、このままの設定でも起動後すぐにこのプラグインが使えます(例えば :TableModeToggle
とか)。
3.4 config
オプションと setup
オプション双方を使った例
config
と setup
はもちろん同時に使えます。この場合はプラグインの読み込み前と後にスクリプトを実行することができます(もちろん opt
プラグインになります)。
use {
"lambdalisue/vim-gista",
cmd = {"Gista"},
setup = function()
vim.keymap.set("n", "gl", "<Cmd>Gista list<CR>")
end,
config = function()
vim.fn["gista#client#register"]("GHE", "https://github.example.com/api/v3")
end,
}
vim-gista は GitHub の gist を操作するためのプラグインです。Gista
コマンドを叩くためのマッピング gl
を定義しているのですが、このマッピングは当然プラグイン読み込み前に行う(つまり setup
オプションを使う)必要があります。
加えて、vim-gista には GitHub Enterprise の特殊なドメイン(この場合は github.example.com
)にアクセスする機能もあります。この設定はプラグインの読み込み後、gista#client#register()
という関数を呼び出すことで行う必要があります。このために config
も必要です。
4. さらに複雑な使い方
今まで書いてきたことは公式の README にも大体載っています。ここからはそこにないディープな使い方について書いていきます。
4.1 Filetype プラグインは遅延読み込みしてはいけない
2022/12/3 この節は完全に古くなってしまいました。新しく書いた記事に書いている通り、FileType プラグインは遅延読み込みできます。FileType プラグインの扱いについても解説していますので詳しくはそちらを読んでください。
4.2 Colorscheme プラグインは opt プラグイン(遅延読み込み)に設定できる
これも Neovim のドキュメントに記載がありますが、:colorscheme
コマンドが実行された際に検索されるディレクトリは start
, opt
全てのプラグインが対象です。そのため、読み込まれるタイミングなどは気にせずに opt = true
してしまって良いです。
-- 特に読み込みタイミングは指定しない
use { "arcticicestudio/nord-vim", opt = true }
この状態でも :colorscheme nord
で Colorscheme が読み込まれます。
4.3 packer.nvim 自体を遅延読み込みする
今までの設定例では init.lua
の冒頭(か、そこから require
したファイル)で packer.nvim をロードし、それから各種設定を行なっていました。
vim.cmd.packadd "packer.nvim"
require("packer").startup(function()
use { "wbthomason/packer.nvim", opt = true }
...
end)
しかし packer.nvim はあくまで Vim パッケージのヘルパーでしかないため、プラグイン設定に変更のない限り読み込んでおく必要はありません。:PackerHogehoge
コマンドを叩いた時だけ読み込んでくれればいいのです。
vim.api.nvim_create_user_command("PackerInstall", [[packadd packer.nvim | lua require("packers").install()]], { bang = true })
vim.api.nvim_create_user_command("PackerUpdate", [[packadd packer.nvim | lua require("packers").update()]], { bang = true })
vim.api.nvim_create_user_command("PackerSync", [[packadd packer.nvim | lua require("packers").sync()]], { bang = true })
vim.api.nvim_create_user_command("PackerClean", [[packadd packer.nvim | lua require("packers").clean()]], { bang = true })
vim.api.nvim_create_user_command("PackerCompile", [[packadd packer.nvim | lua require("packers").compile()]], { bang = true })
local packer
local function init()
if not packer then
packer = require"packer"
packer.init { ... }
end
packer.reset()
packer.use{
-- ここでいろんなプラグインを読み込む
{ "tpope/vim-fugitive" },
{ "vim-jp/vimdoc-ja" },
{
"dhruvasagar/vim-table-mode",
setup = function()
vim.g.table_mode_corner = "|"
end,
},
...
}
end
return setmetatable({}, {
__index = function(_, key)
init()
return packer[key]
end,
})
このアイディアは packer.nvim 作者の wbthomason さん自身の dotfiles からいただきました。
4.4 管理画面をカッコよくする
上記の例で出ていますが、packer.nvim ロード時に packer.init
関数を呼び出すことで細かな設定を行っています。どんなカスタマイズができるかは README のこちらにあるのですが、この中で注目は display.open_fn
です。ここに関数を設定することで、:PackerSync
などの画面を floating windows で表示できたりします。
packer.init {
display = {
open_fn = require("packer.util").float,
},
}
Neovim メンテナの tjdevries さんは自身の plenary.nvim を使ってもっと複雑なことをしてます。
簡単なコードでいい感じにウィンドウがいい感じに装飾できています。こういう拡張が簡単にできるのが Lua のいいところです。
5. dein.vim との比較
dein.vim は Shougo さん作の大変高機能なパッケージマネージャです。これと packer.nvim を比較すると次のようになるでしょう。
5.1 VimL と Lua 双方の知識が要る
dein.vim はそれ自体が Vimscript で書かれており、設定も Vimscript で行うことが前提となっています。そのため Vimscript の知識しか無くても利用を始められます。それに比べると packer.nvim には Lua の知識が必須ですし、Vimscript で書かれたプラグインを使う際は Vimscript の知識も必要です。
dein.vim も勿論 Lua で書かれたプラグインを管理することができ、そのための設定を Lua で記述する構文も用意されています。
この記事を公開した頃(2021/2)ではこれが結構問題だったのですが、2022/12 現在ではだいぶ状況が変わりました。Neovim 自体の設定はほとんど全て Lua で書けるようになりましたし、そもそも Neovim 用に新規に作られるプラグインはほぼ全て Lua 製です。勢い、その設定のために必ず Lua を書く必要があります。古いプラグインのためには Vimscript の知識がまだまだ必要なのですが、そのうち Neovim を使うためには Lua を知るだけで問題無くなる日も来るでしょう。
5.2 Neovim の起動速度が遅くなる
これは packer.nvim の利用者にとっては頭の痛い問題で、この記事を公開した 2021/2 頃には顕著な差が見られた点でした。ですが 2022/12 現在では、特に Lua 製プラグインを多用する場合にはパフォーマンスの差はほとんど無いでしょう。dein.vim にはインストールするプラグインを解析して一つのディレクトリに纏めてくれる “merge” という機能があります。これが高速化に大変寄与しているのですが、遅延読み込みを駆使することで packer.nvim でもかなり高速に起動することができます。新しい記事に詳しいテクニックを書いていますので読んでみてください。
5.3 ftplugin
の管理ができない
これはおまけ的な機能なのですが、dein.vim では ftplugin をも TOML に書いて管理できます。
# dein.vim の設定例
[[plugins]]
repo = "aklt/plantuml-syntax"
on_ft = ["toml"]
[plugins.ftplugin]
toml = """
set noexpandtab
"""
ファイルタイプごとの設定をプラグインの側に置いて見通しよく管理できます。これは dein.vim が管理するディレクトリに after/ftplugin/plantuml.vim
のようなファイルを作ることで実現されています。
packer.nvim にはこのような機能はありませんので、今まで書いていた設定をどうにかする必要があります。僕の場合は以下のように一つの Lua ファイルにまとめて、
local M
M = {
plantuml = function()
vim.bo.expandtab = false
end,
perl = ...
css = ...
...
run = function()
-- 読み込み元ファイル名からファイルタイプを推測する
local ft = vim.fn.expand "<sfile>:t:r"
if ft == "" then
vim.notify("cannot detect filetype from <sfile>", vim.log.levels.WARN)
elseif not M[ft] then
vim.notify("unknown filetype: " .. ft .. " in ftplugins.lua", vim.log.levels.WARN)
end
M[ft]()
end,
}
return M
あとはファイルタイプごとにスクリプトを設置しました。
lua require("ftplugins").run()
他のファイルタイプもファイル名だけ変えて中身は全部一緒です。あとは run()
関数がよしなにやってくれます。
6. まとめ
まとめると、packer.nvim は以下のような点に魅力を感じる人にとっては最適なプラグインマネージャと言えるでしょう。
- Neovim の設定を Lua で書きたい!
- Vim パッケージを使ってシンプルにプラグインを管理したいけど、インストールや更新は自動化したい。
今回の記事には間に合わなかったのですが、Luarocks を使って Lua のパッケージ管理を行う機能もついたようです。Lua で Neovim をどんどん拡張したい人にもお勧めですね。
以下のような人には向きません。
- Neovim と Vim で同じ設定ファイルを使いたい。
5 年前の前回と比べると万人にお勧めできる感じではないのですが、Neovim + Lua のビッグウェーブに乗りたい人には是非 packer.nvim を試して欲しいですね。
2022/12/3 更に詳しく packer.nvim を使い倒すために記事を書いています。こちらも参照してください。