2024-10-14

Neovim の折りたたみを使いやすくカスタマイズする

Vim に標準で備えられている折りたたみ機能について、ちょくちょく使ってはいたものの雑な設定しかしていない状態でした。 今回ユーザマニュアルを読みつつ設定を見直し、ある程度使いやすいと感じる状態になったのでその記録を残します。

そもそもどういう用途で折りたたみを使うか

まず、どういう用途で折りたたみを使うかを改めて言語化してみました。
ここは人によって様々かと思いますが、自分の場合は以下のような用途だと思われました。

  1. 大きなコードの全体像をざっくり把握する
  2. 自分が修正しようとしている箇所と関係ないところを隠す

こういった用途で使いやすいようにカスタマイズしていきます。

インデント基準で折りたためるようにする

ユーザマニュアル を読むと折りたたみ方法としていくつかの選択肢がありますが、全体像の把握や不要な詳細を閉じるという用途においては "indent" が最も適していると思います。

-- init.lua
vim.opt.foldmethod = "indent"

ただ、このままだと Vim でファイルを開いたときにすでに折りたたまれている状態となってしまい、それは望むところではありません。
これは、折りたたみの深さを設定する foldlevel という値がデフォルトで 0 となっていることが原因ですので、以下のように大きな値を設定しておきます。

-- init.lua
vim.opt.foldmethod = "indent"
vim.opt.foldlevel = 99 -- 追加

やや無理矢理感はありますが、致し方なさそうです。

S-Tab で閉じ、Tab で開くキーマッピング

さてここまでの設定で事前準備ができました。

次は折りたたんだり開いたり、というところをサクサクとできるようにしていきます。 ここのキーマップは少々悩みましたが、自分は S-Tab(Shift + Tab) でカーソル位置を基準に折りたたむようにし、Tab で開くようにしました。

-- init.lua
vim.keymap.set("n", "<Tab>", "zo")
vim.keymap.set("n", "<S-Tab>", "zc")
vim.keymap.set("n", "<Leader><Tab>", "zR")
vim.keymap.set("n", "<Leader><S-Tab>", "zM")

以下のような操作感になります。 nvim_fold_open_and_close_demo

応用として、<Leader>S-Tab ですべて折りたたみ、<Leader>Tab ですべて開くようにしました。これは全体像を把握するという目的に対してマッチしていて、<Leader>S-Tab ですべて閉じた後に Tab で徐々に開いて眺めていく、ということができます。

nvim_fold_all_open_and_all_close_demo

foldtext で折りたたんだ際の表示を読みやすくする

最後に、折りたたまれた箇所の表示を少し変更します。

変更するには、foldtext という設定値に自作の関数を渡してあげます。次のようにしてみます。

-- init.lua
function Foldtext()
  local line = vim.fn.getline(vim.v.foldstart)
  local count = vim.v.foldend - vim.v.foldstart + 1
  return string.format("%s (%d lines folded)", line, count)
end

vim.opt.foldtext = "v:lua.Foldtext()"
vim.opt.fillchars = { fold = " " } -- 折りたたんだ際のあまりの部分をスペースにする

fillchars という設定値も変更して折りたたんだ際の余白の表示文字を変更しています。これで、折りたたんだ際に以下のような表示になります。

foldtextをカスタマイズ
foldtextをカスタマイズ

情報量としてはあまり変わっていませんが、行頭の位置が折りたたむ前と後でずれなくなったのでより見やすくなった気がします。ここはもう少し改良の余地はあるかもしれません。

まとめ

Neovim の折りたたみ設定を改善した話でした。
折りたたみ機能自体あまり使っている人は多くない印象ですが、用途がマッチした人の参考になれば幸いです。

最終的な init.lua の状態を載せて終わります。

-- init.lua
function Foldtext()
  local line = vim.fn.getline(vim.v.foldstart)
  local count = vim.v.foldend - vim.v.foldstart + 1
  return string.format("%s (%d lines folded)", line, count)
end

vim.opt.foldmethod = "indent"
vim.opt.foldlevel = 99
vim.opt.foldtext = "v:lua.Foldtext()"
vim.opt.fillchars = { fold = " " }

vim.keymap.set("n", "<Tab>", "zo")
vim.keymap.set("n", "<S-Tab>", "zc")
vim.keymap.set("n", "<Leader><Tab>", "zR")
vim.keymap.set("n", "<Leader><S-Tab>", "zM")