before-change-functions や after-change-functions
の使用は、2つの呼び出しが常に適切にペアになるとは限らない点や、一部の Emacs プリミティブが適切にペアリングできなかった、あるいは inhibit-modification-hooks
の誤用によって一部の呼び出しが欠落する場合があるなど、多くの落とし穴があるため、実際には困難な場合があります。さらに、これらのフック関数には多くの制約が課されており、例えば基本的に現在のバッファを変更してはならず、またブロックする可能性のある操作も使用してはならないほか、一部のコマンドがこれらのフックを非常に多く呼び出す可能性があるため、高速に処理される必要があります。
Track-Changesライブラリは、基本的にこれらのフックを基盤とした代替APIを提供しています。after-change-functionsとの最初の重要な違いは、変更範囲と直前の長さを提供するのではなく、変更範囲とその領域の実際の直前の内容を提供する点にあります。バッファの元の内容から情報を抽出する必要があることが、一部のパッケージでbefore-change-functionsとafter-change-functionsの両方を使用し、それらを照合させようとする主な理由の一つとなっています。
2つ目の違いは、変更の通知とその処理という行為を切り離し、最初の変更から実際の処理までの間に発生したすべての変更を、自動的に単一の変更操作に統合する点です。これにより、コマンドごとに1回といったより粒度の大きい単位で変更を処理することが自然かつ容易になり、通常の変更フック関数に適用される制限の大部分が解消されるため、ブロッキング操作の使用やバッファの変更が可能になります。
変更の追跡を開始するには、track-changes-registerを呼び出し、引数としてsignal関数を渡す必要があります。これによりトラッカーidが返され、これを用いてライブラリの他の関数に変更トラッカーを識別させます。バッファが変更されると、ライブラリはsignal関数を呼び出してその変更を通知し、直ちに後続の変更を単一の統合された変更として蓄積し始めます。signal関数は変更が発生したことを警告するためだけに機能し、変更の内容を受け取ることはありません。また、変更を取得するまで、ライブラリはこの関数を再度呼び出すことはありません。
変更内容を取得するには、track-changes-fetchを呼び出す必要があります。これにより、前回の呼び出し以降に蓄積された変更範囲と、その領域の以前の内容が得られます。また、signal関数を「再有効化」するため、次のバッファ変更後にライブラリが再びこの関数を呼び出すようになります。
この関数は、新しい変更トラッカーを作成します。変更トラッカーは抽象化されているため、単なる識別子として扱い、したがってこの関数はトラッカーのidを返します。
signal
は、変更を通知するためにライブラリが呼び出す関数です。引数を1つ取って呼び出される場合もあれば、2つ取って呼び出される場合もあります。このトラッカーが最後に
track-changes-fetch
を呼び出して以来、バッファに初めて変更が生じた際、ライブラリはこの signal 関数を、トラッカーの
id を保持する単一の引数とともに呼び出します。
デフォルトでは、signal 関数の呼び出しは即座に行われず、代わりに 0
秒のタイマーで遅延されます(遅延実行用のタイマーを参照)。これは通常、signal
関数が頻繁に呼び出されないようにし、許容的なコンテキストで実行させることで、クライアントをパフォーマンス上の懸念や、どの操作が問題となるかという不安から解放するために意図されています。クライアントがより詳細な制御を行いたい場合は、immediate 引数に nil
以外の値を指定でき、その場合、ライブラリは after-change-functions から直接
signal 関数を呼び出します。ただし、この場合、signal
関数はバッファを変更したり、ブロックする可能性のある操作を使用したりしないよう注意する必要がある点に留意してください。
バッファの実際の前回の内容には関心がなく、単に多くの小さな変更を一つにまとめ、処理をより都合の良いタイミングに遅延させるという機能だけを目的としてこのライブラリを使用している場合は、nobefore 引数に nil
以外の値を指定できます。その場合、track-change-fetch は after-change-functions
と同様に、前回の内容の長さのみを提供します。これにより、ライブラリの処理負荷を軽減することも可能です。
小さな変更を数多く積み重ねて大きな変更へとつなげたい場合でも、変更の間隔が離れすぎている場合にはそうしたくないこともあるでしょう。disjoint 引数に nil
以外の値を指定すると、ライブラリは現在保留中の変更から「遠い」ところで変更が発生しそうになったとき、すぐに signal 関数を呼び出して通知します。この際、トラッカーの id
と、次回の変更と既に保留中の変更を隔てる文字数という2つの引数を渡します。これだけでは、この新しい変更を以前の変更と統合することが妨げられるわけではないため、次回の変更が本当に離れすぎていると判断した場合は、すぐに
track-change-fetch を呼び出す必要があります。なお、不連続な変更によって signal 関数が呼び出される場合、これは before-change-functions
から直接行われるため、バッファの変更やブロックを伴う可能性のある操作に関する通常の制限が適用される点にご注意ください。
この関数を使用すると、バッファ内で何が変更されたかを確認できます。トラッカー id
を指定することで、ライブラリはどの変更がすでにトラッカーによって検知済みであるかを判断できます。track-changes-fetch は変更内容の説明を返す代わりに、その説明を3つの引数 beg、end、before の形式で func 関数に渡し、beg..end
で変更された領域を区切り、before でその領域の以前の内容を記述します。通常、before は変更された領域の以前のテキストを含む文字列ですが、track-changes-register に nil
以外の nobefore 引数を指定した場合は、その以前のテキストの文字数に置き換えられます。
前回の呼び出し以降に変更がなかった場合、track-changes-fetch は単に func を呼び出さず、nil
を返します。変更があった場合は、func を呼び出し、func が返した値を返します。ただし、変更が何度あったとしても、func は一度だけ呼び出される点に注意してください。それらは単一の beg/end/before トリプレットにまとめられます。
場合によっては、例えば低レベルのCコードのバグやinhibit-modification-hooksの不適切な使用などにより、すべての変更がライブラリに正しく通知されないことがあります。このような問題が検出されると、funcはバッファ全体をカバーするbeg..end領域を受け取り、before引数には、ライブラリが何が変更されたかを特定できなかったことを示すシンボルerrorが指定されます。
func が完了すると、track-changes-fetch が signal
関数を再度有効にし、次回変更が発生した際にその関数が呼び出されるようにします。説明を返す代わりに func
を呼び出すのはこのためです。func が完了した後にのみ signal が再度有効になるため、処理の途中で signal
関数がトリガーされるリスクを懸念することなく、変更を処理できるようになります。
コード内で track-changes
を使用してバッファにさらなる変更を加える場合(例えば、編集済みのバッファ部分をマークする場合など)、それらの変更がアンドゥの結果であるときは、変更を控えるのが望ましいでしょう(アンドゥによって、対応する編集時に適用した変更もおそらく取り消されるためです)。そのため、track-changes-fetch は、変更がアンドゥの結果であった場合、func の呼び出し時に track-changes-undo-only を non-nil にバインドします。
この関数は、トラッカー id がもはやバッファの変更を追跡する必要がないことをライブラリに通知します。ほとんどのクライアントは変更の追跡を停止することを望みませんが、マイナーモードのようなクライアントの場合、マイナーモードが無効になった際にこの関数を呼び出すことが重要です。そうしないと、トラッカーが変更を蓄積し続け、リソースをますます消費することになります。