Up: Change Hooks [Contents][Index]
Using before-change-functions
and after-change-functions
can be difficult in practice because of a number of pitfalls, such as
the fact that the two calls are not always properly paired, or some
calls may be missing, either because some Emacs primitives failed to
properly pair them or because of incorrect use of
inhibit-modification-hooks
. Furthermore,
many restrictions apply to those hook functions, such as the fact that
they basically should never modify the current buffer, nor use an
operation that may block, and they proceed quickly because
some commands may call these hooks a large number of times.
The Track-Changes library fundamentally provides an alternative API,
built on top of those hooks. Compared to after-change-functions
,
the first important difference is that, instead of providing the bounds
of the change and the previous length, it provides the bounds of the
change and the actual previous content of that region. The need to
extract information from the original contents of the buffer is one of
the main reasons why some packages need to use both
before-change-functions
and after-change-functions
and
then try to match them up.
The second difference is that it decouples the notification of a change from the act of processing it, and it automatically combines into a single change operation all the changes that occur between the first change and the actual processing. This makes it natural and easy to process the changes at a larger granularity, such as once per command, and eliminates most of the restrictions that apply to the usual change hook functions, making it possible to use blocking operations or to modify the buffer.
To start tracking changes, you have to call
track-changes-register
, passing it a signal function as
argument. This returns a tracker id which is used to identify
your change tracker to the other functions of the library.
When the buffer is modified, the library calls the signal
function to inform you of that change and immediately starts
accumulating subsequent changes into a single combined change.
The signal function serves only to warn that a modification
occurred but does not receive a description of the change. Also the
library will not call it again until after you retrieved the change.
To retrieve changes, you need to call track-changes-fetch
, which
provides you with the bounds of the changes accumulated since the
last call, as well as the previous content of that region. It also
“re-arms” the signal function so that the library will call it
again after the next buffer modification.
This function creates a new change tracker. Change trackers are kept abstract, so we refer to them as mere identities, and the function thus returns the tracker’s id.
signal is a function that the library will call to notify of
a change. It will sometimes call it with a single argument and
sometimes with two. Upon the first change to the buffer since this
tracker last called track-changes-fetch
, the library calls this
signal function with a single argument holding the id of
the tracker.
By default, the call to the signal function does not happen
immediately, but is instead postponed with a 0 seconds timer
(see Timers for Delayed Execution). This is usually desired to make sure the signal
function is not called too frequently and runs in a permissive context,
freeing the client from performance concerns or worries about which
operations might be problematic. If a client wants to have more
control, they can provide a non-nil
value as the immediate
argument in which case the library calls the signal function
directly from after-change-functions
. Beware that it means that
the signal function has to be careful not to modify the buffer or
use operations that may block.
If you’re not interested in the actual previous content of the buffer,
but are using this library only for its ability to combine many small
changes into a larger one and to delay the processing to a more
convenient time, you can specify a non-nil
value for the
nobefore argument. In that case, track-change-fetch
provides you only with the length of the previous content, just like
after-change-functions
. It also allows the library to save
some work.
While you may like to accumulate many small changes into larger ones,
you may not want to do that if the changes are too far apart. If you
specify a non-nil
value for the disjoint argument, the library
will let you know when a change is about to occur “far” from the
currently pending ones by calling the signal function right away,
passing it two arguments this time: the id of the tracker, and the
number of characters that separates the upcoming change from the
already pending changes. This in itself does not prevent combining this
new change with the previous ones, so if you think the upcoming change
is indeed too far, you need to call track-change-fetch
right away.
Beware that when the signal function is called because of
a disjoint change, this happens directly from
before-change-functions
, so the usual restrictions apply about
modifying the buffer or using operations that may block.
This is the function that lets you find out what has changed in the
buffer. By providing the tracker id you let the library figure
out which changes have already been seen by your tracker. Instead of
returning a description of the changes, track-changes-fetch
calls
the func function with that description in the form of
3 arguments: beg, end, and before, where
beg..end
delimit the region that was modified and
before describes the previous content of that region.
Usually before is a string containing the previous text of the
modified region, but if you specified a non-nil
nobefore argument
to track-changes-register
, then it is replaced by the number of
characters of that previous text.
In case no changes occurred since the last call,
track-changes-fetch
simply does not call func and returns
nil
. If changes did occur, it calls func and returns the value
returned by func. But note that func is called just once
regardless of how many changes occurred: those are summarized into
a single beg/end/before triplet.
In some cases, the library is not properly notified of all changes,
for example because of a bug in the low-level C code or because of an
improper use of inhibit-modification-hooks
. When it detects such
a problem, func receives a beg..end
region
which covers the whole buffer and the before argument is the
symbol error
to indicate that the library was unable to determine
what was changed.
Once func finishes, track-changes-fetch
re-enables the
signal function so that it will be called the next time a change
occurs. This is the reason why it calls func instead of returning
a description: it lets you process the change without worrying about the
risk that the signal function gets triggered in the middle of it,
because the signal is re-enabled only after func finishes.
This function tells the library that the tracker id does not need to know about buffer changes any more. Most clients will never want to stop tracking changes, but for clients such as minor modes, it is important to call this function when the minor mode is disabled, otherwise the tracker will keep accumulating changes and consume more and more resources.
Up: Change Hooks [Contents][Index]