Subject: bug#70077: An easier way to track buffer changes

From: Stefan Monnier <[email-masked]>
Date: Tue, 09 Apr 2024 19:30:17 -0400

>> +Using @code{before-change-functions} and @code{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 of bugs in the C code or because of
>> +inappropriate use of @code{inhibit-modification-hooks}.
>
> I don't think we should talk about bugs in C code in the manual, at
> least not so explicitly.  I would rephrase
>
>   the fact that the two calls are not always properly paired, or some
>   calls may be missing, either because some Emacs primitives cannot
>   properly pair them or because of incorrect use of
>   @code{inhibit-modification-hooks}.

Thanks.

>> +@code{track-changes-register}, passing it a @var{signal} function as
>> +argument.  This will return a tracker @var{id} which is used to identify
>> +your tracker to the other functions of the library.  The other main
>> +function of the library is @code{track-changes-fetch} which lets you
>> +fetch the changes you have not yet processed.
>
> The last sentence is redundant, since you are about to describe
> track-changes-fetch shortly.

Good point.

>> +When the buffer is modified, the library will call the @var{signal}
>> +function to inform you of that change and will immediately start
>> +accumulating subsequent changes into a single combined change.
>> +The @var{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 processed
>> +the change.
>
> The last sentence should IMO say "...until after you retrieved the
> change by calling @code{track-changes-fetch}."  The important part
> here is to say what "process" means in practice, instead of leaving it
> unsaid.

But then we get:

    ...until after you retrieved the
    change by calling @code{track-changes-fetch}.
    
    To process changes, you need to call @code{track-changes-fetch}, ...

which again is kind of redundant/heavy.

I went with:

    To start tracking changes, you have to call
    @code{track-changes-register}, passing it a @var{signal} function as
    argument.  This returns a tracker @var{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 @var{signal}
    function to inform you of that change and immediately starts
    accumulating subsequent changes into a single combined change.
    The @var{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 @code{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 @var{signal} function so that the
    library will call it again after the next buffer modification.

[ Where I also switched to the present tense. Not sure why I used the
  future tense.  ]

>> +@defun track-changes-register signal &key nobefore disjoint immediate
>> +This function creates a new @emph{tracker}.  Trackers are kept abstract,
>
> I suggest to use "change tracker" instead of just "tracker".  On my
> daytime job, "tracker" has a very different meaning, so I stumble each
> time I see this used like that.
>
> Also, I suggest to use @dfn for its markup (and add a @cindex for it
> for good measure).

I turned some uses of "tracker" into "change tracker" and I think it's
indeed an improvement.  I tried to do it more systematically but with
all the "track-changes" and "change trackers" I started to feel like I was
writing a tongue twister.

>> +By default, the call to the @var{signal} function does not happen
>> +immediately, but is instead postponed with a 0 seconds timer.
>                                                 ^^^^^^^^^^^^^^^
> A cross-reference to where timers are described is in order there.

Thanks.

>> +usually desired to make sure the @var{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 @var{immediate} argument in which case the
>          ^^^
> @code{nil}

Duh.

>> +Once @var{func} finishes, @code{track-changes-fetch} re-enables the
>> +@var{signal} function so that it will be called the next time a change
>> +occurs.  This is the reason why it calls @var{func} instead of returning
>> +a description: it makes sure that the @var{signal} function will not be
>> +called while you're still processing past changes.
>
> I think there's a subtler issue here that needs to be described
> explicitly: if the entire processing of the change is not done inside
> FUNC, there's no guarantee that by the time some other function
> processes it, the change is still valid and in particular SIGNAL will
> not have been called again.  This is not a trivial aspect, since a
> program can use FUNC to do just some partial processing, like squirrel
> the changes to some buffer for later processing.

I tried to be more explicit about that as follows:

    Once @var{func} finishes, @code{track-changes-fetch} re-enables the
    @var{signal} function so that it will be called the next time a change
    occurs.  This is the reason why it calls @var{func} instead of returning
    a description: it lets you process the change without worrying about the
    risk that the @var{signal} function gets triggered in the middle of it,
    because the @var{signal} is re-enabled only after @var{func} finishes.

Note that calling SIGNAL before the change is "processed" is not
necessarily a problem.  E.g. Eglot does exactly that: its FUNC just
turns the NEWBEG/NEWEND/BEFORE into something like OLDBEG/OLDEND/AFTER
and pushes it on a list for later processing, it then waits for more
changes to come before later sending them to the server.

>>  * New Modes and Packages in Emacs 30.1
>> +** New package Track-Changes.
> This should be marked with "+++".

Thanks.

>> No, what it's saying is that these slots can contain values of any type
>> (since any type is a subtype of t).
>> This `Type` information is a way to document what kind of values can be
>> found in those slots.  Very often we don't bother specifying it, in
>> which case `t` is used as a default.
> Then maybe show "Any" or even "N/A".  t is simply not informative at
> all.  Actually, it is counter-productive: it makes a simple issue look
> complex and strange.

OK, I just used the empty string.

        Stefan