update.Diffの計算はそれこそ論文が書けてしまうような難しいコンピュータサイエンスなんですが、かっこよく表示するのもちょっと工夫しないと難しかったりします。多くの人が見慣れている、GitHubのような差分をQt上で表示するウィジェットを作ってみました。コードはQt依存なところもありますが、考え方は他のコードに移植できると思います。Diff-Match-Patchを使ってみた→失敗まず最初に使い慣れたDiff-Match-Patchを使ってみようと思いました。僕はGo版を使い慣れていて、なおかつオリジナル実装にはQtを使ったC++用のライブラリが入っている。普通にやってみたら、以下のようにうまくいきませんでした。適当に"log"という行を足してみた結果です。"log"という行だけの差分になってほしいのに、前の行の末尾のダブルクオートを巻き込んだdiffが取れてしまっています。前の行は実際は差分はないので、差分表示されちゃうと困ってしまいます。パッチを作って当てる用途ではなくて、見やすいdiffを出す用途なので多少冗長でも良いのですが、これができませんでした。あと、Diff-Match-PatchのWikiには行単位Diffの取り方のサンプルがあるんですがGoでやったときには問題なく動いたのですが、これがC++版だとprivateメソッドで使えないという。publicに書き換えても動かず。別のライブラリを探し始めました。C++が見つかりました。PatchはQtのQStringだけだったのですが、dtlはテンプレートでどんな型がきてもOKだったのでこれを使ってみました。は行単位diffGitのdiffはまず行単位で行っています。DTLそのものは文字列の文字単位diffに特化してます。改行を特別に扱うようなコードも無さそう。一発で期待する結果は出ませんが、その分、いろいろミニマムな機能を組み合わせれば結構自由に差分が取れます。ウィジェットはQLabelを継承して作ります。HTML(4.0のサブセット)が使えるので、色をつけたりして表示するにはこれが一番お手頃です。&newString);};}setSource()メソッドがいろいろ実装しているコードです。新旧の文字列を行単位に分けます。その後QMapを使って、行のリストを数値のリストに置換していきます。同じ内容だったら同じ数値になるようにしています。line2idというマップが行の文字列から数値へのマッピング、id2lineがその逆です。デバッグ出力してみます。古い方の文字列を行単位に分けて、idのリストに変換する。新しい方の文字列を行単位に分けて、idのリストに変換する。idのリストの差分を計算して表示してみる。}}}ユニットテスト代わりに網羅的な状態変更の入力データを用意したサンプルを作ってみました。app.exec();}実行結果はこんな感じです。先頭の数値はdtl::edit_t型の定数で、0が新旧で同じ、-1が削除された、1が挿入されたという意味です。いい感じに出ましたね。"yz!"HTML化して表示してみる行番号込みで差分をきれいに表示したいので、HTML化してみます。一部CSSも使えますがなつかしのtableレイアウトです。テンプレートをQStringの文字列で用意しました。width=\"100%\">%3</td></tr>\n";表示する前に、削除が続いているブロック、挿入が続いているブロックでまとめようと思います。Formatにしてくれる機能を提供してくれているのですが、今回は共通部分を間引かないで使用するので、自分でがんばります。ブロックを表現する構造体を作っておきます。旧データ、新データのそれぞれのブロックの開始オフセット行数(ずれることがありえるので)、種類、行のコンテンツの文字列のリストが入ります。{}};}ヘッダーにメンバー変数でこのブロックのリストを取っておきます。_update();};setSourceに追加していきます。_update()は最後にHTMLを完成させて表示を更新するメソッドです。最後のタイプと同じじゃなかったら新しいDiffオブジェクトを作る。種類がごとにテンプレートに当てはめて出力していく。新・旧のコンテンツが存在するところだけ行番号をカラムに入れる。this->setText(contents.join(""));}はい。いい感じに表示できていますね。文字単位diffを追加GitHubのdiffは同じ行内で削除された文字、追加された文字もわかりやすくなっています。同じ行内での編集ということで、隣接するdiffをうまく後処理してあげれば良さそうです。隣接しているといっても、削除5行、追記5行が斑になってしまうと見にくいので、同じ編集内容は固めておく必要があります。このために前のセクションではわざわざ同じ編集がされた行をまとめたのでした。ブロック内の行をまとめて1つの長い文字列にする。新だけの文字は新に、旧だけの文字は旧だけにはいる。共通の文字は両方に入る。this->_update();}はい。文字にも色が付きましたね。同一内容が続いているところを省略して、クリックで開けるようにする1000行あるファイルのうち、5行だけが変更があった場合は、それ以外の箇所は隠れて欲しいですよね?QLabelは内部のHTMLのリンクがクリックされたらシグナルが飛ぶので、それを使って実現します。Diffブロックに今表示しているかフラグを足します。{}};リンク付きのテンプレートも追加します。リンクがクリックされたときのスロットは、ラムダで。リンクはDiff配列のインデックスの数値を使うことにします。クリックされたらvisibleフラグをtrueにして_update()を呼んでHTMLの更新をします。HTML生成をメソッドに分けたのはこのためでした。this->setText(contents.join(""));}リンクになりました。リンクのスタイルは後で直そう。DiffならJSのライブラリとかでそれっぽいのはあるんですが、わざわざDiffのためだけにWebViewコンポーネント追加するのも消費メモリ上げるだけなので、ちょっとC++だけでやってみました。64bit機でQtランタイム含めて消費メモリ28MBぐらい。実際のアプリに追加した時の消費メモリの増加量はきっとMBいくかいかないかぐらいじゃないかな。完成したライブラリ下記のところにあります。他の人は使ってないと思いますが、自分だけ超便利に使っている自作のQt(C++)用のパッケージマネージャから使えるようにしてます。コメントしたところは実は間違いで、そこも、スペースやタブの置換は必要でした。あと、最終要素の1つ前までループしているので、最後の項目のフォーマットの修正が走らなかった時に、やっぱりスペース、タブの置換が発生しないので、そこも修正してあげる必要があります。あとは、行番号表示のスタイルをgithubに合わせたり(カラムの色や行番号の文字の色)、クリックで広がる行のスタイルも修正しています。最新版のコードを見てください。upLoginユーザーは見つかりませんでした。