lazy.nvimの使い方から起動を爆速にする方法までを解説

Neovimのプラグインマネージャーlazy.nvimについて解説します。

基本的な使い方はもちろん、おすすめのディレクトリ構成の例や遅延読み込みによる起動速度の高速化についても紹介します。

lazy.nvimの導入

最終的に目指すのは次のようなディレクトリ構成です。

ディレクトリ構成の例
.config/nvim/
├── init.lua // おなじみの
├── lazy-lock.json // 自動で生成される
├── lua
│ ├── config
│ │ ├── ...
│ │ └── lazy.lua // lazy.nvimの設定
│ └── plugins // 各プラグインの設定
│ ├── bufferline.lua
│ ├── dial.lua
│ ├── ...
│ └── winresizer.lua

全部の設定を1つのファイルに書きたい人は公式ドキュメントを読みましょう。

init.luaに書く内容

まず、init.luaに次のように書きます。

~/.config/nvim/init.lua
require("config.lazy")

これは「./config/nvim/lua/config/lazy.luaを読み込んで!」という処理です。

ファイル名はあくまでも例です。./config/nvim/lua/fooooooo/barrrrr.luaのように書きたいならrequire("fooooooo.barrrrr")と書きましょう。

長いのでこの記事ではlazy.luaと呼びます。

lazy.luaに書く内容

lazy.luaには次のように書きます。

~/.config/nvim/lua/config/lazy.lua
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
local lazyrepo = "https://github.com/folke/lazy.nvim.git"
local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
if vim.v.shell_error ~= 0 then
vim.api.nvim_echo({
{ "Failed to clone lazy.nvim:\n", "ErrorMsg" },
{ out, "WarningMsg" },
{ "\nPress any key to exit..." },
}, true, {})
vim.fn.getchar()
os.exit(1)
end
end
vim.opt.rtp:prepend(lazypath)
require("lazy").setup({
spec = {
-- ここを次の項目で変更します
},
})

いきなり行数が増えましたが、やっていることは単純で「lazy.nvimのダウンロード」と「プラグインの読み込み」です。
このスクリプトを書くことで、Neovim起動時にlazy.nvimがダウンロードされていなければ自動でlazy.nvimをダウンロードしてくれます。

プラグインを読み込む方法

プラグインを読み込む方法はいくつかあります。

たとえば次のように書くと、https://github.com/machakann/vim-highlightedyankをプラグインとして管理できます。

~/.config/nvim/lua/config/lazy.lua
require("lazy").setup({
spec = {
{ "machakann/vim-highlightedyank" },
{ "foo/bar" },
{ "piyo/buz" },
-- ...
},
})

{ "ユーザー名/リポジトリ名" }を書いていくだけで、Neovim起動時にlazy.nvimが自動でプラグインをインストールしてくれます。

プラグインが少ない場合はこのspecの中にプラグイン名を羅列すればよいですが、プラグイン数が多くなるとlazy.luaが長くなってしまいます。そこで、プラグイン毎に設定ファイルを分ける方法を解説します。

ディレクトリをまとめて読み込む

specを次のように書くと、~/.config/nvim/lua/pluginsディレクトリにあるファイルをまとめて読み込んでくれます。

~/.config/nvim/lua/config/lazy.lua
require("lazy").setup({
spec = {
{ import = "plugins" },
},
})

つまり、次のようなbufferline.luadial.luawinresizer.luaが全部読み込まれるということです。

ディレクトリ構成の例
.config/nvim/
├── lua
│ ├── ...
│ └── plugins
│ ├── bufferline.lua
│ ├── dial.lua
│ ├── ...
│ └── winresizer.lua
├── ...

各プラグインのファイルの書き方

~/.config/nvim/lua/plugins/にあるファイルは、次のようにreturnの中に書きます。

ざっくり
return {
"ユーザー名/リポジトリ名",
-- ここに設定を書いていく。後で解説!
}

実際に筆者が使っているnightfox.nvimの例を見てみましょう。

~/.config/nvim/lua/plugins/nightfox.lua
return {
"EdenEast/nightfox.nvim",
config = function()
vim.cmd("colorscheme terafox")
vim.cmd("highlight! link WinSeparator GlyphPalette2")
vim.cmd("highlight! Visual guibg=#4a3332")
end,
}

おやおや、configなるものが出てきましたね。他にbuildeventなどいくつかプロパティがありますが、これらについては後で解説します。

トラブル対応しやすいように読み込む

前述のようにディレクトリをまとめて読み込む方法は書くのが楽です。ただ、何か問題が起きた時にどのプラグインが原因なのか特定しにくいです。

そこで、トラブルが発生しても特定しやすくかつ柔軟に設定を分割できる方法を紹介します。

lazy.luaにて、次のようにimportでひとつずつファイルを指定します。

~/.config/nvim/lua/config/lazy.lua
require("lazy").setup({
spec = {
{ import = "plugins.nightfox" },
{ import = "plugins.bufferline" },
{ import = "plugins.dial" },
{ import = "plugins.lsp.index" },
-- ...
{ "vim-jp/vimdoc-ja" },
},
})

たとえば、plugins.nightfox~/.config/nvim/lua/plugins/nightfox.luaを指します。
ファイル名は自分が分かればプラグイン名と一致しなくても問題ありません。

ディレクトリ構成の例
.config/nvim/
├── lua
│ ├── ...
│ └── plugins
│ ├── bufferline.lua
│ ├── dial.lua
│ ├── lsp
│ │ ├── ...
│ │ └── index.lua
│ ├── ...
│ └── winresizer.lua
├── ...

設定が必要なかったり行数が少ない場合は、ファイルを分けないほうが楽です。例にあるvim-jp/vimdoc-jaのようにspecの中に書いちゃいましょう。

require("lazy").setup({
spec = {
{ import = "plugins.nightfox" },
-- ...
{ "vim-jp/vimdoc-ja" },
},
})

トラブルシューティングの方法

問題が発生してどのプラグインなのか特定したいときに二分探索できます。半分ずつコメントアウトして原因を突き止めるのです。

コメントアウトしたらトラブルが解消された場合、その中に問題のあるプラグインが存在していることになります。

~/.config/nvim/lua/config/lazy.lua
require("lazy").setup({
spec = {
{ import = "plugins.nightfox" },
{ import = "plugins.bufferline" },
{ import = "plugins.dial" },
-- 半分ずつコメントアウト
-- { import = "plugins.foo" }, -- こいつがトラブルメーカー
-- { import = "plugins.lsp.index" },
-- { "vim-jp/vimdoc-ja" },
},
})

1つファイルに複数のプラグインをまとめて書く

再びpluginsディレクトリに書くプラグインファイルについての解説に戻ります。

1つのファイルに複数のプラグインをまとめて書けます。

return {
{ "foo/bar" },
{ "piyo/buz" },
}

次の例では、treesitter関係の2つのプラグインをまとめて書いています。

~/.config/nvim/lua/plugins/nvim-treesitter.lua
return {
{
"nvim-treesitter/nvim-treesitter",
build = ":TSUpdate",
config = function()
-- ...
end,
},
{
"nvim-treesitter/nvim-treesitter-context",
dependencies = { "nvim-treesitter/nvim-treesitter" },
event = { "BufNewFile", "BufRead" },
opts = {
-- ...
},
},
}

のちほど解説しますが、筆者はLSPや自動補完に関するプラグインの読み込みをそれぞれ1つのファイルにまとめています。

依存関係の指定

「プラグインAの中では別のプラグインBが使われている。AはBに依存しているんだ!」
そんなケースでは、dependenciesにそのプラグイン名を書きます。

return {
"foo/plugin-a",
dependencies = { "piyo/plugin-b" },
-- ...
}

プラグインBはプラグインAが読み込まれる前に読み込まれます。

このプラグインBが他の場所で使われていないのであれば、dependenciesに書くだけで問題ありません。

return {
{
"foo/plugin-a",
dependencies = { "piyo/plugin-b" },
}
{ "piyo/plugin-b" }, -- この記述は不要
}

次の例ではnoice.nvimについて書いています。

return {
"folke/noice.nvim",
dependencies = {
"MunifTanjim/nui.nvim",
"rcarriga/nvim-notify"
},
}

プラグインの設定方法

いよいよプラグインの設定の書き方です。

Luaのプラグインの場合

Luaで書かれたプラグインのREADMEにはだいたい「require("foo").setup({})を呼び出してね」と書いてありますが……。

lazy.nvimのoptsを使えば、requireの記述は不要です。setupに渡す引数をoptsに書けば設定できます。

return {
"petertriho/nvim-scrollbar",
event = { "VeryLazy" },
opts = {
excluded_filetypes = {
"prompt",
"TelescopePrompt",
"noice",
"LspsagaHover",
},
},
}

lazy.nvimが自動でrequire("foo").setupの引数として渡し、実行してくれます。

lazy.nvimがこんな感じで処理してくれる
require("scrollbar").setup({
excluded_filetypes = {
"prompt",
"TelescopePrompt",
"noice",
"LspsagaHover",
},
})

複雑な設定を書きたい場合

setupの呼び出し以外にも設定が必要な場合はconfigに書きましょう。

return {
"windwp/nvim-autopairs",
event = "InsertEnter",
config = function()
local npairs = require("nvim-autopairs")
local Rule = require("nvim-autopairs.rule")
npairs.setup({
-- カーソルの後ろに何があってもautopairsを有効にする
ignored_next_char = "",
})
npairs.add_rules({
Rule("```", "```", { "mdx" }),
-- デフォルトだと "not_add_quote_inside_quote" により
-- クオートの中でautopairできないので自前で定義にする
Rule('"', '"'),
Rule("'", "'"),
})
end,
}

configを使う場合は自分でrequire("foo").setupを呼び出す必要があります

Vimプラグインの場合

Vimプラグインの場合、設定はconfiginitに書きます。

たいていのVimプラグインは initで設定したほうがいい でしょう。そうしないと設定したグローバル変数などをプラグインで読み込めないケースがあります。

return {
"simeji/winresizer",
keys = { "<C-e>", mode = { "n" } },
init = function()
vim.g.winrisizer_vert_resize = 1
vim.g.winresizer_horiz_resize = 1
end,
}

ビルドが必要なプラグイン

ビルドが必要な場合、buildに指定できます。そういうプラグインはだいたいREADMEに書いてあるので、buildのことは頭の片隅にどうぞ。

return {
"iamcco/markdown-preview.nvim",
build = function()
vim.fn["mkdp#util#install"]()
end,
-- ...
}

プラグインのアップデート方法

:Lazyで立ち上がる画面にてUを入力するか、:Lazy updateでプラグインをアップデートできます。

プラグインのアンインストール方法

プラグインをアンインストールしたい場合は、「設定を書いた{}」や「ファイルとimportの部分」を削除します。

ファイルに分けていたいたらファイルごと削除
return {
"ユーザー名/リポジトリ名",
-- 設定
}
lazy.luaから該当する部分を削除
require("lazy").setup({
spec = {
{ import = "plugins.ファイル名" }, -- 別のファイルに切り出していた場合
{ "ユーザー名/リポジトリ名" }, -- 直接lazy.nvimに書いていた場合
{ "foo/bar" },
{ "vim-jp/vimdoc-ja" },
},
})

最後に、:Lazyで立ち上がる画面にてXを入力します。または:Lazy cleanを実行します。

遅延読み込みで爆速起動

プラグインを遅延読み込みさせることで、Neovimの起動がめっちゃ速くなります。
筆者のNeovimは改善前が0.452秒、改善後が0.044秒でした。やってみると体感でも速さを感じられます。せっかくなら速いほうがいいですよね。

起動にかかった時間を調べよう

実際に起動速度を測ってみましょう。

次のコマンドで起動の処理時間などがstart.logに記録されます。

Terminal window
nvim --startuptime start.log

最後の行の1列目が起動にかかった時間です。例では054.547ミリ秒です。

start.log
--- Startup times for process: Primary/TUI ---
times in msec
clock self+sourced self: sourced script
clock elapsed: other lines
000.001 000.001: --- NVIM STARTING ---
000.095 000.094: event init
// ...
054.384 000.113: opening buffers
054.450 000.065: BufEnter autocommands
054.453 000.004: editing files in windows
054.547 000.093: --- NVIM STARTED ---

参考:Vimのstartuptime出力結果の読み方 - rbtnn雑記

遅延読み込みの方法

プラグインがどのタイミングで必要になるのかを記載することで、遅延読み込みできます。

ft

次のようにftにファイルタイプを指定すると、そのファイルタイプが開かれるときにプラグインが読み込まれます。

return {
"MeanderingProgrammer/render-markdown.nvim",
dependencies = { "nvim-treesitter/nvim-treesitter", "nvim-tree/nvim-web-devicons" },
ft = { "markdown", "mdx" },
opts = {
render_modes = true,
heading = {
width = "block",
left_pad = 0,
right_pad = 4,
icons = {},
},
code = {
width = "block",
},
},
}

ft = "markdown"のようにリストではなく文字列でもかまいません。

cmd

次のようにcmdにコマンドを指定すると、そのコマンドが実行されるときにプラグインが読み込まれます。

return {
"folke/trouble.nvim",
cmd = "Trouble",
opts = {},
}

cmd = {"Foo", "Bar"}のようにリストでも指定できます。

cmdに書いたコマンドは、プラグイン読み込み前でも補完に表示されます。

cmdに書いたコマンドの例

画像の例では、プラグインtrouble.nvim読み込み前でも:Troubleが補完候補に表示されている様子です。

keys

keysではキーマップと遅延読み込みが同時に設定できます。

次の例は、<leader>wのキーマップを設定しています。また、<leader>wが呼び出されたらプラグインを読み込むように指定しています。

return {
"bkad/CamelCaseMotion",
keys = {
{ "<leader>w", "<Plug>CamelCaseMotion_w", mode = { "n", "x" }, silent = true },
{ "<leader>b", "<Plug>CamelCaseMotion_b", mode = { "n", "x" }, silent = true },
{ "<leader>e", "<Plug>CamelCaseMotion_e", mode = { "n", "x" }, silent = true },
{ "<leader>ge", "<Plug>CamelCaseMotion_ge", mode = { "n", "x" }, silent = true },
},
}
keysの書き方

keysの中身は次のように書きます。

{ "key", "実行されるコマンド", オプション…… }

オプションmodeを書かない場合はノーマルモードのみにマップされます。具体的な文字列の指定方法は:help map-tableを読みましょう。

オプションftでfiletypeごとのキーマップを設定できます。

その他のオプションはvim.keymap.setと同じです。

いろいろなkeyの書き方

プラグイン側でキーマップを設定してくる場合はコマンドなしで書きます。

{ "key", オプション…… }

プラグインsimeji/winresizerを例にあげます。このプラグインは<C-e>を設定してきます。プラグインの読み込みを<C-e>入力時に設定するには次のように書きます。

return {
"simeji/winresizer",
keys = { "<C-e>" },
init = function()
vim.g.winrisizer_vert_resize = 1
vim.g.winresizer_horiz_resize = 1
end,
}

次のように、各keyの2番目にはコマンドではなくluaの関数も書けます。

return {
"monaqa/dial.nvim",
keys = {
{
"<C-a>",
function()
require("dial.map").manipulate("increment", "normal")
end,
mode = "n",
},
-- ...
},
-- ...
}

event

次のようにeventを指定すると、そのイベント発火時にプラグインが読み込まれます。

return {
"windwp/nvim-autopairs",
event = "InsertEnter",
config = function()
local npairs = require("nvim-autopairs")
local Rule = require("nvim-autopairs.rule")
npairs.setup({
-- カーソルの後ろに何があってもautopairsを有効にする
ignored_next_char = "",
})
npairs.add_rules({
Rule("```", "```", { "mdx" }),
-- デフォルトだと "not_add_quote_inside_quote" により
-- クオートの中でautopairできないので自前で定義にする
Rule('"', '"'),
Rule("'", "'"),
})
end,
}

Neovimで定義されているイベントの一覧は:help eventsから探せます。

event VeryLazy

「起動してすぐ必要ではないけど、起動してちょっとしたら読み込んでほしいなぁ」という願いを叶えるのがVeryLazyeventです。

return {
"delphinus/auto-cursorline.nvim",
event = "VeryLazy" ,
config = function()
require("auto-cursorline").setup()
vim.api.nvim_create_autocmd("FileType", {
group = "my_nvim_rc",
pattern = { "TelescopePrompt", "TelescopeResults", "gitblame", "css" },
callback = function()
vim.b.auto_cursorline_disabled = true
end,
})
end,
}

VeryLazyといっても感覚としてはすぐ読み込まれますVeryLazyはlazy.nvimが定義してくれているイベントです。

その他のlazy.nvimのイベントは:help lazy.nvim-🚀-usage-📆-user-eventsを参照してください(絵文字つきなのすごい)。

lazy=trueって何?

lazy = trueを指定すると、そのプラグインが呼び出されるまでは読み込まれません。

-- ...
require("lazy").setup({
spec = {
{ "nvim-lua/plenary.nvim", lazy = true },
-- ...
},
})

いろんなプラグインで使われているライブラリのような位置付けのLuaプラグインであれば、毎回dependenciesに書かずともlazy = trueで1行の記述だけですみます。plenary.nvimnvim-web-deviconsなどです。

実際に遅延読み込みされているか確認する方法

遅延読み込みが想定どおりに動いたのかどうかは:Lazyコマンドで確認できます。

Loadedのプラグイン名の右を見ればどのタイミングで読み込まれたのか分かります。

すでに読み込まれたプラグインの一覧

画像の例ではauto-cursorline.nvimVeryLazyeventのタイミングで読み込まれたのが分かります。
cellwidths.nvimstartは「遅延読み込みせずNeovim起動時に読み込まれた」ということです。

一方、読み込まれていないプラグインはNot Loadedに表示されます。

まだ読み込まれていないプラグインの一覧

画像ではactions-preview.nvimlazy=trueにしており、プラグイン名の右には何も表示されていません。

設定が大きくなったら分割してみる

LSPや自動補完、ステータスラインなどのプラグインは行数が多くなりがちです。
ここではLSPの設定をそこそこ分割している筆者の構成を紹介します。

筆者はpluginsの中にlspというディレクトリを作り、そこにLSP関連の設定をまとめています。

ディレクトリ構成の例
.config/nvim/
├── lua
│ └── plugins
│ ├── lsp
│ │ ├── ...
│ │ └── lspsaga.lua
│ │ └── format.lua
│ │ └── index.lua
│ ├── ...
├── ...

lazy.luaではplugins.lsp.indexをimportに指定します。

lazy.luaの例
require("lazy").setup({
spec = {
{ import = "plugins.lsp.index" },
-- ...
{ "vim-jp/vimdoc-ja" },
},
})

index.luaにLSP関係のプラグインの読み込みをまとめています。各プラグインの設定はほとんど別のファイルに書き、requireimportしています。

.config/nvim/lua/plugins/lsp/index.lua
return {
{
"neovim/nvim-lspconfig",
event = { "BufReadPost", "BufNewFile" },
cmd = { "LspInfo", "LspInstall", "LspUninstall" },
dependencies = {
{ "williamboman/mason.nvim" },
{ "williamboman/mason-lspconfig.nvim" },
{ "b0o/schemastore.nvim" },
{ "hrsh7th/cmp-nvim-lsp" },
},
config = function()
require("plugins.lsp.diagnostic")
require("plugins.lsp.format")
require("plugins.lsp.attach")
require("plugins.lsp.code-actions")
require("plugins.lsp.server-register")
end,
},
{ import = "plugins.lsp.lspsaga" },
{ import = "plugins.lsp.conform" },
{ import = "plugins.lsp.none-ls" },
{ import = "plugins.lsp.actions-preview" },
{
-- Neovimの設定をluaを書きやすくなる
"folke/lazydev.nvim",
ft = "lua",
opts = {
library = {
{ path = "${3rd}/luv/library", words = { "vim%.uv" } },
},
},
},
}

importではプラグインの設定をreturnしていましたね。requireでは単純に処理を読み込むだけです。

.config/nvim/lua/plugins/lsp/attach.lua
vim.api.nvim_create_autocmd("LspAttach", {
group = "my_nvim_rc",
callback = function(ev)
local bufopts = { noremap = true, silent = true, buffer = ev.buf }
vim.keymap.set("n", "gr", "<cmd>Telescope lsp_references<CR>", bufopts)
vim.keymap.set("n", "[d", "<cmd>Lspsaga diagnostic_jump_prev<CR>", bufopts)
-- ...
end,
})

気になる人は記事執筆時のdotfilesをどうぞ。

Specっていうんだよ

今までreturn { "ユーザー名/リポジトリ名" }のように書いていた{}のまとまりはSpecという名前があるようです。実はdependenciesも「SpecまたはSpecのリスト」として書けるため、次のようにdependencies内で設定可能です。

return {
"folke/noice.nvim",
event = "VeryLazy",
dependencies = {
"MunifTanjim/nui.nvim",
{ "rcarriga/nvim-notify", opts = { top_down = false, stages = "static" } },
},
config = function()
-- ...
end,
}

公式リソースと参考リンク

最後に一次情報をまとめておきます。

dependencieslazy = trueについて深堀りすると読む気が無くなってしまうと考えて解説を短くしたため、気になる方は調べてみてください。

ちなみに名前が超絶似ているLazyVimはlazy.nvimの作者folke氏が作ったNeovimのフレームワークです。


今まではREADMEにある設定をコピペしてちょろっと変えたり、オレオレファイル分割でした。

今回の作業で速度やディレクトリ構成を改善できてうれしいです。突き詰めればもっと速くできそうです。