続・Mercurial の diff を美しく表示するために必要なたった 1つの設定

つい一昨日、Mercurial の diff を美しく表示するために必要なたった 1つの設定 という記事を書いたばかりですが、
diff-highlight の動作を見ているといくつか気になるところを見つけてしまいました。

diff-highlight のイケてないところ

差分の中で +/- の行数が一致していないと文字単位でのハイライトをしてくれない

こんな感じです。
f:id:tk0miya:20131218221141p:plain

編集前後の行数が一致するときだけハイライトしてくれるようです。
f:id:tk0miya:20131218221318p:plain

diff-highlight のコードを読むとこんなコメント付きで、行数が一致しない時は早々に諦めています。

        # If we have mismatched numbers of lines on each side, we could try to
        # be clever and match up similar lines. But for now we are simple and
        # stupid, and only handle multi-line hunks that remove and add the same
        # number of lines.
        if (@$a != @$b) {
                print @$a, @$b;
                return;
        }

+/- の行が一致していても、行がずれてしまうと文字単位でのハイライトをしてくれない

行数が一致するときはちゃんとやってくれるよ!と思いつつ、実はそうではありません。
ハイライトの判定は + の 1行目と - の 1行目、+ の 2行目と - の 2行目…と順に比較していきます。

そのため、上の行を消して、下に一行追加して…のような編集をするとちゃんとハイライトしてくれなくなります。
f:id:tk0miya:20131218221849p:plain

この例だと、

  • "-mq =" の行と "+pager = /path/to/pager.py" の行を比較
  • "-pager =" の行と空行を比較

しているので、書き足したはずの /path/to/pager.py はハイライトされません。

一行で 2箇所書き換えた場合は、ハイライト幅がとても大きくなる

例えば、ある箇所に括弧を付け加えてみると、書き換えていない箇所までハイライトされます。
f:id:tk0miya:20131218222313p:plain

これは、diff-highlight の実装が行頭と行末の一致する部分(common prefix, common suffix)を除いて、
それ以外をハイライトしているのでこうなります。

この動きは割とダメージが大きくて、ダブルクオートをシングルクオートに書き換えるようなケースでは、
文字列全体がハイライトされることになります。

困ったら手を動かせ

というわけで、hg-diff-highlight という Mercurial 拡張を書きました。
まだできたてホヤホヤで README すら書いていない体たらくなのですが、
ひとまず上記の問題はクリアするようなものになっています。
f:id:tk0miya:20131218223714p:plain
f:id:tk0miya:20131218223716p:plain
f:id:tk0miya:20131218223718p:plain

ちなみに 2枚目の例が /path/to/pager.py ではなく /path になっていますが、
変更箇所が多すぎる場合は異なる行とみなしてしまうため、ハイライトされなくなります。
ちょっと恣意的な例の上げ方でごめんなさい orz

hg-diff-highlight の使い方

任意のパスに hg clone します。

$ hg clone https://bitbucket.org/tk0miya/hg-diff-highlight

訂正 12/20: バージョン 0.1.0 リリースによりインストール方法が変更になりました。

diff-highlight パッケージをインストールします。

$ pip install diff-highlight

hgrc の extensions に書き足します。
なお、ハイライト処理は color 拡張に依存しているので、color 拡張が有効になっている必要があります。

[extensions]
color =
diff-highlight =

あとは hg diff して楽しみましょう。
なお、mercurial では diff が出てくる箇所がたくさんありますが、hg-diff-highlight は全てに対応している(はず)です。


まとめ

  • diff-highlight は思ったより簡易な作りになっていた
  • 勢い余って hg-diff-highlight という拡張を作った
  • 今では反省している

(おまけ) 色を変更する場合

hg-diff-highlight は diff.inserted と diff.deleted をみて色を切り替えています(inverse 属性を付けている)
なので、diff の色を変更したい場合は通常の color 拡張と同じように書きます。

[color]
diff.inserted = blue
diff.deleted = magenta

もしあなたがハイライトしている箇所だけ色を変えたいという奇特な人の場合は、
diff.inserted_highlight と diff.deleted_highlight の値を設定するとよいでしょう。