八百万あるVimのコマンドで特に有用なもののひとつとしてgf
があります。
このコマンドはカーソル下にあるファイル名らしき文字列を探し、
該当するファイルがあればそれを開くというものです。
gf
はカーソル下にあるファイル名らしき文字列をそのまま使うだけでなく、
特定のディレクトリ下にあるかどうか検索(例えばC言語でなら /usr/include
や ./include
を検索)したり、
特定の拡張子を付加して検索(例えばJavaなら SomeClass
のファイル名は SomeClass.java
なので、 .java
を付加して検索)することができ、
そこそこ賢く動いてくれます。
さて、日常的に git を使っている身としては
日常的に git diff
の出力を眺める機会も多いです。
「git diff
の出力を眺めて変更のあったファイルを開く」ということも頻繁に行います。
これには gf
を使えばよいと思うことでしょう。
ところが深遠なる事情により git diff
の出力フォーマットでは変更のあったパスにプレフィックスとして a/
と b/
が付加されます。
例えば master/Foo.cs
の変更を含む git diff
の出力にはa/master/Foo.cs
や b/master/Foo.cs
といった名前が含まれます。a/master/Foo.cs
や b/master/Foo.cs
の上でgf
を実行しても該当するファイルは存在しないためエラーとなってしまいます。
これは不便です。
一応、ビジュアルモードで a/
や b/
を含まないよう範囲選択をして gf
を実行すれば、
選択範囲のファイル名に該当するファイルを開くことができます。
しかしいちいち範囲選択をするのも面倒な話です。
どうにかして git diff
固有の出力フォーマットでも gf
で適切なファイルを開きたいところです。
しかしどうすればよいでしょうか。
以下の設定を vimrc
へ追加しましょう:
" git-diff-aware version of gf commands.
nnoremap <expr> gf <SID>do_git_diff_aware_gf('gf')
nnoremap <expr> gF <SID>do_git_diff_aware_gf('gF')
nnoremap <expr> <C-w>f <SID>do_git_diff_aware_gf('<C-w>f')
nnoremap <expr> <C-w><C-f> <SID>do_git_diff_aware_gf('<C-w><C-f>')
nnoremap <expr> <C-w>F <SID>do_git_diff_aware_gf('<C-w>F')
nnoremap <expr> <C-w>gf <SID>do_git_diff_aware_gf('<C-w>gf')
nnoremap <expr> <C-w>gF <SID>do_git_diff_aware_gf('<C-w>gF')
function! s:do_git_diff_aware_gf(command)
let target_path = expand('<cfile>')
if target_path =~# '^[ab]/' " with a peculiar prefix of git-diff(1)?
if filereadable(target_path) || isdirectory(target_path)
return a:command
else
" BUGS: Side effect - Cursor position is changed.
let [_, c] = searchpos('\f\+', 'cenW')
return c . '|' . 'v' . (len(target_path) - 2 - 1) . 'h' . a:command
endif
else
return a:command
endif
endfunction
これで a/master/Foo.cs
のようなテキスト上でgf
を実行すると master/Foo.cs
を開くようになります。
さらに a/master/Foo.cs
が実在する可能性も考えられるので、
もし a/master/Foo.cs
が実在する場合はそちらを優先して開くようにもなっています。
また、 gf
には <C-w>f
(新しくウィンドウを開いてから gf
する)等のバリエーションがいくつかあるため、
他の gf
系コマンドでも同様に処理されるようにしてみました。
これで git diff
が多い日も安心です。
やりましたね。
{lhs}
がタイプされた時、{lhs}
の代わりに {expr}
の評価結果を実行するよう設定します。
カーソル下のファイル名らしき文字列を取得します。
カーソル下のファイル名らしき文字列の末尾の行番号や列番号を取得します
(厳密には異なるのですが、上記の設定例ではそういう挙動になるように実行される文脈を調整してあります)。
ファイル名らしき文字にマッチするVimの正規表現です。
「ファイル名らしき文字」とは何かを定義しているオプションです。
これまで散々「ファイル名らしき文字列」と表現してきましたが、
環境によってファイル名に使える文字が若干異なるため、
それに対応できるようVimでは専用のオプションや正規表現が用意されています。
別解としてこのオプションを使う方法もあるのですが、
本来の目的から外れた使い方になってしまうので取り止めています。