before-change-functions
とafter-change-functions
を実際に使用するのは困難かもしれません。2つの呼び出しが常に正しくペアーになっていないという事実、一部の呼び出しが失われるかもしれない、一部のEmacsプリミティブが2つを正しくペアーとして呼び出すのに失敗する、あるいはinhibit-modification-hooks
の不正な使用といった数々の落とし穴が存在するからです。さらにこれらのフック関数には基本的にカレントバッファーの修正やブロックするような操作を行ってはならない、そしてこれらのフックを多数回呼び出すコマンドがあるかもしれないので処理を迅速に行わなければならないといった、多くの制約が課せられるからです。
Track-Changesライブラリーは、基本的にはこれらのフックの上位に構築された代替えAPIを提供するライブラリーです。after-change-functions
と比べて1つ目の大きな違いは変更の境界と前の長さではなく、変更の境界とそのリージョンの前の実際の内容を提供することです。一部のパッケージがbefore-change-functions
とafter-change-functions
の両方を使うのは、バッファーの元の内容から情報を抽出してそれらをマッチする必要があるから、というのが主な理由の1つなのです。
2つ目の違いは変更の通知とその実際の処理動作が切り離されており、最初の変更と実際の処理の間に発生したすべての変更が1つの変更操作に自動的に統合されることです。これによりコマンドごとに1回といったように変更をより大きな粒度で自然かつ容易に処理できるようになるとともに、通常の変更フック関数に課せられる制限のほとんどが排除されてブロック操作の使用やバッファーの変更が可能になります。
変更の追跡を開始するにはsignal関数を引数として渡してtrack-changes-register
を呼び出す必要があります。これによりライブラリーの他の関数があなたの変更トラッカーを識別する、トラッカーのidがリターンされます。バッファーが変更されると変更を知らせるためにライブラリーがsignal関数を呼び出して、同時にそれ以降の変更を単一の変更に統合するための変更の累積が即座に開始されます。signal関数の役目は変更発生の警告だけであり、変更の詳細を受け取ることはできません。さらにその変更が取得されるまでは、ライブラリーが再度これを呼び出すことはありません。
変更を取得するためにはtrack-changes-fetch
を呼び出す必要があります。これは最後に呼び出されたて以降に蓄積された変更、およびそのリージョンの以前の内容を提供する関数です。さらに次にバッファーが変更された後にライブラリーが再度呼び出せるように、signal関数の“再セット”も行います。
この関数は変更トラッカー(change tracker)を新たに作成する。変更トラッカー自体は抽象的なままなので、それらを明確に参照するためのトラッカーのidがリターンされる。
signalはライブラリーが変更を通知するために呼び出す関数。この関数は引数が1つ、あるいは2つの引数で呼び出されることもある。このトラッカーが最後にtrack-changes-fetch
を呼び出して以降、最初のバッファーへの変更において、ライブラリーがトラッカーのidを引数にセットしてこのsignal関数を呼び出す。
デフォルトではsignal関数の呼び出しは即座に発生しないが、0秒のタイマーによって延期される(遅延実行のためのタイマーを参照)。これはsignal関数の呼び出しが頻繁すぎないよう寛容なコンテキストで実行することで、性能上の懸念や問題となり得る操作に関する心配からクライアントを開放するので通常は望ましい。クライアントがより詳細な制御を望む場合には、immediate引数に非nil
値を指定できる。この場合にはライブラリーはafter-change-functions
から直接、即座にsignal関数を呼び出す。これはsignal関数がバッファーを変更したり、ブロックするかもしれない操作をしないよう配慮する必要があることを意味することに注意。
バッファーの以前の実際の内容に興味がなく、多くの小さな変更を1つの大きな変更に統合したり、より都合のいいタイミングまで処理を遅延するというこのライブラリーの能力だけのためにこのライブラリーを使用する場合には、nobefore引数に非nil
値を指定できる。この場合にはtrack-change-fetch
はafter-change-functions
と同じように以前の内容の長さだけを提供する。これによりライブラリーも一部の作業を節約できる。
多数の小さな変更を1つの大きな変更に蓄積することに問題はなくとも、変更があまりに遠く離れてしまうようなら、それは望ましくないかもしれない。disjoint引数に非nil
値を指定すると、現在保留中の変更から“離れた”変更が発生した際に、ライブラリーがsignal関数を即座に呼び出して通知する。この場合にはトラッカーのid、およびすでに保留中の変更と新たな変更を隔てる文字数という2つの引数とともにsignalが呼び出される。これ自体が新たな変更と以前の変更の統合を妨げることはないので、新たな変更が実際に遠すぎると判断したら、即座にtrack-change-fetch
を呼び出す必要がある。解体された変更によってsignal関数が呼び出されると、それはbefore-change-functions
から直接発生したものなので、バッファー変更やブロックするかもしれない操作の使用に関する通常の制約が適用されることに注意。
これはバッファーで何が変更されたかを探すための関数である。トラッカーのidを与えることにより、トラッカーがすでに確認済みの変更をライブラリーに認識させる。track-changes-fetch
は変更の記述をリターンするかわりに、beg、end、beforeという3つの引数として表された変更の記述とともにfunc関数を呼び出す。ここでbeg..end
は変更されたリージョンの区切り、beforeはリージョンの以前の内容を記述する。beforeは通常は変更されたリージョンの以前のテキストを含んだ文字列だが、track-changes-register
のnobefore引数に非nil
を指定した場合には以前のテキストの文字数で置き換えられる。
最後の呼び出し以降に何も変更が発生していなければ、track-changes-fetch
はfuncを呼び出さずに単にnil
をリターンする。変更が発生していたらfuncを呼び出して、funcがリターンした値をリターンする。ただし変更が何回発生したとしても、funcは1回しか呼び出されないことに注意。発生した変更はbeg/end/beforeという単一のトリプレットにまとめられる。
たとえば低レベルのCコードのバグや、inhibit-modification-hooks
の不正な使用によって、ライブラリーがすべての変更を正しく通知しない場合がある。そのような問題を検知するとfuncはバッファー全体をカバーするようなbeg..end
、そして何が変更されたかライブラリーには判断不能であることうぃ示すシンボルerror
がセットされたbefore引数を受け取ることになる。
funcが完了すると変更が次回発生した際に呼び出せるように、track-changes-fetch
はsignal関数をふたたび有効化する。これはなぜこの関数が変更の記述をリターンするのではなく、funcを呼び出すのかという理由である。これによりsignalがふたたび有効化されるのがfuncの完了後だけなので、途中でsignal関数がトリガーされるというリスクへの懸念なしで変更を処理できるのだ。
この関数はライブラリーにたいして、トラッカーidがこれ以上変更について知る必要がないことを告げる。変更の追跡を停止したくないクライアントがほとんどだとしても、マイナーモードのようなクライアントはモードが無効化される前にこの関数を呼び出すことが重要である。さもなくばトラッカーは変更を累積し続けてますます多くのリソースが消費されることになるだろう。