Tags: patch
Our `*-change-functions` hook are fairly tricky to use right.
Some of the issues are:
- before and after calls are not necessarily paired.
- the beg/end values don't always match.
- there can be thousands of calls from within a single command.
- these hooks are run at a fairly low-level so there are things they
really shouldn't do, such as modify the buffer or wait.
- the after call doesn't get enough info to rebuild the before-change
state,
so some callers need to use both before-c-f and after-c-f (and then
deal with the first two points above).
The worst part is that those problems occur rarely, so many coders
don't
see it at first and have to learn them the hard way, sometimes forcing
them to rethink their original design.
So I think we should provide something simpler.
I attached a proof-of-concept API which aims to do that, with the
following entry points:
(defun track-changes-register ( signal)
"Register a new tracker and return a new tracker ID.
SIGNAL is a function that will be called with no argument when
the current buffer is modified, so that we can react to the change.
Once called, SIGNAL is not called again until `track-changes-fetch'
is called with the corresponding tracker ID."
(defun track-changes-unregister (id)
"Remove the tracker denoted by ID.
Trackers can consume resources (especially if `track-changes-fetch'
is
not called), so it is good practice to unregister them when you
don't
need them any more."
(defun track-changes-fetch (id func)
"Fetch the pending changes.
ID is the tracker ID returned by a previous
`track-changes-register'.
FUNC is a function. It is called with 3 arguments (BEGIN END
BEFORE)
where BEGIN..END delimit the region that was changed since the last
time `track-changes-fetch' was called and BEFORE is a string
containing
the previous content of that region.
If no changes occurred since the last time, FUNC is not called and
we return nil, otherwise we return the value returned by FUNC,
and re-enable the TRACKER corresponding to ID."
It's not meant as a replacement of the existing hooks since it doesn't
try to accommodate some uses such as those that use before-c-f to
implement a finer-grained form of read-only text.
The driving design was:
- Try to provide enough info such that it is possible and easy to
maintain a copy of the buffer simply by applying the reported
changes.
E.g. for uses such as `eglot.el` or `crdt.el`.
- Make the API less synchronous: take care of combining small changes
into larger ones, and let the clients decide when they react to
changes.
If you're in the Cc, it's because I believe you have valuable
experience
with those hooks, so I'd be happy to hear your thought about whether
you think this would indeed (have) be(en) better than what we have.