Neovim標準の:Undotreeと:DiffToolを使う
undo履歴の分岐をたどるundotree.vim、差分を見比べるためのdiffツール。どちらもプラグインで補うのが当たり前でしたが、Neovim 0.12からは本体に同梱されています。しかも既定では読み込まれないので、入っていること自体が知られていません。:packaddを1行打つだけで:Undotreeと:DiffToolが生えます。
検証環境:安定版のNVIM v0.12.4とnightlyビルドのNVIM v0.13.0-dev-1336+g8c0bf18374(macOS)の両方で動かしました。掲載している画面の中身とquickfixの内容は実行結果です。どちらのバージョンでも同じように動きました。
目次(4項目)
| コマンド | これだけ覚える |
|---|---|
:packadd nvim.undotree | undo履歴のプラグインを読み込む |
:Undotree | 履歴の木を開く。もう一度打つと閉じる |
:packadd nvim.difftool | 比較のプラグインを読み込む |
:DiffTool 左 右 | ファイルまたはディレクトリを比較する |
undo履歴の分岐を目で見る
undoとredoで困るのは、戻ってから別の編集をした瞬間に、元の履歴が枝分かれして見えなくなることです。g-とg+で時系列をたどれますが、いま自分がどの枝にいるのかは分かりません。:Undotreeはこれを絵にします。
:packadd nvim.undotree
:Undotree
試しに、1行のバッファを2回書き換えて途中で1回戻す、という履歴を作ってから開いた結果がこれです。
* 0 (origin)
| * 1 (0 seconds ago)
* 2 (0 seconds ago)
縦棒が枝分かれです。0が最初の状態、1が戻したときに置き去りにされた状態、2がいまの状態にあたります。このウィンドウの中でカーソルを上下に動かすと、それだけでバッファの中身がその時点に切り替わります。Enterのような確定操作は要りません。実際にカーソルを1行ずつ下げながら元のバッファを覗くと、次のように連動していました。
カーソル 1行目 -> バッファ = (空)
カーソル 2行目 -> バッファ = second -- 捨てたはずの枝
カーソル 3行目 -> バッファ = third
secondはuで戻したあとに別の編集をしたせいで、通常のredoでは戻れなくなった状態です。それが一覧に残っていて、カーソルを合わせるだけで戻せます。
開かれるのはbuftypeがnofile、filetypeがnvim-undotreeのバッファで、名前はUndo tree for <ファイル名>でした。幅は既定で30桁の縦分割です。Luaから開くこともでき、置き場所を変えたいときはこちらを使います。
-- 横分割で開く
require('undotree').open({ command = '15new' })
有名なmbbill/undotreeと比べると、こちらは差分のプレビューが無く、表示も簡素です。ただ「どの枝にいるか」を確認して移動するという用途なら十分に足ります。プラグインを1つ減らせるなら、まずこれで足りるか試す価値があります。undo履歴をファイルに残す設定(undofile)と組み合わせると、前日の作業の枝までたどれます。
ディレクトリ同士を比較する
:DiffToolのほうは、ファイルだけでなくディレクトリを丸ごと比較できるのが特徴です。vimdiffや:diffsplitは基本的にファイル単位なので、ここが実質的な追加分になります。
:packadd nvim.difftool
:DiffTool dir_a dir_b
片方にしか無いファイルと、中身が違うファイルを1つずつ置いたディレクトリで実行したところ、quickfixに次の2件が並びました。
A dir_b/extra.txt -- 追加されたファイル
M dir_b/f.txt -- 中身が変わったファイル
quickfixの行を選ぶと、その組み合わせが左右に並んだdiff表示に切り替わります。quickfixの操作をそのまま流用できるので、:cnextで順番に見ていく使い方になります。リネームの検出にも対応していますが、既定では無効なのでLua側から指定します。
-- リネーム検出を有効にし、ログファイルを除外する
require('difftool').open('dir_a', 'dir_b', {
rename = { detect = true },
ignore = { '.git', '*.log' },
})
ヘルプはこのプラグインをnvim -dの置き換えと位置づけていて、gitの差分ツールとして登録する例まで載せています。
# ~/.gitconfig
[difftool "nvim_difftool"]
cmd = nvim -c "packadd nvim.difftool" -d "$LOCAL" "$REMOTE"
[diff]
tool = nvim_difftool
この状態でgit difftool -dを実行すると、ブランチ間の差分をディレクトリ単位でNeovimに渡せます。マージの解決をVimでやっている人にとっては、同じ操作体系に寄せられる話です。
なぜ既定で読み込まれないのか
2つともpack/dist/opt/に置かれた任意読み込みのパッケージです。同じ場所にはnvim.tohtml・termdebug・matchit・cfilterなども入っています。起動時間に響かせないための配置で、使う人だけが:packaddで拾う形になっています。
毎回打つのが面倒なら設定に入れておきます。:packaddはコマンドを定義するだけなので、起動時に読み込んでも重くはなりません。
-- init.lua
vim.cmd('packadd nvim.undotree')
vim.cmd('packadd nvim.difftool')
なおvim.packで入れるプラグインも同じoptの仕組みに乗っています。外から持ってくるものと本体に同梱されているものが、同じ置き場所で並ぶ形です。
この2つの存在に気づいたのは、0.13の変更点を調べていてpack/dist/opt/の中身を眺めたときでした。0.12のリリースノートにはちゃんと書いてあるのに、日本語の情報がほとんど無いまま半年近く経っていることになります。:Undotreeはカーソルを動かすだけでバッファが変わるので、最初は誤操作したのかと思って驚きました。慣れると速いです。:DiffToolのほうは、実際にはgitのdifftoolとして登録したときがいちばん出番があると感じます。単体で打つ機会はあまり多くありません。
まとめ
:Undotreeと:DiffToolはNeovim 0.12から本体に同梱されていて、:packadd nvim.undotreeと:packadd nvim.difftoolで有効になります。前者はundo履歴の分岐を絵にして、カーソルを動かすだけでその時点へ移動できます。後者はディレクトリ同士の比較ができ、結果がquickfixに並ぶので:cnextでたどれます。どちらも既定では読み込まれないので、使うならinit.luaにpackaddの2行を足しておくのが手軽です。プラグインを2つ減らせる可能性があるという意味で、一度は試しておく価値があります。