sim がテキストについて約束していることは、一文です。明示的な操作なしには、誰のテキストも失われません。あなたのものでも、別のプロセスのものでも、ツールのものでも同じです。この記事では、その約束にどんなコードが要るのか、どうテストしているのか、どこまでが限界か、そして秘密情報にとって何を意味するのかを説明します。秘密情報とは、編集しているファイルの中のパスワードや、ヤンクしたトークンのことです。
これは sim 自身についての説明で、仕様とノートに基づいています。他のエディタと比べるときは、そのエディタのドキュメントかイシュートラッカーを引きます。
テキストを失う 3 つの道
エディタがテキストを失う道は 3 つあり、それぞれに別の守りが要ります。
- 途中で終わる保存。 古いファイルを切り詰めてから新しいファイルを書き終えるまでの間に、プロセスが死ぬ、あるいはディスクが一杯になる。
- 誰かを上書きする保存。 あなたがファイルを開いたあとで、ビルド、フォーマッタ、ツールがそのファイルを書き換え、あなたの保存が、古いバージョンをその上に載せてしまう。
- 保存する前のクラッシュ。 エディタが死ぬ、端末が閉じる、プロセスが
kill -9で殺される。保存していなかった変更が消える。
守り 1: 中途半端に書き込まない保存
sim は、決まった順序で保存します(SAFE-1)。
- 保存先と同じディレクトリに、モード 0600 と
O_EXCLで一時ファイルを作る(誰かが仕掛けたリンクにはなりえない)。 - 新しい内容をそこに書く。
fsyncする。- 保存先へ rename する。
- ディレクトリを
fsyncする。
どの段階で失敗しても、古いバージョンか新しいバージョンのどちらかが無傷で残ります。同じファイルシステム内なら rename はアトミックで、一時ファイルを保存先の隣に作る理由はそこにあります。rename の前に、一時ファイルには元のファイルのモードとグループが与えられるので、非公開にしていたファイルは非公開のままです。シンボリックリンクは、リンク先に保存され、リンク自体はそのまま残ります。
rename では置き換えられないファイルもあります。他人が所有するファイル、ハードリンクが 2 つ以上あるファイル、書き込めないディレクトリにあるファイル、マウントポイントになっているファイル(コンテナにバインドマウントされた設定ファイルは、rename に EBUSY を返します)です。こうしたファイルには、その場での保存を使います(SAFE-2)。先に復旧用のコピーを書いて同期し、保存し、保存が成功したらコピーを消します。その場での書き込みが失敗したら、ステータス行が復旧用コピーの場所を知らせます。その場での保存は、最後のシンボリックリンクをたどらずにファイルを開き、開いたファイルが検証したものと同じかを確かめるので、途中ですり替えられたファイルは、書き込まずに拒否します。
sim は、他人が所有するリンクをたどって書き込むことも拒否します。パスのどの部分でも同じで、Linux の fs.protected_symlinks と同じ考え方です。/tmp のような共有ディレクトリに仕掛けられたリンクが標的です。
ファイルを開いて、編集せずに保存した結果は、バイト単位で同一です。改行コード、末尾の改行の有無、バイトオーダーマーク、パーミッションはそのまま保たれます(SAFE-3)。有効な UTF-8 でないファイル、先頭 8 KB に NUL があるファイルは、理由を示して読み取り専用で開きます。画面のテキストがファイルそのものではなくなるからです(SAFE-4)。
書き方より、どうテストしているかのほうが大切です。保存処理には、手順の各段階を失敗させられるテスト専用のフックがあり、テストは、どこで失敗しても、古いバージョンか新しいバージョンのどちらかが完全であることを確かめます。プロパティテストは、往復してもバイト単位で同一であることを確かめます。シェル全体のモデル(保存スレッド、監視、外部のプロセス)は、ランダムな手順を、誰の言葉も失われないという規則に照らして実行します。
比較のために書きます。Vim と Neovim は、どちらも既定でオンの ‘fsync’ オプションをドキュメントに載せています [1] [2]。Helix については、2026 年 4 月に立てられ、確認した時点で未解決のイシュー #15615 が、ファイルがすでに存在するとき、一時ファイルに書いて fsync と rename をする代わりに、Helix は非アトミックな方法で書き込むと報告しています [4]。トレースを添えた報告で、私たちは再現していません。
守り 2: 保存の前に確かめる
アトミックなだけでは足りません。あなたがファイルを開いたあとで別のプロセスが書き換えていたら、アトミックな保存も、相手の作業をあなたの作業で置き換えます。そこで sim は、読み込んだときのファイルのハッシュを持っておき、rename の直前に、ディスク上のファイルのハッシュと比べます(SAFE-5)。違っていれば、何も書き込みません。この検査は compare-and-swap で、保存用のスレッドで実行されるので、キー入力を待たせません。
検査には 2 層目があります。大きなファイルを読んでハッシュするには時間がかかるので、rename の直前に、sim は読み取りの前後でファイルのスタンプ(デバイス、inode、サイズ、更新時刻と変更時刻)を見て、スタンプが動いたときだけ、ハッシュをやり直します。通常のファイルでないもの(たとえば、ファイルの代わりに置かれた FIFO)は、読む前に拒否します。読み取りが永久に待ち続けるかもしれないからです。
仕様は、どこまでが限界かも書いています。「A write in the same microsecond, or a same-size rewrite within one timestamp tick of a coarse file system, can still slip through.」(同じマイクロ秒内の書き込みや、粗いファイルシステムで同じサイズのまま 1 タイムスタンプ刻み以内に行われた書き換えは、なお通り抜けうる)。この一文は、省くより載せるべきだと考えます。
他のエディタが何をするかを、そのドキュメントから書きます。Vim と Neovim は、編集を始めたときにファイルのタイムスタンプ、モード、サイズを覚え、いくつかの場面で比べて、違っていれば警告します。‘autoread’ は、変更していないバッファを再読み込みします [3] [8]。Helix は、書き込みを「file modified by an external process, use :w! to overwrite」(外部のプロセスがファイルを変更した。上書きするには :w! を使え)というメッセージで拒否します。メッセージをもっと目立たせるよう求めるイシュー #6492 に、そう引用されています [5]。自動の再読み込みは未解決の要望で、イシュー #1125 です [6]。
外部からの書き込みを、その場で見る
sim は、編集中にも変更に気づきます。ファイルの置かれたディレクトリを監視するので、その場での書き込みだけでなく、rename、削除、再作成による保存も見えます。自分の保存は、書き出したバイト列のハッシュで見分け、外部の変更と取り違えません。監視は、気づかれないまま死ぬことがあります。ディレクトリが削除されて作り直されたときや、ファイルシステムがイベントを送らないときです。そのため監視は、1 秒に 1 回、ファイルのスタンプも確認します。
ファイルが変わり、バッファに変更がなければ、sim はファイルを再読み込みして、変更箇所に印をつけます。本文の行、枠に出るディスク上の変更数のバッジ、タブの点、ツリーのファイルの印です。印は、カーソルがその行に入るまで残るので、どの変更も、少なくとも 1 回は目にとまります。

守り 3: スナップショット
最後の守りは、まだ保存していないテキストのためのものです。sim は、未保存の変更を、最短で 1 秒おきに、ステート用ディレクトリへスナップショットとして保存します(SAFE-6)。スナップショットには、パス、ファイルを開いたときのテキスト、バッファのテキストが入り、モード 0600 のファイルになります。クラッシュ、kill -9、端末を閉じるなどでプロセスが死んだとき、そのファイルを開き直すと、未保存の変更を取り消し 1 回分として戻すことを提案し、ステータス行がそれがどれだけ前のものかを知らせます。失われる作業は、最大でも 1 秒分です。SIGTERM と SIGHUP も捕まえるので、sim は最後のスナップショットを書き、128 にシグナル番号を足した値で終了します。
スナップショットは、保存に成功したあとと、:q、:q!、:cq で終了するときに削除されます。クラッシュやシグナルのあとには残ります。そのためにあるからです。
ディレクトリには決まりがあります。シンボリックリンクではない実ディレクトリで、あなたが所有し、グループにも他人にもアクセス権がない(モード 0700)ことが必要です。$XDG_STATE_HOME も $HOME もなければ、ディレクトリは存在せず、sim は /tmp のような共有の場所には決してフォールバックしません。スナップショットはオフになり、ステータス行が 1 度だけそれを知らせます。あなたが所有していないファイルのスナップショットは、無視します。
秘密情報
テキストエディタにとって、秘密情報はただのテキストで、どのテキストが秘密かを sim は知りません。ここまでの仕組みが秘密情報にとって何を意味するか、率直に書きます。
保存するファイルでは。 非公開にしていたファイルは、非公開のままです。一時ファイルは rename の前に元のファイルのモードを引き継ぎ、最初のモードは 0600 です。新しいファイルには、umask から決まる、新しいファイルの通常のモードがつきます。
まだ保存していないバッファでは。 スナップショットはバッファのテキストを持つので、未保存のバッファに貼りつけた秘密情報は、1 秒以内に、あなただけが読めるステート用ディレクトリに書き込まれます。正常に保存または終了すれば削除されます。エディタがクラッシュしたり殺されたりしたときは、そのファイルを開き直して終えるまで、残ります。これは「何も失わない」という約束の帰結で、知っておくべきことだと考えます。その場での保存でできる復旧用コピーも、同じように書かれます。
クリップボードでは。 sim のヤンクはすべて、端末の OSC 52 シーケンスを通じて、システムのクリップボードにも送られます(VI-8)。削除や変更は sim 自身のレジスタにだけ入るので、x や dd が、コピーしたものを上書きすることも、削除したものをクリップボードに載せることもありません。tmux の中では、sim は tmux のパススルーで包んだシーケンスも送ります。tmux の set-clipboard external の設定が、アプリケーションが tmux 自身のバッファを設定しようとすることを無視するからです [7]。ヤンクは、何を取ったかをメッセージで知らせます(「3 lines yanked」)。クリップボードには、ほかに合図がないからです。
ここから言えることは、秘密情報をヤンクすれば、それはシステムのクリップボードに届き、そこから先は、クリップボードにあるほかのものと同じように、それを読むものすべてに晒される、ということです。sim にはそれを制御できません。健全な習慣は昔からのものです。要らないなら秘密情報をヤンクしない、そのあとでクリップボードを消す。sim のドキュメントは、より広いクリップボード連携を予定として挙げていますが、何をするかは、できる前から言うものではありません。いま存在するのは、この OSC 52 によるコピーです。
できないこと
仕様自身のノートが、いくつかの限界を挙げており、ここでも繰り返します。
- アトミックな保存は、ACL と拡張属性を引き継ぎません。そのためには依存ライブラリが要るからです。
- 死んだセッションのスナップショットは、そのプロセス ID を、動いている別のプロセスが再利用している間は、復元を提案されません。
- 同じタイムスタンプの刻みの中で変わったファイルは、上に引いたとおり、比較をすり抜けうります。
- sim はスナップショットを暗号化せず、何が秘密かも知りません。
これらは、守りを省く理由にはなりません。どこまで信頼するかを決めるときに、見るべき場所です。