>>>>>>> 2019.01.05 追記ここから
vim 8.1.0360 以降では vim に diff の機能が内蔵されたため、この記事に書いたことは vim の設定のみで実現可能になりました。(@tsuyoshi_cho @fumiyas 情報ありがとうございました。)
set diffopt=internal,filler,algorithm:histogram,indent-heuristic
vim 内蔵の diff を使う internal
指定は diffexpr
がセットされていると無視されてしまうので注意してください。また、 algorithm:アルゴリズム名
に加えて、差分の位置を最適化する indent-heuristic
も指定しておくのがおすすめです。
もし diffchar.vim をお使いの場合、 diffexpr
がセットされないように let g:DiffExpr = 0
も書いておく必要があります(diffchar.vim については別途記事を書いています)。
<<<<<<< 2019.01.05 追記ここまで
vimdiff使ってますか?差分を取る際には非常に便利ですよね。git difftoolに設定して使っている人も多いと思います。しかしgit diffは差分計算のアルゴリズムを選択できますが、vimdiffはデフォルトでは差分計算のアルゴリズムを選択できません。
git diffの差分アルゴリズムには標準のもの以外にpatienceやhistogramがあり、より人間に読みやすい差分を表示してくれる「賢い」アルゴリズムになっています。それぞれgit diffコマンドのオプション--patienceや--histogramを付けるか、または~/.gitconfig設定ファイルにアルゴリズムを指定することで利用できます。
具体的なアルゴリズムの詳細は僕も詳しくないですが、patienceアルゴリズムは「ファイル内でユニークかつ比較ファイル同士で一致する行をなるべく"変化していない行"と認識する」よう働きます。また、histogramアルゴリズムは「patienceアルゴリズムの基本的ルールを継承しつつ高速化を図ったアルゴリズム」で、多くのファイルでpatienceアルゴリズムと同一の結果が得られるようです。また、histogramアルゴリズムはEclipseに統合されたGitクライアントEGit(が使うJGit)の標準アルゴリズムです。僕はコマンドラインツールのgit diffにおいてもhistogramアルゴリズムを使ってます。
さて、vimdiffにおいてもhistogramアルゴリズムを使いたいところです。vimの設定ではvimdiffの差分計算に使用するコマンドをdiffexprという設定値で指定できます。そして、git diffコマンドも--no-indexオプションを指定することでdiffコマンドと同じようにスタンドアロンツールとして利用できます。じゃあ、diffexprにgit diff --no-indexを指定すればいいのか・・・というと実はそれだけでは失敗します。
理由は、vimがdiffexprに設定したコマンドから受け付ける差分形式が、diffコマンドのnormal形式かed形式でなければならないからです(vimのヘルプにはed形式とだけ書いてあるのですが、normal形式も受け付けます)。そして、git diffコマンドでは直接diffコマンドのnormal形式やed形式で出力することができずunified形式のみに対応しています。
そこで、unified形式をnormal形式に変換するrubyスクリプトを書いて使うことにしました。以下がその変換スクリプトです。利用にはrubyが必要です。
#!/usr/bin/env ruby
iwhite = ''
if (ARGV[0] == '-b')
iwhite = ARGV.shift.dup << ' '
end
diffout = `git diff --no-index --no-color -U0 #{iwhite}#{ARGV[0]} #{ARGV[1]}`
diffout.sub!(/\A.*?@@/m, '@@')
diffout.gsub!(/^\+/, '> ')
diffout.gsub!(/^-/, '< ')
diffout.gsub!(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@.*/) do
before_start = $1
before_size = $2
after_start = $3
after_size = $4
action = 'c'
if (before_size == '0')
action = 'a'
elsif (after_size == '0')
action = 'd'
end
before_end = ''
if (before_size && before_size != '0')
before_end = ",#{before_start.to_i + before_size.to_i - 1}"
end
after_end = ''
if (after_size && after_size != '0')
after_end = ",#{after_start.to_i + after_size.to_i - 1}"
end
"#{before_start}#{before_end}#{action}#{after_start}#{after_end}"
end
diffout.gsub!(/^(<.*)(\r|\n|\r\n)(>)/, '\1\2---\2\3')
print diffout
上記スクリプトを~/bin/git-diff-normal-formatとして保存し、~/binにPATHを通しておきます。そしたら、.vim設定ファイルに次の記述を追加します。
" diffのコマンド
set diffexpr=MyDiff()
function MyDiff()
let opt = ""
if &diffopt =~ "iwhite"
let opt = opt . "-b "
endif
silent execute "!git-diff-normal-format " . opt . v:fname_in . " " . v:fname_new . " > " . v:fname_out
redraw!
endfunction
最後に、git diffで使うアルゴリズムを指定します。下記記述ではgit difftoolで起動するコマンドにvimdiffを指定する設定も一緒に行っています。
[diff]
tool = vimdiff
algorithm = histogram
これでvimdiffやgit difftoolを実行した時にhistogramアルゴリズムが使えるようになります(patienceを使いたい場合は、上記設定のalgorithmの値をpatienceに変えてください)。
では具体的な効果を見てみましょう。サンプルとして用意した2つの比較対象のファイルをvimでウィンドウを分割して並べたものが下の画像です。
まずは、今回の設定をする前、デフォルトのアルゴリズムでvimdiffによる差分をとった結果が以下です。
そして、今回の設定をした後の、histogramアルゴリズムでvimdiffによる差分をとった結果が以下です。
こちらのほうが、より差分内容も分かりやすく、かつ実際に手で行った変更の操作に近いものになっていますね。
※ちなみにこのvimdiffの色設定は過去に書いたこの記事を参照。
※自分のブログより転載。(一部をQiita用に修正)