1. ayamir/nvimdots の衝撃
このエントリーを書くきっかけとなったのは以下のレポジトリーを見たことでした。
これは Neovim の設定ファイルを公開しているレポジトリーです。特徴的なのはその起動速度。Neovim 界の三種の神器、nvim-telescope/telescope.nvim、nvim-lualine/lualine.nvim、hrsh7th/nvim-cmp を含む、割合新し目で重たいプラグインを使いながら、10 ミリ秒台の起動速度を実現しているのです。
これに触発され、僕自身の設定も大幅に見直すことができ、ayamir/nvimdots とほとんど遜色無い速度を実現することができました。この記事ではその設定に使った様々なテクニックを紹介します。
なお、僕の設定例は以下のレポジトリーの .config/nvim
ディレクトリに纏めています。
- 利用している Neovim は記事時点の
HEAD
、つまり開発中の最新版です。利用するバージョンによっては使えない機能があるかも知れません。 - プラグインマネージャーには wbthomason/packer.nvim を利用しています。読者としてはこのプラグインについて基本的な使い方を理解している人が対象です。以前解説記事も書いておりますので、適宜参照してください。
ayamir/nvimdots を自分の環境で試す時はちょっと注意が必要です。これはこの記事の本題とズレてしまうので、一番最後に「9. おまけ」として記載しています。
2. 起動速度の測り方
10 ms というのがどのくらい凄いことなのか、そもそも起動速度を測ったこともない人も多いと思うのでそこから説明していきます。
2.1. --startuptime
オプション
CLI でサッと起動できてすぐに編集が始められるのが (Neo)Vim のいいところです。それは存在意義と言ってもいいでしょう。そのため (Neo)Vim 本体にも起動速度を細かく調べる機能が付いています。
$ nvim --startuptime ~/nvim.log +q
--startuptime
は Neovim だけでなく Vim でも使える機能です。
このコマンドでは ~/nvim.log
に以下のようなログを記録した上ですぐに終了しています。
times in msec
clock self+sourced self: sourced script
clock elapsed: other lines
000.003 000.003: --- NVIM STARTING ---
000.076 000.073: event init
000.151 000.075: early init
000.646 000.494: locale set
……(省略)……
031.783 000.604 000.604: require('vim.diagnostic')
031.785 000.724 000.120: require('scrollbar.handlers.diagnostic')
031.922 000.077 000.077: require('scrollbar.handlers')
032.029 000.774: opening buffers
032.136 000.017 000.017: sourcing /Users/jinnouchi.yasushi/.local/share/nvim/site/pack/packer/start/direnv.vim/autoload/direnv/extra_vimrc.vim
032.151 000.106: BufEnter autocommands
032.153 000.002: editing files in windows
--- NVIM STARTING ---
から起動プロセスが始まり、editing files in windows
が完了、つまりカーソルを動かしたりキーが反応するタイミングです。この例では起動に 32 ms 掛かっていることになります。
2.2. rhysd/vim-startuptime コマンド
--staruptime
オプションで出力されるログでは起動時に読み込まれるファイル全てについて記載されています。これはこれで、細かく処理に掛かった時間が分かって便利なのですが、いくつか問題があります。
- 試すたびに値が結構変わる。
- パッと見て、起動速度が分かりづらい。
- すでに起動してるのに
editing files in windows
以降もログが出たりします(ステータスラインの描画に掛かった部分など)。
- すでに起動してるのに
これを解決するため、単純に複数回実行して平均値を取るユーティリティがあります。rhysd/vim-startuptime です。
$ vim-startuptime -vimpath nvim -count 1000
Extra options: []
Measured: 1000 times
Total Average: 29.251860 msec
Total Max: 45.369000 msec
Total Min: 28.025000 msec
……(以下、各種ファイルの読み込みに掛かった時間がソートされて出る)……
この例では 1000 回実行して平均値を計算しています。便利ですね。
2.3. dstein64/vim-startuptime プラグイン
こちらは外部コマンドではなくてプラグインです。このプラグインをインストールすると (Neo)Vim 上で :StartupTime
コマンドが使えるようになり、起動する際に各ファイルの読み込みに掛かった時間が分かります。
こんな感じで、綺麗なグラフと共に結果が表示されるので分かり易いです。ただ、“percent” の欄は合計値が 100 にならないことに注意しましょう。この例で言うと、init.lua
→ core
→ _compiled
の順にそれぞれのファイルで require
してますので、掛かった時間は包含されています。
3. この記事で達成する目標
さて、皆さんの (Neo)Vim がどの程度の速度で起動するか測ってみたでしょうか。マシンスペックやインストールしているプラグインの数、init.lua
の大きさにも依りますので一概には言えませんが、感覚としては大体以下のように分類できるでしょう。
- 500 ms 以上
- このレベルだと明らかに遅さを実感します。プラグインてんこ盛りの上、遅延読み込み(後述)していないとこうなってしまいます。これでは「サッと起動してすぐに編集できる」とはお世辞にも言えない状態です。
- 100 ms~500 ms
- あまりプラグインを入れていない、又は、適度にプラグインを遅延読み込みさせるとこの程度の速度になると思います。頻繁に使ってもそんなに苦にならない速度ですが、偶に素の (Neo)Vim を起動したりすると速度の違いに驚いたりします。
- 50 ms~100 ms
- 遅延読み込みについてかなり詳しい人でもこの程度が限度ではないでしょうか。
- 50 ms 以下
- ここまで来ると素の (Neo)Vim との差はほとんど感じられなくなります。この記事ではこの速度を目標とします。
素の、何もプラグインがなく、何も設定ファイルに書いていない (Neo)Vim が、そりゃ一番速いです。とはいえ快適に編集したいなら誰しもある程度プラグインをインストールしたり設定ファイルに追記したりするでしょう。この記事では僕が必要と思うプラグイン(現時点で 159 個)入れた状態で 29 ms の起動速度を実現した設定を元に、高速化の秘訣を書いていきます。
ayamir/nvimdots は「最速で 10 ms で起動する」ことを謳う設定例ですが、あくまでこれはマシンスペックや OS にも依ります。僕の環境では ayamir/nvimdots は 26 ms で起動するのがせいぜいでした。そのため僕自身の設定で 29 ms というのはほぼ理想に近い値と言えます。
4. プラグインの遅延読み込み
何はともあれ、プラグインを遅延読み込みさせる方法を学びましょう。ここでは遅延読み込み自体の方法、依存関係の解決、複雑な設定例について述べます。
4.1. 遅延読み込みの種類
packer.nvimにおいては、何も指定せずにプラグイン名を追加すると start プラグインとして認識され、Neovim 起動時に読み込まれるようになります。
use { "vim-jp/vimdoc-ja" }
これに対し、いくつかのオプションを指定するとそれを opt プラグインとして配置し、起動後に必要に応じて読み込めるようになります。
オプションの例 | 意味 |
---|---|
opt = true |
opt プラグインとして配置するが読み込みタイミングは特に指定しない。 |
cmd = { "StartupTime" } |
ex コマンドを呼び出すと読み込む。 |
ft = { "perl" } |
指定されたファイルタイプのファイルを開くと読み込む。 |
keys = { { "n", "'j" } } |
キーマッピングで読み込む。 |
event = { "BufRead" } |
イベントで読み込む。 |
fn = { "searchx#*" } |
Vimscript 関数を呼び出すと読み込む。 |
module = { "neodev" } |
Lua モジュールを require すると読み込む。 |
module_pattern = { "plenary.*" } |
module は module_pattern の糖衣構文です。 |
大体は読んだ通りなのですが、opt
オプションだけはちょっと特殊です。これは依存プラグインとして列挙するだけで、実際の読み込みタイミングは後述の after
や wants
で指定するものに利用します。
4.2. 依存関係や読み込み順序の指定
一部のプラグインには依存関係があり、読み込み順序の指定が必要な場合があります。ただ、packer.nvim のこの部分はあまり整理されておらず、似たような機能を持つオプションが 3 つもあります。
4.2.1. requires
オプション
依存関係を指定するオプションです。inkarkat/vim-LineJuggler というプラグインがあります。プラグイン自体については大昔に記事を書いたのでそっち読んで貰うとして、こいつの特徴は依存するプラグインが多いことです。下記の設定では依存する 3 つのプラグインも一緒にインストールされます。この際、特に遅延読み込みの設定をしていないので、全て start プラグインとして配置しています。
use {
"inkarkat/vim-LineJuggler",
requires = {
{ "inkarkat/vim-ingo-library" },
{ "tpope/vim-repeat" },
{ "vim-scripts/visualrepeat" },
},
}
重要なのはこれ、依存関係なんですね。読み込み順序ではないんです。遅延読み込みではこの二種の概念を整理しないと上手く動かないんです。
問題は、これを遅延読み込みさせる時です。
use {
"inkarkat/vim-LineJuggler",
keys = { { "n", "[e" }, { "n", "]e" } },
requires = {
{ "inkarkat/vim-ingo-library" },
{ "tpope/vim-repeat" },
{ "vim-scripts/visualrepeat" },
},
}
keys
オプションを加えることで、該当のキーを押すまでプラグインの読み込みを遅延させたとします。この場合、inkarkat/vim-LineJuggler は想定通り opt プラグインとなりますが、依存する 3 つのプラグインは start プラグインのままです。
use {
"inkarkat/vim-LineJuggler",
keys = { { "n", "[e" }, { "n", "]e" } },
requires = {
{ "inkarkat/vim-ingo-library", opt = true },
{ "tpope/vim-repeat", opt = true },
{ "vim-scripts/visualrepeat", opt = true },
},
}
じゃあ依存プラグインも opt にして該当のキーを押した時に連続して読み込ませよう、とすると、これは上手く行かず、エラーになってしまいます。この例で言うと、[e
を押した時は inkarkat/vim-LineJuggler のみが読み込まれ、依存する 3 つのプラグインは読み込まれません。これは依存関係だけ定義されて、読み込み順序は未定義だからなんですね。
これを解決するには後述の after オプションか、wants オプションを使います。
4.2.2. after
オプション
最初に書いておきますが、 after
オプションは問題が多いので使わない方がいいです。 なのに先にこれを例示しているのは、次項の wants
オプションがドキュメントに書かれていない、隠しオプションだからです。「packer.nvim のこの辺はあまり整理されていない」というのはこれが理由です。
上述の例は以下のように after
オプションを指定することで上手く行きます。
use {
"inkarkat/vim-LineJuggler",
keys = { { "n", "[e" }, { "n", "]e" } },
requires = {
{ "inkarkat/vim-ingo-library", opt = true, after = "vim-LineJuggler" },
{ "tpope/vim-repeat", opt = true, after = "vim-LineJuggler" },
{ "vim-scripts/visualrepeat", opt = true, after = "vim-LineJuggler" },
},
}
しかしこれ……端的に言って見辛くないですか? 普通に英語として読むと、“after vim-LineJuggler” は「inkarkat/vim-LineJuggler の後に読み込む」のようになると思うんですが、実際には inkarkat/vim-LineJuggler の前に読み込まれるんです。
after
オプションとできることはほぼ同じなのですが、次項の wants
オプションの方が記法として素直なのでお勧めです。
4.2.3. wants
オプション
wants
オプションを使うとこれがすっきりします。
use {
"inkarkat/vim-LineJuggler",
keys = { { "n", "[e" }, { "n", "]e" } },
requires = {
{ "inkarkat/vim-ingo-library", opt = true },
{ "tpope/vim-repeat", opt = true },
{ "vim-scripts/visualrepeat", opt = true },
},
wants = { "vim-ingo-library", "vim-repeat", "visualrepeat" },
}
読んだまま、分かり易いですね! 先に書いた通り、wants
オプションは隠しオプションなのですが、多くの人に使われているので素直にこれ使っちゃいましょう。
4.3. お勧めの遅延読み込みパターン
いくつかのプラグインについて遅延読み込みの実例を見てみましょう。
4.3.1. keys
オプションの例
すでに例示した inkarkat/vim-LineJuggler はプラグイン自体でキーマッピングを定義してくれます。
-
[e
を押す。 - inkarkat/vim-LineJuggler とその依存プラグインが読み込まれ、
map [e
も正しく定義される。 - 期待した動作が行われる。
このような順序で処理されるのですが、では、マッピングをユーザーが自前で行うプラグインの場合はどうでしょう? junegunn/vim-easy-align というプラグインを例として考えます。このプラグインはヘルプを読むと、<Plug>(EasyAlign)
を呼び出すマッピングを自前で定義するように書かれています。
xmap ga <Plug>(EasyAlign)
nmap ga <Plug>(EasyAlign)
この設定はどこに書くべきでしょうか? もちろん init.lua
に直に書いてもいいんですが、プラグインの設定は一箇所に纏めたいですよね? このような場合、setup
オプションを使うと綺麗に書けます。
use {
"junegunn/vim-easy-align",
keys = { "<Plug>(EasyAlign)" },
setup = function()
vim.keymap.set("x", "ga", "<Plug>(EasyAlign)")
vim.keymap.set("n", "ga", "<Plug>(EasyAlign)")
end,
}
setup
オプションに指定した関数は Neovim 起動時に実行されます。そのため、
- (起動時)
ga
というマッピングを定義する。 -
ga
を押す。 -
<Plug>(EasyAlign)
が呼ばれる。 -
vim-easy-align
が読み込まれる。 - 期待した動作が行われる。
というピタゴラスイッチが機能するわけです。
4.3.2. module
オプション、module_pattern
オプションの例
最近の Lua 製のプラグインは <Plug>(Hoge)
みたいなマッピングを余り定義しません。Neovim では直接 Lua の関数をマッピングする方が見た目も素直ですからね。こういう時は keys
よりも module
/ module_pattern
で書いた方が綺麗になります。
numToStr/FTerm.nvim というプラグインがあります。Floating Windows でいい感じにターミナルが開けるプラグインなのですが、デフォルトでは特にマッピングを提供していません。例えば、<A-c>
というマッピングでターミナルを開閉する時は以下のように定義します。
use {
"numToStr/FTerm.nvim",
module = { "FTerm" },
setup = function()
vim.keymap.set("n", "<A-c>", function()
require("FTerm").toggle()
end)
vim.keymap.set("t", "<A-c>", function()
require("FTerm").toggle()
end)
end,
config = function()
require("Fterm").setup {
-- 様々な設定
}
end,
}
- (起動時)
<A-c>
というマッピングを定義する。 -
<A-c>
を押す。 - 定義した関数が実行され
FTerm
というモジュールがrequire
される。 - numToStr/FTerm.nvim プラグインが読み込まれ、
config
オプションに定義した関数が実行される。 - 期待した動作(
toggle()
関数の呼び出し)が行われる。
存在しないモジュールを require
した時に期待するプラグインを読み込むことができるのは、packer.nvim によって package.loaders
が上書きされているからです。
プラグイン自体の設定は config
オプションの関数に纏め、setup
オプションでキーマッピングを定義しています。役割が分けられていて読み易いです。
module
オプションは、実は正規表現版の module_pattern
オプションの糖衣構文となっています。上記の module = { "Fterm" }
というのは module_pattern = { "^Fterm" }
という設定と同じです。
4.3.3. 起動後すぐに必要ないものは event
オプションで遅延読み込み
これは色んなパターンがありますね。起動時すぐには要らないけれど、ふと気付いたらあって欲しいというものがあります。例えば、b0o/incline.nvim はウィンドウ毎にファイル名などを表示するプラグインですが、気付いた時にあればいい、程度のものです。以下のようにすると、カーソルが暫く止まった時、あるいは、別のアプリケーションやペイン(tmux を使っている場合)に切り替えた時に読み込みます。
use {
"b0o/incline.nvim",
event = { "FocusLost", "CursorHold" },
config = function()
require("incline").setup {}
end,
}
起動時には必要なく、ファイルを開いた時にだけ必要なものもありますね。ahmedkhalf/project.nvim はファイルを開くと自動的にカレントディレクトリを移動してくれるプラグインですが、Neovim 起動時はまだファイルを開いてないので移動する必要がありません。
use {
"ahmedkhalf/project.nvim",
event = { "BufRead", "BufNewFile" },
config = function()
require("project_nvim").setup {}
end,
}
petertriho/nvim-scrollbar は Neovim にスクロールバーを追加してくれるプラグインです。これはプラグインのソースを読むと実際に描画すべきイベントが定義されています。これに応じて遅延読み込みするのが最善です。
use {
"petertriho/nvim-scrollbar",
event = {
"BufWinEnter",
"CmdwinLeave",
"TabEnter",
"TermEnter",
"TextChanged",
"VimResized",
"WinEnter",
"WinScrolled",
},
config = function()
require("scrollbar").setup {}
end,
}
後はコマンドラインでのみ意味のあるプラグイン(例えば houtsnip/vim-emacscommandline)は CmdlineEnter
で読み込むとか、挿入モードでのみ意味のあるプラグイン(例えば delphinus/skkeleton_indicator.nvim)は InsertEnter
で読み込むとか、本当に様々なパターンが考えられます。Neovim で定義されているイベントは多数ありますから一度一覧を見てみると良いでしょう。
4.4. 複雑な遅延読み込み
依存関係や設定の都合上、特殊な遅延読み込みを行うパターンもあります。少し雑多な内容ですがここに纏めておきます。
4.4.1. hrsh7th/nvim-cmp
Neovim 界で補完プラグインのデファクトは hrsh7th/nvim-cmp でしょう。この種のプラグインは往々にして依存プラグインが多く、設定も複雑になりがちです。補完プラグインは InsertEnter
イベントで読み込むのがセオリーなのですが、依存する複数のプラグイン全てをこのイベントで遅延読み込みさせようとすると、これは上手く行きません。
-- 上手く行かない例
use {
"hrsh7th/nvim-cmp",
event = { "InsertEnter" },
requires = {
{ "hrsh7th/cmp-buffer", event = { "InsertEnter" } },
{ "hrsh7th/cmp-emoji", event = { "InsertEnter" } },
-- ……以下各種ソースプラグインが続く
},
config = function()
require("cmp").setup {
-- 各種設定
}
end
}
なぜ上手く行かないかと言うと、event
オプションによる読み込みでは順序が前後する可能性があるのからです。ソースプラグイン内で require "cmp"
した時に「そんなモジュールはないよ」と言われてしまうことがあるんですね。
各種ソースプラグインには幸い、遅延読み込みに使い易い一つの共通点があります。各々のソースプラグインは必ず after/plugin/cmp_hoge.lua
というファイルを含んでおり、このファイルはプラグイン追加時に必ず呼ばれます。そしてその中で require "cmp"
しているんですね。
-- 上手く遅延読み込みできる例
use {
"hrsh7th/nvim-cmp",
module = { "cmp" },
requires = {
{ "hrsh7th/cmp-buffer", event = { "InsertEnter" } },
{ "hrsh7th/cmp-emoji", event = { "InsertEnter" } },
-- ……以下各種ソースプラグインが続く
},
config = function()
require("cmp").setup {
-- 各種設定
}
end
}
-
i
などで挿入モードに入る。 -
InsertEnter
イベントが発生。 - 各種ソースプラグインが読み込まれる。
-
after/plugin/cmp_hoge.lua
内でrequire "cmp"
が呼ばれる。 - hrsh7th/nvim-cmp が読み込まれる。
- ここで
config
オプションに指定した関数も実行されます。
- ここで
- 期待した動作が行われる。
一部、after/plugin/cmp_hoge.lua
を含んでいないソースプラグインもあるようです。これが hrsh7th/nvim-cmp 的に正しい実装なのかは分からないですが、このようなソースプラグインを使う時はこれだけを start プラグインとして配置するといいかも知れません。
4.4.2. nvim-telescope/telescope.nvim
こちらも Neovim 界では一番人気のファジーファインダー、nvim-telescope/telescope.nvim です。こちらはそれほど複雑な設定にはならず、wants
オプションなどを適切に指定すれば OK です。ファジーファインダーでは良くあることなのですが、マッピングなどはユーザー個々の好き嫌いがありますので、ここに挙げる設定はあくまで参考程度にしてください。
use {
"nvim-telescope/telescope.nvim",
module = { "telescope" },
requires = {
{ "nvim-telescope/telescope-ghq.nvim", opt = true },
{ "nvim-telescope/telescope-z.nvim", opt = true },
-- その他の拡張プラグイン……
},
wants = {
"telescope-ghq.nvim",
"telescope-z.nvim",
-- ……
},
setup = function()
local function builtin(name)
return function(opt)
return function()
return require("telescope.builtin")[name](opt or {})
end
end
end
local function extensions(name, prop)
return function(opt)
return function()
local telescope = require "telescope"
telescope.load_extension(name)
return telescope.extensions[name][prop](opt or {})
end
end
end
vim.keymap.set("n", "<Leader>f:", builtin "command_history" {})
vim.keymap.set("n", "<Leader>fG", builtin "grep_string" {})
vim.keymap.set("n", "<Leader>fH", builtin "help_tags" { lang = "en" })
vim.keymap.set("n", "<Leader>fm", builtin "man_pages" { sections = { "ALL" } })
vim.keymap.set("n", "<Leader>fq", extensions("ghq", "list") {})
vim.keymap.set("n", "<Leader>fz", extensions("z", "list") {})
-- ……以降設定が続く
end,
config = function()
require("telescope").setup {
-- 様々な設定
}
end
}
-
<Leader>fH
を押す。 -
builtin
関数が呼び出され、中でrequire "telescope.builtin"
される。 - nvim-telescope/telescope.nvim が読み込まれる。
- 上述したように、
module = { "telescope" }
は^telescope
という正規表現でモジュール読み込みにフックします。そのため、telescope.builtin
という指定でプラグインが読み込まれるのです。
- 上述したように、
- 期待した動作が行われる。
ここでは builtin
や extensions
と言った便利関数を用意してマッピングを読み易くしています。Lua では関数呼び出しの括弧を省けるので、DSL のように書けて良いですね。
4.4.3. folke/noice.nvim
folke/noice.nvim は開発の活発なプラグインですので、ここに書いた設定もすぐに陳腐化するかも知れません。ここに載せたものはあくまで執筆当時に利用できた例として読んでください。
folke/noice.nvim は set cmdheight=0
と共に使うことを意図した、Neovim の UI 全般を改善するプラグインです。このプラグインには様々な機能があります。例えばコマンドラインへのメッセージ表示にフックして起動し、メッセージを OS の通知センターのように画面右上に表示する形にしてくれます。set cmdheight=0
はコマンドライン自体を全く表示しないようにするオプションですから、folke/noice.nvim はほとんど必須と言っていいプラグインです。
未だ不完全なのですが、folke/noice.nvim を遅延読み込みする例を以下に載せます。
use {
"folke/noice.nvim",
event = { "BufRead", "BufNewFile", "InsertEnter", "CmdlineEnter" },
module = { "noice" },
requires = {
{ "MunifTanjim/nui.nvim" },
{
"rcarriga/nvim-notify",
module = { "notify" },
config = function()
require("notify").setup {
-- nvim-notify の設定
}
end
},
},
wants = { "nvim-treesitter" },
setup = function()
if not _G.__vim_notify_overwritten then
vim.notify = function(...)
local arg = { ... }
require "notify"
require "noice"
vim.schedule(function()
vim.notify(unpack(args))
end)
end
_G.__vim_notify_overwritten = true
end
end,
config = function()
require("noice").setup {
-- noice.nvim の設定
}
end
}
folke/noice.nvim は MunifTanjim/nui.nvim, rcarriga/nvim-notify という 2 つのプラグインに依存しています。後者がコマンドラインのメッセージを OS の通知センターのように表示する役目を負っています。folke/noice.nvim が難しいのは、通常の遅延読み込みフックである event
や module
以外に、「vim.notify
が呼ばれた時」に起動する必要があることです。
vim.notify は :echo
や :echomsg
に代わるメッセージ表示 API です。上記の setup
オプションではこれを上書きし、次のようにして folke/noice.nvim に処理をバトンタッチしています。
- Neovim 起動時、
setup
オプションに指定された関数が呼ばれます。 -
vim.notify
を上書きします。中では rcarriga/nvim-notify, folke/noice.nvim をrequire
しますが、それぞれ読み込み契機としてmodule = { "noice.nvim" }
のように指定しています。これにより、プラグインが読み込まれると同時にそれぞれのconfig
オプションに設定された関数も呼ばれます。 -
require("noice").setup {}
の中では、vim.notify
が folke/noice.nvim のものに置き換わります(実際の処理はここ)。 - 最後に、上書きされた
vim.notify
で自身を呼び出して完了です。-
vim.notify
の上書きはvim.schedule
を使ってイベントループにスケジューリングされています。そのため、ここでの呼び出しもvim.schedule
で遅らせないと効果がありません。
-
遅延読み込みしたい場合はこのように、プラグインの内部実装についても詳しく調べる必要があるのです。
folke/noice.nvim は他のメッセージ表示コマンド(echo
, echomsg
, confirm
などなど)についてもフックして起動します。今回 vim.notify
だけを上書きしているのは、僕の環境ではこれが folke/noice.nvim 自身より前に呼ばれる可能性の高いものだったからです。プラグイン構成によっては起動時に echo
を呼ぶ場合もあると思います。そういう場合にこれは上手く働きませんので folke/noice.nvim は遅延読み込みしない方が良いでしょう。
4.4.4. vim-denops/denops.vim を使ったプラグインを遅延読み込みする
vim-denops/denops.vim は TypeScript を使ったプラグインエコシステムです。このプラグインを利用すると、TypeScript で Neovim / Vim 両対応のプラグインが書けます。開発ツールも豊富な TypeScript でプラグインを書けるとあって、(特に日本で)広く受け入れられています。有名なものとして、以下のようなプラグインが vim-denops/denops.vim を利用して開発されています。
最初に言っときますと、packer.nvim では vim-denops/denops.vim を使ったプラグインの遅延読み込みをサポートしていません。
vim-denops/denops.vim を使ったプラグインを遅延読み込みする時は packadd
するだけではダメなのです。Deno プロセス自体を起動したり、プラグインを指定して関数を呼び出す必要があります。これに対応したプラグインマネージャーとしては Shougo/dein.vim が挙げられます。
これには一応解決法もありまして(というか僕が作ったんですが)packer.nvim に対して PR があります。
-- denops.vim を使ったプラグインを遅延読み込みする例
use {
-- fork した packer.nvim を使います。
{ "delphinus/packer.nvim", branch = "feature/denops", opt = true },
-- カーソルが暫く動かなかった時や、別のウィンドウ・ペインに移動した時に
-- 裏で読み込んでおきます。
{ "vim-denops/denops.vim", event = { "CursorHold", "FocusLost" } },
{
"vim-skk/skkeleton",
wants = { "denops.vim" },
keys = { { "i", "<Plug>(skkeleton-toggle)" } },
setup = function()
vim.keymap.set("i", "<C-j>", "<Plug>(skkeleton-toggle)")
end
config = function()
vim.fn["skkeleton#config"] {
-- ……様々な設定……
}
end
},
}
僕自身も使っていて特に問題は起きていないのですが、如何せん packer.nvim ユーザーには余り需要がないのか、マージされる気配がありません。みなさんから wbthomason さん(作者)にマージして!と言っていただけると早くマージされるかも知れません。
4.4.5. packer.nvim 自身を遅延読み込みする
packer.nvim は Neovim の packages 機能を利用するためのユーティリティ集と言っていい存在です。そのため、一度プラグイン構成を決めてしまうとそれ以上読み込んでおく必要はありません。この辺は以前の記事にも書いたので詳しい話は省略します。
use {
"wbthomason/packer.nvim",
module = { "packer" },
setup = function()
local packer
local function run_packer(method)
return function(opts)
if not packer then
vim.cmd.packadd "packer.nvim"
packer = require "packer"
packer.init {
-- 様々な設定
}
packer.use {
-- 様々なプラグイン
}
end
end
packer[method](opts)
end
vim.api.nvim_create_user_command("PackerInstall", run_packer "install", { desc = "[Packer] Install plugins" })
vim.api.nvim_create_user_command("PackerUpdate", run_packer "update", { desc = "[Packer] Update plugins" })
vim.api.nvim_create_user_command("PackerClean", run_packer "clean", { desc = "[Packer] Clean plugins" })
vim.api.nvim_create_user_command("PackerStatus", run_packer "status", { desc = "[Packer] Output plugins status" })
vim.api.nvim_create_user_command("PackerCompile", run_packer "compile", { desc = "[Packer] Output plugins status" })
vim.api.nvim_create_user_command("PackerSync", run_packer "sync", { desc = "[Packer] Update plugins" })
-- …… 以下、他のコマンドも定義する
end,
}
setup
オプションで設定していますのでこれらのコマンドは Neovim 起動時に定義されます。ですが、その時点で packer.nvim
は読み込まれていません。例えばプラグインの更新のために :PackerSync
とコマンドを打ったとします。するとその時に初めて packer.nvim
の設定が行われます。普通に Neovim で作業している間は読み込まれないのです。
5. ファイルタイププラグインの扱い
さて、ここまで長々と「プラグインの遅延読み込み」について述べてきました。ここからは遅延読み込みに直接関わらない、Neovim 起動ステップの高速化について見ていきます。
5.1. ファイルタイププラグインとは?
ファイルタイププラグインとは、特定のファイルタイプ(例えば c
, perl
, dockerfile
)のファイルを開いた時に行われる専用の設定(構文ハイライトやインデントなど)を纏めたものです。ファイルタイププラグインはその「専用の設定」とは別に、「どうやってファイル(正確にはバッファー)をそのファイルタイプと認識するのか」という仕組みがあります。
例えば、delphinus/vim-firestore というプラグインがあります。Firestore Security Rules を書く時に便利なものですが、このプラグインには ftdetect/firestore.vim
というファイルが含まれています。
autocmd BufNewFile,BufRead *.rules set filetype=firestore
中にはこんな感じで自動コマンドが定義されており「これらの拡張子ならファイルタイプを firestore
にする」と書かれています。
5.2. ファイルタイププラグインを遅延読み込みする
ここで重要なことがあります。この仕組みは当然ですが、ファイルを読み込む前にこの設定がないと、そもそも firestore
として認識されません。つまりこれに従うなら、delphinus/vim-firestore は Neovim 起動時に読み込んでおかないと意味を為さないことになってしまいます。
これは初期の packer.nvim においても同様で、前回の記事でも初稿では「ファイルタイププラグインは遅延読み込みできない」としていました。その後、packer.nvim も改善されて制限は無くなっています。例えば以下のようにファイルタイププラグインを遅延読み込みした場合、
use {
"delphinus/vim-firestore",
ft = { "firestore" },
}
コンパイルした設定ファイルには次のように追記されます。
-- augroup 開始
vim.cmd [[augroup filetypedetect]]
-- プロファイリング用関数
time([[Sourcing ftdetect script at: /Users/jinnouchi.yasushi/.local/share/nvim/site/pack/packer/opt/vim-firestore/ftdetect/firestore.vim]], true)
-- ftdetect スクリプトを読み込む
vim.cmd [[source /Users/jinnouchi.yasushi/.local/share/nvim/site/pack/packer/opt/vim-firestore/ftdetect/firestore.vim]]
-- augroup 終了
vim.cmd("augroup END")
コメントは全部僕が付けたものです。このように、Neovim 起動時にプラグイン全体を読み込むことなく、ftdetect
スクリプトのみを先に読み込んでいるのです。
5.3. ftdetect
スクリプトを自作する
上記の方式は単純に ftdetect
スクリプトを読み込んでいるだけなので、ファイルタイププラグインの数が増えていくと段々起動速度も遅くなります。これを解決するには自分で ftdetect
スクリプトを 1 ファイルに纏めると良いです。
今回の例の場合、自分の設定ディレクトリに作ったファイルにそのまま内容をコピペするか、
" vim-firestore/ftdetect/firestore.vim
autocmd BufNewFile,BufRead *.rules set filetype=firestore
" その他のファイルタイププラグインについてもコピペしていく……
この際ですから Lua のコードに変換してしまうのも手ですね。
-- vim-firestore/ftdetect/firestore.vim
vim.filetype.add {
extension = {
rules = "firestore",
-- ……以下続く……
},
-- 自分独自の設定を追加できるのも強みです。
pattern = {
["%.rules$"] = function(_, _, _)
local is_exist = vim.loop.fs_stat ".gcloudignore"
if is_exist then
vim.opt.filetype = "firestore"
end
end
},
}
-- その他のファイルタイププラグインについても変換していく……
vim.filetype
は最近追加されたファイルタイプ判別用の API です。詳しくはヘルプを読んでください。
その上で、コンパイルした packer.nvim の設定ファイルからは読み出し部を消してしまいましょう。PackerCompile
の後に手で消してもいいですが、どうせなら自動化してみます。
vim.api.nvim_create_autocmd("User", {
pattern = "PackerCompileDone",
callback = function()
-- これはデフォルトのファイル名です。packer.nvim のオプションで指定できます。
local compile_path = "~/.config/nvim/plugin/packer_compiled.lua"
vim.cmd("split ++enc=latin1 " .. compile_path)
-- この行から
vim.cmd [[/^vim\.cmd \[\[augroup filetypedetect\]\]$]]
-- この行までを削除します。
vim.cmd [[.,/^vim\.cmd("augroup END")$/delete]]
vim.cmd.wq { bang = true }
vim.notify("Successfully edited " .. vim.fs.basename(compile_path))
end
})
PackerCompileDone
は PackerCompile
コマンドの実行直後に発火するイベントです。コンパイル後のファイルに対してここで後処理を加えることができます。
この、「ftdetect
スクリプトを一つのファイルに纏める」というのは Shougo/dein.vim では標準で対応してくれる機能です。packer.nvim ではこのようなチューニングは自分でやらないといけません。
5.4. そもそもファイルタイププラグインを使わない
前段を読んで「なんだかめんどくさいなあ。これそこまでして使う必要ある?」と思った方、正解です。実は、ファイルタイププラグインは現代の Neovim ではそこまで必須のものではなくなっています。これは以下の理由に寄るものです。
5.4.1. Tree-sitter の存在
Tree-sitter は元々 Atom の一機能として開発されましたが、Neovim ではこれを本体に組み込むことで従来の正規表現ベースだった構文ハイライトを改善しています。現時点でも多くのファイルタイプが Tree-sitter による構文解析に対応しており、有効なファイルタイプについては従来の(Vimscript で書かれた)syntax
ファイルは利用されません。マイナーなファイルタイプ(例えば拙作 delphinus/vim-firestore)のためには相変わらず必要ですが、ある程度有名なファイルタイプのために専用のプラグインは不要かも知れません。
5.4.2. 標準で認識できるファイルタイプが増えた
Neovim だけでなく Vim もですが、標準で認識できるファイルタイプはどんどん増えています。以前はファイルタイププラグインが必要だったファイルタイプでも ftdetect
スクリプトのためだけに導入していたとしたら必要無くなっている可能性もあります。
もっとも、インデントや compiler
設定など、Neovim 本体にはない設定もあるでしょう。もう一度ファイルタイププラグインの機能を確認し、本当に必要か見定めた方が良いです。
5.4.3. LSP や整形ツールによっても代替できる
ファイルタイププラグインの機能は LSP によって代替できるものも多いです。例え対応する Language Server がインストールできなくても、先に挙げたインデント機能などは整形ツールをファイル保存時に実行するだけで十分かも知れません。例えば、Lua ファイルを書く時は Neovim 本体のインデント機能よりも、ファイル保存後に JohnnyMorganz/StyLua を実行する方が遥かに綺麗になるでしょう。
LSP についてはこの記事では深く触れないことにします。上記のように整形ツールを連携して使う際は jose-elias-alvarez/null-ls.nvim の利用を検討すると良いでしょう。
プラグインには様々な副作用があります。不要なプラグインを設定に残しておくのはトラブルの元です。。是非これを機に自分の使っているプラグインリストを整理してみましょう。
6. 高速なカラースキームを使う
カラースキームについても述べておかなければなりません。カラースキームはそれこそ星の数ほど存在し、ユーザーの環境によって最適なものも異なります。個人の好みも千差万別ですからどれが良いというのもありません。
ただ、Neovim の起動を高速化するに当たって、考慮しておくべきポイントがあります。
6.1. Tree-sitter の要素を考慮しているかどうか
前述しました通り、現代的な Neovim の構文ハイライトは Tree-sitter と切り離して語ることはできません。Tree-sitter により解析されたノードに対し、以下のようにハイライトを定義することができます。
vim.api.nvim_set_hl(0, "@boolean", { fg = "#81A1C1", bold = true })
vim.api.nvim_set_hl(0, "@constant.builtin" { fg = "#8FBCBB", bold = true })
vim.api.nvim_set_hl(0, "@constant.macro", { fg = "#8FBCBB", bold = true })
vim.api.nvim_set_hl(0, "@error", { fg = "#BF616A" })
vim.api.nvim_set_hl(0, "@exception", { fg = "#B48EAD" })
vim.api.nvim_set_hl(0, "@function.macro", { fg = "#8FBCBB" })
-- ……以下沢山の定義が続く……
従来の正規表現ベースのハイライトと違って細かく指定することでコードを分かり易く表示できます。Neovim では(正確には nvim-treesitter/nvim-treesitter プラグインにより)標準的なハイライトを定義しています。そのためカラースキーム側の対応は必須ではないのですが、やはり細かく指定して提供されている方が良いでしょう。
6.2. 古い不要な定義が残っていないか
Tree-sitter が無い時代に定義されたハイライトがカラースキームにそのまま残っている場合があります。例えば Neovim の runtime/syntax/markdown.vim
には以下のように markdownH1
~markdownH6
という構文リージョンを定義しています(ソースへのリンク)。
syn region markdownH1 matchgroup=markdownH1Delimiter start=" \{,3}#\s" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained
syn region markdownH2 matchgroup=markdownH2Delimiter start=" \{,3}##\s" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained
syn region markdownH3 matchgroup=markdownH3Delimiter start=" \{,3}###\s" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained
syn region markdownH4 matchgroup=markdownH4Delimiter start=" \{,3}####\s" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained
syn region markdownH5 matchgroup=markdownH5Delimiter start=" \{,3}#####\s" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained
syn region markdownH6 matchgroup=markdownH6Delimiter start=" \{,3}######\s" end="#*\s*$" keepend oneline contains=@markdownInline,markdownAutomaticLink contained
これを利用したカラースキームとしては morhetz/gruvbox が挙げられます。このプラグインでは実際に markdownH1
~markdownH6
に違ったハイライトを設定しています。
hi! link markdownH1 GruvboxGreenBold
hi! link markdownH2 GruvboxGreenBold
hi! link markdownH3 GruvboxYellowBold
hi! link markdownH4 GruvboxYellowBold
hi! link markdownH5 GruvboxYellow
hi! link markdownH6 GruvboxYellow
しかしこれらの構文リージョンに対するハイライトは、Tree-sitter を有効にしている場合全て無視されてしまいます。カラースキームはその性質上、Neovim 起動時に必ず読み込むものですから、利用しない構文ハイライトまで定義すると起動が遅くなってしまいます。できればここまで考慮したカラースキームを選びたいですね。
ここでは Tree-sitter の詳しい使い方は説明しません。上述のようにタイトル行のレベルに応じてハイライトを変えたい場合は以下のような記事・プラグインを参照してください。
6.3. Lua で書いたカラースキームってどう?速いの?
他のプラグインと同様、カラースキームの中には完全に Lua で書かれたものも存在します(folke/tokyonight.nvim などが有名でしょうか)。Lua で書かれてるのだから Vimscript で書かれた従来のものより起動が早くなる……かと思いきや、実はそんなに違いがありません。
Vimscript と Lua で差が出るのは、頻繁に呼ばれるコードや runtimepath
の探索部分です。カラースキームの読み込みは API をひたすら呼び出す作業ですし、条件分岐などのロジックもほとんどありません。あくまで「不要なハイライトまで定義していないか」が速度に影響します。
Lua で書かれたカラースキームが明らかに優れていると思うのはその保守性・可読性です。ハイライトをモジュールに分けて管理し、その単位で機能をオン・オフすることができたりします。コントリビュートする際もやり易いですね。
6.4. ……なんか難しいな。結局どのカラースキームがいいの?
と思われたでしょうが、こればっかりは好みがありますのでどれが良いともお勧めすることができません。一つ言えるのは、「カラースキームは自作するのが最善」であることです。完全にゼロから作るのは難しいですが、公開されているものをカスタマイズして自分専用のカラースキームを作ってみるのが良いでしょう。
僕自身は delphinus/nord-nvim という、arcticicestudio/nord-vim をカスタマイズしたものを使っています。好みの設定を適当に追加・削除したものなので、ドキュメントなどは書いておりません ;)
6.5. ColorScheme
イベント
プラグインの管理と関連するところでは、ColorScheme
イベントの利用が挙げられます。例えば lewis6991/gitsigns.nvim を使う場合、それに関連したハイライトはプラグインを追加する際纏めて書いておきたいです。
use {
"lewis6991/gitsigns.nvim",
event = { "FocusLost", "CursorHold" },
setup = function()
vim.api.nvim_create_autocmd("ColorScheme", {
callback = function(opts)
if opts.match == "nord" then
vim.api.nvim_set_hl(0, "GitSignsAdd", { fg = "#a3be8c" })
vim.api.nvim_set_hl(0, "GitSignsChange", { fg = "#ebcb8b" })
vim.api.nvim_set_hl(0, "GitSignsDelete", { fg = "#bf616a" })
else
-- ……他のカラースキームの場合の設定……
end
end
})
end,
config = function()
require("gitsign").setup {
-- 様々な設定
}
end,
}
特にカラースキームを自作している場合は、カラースキーム自身にプラグイン向けのハイライトを定義したくなります。しかしそれだと、将来プラグインを使わなくなった時に消し忘れることもあるでしょう。この例のように、プラグインを使わなくなった時に纏めて消せるようにしておくと間違いがありません。遅延読み込みする場合は config
オプションでプラグイン読み込み後に指定する手もありますが、このように ColorScheme
イベントで定義しておくと Neovim 起動中にカラースキームを変えた場合でも適切に設定されます。
自動コマンドの callback
に関数を設定した場合は引数から様々な情報を受け取ることができます。nvim_create_autocmd
のドキュメントや ColorScheme
イベントのドキュメントを参照してください。
7. ステータス行・タブページ行を遅延読み込みする
これは Neovim の見た目や使い勝手にも影響しますので禁じ手に近いやり方です。ですが、ステータス行・タブページ行の描画が重い場合には大変効果があります。
use {
"nvim-lualine/lualine.nvim",
event = { "InsertEnter", "CursorHold", "FocusLost", "BufRead", "BufNewFile" },
requires = {
{ "kyazdani42/nvim-web-devicons", module = { "nvim-web-devicons" } },
},
setup = function()
vim.opt.laststatus = 0
vim.opt.showtabline = 0
end
config = function()
require("lualine").setup {
-- ……様々な設定……
}
end
}
nvim-lualine/lualine.nvim は Neovim 界でステータス行・タブページ行をカスタマイズするプラグインのデファクトです。これは人に寄ると思いますが、僕の場合ステータス行・タブページ行を見るのは実際に編集を開始した後で、Neovim を起動後にファイルを開くまでは必要ありません。そのため、setup
オプションに指定した関数で描画自体を無くしています。その後、挿入モードに入った時(InsertEnter
)や、ファイルを開いた時(BufRead
など)、あるいは編集する手を止めた時(CursorHold
など)でプラグインを読み込むようにしているのです。
これは、僕がいつも Neovim を nvim
のように、ファイルを開かずに起動しているから使える手です。nvim filename.txt
のように直接ファイルを開いて使うような人には余り効果が無いかも知れませんね。
8. 更に上を目指すには
さて、ここまでで Neovim の起動を高速化するテクニックはあらかた網羅できたと思います。しかし当初の話題に戻ると、僕の設定では平均起動速度は 29 ms です。ayamir/nvimdots を僕のマシンで動かした時は 26 ms 程度で起動していました。後はどのようなことで差が出ているのでしょうか。
8.1. 利用するプラグインの数を減らす
単純ですが、これが一番大きな問題です。僕は常に 160 個程度のプラグインを使っていますが、ayamir/nvimdots はその半分ほどしかプラグインを読み込みません。もちろんほとんどは遅延読み込みさせているのですが、その設定にもコードが必要です。例えば以下のような単純なプラグイン設定を追加したとしましょう。
use {
"petertriho/nvim-scrollbar",
event = { "BufWinEnter" },
config = function()
require("scrollbar").setup {}
end,
},
これにより、コンパイルしたファイルには以下のように追記されます。
_G.packer_plugins = {
["nvim-scrollbar"] = {
config = { "\27LJ\2\n;\0\0\3\0\3\0\a6\0\0\0'\2\1\0B\0\2\0029\0\2\0004\2\0\0B\0\2\1K\0\1\0\nsetup\14scrollbar\frequire\0" },
loaded = false,
needs_bufread = false,
only_cond = false,
path = "/Users/jinnouchi.yasushi/.local/share/nvim/site/pack/packer/opt/nvim-scrollbar",
url = "https://github.com/petertriho/nvim-scrollbar"
},
-- ……他のプラグイン設定が続きます……
}
-- このイベントでプラグインを読み込みます。
vim.cmd [[au BufWinEnter * ++once lua require("packer.load")({'nvim-scrollbar'}, { event = "BufWinEnter *" }, _G.packer_plugins)]]
Lua という言語は非常に高速ですが、Neovim の API を呼び出す際はそれなりの時間が掛かります。自動コマンド(au BufWinEnter
)が積もり積もれば差は無視できません。例え遅延読み込みしたとしても起動速度への影響はゼロにならないのです。
8.2. 設定自体を簡潔にする
僕の Neovim の画面は以下のようになっています。
ayamir/nvimdots と比べてまあ見るからに複雑になっています(ステータス行・タブページ行は遅延読み込みしていますが)。見えない所では nvim-telescope/telescope.nvim や hrsh7th/nvim-cmp についても関連するプラグイン・設定が多くありますので、勢いコンパイルされた時のコード量も増えてしまうのです(例えば nvim-telescope/telescope.nvim の設定は 370 行もあります)。これは簡略化しようと思えばできるんでしょうが、これこそ好みの問題ですので難しいです。
というわけで、これ以上の高速化を望むのは自らが Neovim に望むこととのトレードオフになってきます。ayamir/nvimdots でさえ、素の Neovim には及ばないのですからね。自分なりに納得できるポイントを定めて、そこに向かって高速化を目指しましょう。
Neovim を ayamir/nvimdots 以上に速く起動させることはできるのでしょうか? 実はその方法があります。Neovim の runtime
ファイル(標準で添付されたスクリプト群)には様々なプラグインを含んでいます。
# Neovim 内のターミナルで試してみましょう
$ ls -l $VIMRUNTIME/plugin
この他、起動プロセスで必ず読み込まれるファイルがいくつかありますので、それらを物理的に削除してしまえば良いのです。どのようなファイルがそれに当たるのかは「2.1. --staruptime
オプション」のログを確認してみましょう。
これらは正に禁じ手で、行為の意味を把握せずに誤ってファイルを削除してしまうと Neovim が起動しなくなります。気を付けて実践しましょう。
9. おまけ - ayamir/nvimdots を試してみよう
最後におまけとして、超高速な設定ファイル集、ayamir/nvimdots を自分の環境で試してみましょう。↓単純に以下のようにするだけで起動はするのですが、注意しないと環境を汚してしまいます。
$ cd /tmp
$ git clone https://github.com/ayamir/nvimdots
$ cd ~
# 自分の設定を退避
$ mv .config/nvim{,.orig}
# ayamir/nvimdots をリンク
$ ln -s /tmp/nvimdots .config/nvim
このままだと、起動時に fatih/vim-go の色んなツールをインストールしてしまいますし、Shada など重要なファイルも上書きしてしまいます。
以下ではこれを避け、普段使っている Neovim 環境を汚さない方法を書いています。
ayamir/nvimdots を気軽に試せる Dockerfile
も書いてみました。これを使うと次のコマンドで ayamir/nvimdots を使った Neovim が起動できます。
mkdir /tmp/hoge
cd /tmp/hoge
curl -LO https://gist.githubusercontent.com/delphinus/afe3d94cab35f55bb1d4f79e74245cc5/raw/6d3917a8e2740845a35567fad8bbd6c80be0bb50/Dockerfile
docker build -t nvimdots .
docker run -it nvimdots
nvim
ただ、起動速度を比較する場合は Docker 上だと余り意味がありませんから、以下に示した方法で行った方が良いですね。
先ずは、ayamir/nvimdots 内でプラグインが関連ファイルをインストールするのを止めます。
# run = ... 行を全てコメントアウト
$ cd /tmp/nvimdots
$ perl -i -pe 's/^(?=\s*run =)/--/' **/*.lua
Shada やインストール済みのプラグインがあるディレクトリを退避します。
$ cd ~
$ mv .local/share/nvim{,.orig}
$ mv .local/state/nvim{,.orig}
これでやっと起動できます。
$ mv .config/nvim{,.orig}
$ ln -s /tmp/nvimdots .config/nvim
$ nvim
初回起動時はプラグインのインストールなどが始まるので適当に <Enter>
を押してください。何回か Neovim 自体を起動・終了させる必要があるかも知れません。上手く起動できたら起動速度も測ってみましょう。みなさんの起動速度とどのくらいの差があるでしょうか?
$ vim-startuptime -vimpath nvim -count 1000
最後に、元通りに戻しておきましょう。
$ rm .config/nvim
$ rm -fr .local/share/nvim
$ rm -fr .local/state/nvim
$ mv .config/nvim{.orig,}
$ mv .local/share/nvim{.orig,}
$ mv .local/state/nvim{.orig,}