118
85

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

packer.nvim で Neovim + Lua のビッグウェーブに乗る

Last updated at Posted at 2021-02-01

2022/12/3 本日公開の 爆速で起動する Neovim を packer.nvim で作る - Qiita に合わせ、最新の情報を含めて修正しました。

0. 前置き

昔々、NeoBundle から dein.vim に乗り換える話を書きました。

しかし 5 年も経てば世の中色々変わります。Neovim も色々変わりましたが、最近一番ホットな話題といえばなんと言っても Lua でしょう。プラグインを書くための言語としてだけではなく、設定ファイルである init.vim すら Lua で書くことが可能になったのです。

image.png

(クソコラみたいな画像ですね……)

このビッグウェーブに乗るべく、今までプラグインマネージャーとして使ってきた 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
init.lua
require "plugins"

packer.nvim の設定は init.lua に直接書くこともできますが、後述の理由から別ファイルに分けることをお勧めします。上記のように書いておくと、~/.config/nvim/lua/plugins.lua を読み込んでくれます。

  • ~/.config/nvim/lua ディレクトリなど、&runtimepath + /lua ディレクトリに置かれたファイルは require "hogehoge" という単純な書式で読み込むことができます。
lua/plugins.lua
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 を設定しておきましょう。

init.lua
vim.api.nvim_create_autocmd("BufWritePost", {
  pattern = { "plugins.lua" },
  command = "PackerCompile",
})

plugins.luainit.lua と分けて記述したのはこれが理由です。

2.3 コマンド一覧

packer.nvim をインストールすると以下のコマンドが使えるようになります。

PackerInstall
プラグインをインストールする。
PackerUpdate
追加されたプラグインをインストールし、既存のものは更新する。
PackerClean
必要なくなったプラグインを削除する。
PackerSync
PackerClean の後に PackerUpdate を行う。
PackerCompile
設定ファイルを「コンパイル」する。

簡単ですね。一度設定した後は PackerSync を定期的に叩くだけでプラグインは最新のものになります。

3. プラグインの設定を書く

多くのプラグインは様々な設定をカスタマイズして利用します。このカスタマイズスクリプトを書く方法が 2 つ(configsetup)あり、これらを使い分けるには packer.nvim の元になっている Vim パッケージについて知る必要があります。

3.1 Vim パッケージの仕組み

Neovim 起動時に Vim パッケージによって管理されたプラグインやスクリプトは以下の順序で読み込まれます(完全なリストはこちら)。Vim パッケージは Vim8 / Neovim 組み込みの機能ですので、この順序は packer.nvim とは直接関係ありません。

  1. init.lua とそれが require するもの。
  2. start プラグイン。
  3. その他、&runtimepath 内のプラグインスクリプト。
    • この中には PackerCompile によって生成される ~/.config/nvim/plugin/packer_compiled.vim を含みます。
  4. opt プラグインのうち、setup オプションのみが指定されたもの(後述)。
    • ここだけは packer.nvim による拡張です。
  5. VimEnter イベントによって読み込まれるもの。
    • ここで Neovim の起動完了です。
  6. その後、cmdfn その他の設定によって packadd コマンドされる opt プラグインたち。

キモは 3. の Vim スクリプトです。この中では各プラグインの設定を行なっているのですが、この内容を記述するのが config, setup オプションです。

3.2 config オプションの使い所

プラグインを読み込んだ後に設定を適用するには config オプションを使います。

configオプションを使った例
use {
  "neovim/nvim-lspconfig",
  config = function()
    require("lspconfig").tsserver.setup {}
  end,
},

この例では nvim-lspconfigstart プラグインとして、つまり、Neovim 起動時に読み込まれています。require "lspconfig" する必要があるため、プラグインの設定は config オプションを使ってプラグインの読み込み後に行います。

3.3 setup オプションの使い所

プラグインを読み込む前に設定を適用するには 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 オプション双方を使った例

configsetup はもちろん同時に使えます。この場合はプラグインの読み込み前と後にスクリプトを実行することができます(もちろん opt プラグインになります)。

configオプションとsetupオプション双方を使った例
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 をロードし、それから各種設定を行なっていました。

init.lua
vim.cmd.packadd "packer.nvim"

require("packer").startup(function()
  use { "wbthomason/packer.nvim", opt = true }

  ...
end)

しかし packer.nvim はあくまで Vim パッケージのヘルパーでしかないため、プラグイン設定に変更のない限り読み込んでおく必要はありません。:PackerHogehoge コマンドを叩いた時だけ読み込んでくれればいいのです。

init.lua
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 })
~/.config/nvim/lua/packers.lua
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 を使ってもっと複雑なことをしてます

packersync.gif

簡単なコードでいい感じにウィンドウがいい感じに装飾できています。こういう拡張が簡単にできるのが 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 ファイルにまとめて、

~/.config/nvim/lua/ftplugins.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

あとはファイルタイプごとにスクリプトを設置しました。

~/.config/nvim/ftplugin/plantuml.vim
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 を使い倒すために記事を書いています。こちらも参照してください。

118
85
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
118
85

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?