bug-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

bug#70077: An easier way to track buffer changes


From: phillip . lord
Subject: bug#70077: An easier way to track buffer changes
Date: Fri, 29 Mar 2024 18:20:32 -0400
User-agent: Roundcube Webmail/1.6.0

On 2024-03-29 12:15, Stefan Monnier wrote:
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.


Your description of the problem is entirely consistent with my experience. The last time I checked it was `subst-char-in-region' which was causing most of the difficulties, normally as a result of `fill-paragraph'.

If I remember correctly, I think this wouldn't be enough for my use. You keep two buffers in sync, you have to use before-change-function -- it is only before any change that the two buffers are guaranteed to be in sync and it is this that allows you to work out what the `start' and `end' positions mean in the copied buffer. Afterward, you cannot work out what the end position because you don't know if the change is a change, insertion, deletion or both.

Last time I checked, I did find relatively few primitives that were guilty of being inconsistent -- in the case of `subst-char-in-region', it returned the maximal area of effect before the and the minimal area of effect after. Would it not be easier to fix these?

Phil






reply via email to

[Prev in Thread] Current Thread [Next in Thread]