git checkout後も文書が空にならないよう、時計ではなくバイトを見る
ブランチを切り替えたら、開いていた文書が空の画面になったことはありませんか。ブログに載せるスクリーンショットを撮っている途中で私たちのアプリでもその場面に遭遇し、修正するついでに、もっと静かなバグも一つあわせて直しました。

moteはファイルをそのまま信じます。画面の内容はディスク上のバイトと同じで、保存するときもそのバイトをそのまま書き込みます。
しかし、ファイルがアプリの外部で変更されたらどうなるでしょうか? moteがそれに気づかなければ、次に保存するとき、ほかの誰かが書いた内容を上書きしてしまう可能性があります。
この問題はスクリーンショットを撮っている途中で見つかりました。ブランチを行き来しながら「変化するバイト」の画面を準備していたところ、文書がときどき空の画面のまま止まりました。
空の画面の裏に隠れていた二つの問題
git checkoutはファイルを一度に変更するわけではありません。既存のファイルを削除し、新しく作成してから、内容を書き込みます。
ファイルウォッチャーはこの過程を複数のイベントとして通知します。ところがmoteは、最初のイベントが届くとすぐにファイルを読み込んでいました。まだ内容が書き込まれる前なので、空のファイルを読み込むことがあったのです。
ファイルを読み直すときに、ウォッチャーも作り直していました。その間に以前のウォッチャーに残っていたイベントは消え、画面は空のまま止まりました。
さらに危険な問題もありました。
moteは保存後1.5秒以内に届いたイベントを「自分が加えた変更」と見なして無視していました。しかし、その間にフォーマッターや保存フックがファイルを再び変更することもあります。
実際に、保存してから0.3秒後にファイルを変更すると、moteは何の変化も表示しませんでした。ユーザーがもう一度保存すると、外部で変更された内容はひっそりと消えてしまいました。
一つは目に見え、もう一つは目に見えませんでした。原因は同じでした。
ファイルの変化を、内容ではなく時間で判断していたのです。
時間ではなく内容を比較するようにしました
これからは、ファイルイベントが届いてもすぐには読み込みません。200ms待ち、新しいイベントが届くたびに待ち時間を最初からやり直します。
ファイルを削除し、作成し、書き込む過程が終わってから、最後の状態だけを一度読み込むのです。古いタイマーは世代番号で無効化します。何も起きていないときにも動き続けるタイマーは作りませんでした。
読み込んだ内容は、バッファが最後に確認したディスク上のバイトと比較します。
同じなら、アプリが保存したか、同じ内容が再び書き込まれたということです。違っていれば、本当の外部変更です。保存してから0.1秒が経ったのか、1秒が経ったのかは重要ではありません。
同じファイルを読み直すときは、ウォッチャーも維持します。ウォッチャーを切り替える間にイベントが消えないようにしました。
編集中のときだけ尋ねます
外部変更を検出したあとは、ユーザーの状態を確認します。
保存していない編集があれば、バナーを表示します。変更されたファイルを再読み込みするか、現在の内容をそのまま編集し続けるかをユーザーが選べます。どちらを選んでも失われる可能性のある内容があるため、アプリが代わりに決めることはありません。
編集中でなければ、すぐに新しい内容を読み込みます。バナーは表示せず、キャレットとスクロール位置はそのまま維持します。ブランチを行き来しながら文書を読むときは、通知を出すよりも読んでいた位置を保つほうが自然だからです。

ファイルが消えたときも、すぐには判断しません。別のエディターがファイル名を変更している途中かもしれないため、300ms後にもう一度確認します。
その時点でもファイルがなければ、ユーザーに知らせます。書いていた内容はバッファに残っており、再び保存すればファイルを作成します。
実際の環境で六つの項目を確認します
この問題は単体テストだけでは確認が困難です。ファイルウォッチャーと実際のディスク、ウィンドウマネージャーが一緒に動くときに起きるためです。
そこで、実際の環境で実行する検査を作りました。
ブランチを十回切り替えてもバッファがファイルに追従するか、保存してから300ms後にファイルを変更しても反映されるかを確認します。アプリが直接保存したときには通知が表示されないか、編集中に外部変更が発生した場合は書いた内容を守ったままバナーを表示するかも検査します。
全部で六つです。リリースするたびに実行します。

tools/e2e/external.shを実機のディスプレイで実行した出力。六行すべてがokで、最後の行はPASS=6 FAIL=0です。ファイルを真実として扱うということは、保存した内容をそのまま書き込むだけでは終わりません。
ファイルが私たちの知らないうちに変更されたとき、その変化まで見逃さない必要があります。