vimdiffでより賢いアルゴリズム (patience, histogram) を使う

`>>>>>>> 2019.01.05 追記ここから`

vim 8.1.0360 以降では vim に diff の機能が内蔵されたため、この記事に書いたことは vim の設定のみで実現可能になりました。(@tsuyoshi_cho @fumiyas 情報ありがとうございました。）

```vim:~/.vimrc
set diffopt=internal,filler,algorithm:histogram,indent-heuristic
```

vim 内蔵の diff を使う `internal` 指定は `diffexpr` がセットされていると無視されてしまうので注意してください。また、 `algorithm:アルゴリズム名` に加えて、差分の位置を最適化する `indent-heuristic` も指定しておくのがおすすめです。
もし diffchar.vim をお使いの場合、 `diffexpr` がセットされないように `let g:DiffExpr = 0` も書いておく必要があります（[diffchar.vim については別途記事を書いています](https://qiita.com/takaakikasai/items/0d617b6e0aed490dff35)）。

`<<<<<<< 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が必要です。

```rb:~/bin/git-diff-normal-format
#!/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設定ファイルに次の記述を追加します。

```vim:~/.vimrc
" 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を指定する設定も一緒に行っています。

```text:~/.gitconfig
[diff]
  tool = vimdiff
  algorithm = histogram
```

これでvimdiffやgit difftoolを実行した時にhistogramアルゴリズムが使えるようになります（patienceを使いたい場合は、上記設定のalgorithmの値をpatienceに変えてください）。

では具体的な効果を見てみましょう。サンプルとして用意した2つの比較対象のファイルをvimでウィンドウを分割して並べたものが下の画像です。

![スクリーンショット 2014-07-07 19.04.36.png](https://qiita-image-store.s3.amazonaws.com/0/48289/de1548f8-d292-95b2-3058-c520d62a663e.png)

まずは、今回の設定をする前、デフォルトのアルゴリズムでvimdiffによる差分をとった結果が以下です。

![スクリーンショット 2014-07-07 18.58.18.png](https://qiita-image-store.s3.amazonaws.com/0/48289/0b4ccd77-75d1-e248-05f1-9d1facf5ced4.png)

そして、今回の設定をした後の、histogramアルゴリズムでvimdiffによる差分をとった結果が以下です。

![スクリーンショット 2014-07-07 18.59.32.png](https://qiita-image-store.s3.amazonaws.com/0/48289/6964c8cf-9805-562d-55d6-69c15dd978b3.png)

こちらのほうが、より差分内容も分かりやすく、かつ実際に手で行った変更の操作に近いものになっていますね。
※ちなみにこのvimdiffの色設定は[過去に書いたこの記事](http://qiita.com/takaakikasai/items/b46a0b8c94e476e57e31)を参照。

※[自分のブログ](http://takaaki-kasai-tech.blogspot.jp/2014/07/use-smarter-algorithm-for-vimdiff-such-as-patience-or-histogram.html)より転載。（一部をQiita用に修正）